CorpusController.php 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078
  1. <?php
  2. namespace App\Http\Controllers;
  3. use Carbon\Carbon;
  4. use App\Models\Sentence;
  5. use App\Models\Channel;
  6. use App\Models\PaliText;
  7. use App\Models\WbwTemplate;
  8. use App\Models\WbwBlock;
  9. use App\Models\Wbw;
  10. use App\Models\Discussion;
  11. use App\Models\PaliSentence;
  12. use App\Models\SentSimIndex;
  13. use App\Models\CustomBookSentence;
  14. use Illuminate\Support\Str;
  15. use Illuminate\Http\Request;
  16. use Illuminate\Support\Facades\Cache;
  17. use App\Tools\RedisClusters;
  18. use App\Http\Api\MdRender;
  19. use App\Http\Api\SuggestionApi;
  20. use App\Http\Api\ChannelApi;
  21. use App\Http\Api\UserApi;
  22. use App\Http\Api\StudioApi;
  23. use App\Http\Api\AuthApi;
  24. use Illuminate\Support\Facades\Log;
  25. use Illuminate\Support\Arr;
  26. use App\Http\Resources\TocResource;
  27. use Illuminate\Support\Facades\Redis;
  28. class CorpusController extends Controller
  29. {
  30. protected $result = [
  31. "uid"=> '',
  32. "title"=> '',
  33. "path"=>[],
  34. "sub_title"=> '',
  35. "summary"=> '',
  36. "content"=> '',
  37. "content_type"=> "html",
  38. "toc" => [],
  39. "status"=>30,
  40. "lang"=> "",
  41. "created_at"=> "",
  42. "updated_at"=> "",
  43. ];
  44. protected $wbwChannels = [];
  45. //句子需要查询的列
  46. protected $selectCol = [
  47. 'uid',
  48. 'book_id',
  49. 'paragraph',
  50. 'word_start',
  51. "word_end",
  52. 'channel_uid',
  53. 'content',
  54. 'content_type',
  55. 'editor_uid',
  56. 'acceptor_uid',
  57. 'pr_edit_at',
  58. 'create_time',
  59. 'modify_time',
  60. 'created_at',
  61. 'updated_at',
  62. ];
  63. protected $userUuid=null;
  64. protected $debug=[];
  65. public function __construct()
  66. {
  67. }
  68. /**
  69. * Display a listing of the resource.
  70. *
  71. * @return \Illuminate\Http\Response
  72. */
  73. public function index(Request $request)
  74. {
  75. //
  76. switch ($request->get('view')) {
  77. case 'para':
  78. return $this->showPara($request);
  79. break;
  80. default:
  81. # code...
  82. break;
  83. }
  84. }
  85. /**
  86. * Store a newly created resource in storage.
  87. *
  88. * @param \Illuminate\Http\Request $request
  89. * @return \Illuminate\Http\Response
  90. */
  91. public function store(Request $request)
  92. {
  93. //
  94. }
  95. /**
  96. * Display the specified resource.
  97. *
  98. * @param \App\Models\Sentence $sentence
  99. * @return \Illuminate\Http\Response
  100. */
  101. public function show(Sentence $sentence)
  102. {
  103. //
  104. }
  105. public function getSentTpl($id,$channels,$mode='edit',$onlyProps=false){
  106. $sent = [];
  107. $sentId = \explode('-',$id);
  108. if(count($sentId) !== 4){
  109. return false;
  110. }
  111. if((int)$sentId[0] < 1000){
  112. if($mode==='read'){
  113. $channelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
  114. }else{
  115. $channelId = ChannelApi::getSysChannel('_System_Wbw_VRI_');
  116. }
  117. }else{
  118. }
  119. if(isset($channelId) && $channelId){
  120. array_push($channels,$channelId);
  121. }
  122. $record = Sentence::select($this->selectCol)
  123. ->where('book_id',$sentId[0])
  124. ->where('paragraph',$sentId[1])
  125. ->where('word_start',(int)$sentId[2])
  126. ->where('word_end',(int)$sentId[3])
  127. ->whereIn('channel_uid',$channels)
  128. ->get();
  129. $channelIndex = $this->getChannelIndex($channels);
  130. if((int)$sentId[0] >= 1000){
  131. $orgChannelId = (string)Str::uuid();
  132. //载入自定义原文
  133. $customOrigin = CustomBookSentence::
  134. where('book',(int)$sentId[0])
  135. ->where('paragraph',(int)$sentId[1])
  136. ->where('word_start',(int)$sentId[2])
  137. ->where('word_end',(int)$sentId[3])
  138. ->get();
  139. $toSentFormat = array();
  140. $owner_uid = null;
  141. foreach ($customOrigin as $custom) {
  142. if($owner_uid === null){
  143. $owner_uid = $custom->owner;
  144. }
  145. $toSentFormat[] = (object)[
  146. 'uid'=>$custom->id,
  147. 'book_id'=>$custom->book,
  148. 'paragraph'=>$custom->paragraph,
  149. 'word_start'=>$custom->word_start,
  150. "word_end"=>$custom->word_end,
  151. 'channel_uid'=>$orgChannelId,
  152. 'content'=>$custom->content,
  153. 'content_type'=>$custom->content_type,
  154. 'editor_uid'=>$custom->owner,
  155. 'acceptor_uid'=>null,
  156. 'pr_edit_at'=>null,
  157. 'create_time'=>$custom->create_time,
  158. 'modify_time'=>$custom->modify_time,
  159. 'created_at'=>$custom->created_at,
  160. 'updated_at'=>$custom->updated_at,
  161. ];
  162. }
  163. $orgChannel = (object)array(
  164. 'uid'=>$orgChannelId,
  165. 'type'=>'original',
  166. 'name'=>'custom',
  167. 'owner_uid'=>$owner_uid,
  168. 'studio'=>StudioApi::getById($owner_uid),
  169. );
  170. $channelIndex[$orgChannelId] = $orgChannel;
  171. }
  172. if(isset($toSentFormat)){
  173. foreach ($toSentFormat as $key => $org) {
  174. $record[] = $org;
  175. }
  176. }
  177. //获取wbw channel
  178. //目前默认的 wbw channel 是第一个translation channel
  179. foreach ($channels as $channel) {
  180. # code...
  181. if($channelIndex[$channel]->type==='translation'){
  182. $this->wbwChannels[] = $channel;
  183. break;
  184. }
  185. }
  186. return $this->makeContent($record,$mode,$channelIndex,[],$onlyProps);
  187. }
  188. /**
  189. * Display the specified resource.
  190. * @param \Illuminate\Http\Request $request
  191. * @param string $id
  192. * @return \Illuminate\Http\Response
  193. */
  194. public function showSent(Request $request, string $id)
  195. {
  196. $user = AuthApi::current($request);
  197. if($user){
  198. $this->userUuid = $user['user_uid'];
  199. }
  200. $channels = \explode('_',$request->get('channels'));
  201. $this->result['uid'] = "";
  202. $this->result['title'] = "";
  203. $this->result['subtitle'] = "";
  204. $this->result['summary'] = "";
  205. $this->result['lang'] = "";
  206. $this->result['status'] = 30;
  207. $this->result['content'] = $this->getSentTpl($id,$channels,$request->get('mode','edit'));
  208. return $this->ok($this->result);
  209. }
  210. /**
  211. * 获取某句子的全部译文
  212. * @param \Illuminate\Http\Request $request
  213. * @param string $type
  214. * @param string $id
  215. * @return \Illuminate\Http\Response
  216. */
  217. public function showSentences(Request $request, string $type, string $id){
  218. $user = AuthApi::current($request);
  219. if($user){
  220. $this->userUuid = $user['user_uid'];
  221. }
  222. $param = \explode('_',$id);
  223. $sentId = \explode('-',$param[0]);
  224. $channels = [];
  225. #获取channel类型
  226. $sentChannel = Sentence::select('channel_uid')
  227. ->where('book_id',$sentId[0])
  228. ->where('paragraph',$sentId[1])
  229. ->where('word_start',$sentId[2])
  230. ->where('word_end',$sentId[3])
  231. ->get();
  232. foreach ($sentChannel as $key => $value) {
  233. # code...
  234. $channels[] = $value->channel_uid;
  235. }
  236. $channelInfo = Channel::whereIn("uid",$channels)->select(['uid','type','name'])->get();
  237. $indexChannel = [];
  238. $channels = [];
  239. foreach ($channelInfo as $key => $value) {
  240. # code...
  241. if($value->type === $type){
  242. $indexChannel[$value->uid] = $value;
  243. $channels[] = $value->uid;
  244. }
  245. }
  246. //获取句子数据
  247. $record = Sentence::select($this->selectCol)
  248. ->where('book_id',$sentId[0])
  249. ->where('paragraph',$sentId[1])
  250. ->where('word_start',$sentId[2])
  251. ->where('word_end',$sentId[3])
  252. ->whereIn('channel_uid',$channels)
  253. ->orderBy('paragraph')
  254. ->orderBy('word_start')
  255. ->get();
  256. if(count($record) ===0){
  257. return $this->error("no data");
  258. }
  259. $this->result['uid'] = "";
  260. $this->result['title'] = "";
  261. $this->result['subtitle'] = "";
  262. $this->result['summary'] = "";
  263. $this->result['lang'] = "";
  264. $this->result['status'] = 30;
  265. $this->result['content'] = $this->makeContent($record,$mode,$indexChannel);
  266. return $this->ok($this->result);
  267. }
  268. /**
  269. * Store a newly created resource in storage.
  270. * @param \Illuminate\Http\Request $request
  271. * @param string $id
  272. * @param string $mode
  273. * @return \Illuminate\Http\Response
  274. */
  275. public function showPara(Request $request)
  276. {
  277. if($request->has('debug')){
  278. $this->debug = explode(',',$request->get('debug'));
  279. }
  280. $user = AuthApi::current($request);
  281. if($user){
  282. $this->userUuid = $user['user_uid'];
  283. }
  284. //
  285. $channels = [];
  286. if($request->get('mode') === 'edit'){
  287. //翻译模式加载json格式原文
  288. $channels[] = ChannelApi::getSysChannel('_System_Wbw_VRI_');
  289. }else{
  290. //阅读模式加载html格式原文
  291. $channels[] = ChannelApi::getSysChannel('_System_Pali_VRI_');
  292. }
  293. if($request->has('channels')){
  294. if(strpos($request->get('channels'),',') === FALSE){
  295. $getChannel = explode('_',$request->get('channels'));
  296. }else{
  297. $getChannel = explode(',',$request->get('channels'));
  298. }
  299. $channels = array_merge($channels,$getChannel );
  300. }
  301. $para = explode(",",$request->get('par'));
  302. //段落所在章节
  303. $parent = PaliText::where('book',$request->get('book'))
  304. ->where('paragraph',$para[0])->first();
  305. $chapter = PaliText::where('book',$request->get('book'))
  306. ->where('paragraph',$parent->parent)->first();
  307. if($chapter){
  308. if(empty($chapter->toc)){
  309. $this->result['title'] = "unknown";
  310. }else{
  311. $this->result['title'] = $chapter->toc;
  312. $this->result['sub_title'] = $chapter->toc;
  313. $this->result['path'] = json_decode($parent->path);
  314. }
  315. }
  316. $paraFrom = $para[0];
  317. $paraTo = end($para);
  318. $indexedHeading = [];
  319. #获取channel索引表
  320. $tranChannels = [];
  321. $channelInfo = Channel::whereIn("uid",$channels)->select(['uid','type','name'])->get();
  322. foreach ($channelInfo as $key => $value) {
  323. # code...
  324. if($value->type==="translation" ){
  325. $tranChannels[] = $value->uid;
  326. }
  327. }
  328. $indexChannel = [];
  329. $indexChannel = $this->getChannelIndex($channels);
  330. //获取wbw channel
  331. //目前默认的 wbw channel 是第一个translation channel
  332. foreach ($channels as $key => $value) {
  333. # code...
  334. if($indexChannel[$value]->type==='translation'){
  335. $this->wbwChannels[] = $value;
  336. break;
  337. }
  338. }
  339. //章节译文标题
  340. $title = Sentence::select($this->selectCol)
  341. ->where('book_id',$parent->book)
  342. ->where('paragraph',$parent->parent)
  343. ->whereIn('channel_uid',$tranChannels)
  344. ->first();
  345. if($title){
  346. $this->result['title'] = MdRender::render($title->content,[$title->channel_uid]);
  347. }
  348. /**
  349. * 获取句子数据
  350. */
  351. $record = Sentence::select($this->selectCol)
  352. ->where('book_id',$request->get('book'))
  353. ->whereIn('paragraph',$para)
  354. ->whereIn('channel_uid',$channels)
  355. ->orderBy('paragraph')
  356. ->orderBy('word_start')
  357. ->get();
  358. if(count($record) ===0){
  359. $this->result['content'] = "<span>No Data</span>";
  360. }else{
  361. $this->result['content'] = $this->makeContent($record,$request->get('mode','read'),$indexChannel,$indexedHeading,false,true);
  362. }
  363. return $this->ok($this->result);
  364. }
  365. /**
  366. * Store a newly created resource in storage.
  367. * @param \Illuminate\Http\Request $request
  368. * @param string $id
  369. * @return \Illuminate\Http\Response
  370. */
  371. public function showChapter(Request $request, string $id)
  372. {
  373. if($request->has('debug')){
  374. $this->debug = explode(',',$request->get('debug'));
  375. }
  376. $user = AuthApi::current($request);
  377. if($user){
  378. $this->userUuid = $user['user_uid'];
  379. }
  380. //
  381. $sentId = \explode('-',$id);
  382. $channels = [];
  383. if($request->has('channels')){
  384. if(strpos($request->get('channels'),',') === FALSE){
  385. $channels = explode('_',$request->get('channels'));
  386. }else{
  387. $channels = explode(',',$request->get('channels'));
  388. }
  389. }
  390. $mode = $request->get('mode','read');
  391. if($mode === 'read'){
  392. //阅读模式加载html格式原文
  393. $channelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
  394. }else{
  395. //翻译模式加载json格式原文
  396. $channelId = ChannelApi::getSysChannel('_System_Wbw_VRI_');
  397. }
  398. if($channelId !== false){
  399. $channels[] = $channelId;
  400. }
  401. $chapter = PaliText::where('book',$sentId[0])->where('paragraph',$sentId[1])->first();
  402. if(!$chapter){
  403. return $this->error("no data");
  404. }
  405. if(empty($chapter->toc)){
  406. $this->result['title'] = "unknown";
  407. }else{
  408. $this->result['title'] = $chapter->toc;
  409. $this->result['sub_title'] = $chapter->toc;
  410. $this->result['path'] = json_decode($chapter->path);
  411. }
  412. $paraFrom = $sentId[1];
  413. $paraTo = $sentId[1]+$chapter->chapter_len-1;
  414. //获取标题
  415. $heading = PaliText::select(["book","paragraph","level"])
  416. ->where('book',$sentId[0])
  417. ->whereBetween('paragraph',[$paraFrom,$paraTo])
  418. ->where('level','<',8)
  419. ->get();
  420. //将标题段落转成索引数组 以便输出标题层级
  421. $indexedHeading = [];
  422. foreach ($heading as $key => $value) {
  423. # code...
  424. $indexedHeading["{$value->book}-{$value->paragraph}"] = $value->level;
  425. }
  426. #获取channel索引表
  427. $tranChannels = [];
  428. $channelInfo = Channel::whereIn("uid",$channels)->select(['uid','type','name'])->get();
  429. foreach ($channelInfo as $key => $value) {
  430. # code...
  431. if($value->type==="translation" ){
  432. $tranChannels[] = $value->uid;
  433. }
  434. }
  435. $indexChannel = [];
  436. $indexChannel = $this->getChannelIndex($channels);
  437. //获取wbw channel
  438. //目前默认的 wbw channel 是第一个translation channel
  439. foreach ($channels as $key => $value) {
  440. # code...
  441. if($indexChannel[$value]->type==='translation'){
  442. $this->wbwChannels[] = $value;
  443. break;
  444. }
  445. }
  446. $title = Sentence::select($this->selectCol)
  447. ->where('book_id',$sentId[0])
  448. ->where('paragraph',$sentId[1])
  449. ->whereIn('channel_uid',$tranChannels)
  450. ->first();
  451. if($title){
  452. $this->result['title'] = MdRender::render($title->content,[$title->channel_uid]);
  453. }
  454. /**
  455. * 获取句子数据
  456. * 算法:
  457. * 1. 如果标题和下一级第一个标题之间有段落。只输出这些段落和子目录
  458. * 2. 如果标题和下一级第一个标题之间没有间隔 且 chapter 长度大于10000个字符 且有子目录,只输出子目录
  459. * 3. 如果二者都不是,lazy load
  460. */
  461. //1. 计算 标题和下一级第一个标题之间 是否有间隔
  462. $nextChapter = PaliText::where('book',$sentId[0])
  463. ->where('paragraph',">",$sentId[1])
  464. ->where('level','<',8)
  465. ->orderBy('paragraph')
  466. ->value('paragraph');
  467. $between = $nextChapter - $sentId[1];
  468. //输出子目录
  469. $chapterLen = $chapter->chapter_len;
  470. $toc = PaliText::where('book',$sentId[0])
  471. ->whereBetween('paragraph',[$paraFrom+1,$paraFrom+$chapterLen-1])
  472. ->where('level','<',8)
  473. ->orderBy('paragraph')
  474. ->select(['book','paragraph','level','toc'])
  475. ->get();
  476. if($between > 1){
  477. //有间隔
  478. $paraTo = $nextChapter - 1;
  479. }else{
  480. if($chapter->chapter_strlen>2000){
  481. if(count($toc)>0){
  482. //有子目录只输出标题和目录
  483. $paraTo = $paraFrom;
  484. }else{
  485. //没有子目录 全部输出
  486. }
  487. }else{
  488. //章节小。全部输出 不输出章节
  489. $toc = [];
  490. }
  491. }
  492. $pFrom = $request->get('from',$paraFrom);
  493. $pTo = $request->get('to',$paraTo);
  494. //根据句子的长度找到这次应该加载的句子
  495. $maxLen = 3000;
  496. $paliText = PaliText::select(['paragraph','lenght'])
  497. ->where('book',$sentId[0])
  498. ->whereBetween('paragraph',[$pFrom,$pTo])
  499. ->get();
  500. $sumLen = 0;
  501. $currTo = $pTo;
  502. foreach ($paliText as $para) {
  503. $sumLen += $para->lenght;
  504. if($sumLen > $maxLen){
  505. $currTo = $para->paragraph;
  506. break;
  507. }
  508. }
  509. $record = Sentence::select($this->selectCol)
  510. ->where('book_id',$sentId[0])
  511. ->whereBetween('paragraph',[$pFrom,$currTo])
  512. ->whereIn('channel_uid',$channels)
  513. ->orderBy('paragraph')
  514. ->orderBy('word_start')
  515. ->get();
  516. if(count($record) ===0){
  517. return $this->error("no data");
  518. }
  519. $this->result['content'] = $this->makeContent($record,$mode,$indexChannel,$indexedHeading,false,true);
  520. if(!$request->has('from')){
  521. //第一次才显示toc
  522. $this->result['toc'] = TocResource::collection($toc);
  523. }
  524. if($currTo < $pTo){
  525. $this->result['from'] = $currTo+1;
  526. $this->result['to'] = $pTo;
  527. $this->result['paraId'] = $id;
  528. $this->result['channels'] = $request->get('channels');
  529. $this->result['mode'] = $request->get('mode');
  530. }
  531. return $this->ok($this->result);
  532. }
  533. private function getChannelIndex($channels,$type=null){
  534. #获取channel索引表
  535. $channelInfo = Channel::whereIn("uid",$channels)
  536. ->select(['uid','type','name','owner_uid'])->get();
  537. $indexChannel = [];
  538. foreach ($channelInfo as $key => $value) {
  539. # code...
  540. if($type !== null && $value->type !== $type){
  541. continue;
  542. }
  543. $indexChannel[$value->uid] = $value;
  544. }
  545. foreach ($indexChannel as $uid => $value) {
  546. # 查询studio
  547. $indexChannel[$uid]->studio = StudioApi::getById($value->owner_uid);
  548. }
  549. return $indexChannel;
  550. }
  551. /**
  552. * 根据句子库数据生成文章内容
  553. * $record 句子数据
  554. * $mode read | edit | wbw
  555. * $indexChannel channel索引
  556. * $indexedHeading 标题索引 用于给段落加标题标签 <h1> ect.
  557. */
  558. private function makeContent($record,$mode,$indexChannel,$indexedHeading=[],$onlyProps=false,$paraMark=false){
  559. $content = [];
  560. $lastSent = "0-0";
  561. $sentCount = 0;
  562. $sent = [];
  563. $sent["origin"] = [];
  564. $sent["translation"] = [];
  565. //获取句子编号列表
  566. $sentList = [];
  567. foreach ($record as $key => $value) {
  568. $currSentId = "{$value->book_id}-{$value->paragraph}-{$value->word_start}-{$value->word_end}";
  569. $sentList[$currSentId]=[$value->book_id ,$value->paragraph,$value->word_start,$value->word_end];
  570. $value->sid = "{$currSentId}_{$value->channel_uid}";
  571. }
  572. $channelsId = array();
  573. $count = 0;
  574. foreach ($indexChannel as $channelId => $info){
  575. if($count>0){
  576. $channelsId[] = $channelId;
  577. }
  578. $count++;
  579. }
  580. //遍历列表查找每个句子的所有channel的数据,并填充
  581. $currPara = "";
  582. foreach ($sentList as $currSentId => $arrSentId) {
  583. $para = $arrSentId[0]."-".$arrSentId[1];
  584. if($currPara !== $para){
  585. $currPara = $para;
  586. //输出段落标记
  587. if($paraMark){
  588. $sentInPara = array();
  589. foreach ($sentList as $sentId => $sentParam) {
  590. if($sentParam[0]===$arrSentId[0] &&
  591. $sentParam[1]===$arrSentId[1]){
  592. $sentInPara[] = $sentId;
  593. }
  594. }
  595. $mark = [
  596. 'book'=>$arrSentId[0],
  597. 'para'=>$arrSentId[1],
  598. 'channels'=>$channelsId,
  599. 'sentences'=>$sentInPara,
  600. ];
  601. $markProps = base64_encode(\json_encode($mark)) ;
  602. $paraWidget = "<MdTpl tpl='para' props='{$markProps}' ></MdTpl>";
  603. $content[] = $paraWidget;
  604. }
  605. }
  606. $sent = $this->newSent($arrSentId[0],$arrSentId[1],$arrSentId[2],$arrSentId[3]);
  607. foreach ($indexChannel as $channelId => $info) {
  608. # code...
  609. $sid = "{$currSentId}_{$channelId}";
  610. if(isset($info->studio)){
  611. $studioInfo = $info->studio;
  612. }else{
  613. $studioInfo = null;
  614. }
  615. $newSent = [
  616. "content"=>"",
  617. "html"=> "",
  618. "book"=> $arrSentId[0],
  619. "para"=> $arrSentId[1],
  620. "wordStart"=> $arrSentId[2],
  621. "wordEnd"=> $arrSentId[3],
  622. "channel"=> [
  623. "name"=>$info->name,
  624. "type"=>$info->type,
  625. "id"=> $info->uid,
  626. ],
  627. "studio" => $studioInfo,
  628. "updateAt"=> "",
  629. "suggestionCount" => SuggestionApi::getCountBySent($arrSentId[0],$arrSentId[1],$arrSentId[2],$arrSentId[3],$channelId),
  630. ];
  631. $row = Arr::first($record,function($value,$key) use($sid){
  632. return $value->sid===$sid;
  633. });
  634. if($row){
  635. $newSent['id'] = $row->uid;
  636. $newSent['content'] = $row->content;
  637. $newSent['contentType'] = $row->content_type;
  638. $newSent['html'] = "";
  639. $newSent["editor"]=UserApi::getByUuid($row->editor_uid);
  640. /**
  641. * TODO 刷库改数据
  642. * 旧版api没有更新updated_at所以造成旧版的数据updated_at数据比modify_time 要晚
  643. */
  644. $newSent['updateAt'] = $row->updated_at; //
  645. $newSent['updateAt'] = date("Y-m-d H:i:s.",$row->modify_time/1000).($row->modify_time%1000)." UTC";
  646. $newSent['createdAt'] = $row->created_at;
  647. if($mode !== "read"){
  648. if(isset($row->acceptor_uid) && !empty($row->acceptor_uid)){
  649. $newSent["acceptor"]=UserApi::getByUuid($row->acceptor_uid);
  650. $newSent["prEditAt"]=$row->pr_edit_at;
  651. }
  652. }
  653. switch ($info->type) {
  654. case 'wbw':
  655. case 'original':
  656. //
  657. // 在编辑模式下。
  658. // 如果是原文,查看是否有逐词解析数据,
  659. // 有的话优先显示。
  660. // 阅读模式直接显示html原文
  661. // 传过来的数据一定有一个原文channel
  662. //
  663. if($mode !== "read"){
  664. $newSent['channel']['type'] = "wbw";
  665. if(isset($this->wbwChannels[0])){
  666. $newSent['channel']['name'] = $indexChannel[$this->wbwChannels[0]]->name;
  667. $newSent['channel']['id'] = $this->wbwChannels[0];
  668. //存在一个translation channel
  669. //尝试查找逐词解析数据。找到,替换现有数据
  670. $wbwData = $this->getWbw($arrSentId[0],$arrSentId[1],$arrSentId[2],$arrSentId[3],$this->wbwChannels[0]);
  671. if($wbwData){
  672. $newSent['content'] = $wbwData;
  673. $newSent['html'] = "";
  674. }
  675. }
  676. }else{
  677. $newSent['content'] = "";
  678. $newSent['html'] = $row->content;
  679. }
  680. break;
  681. case 'nissaya':
  682. $newSent['html'] = RedisClusters::remember("/sent/{$channelId}/{$currSentId}",
  683. config('mint.cache.expire'),
  684. function() use($row,$mode){
  685. return MdRender::render($row->content,[$row->channel_uid],null,$mode,"nissaya",$row->content_type);
  686. });
  687. break;
  688. default:
  689. /**
  690. * 译文需要markdown渲染
  691. * 包涵术语的不用cache
  692. */
  693. if(strpos($row->content,'[[')===false){
  694. $newSent['html'] = RedisClusters::remember("/sent/{$channelId}/{$currSentId}",
  695. config('mint.cache.expire'),
  696. function() use($row){
  697. return MdRender::render($row->content,[$row->channel_uid]);
  698. });
  699. }else{
  700. $mdRender = new MdRender(['debug'=>$this->debug]);
  701. $newSent['html'] = $mdRender->convert($row->content,[$row->channel_uid]);
  702. }
  703. break;
  704. }
  705. }
  706. switch ($info->type) {
  707. case 'wbw':
  708. case 'original':
  709. array_push($sent["origin"],$newSent);
  710. break;
  711. default:
  712. array_push($sent["translation"],$newSent);
  713. break;
  714. }
  715. }
  716. if($onlyProps){
  717. return $sent;
  718. }
  719. $content = $this->pushSent($content,$sent,0,$mode);
  720. }
  721. $output = \implode("",$content);
  722. return "<div>{$output}</div>";
  723. }
  724. public function getWbw($book,$para,$start,$end,$channel){
  725. /**
  726. * 非阅读模式下。原文使用逐词解析数据。
  727. * 优先加载第一个translation channel 如果没有。加载默认逐词解析。
  728. */
  729. //获取逐词解析数据
  730. $wbwBlock = WbwBlock::where('channel_uid',$channel)
  731. ->where('book_id',$book)
  732. ->where('paragraph',$para)
  733. ->select('uid')
  734. ->first();
  735. if(!$wbwBlock){
  736. return false;
  737. }
  738. //找到逐词解析数据
  739. $wbwData = Wbw::where('block_uid',$wbwBlock->uid)
  740. ->whereBetween('wid',[$start,$end])
  741. ->select(['book_id','paragraph','wid','data','uid'])
  742. ->orderBy('wid')
  743. ->get();
  744. $wbwContent = [];
  745. foreach ($wbwData as $wbwrow) {
  746. $wbw = str_replace("&nbsp;",' ',$wbwrow->data);
  747. $wbw = str_replace("<br>",' ',$wbw);
  748. $xmlString = "<root>" . $wbw . "</root>";
  749. try{
  750. $xmlWord = simplexml_load_string($xmlString);
  751. }catch(Exception $e){
  752. continue;
  753. }
  754. $wordsList = $xmlWord->xpath('//word');
  755. foreach ($wordsList as $word) {
  756. $case = \str_replace(['#','.'],['$',''],$word->case->__toString());
  757. $case = \str_replace('$$','$',$case);
  758. $case = trim($case);
  759. $case = trim($case,"$");
  760. $wbwId = explode('-',$word->id->__toString());
  761. $wbwData = [
  762. 'uid'=>$wbwrow->uid,
  763. 'book'=>$wbwrow->book_id,
  764. 'para'=>$wbwrow->paragraph,
  765. 'sn'=> array_slice($wbwId,2),
  766. 'word'=>['value'=>$word->pali->__toString(),'status'=>0],
  767. 'real'=> ['value'=>$word->real->__toString(),'status'=>0],
  768. 'meaning'=> ['value'=>$word->mean->__toString() ,'status'=>0],
  769. 'type'=> ['value'=>$word->type->__toString(),'status'=>0],
  770. 'grammar'=> ['value'=>$word->gramma->__toString(),'status'=>0],
  771. 'case'=> ['value'=>$word->case->__toString(),'status'=>0],
  772. 'parent'=> ['value'=>$word->parent->__toString(),'status'=>0],
  773. 'style'=> ['value'=>$word->style->__toString(),'status'=>0],
  774. 'factors'=> ['value'=>$word->org->__toString(),'status'=>0],
  775. 'factorMeaning'=> ['value'=>$word->om->__toString(),'status'=>0],
  776. 'confidence'=> $word->cf->__toString(),
  777. 'hasComment'=>Discussion::where('res_id',$wbwrow->uid)->exists(),
  778. ];
  779. if(isset($word->parent2)){
  780. $wbwData['parent2']['value'] = $word->parent2->__toString();
  781. if(isset($word->parent2['status'])){
  782. $wbwData['parent2']['status'] = (int)$word->parent2['status'];
  783. }else{
  784. $wbwData['parent2']['status'] = 0;
  785. }
  786. }
  787. if(isset($word->pg)){
  788. $wbwData['grammar2']['value'] = $word->pg->__toString();
  789. if(isset($word->pg['status'])){
  790. $wbwData['grammar2']['status'] = (int)$word->pg['status'];
  791. }else{
  792. $wbwData['grammar2']['status'] = 0;
  793. }
  794. }
  795. if(isset($word->rela)){
  796. $wbwData['relation']['value'] = $word->rela->__toString();
  797. if(isset($word->rela['status'])){
  798. $wbwData['relation']['status'] = (int)$word->rela['status'];
  799. }else{
  800. $wbwData['relation']['status'] = 7;
  801. }
  802. }
  803. if(isset($word->bmt)){
  804. $wbwData['bookMarkText']['value'] = $word->bmt->__toString();
  805. if(isset($word->bmt['status'])){
  806. $wbwData['bookMarkText']['status'] = (int)$word->bmt['status'];
  807. }else{
  808. $wbwData['bookMarkText']['status'] = 7;
  809. }
  810. }
  811. if(isset($word->bmc)){
  812. $wbwData['bookMarkColor']['value'] = $word->bmc->__toString();
  813. if(isset($word->bmc['status'])){
  814. $wbwData['bookMarkColor']['status'] = (int)$word->bmc['status'];
  815. }else{
  816. $wbwData['bookMarkColor']['status'] = 7;
  817. }
  818. }
  819. if(isset($word->note)){
  820. $wbwData['note']['value'] = $word->note->__toString();
  821. if(isset($word->note['status'])){
  822. $wbwData['note']['status'] = (int)$word->note['status'];
  823. }else{
  824. $wbwData['note']['status'] = 7;
  825. }
  826. }
  827. if(isset($word->cf)){
  828. $wbwData['confidence'] = (float)$word->cf->__toString();
  829. }
  830. if(isset($word->attachments)){
  831. $wbwData['attachments'] = json_decode($word->attachments->__toString());
  832. }
  833. if(isset($word->pali['status'])){
  834. $wbwData['word']['status'] = (int)$word->pali['status'];
  835. }
  836. if(isset($word->real['status'])){
  837. $wbwData['real']['status'] = (int)$word->real['status'];
  838. }
  839. if(isset($word->mean['status'])){
  840. $wbwData['meaning']['status'] = (int)$word->mean['status'];
  841. }
  842. if(isset($word->type['status'])){
  843. $wbwData['type']['status'] = (int)$word->type['status'];
  844. }
  845. if(isset($word->gramma['status'])){
  846. $wbwData['grammar']['status'] = (int)$word->gramma['status'];
  847. }
  848. if(isset($word->case['status'])){
  849. $wbwData['case']['status'] = (int)$word->case['status'];
  850. }
  851. if(isset($word->parent['status'])){
  852. $wbwData['parent']['status'] = (int)$word->parent['status'];
  853. }
  854. if(isset($word->org['status'])){
  855. $wbwData['factors']['status'] = (int)$word->org['status'];
  856. }
  857. if(isset($word->om['status'])){
  858. $wbwData['factorMeaning']['status'] = (int)$word->om['status'];
  859. }
  860. $wbwContent[] = $wbwData;
  861. }
  862. }
  863. if(count($wbwContent)===0){
  864. return false;
  865. }
  866. return \json_encode($wbwContent,JSON_UNESCAPED_UNICODE);
  867. }
  868. /**
  869. * 将句子放进结果列表
  870. */
  871. private function pushSent($result,$sent,$level=0,$mode='read'){
  872. $sentProps = base64_encode(\json_encode($sent)) ;
  873. if($mode === 'read'){
  874. $sentWidget = "<MdTpl tpl='sentread' props='{$sentProps}' ></MdTpl>";
  875. }else{
  876. $sentWidget = "<MdTpl tpl='sentedit' props='{$sentProps}' ></MdTpl>";
  877. }
  878. //增加标题的html标记
  879. if($level>0){
  880. $sentWidget = "<h{$level}>".$sentWidget."</h{$level}>";
  881. }
  882. array_push($result,$sentWidget);
  883. return $result;
  884. }
  885. private function newSent($book,$para,$word_start,$word_end){
  886. $sent = [
  887. "id"=>"{$book}-{$para}-{$word_start}-{$word_end}",
  888. "book"=>$book,
  889. "para"=>$para,
  890. "wordStart"=>$word_start,
  891. "wordEnd"=>$word_end,
  892. "origin"=>[],
  893. "translation"=>[],
  894. ];
  895. if($book<1000){
  896. #生成channel 数量列表
  897. $sentId = "{$book}-{$para}-{$word_start}-{$word_end}";
  898. $channelCount = CorpusController::_sentCanReadCount($book,$para,$word_start,$word_end,$this->userUuid);
  899. $path = json_decode(PaliText::where('book',$book)->where('paragraph',$para)->value("path"),true);
  900. $sent["path"] = [];
  901. foreach ($path as $key => $value) {
  902. # code...
  903. $value['paliTitle'] = $value['title'];
  904. $sent["path"][] = $value;
  905. }
  906. $sent["tranNum"] = $channelCount['tranNum'];
  907. $sent["nissayaNum"] = $channelCount['nissayaNum'];
  908. $sent["commNum"] = $channelCount['commNum'];
  909. $sent["originNum"] = $channelCount['originNum'];
  910. $sent["simNum"] = $channelCount['simNum'];
  911. }
  912. return $sent;
  913. }
  914. public static function _sentCanReadCount($book,$para,$start,$end,$userUuid=null){
  915. $keyCanRead="/channel/can-read/";
  916. if($userUuid){
  917. $keyCanRead .= $userUuid;
  918. }else{
  919. $keyCanRead .= 'guest';
  920. }
  921. $channelCanRead = RedisClusters::remember($keyCanRead,
  922. config('mint.cache.expire'),
  923. function() use($userUuid){
  924. return ChannelApi::getCanReadByUser($userUuid);
  925. });
  926. $channels = Sentence::where('book_id',$book)
  927. ->where('paragraph',$para)
  928. ->where('word_start',$start)
  929. ->where('word_end',$end)
  930. ->where('strlen','<>',0)
  931. ->whereIn('channel_uid',$channelCanRead)
  932. ->select('channel_uid')
  933. ->groupBy('channel_uid')
  934. ->get();
  935. $channelList = [];
  936. foreach ($channels as $key => $value) {
  937. # code...
  938. if(Str::isUuid($value->channel_uid)){
  939. $channelList[] = $value->channel_uid;
  940. }
  941. }
  942. $simId = PaliSentence::where('book',$book)
  943. ->where('paragraph',$para)
  944. ->where('word_begin',$start)
  945. ->where('word_end',$end)
  946. ->value('id');
  947. if($simId){
  948. $output["simNum"]=SentSimIndex::where('sent_id',$simId)->value('count');
  949. }else{
  950. $output["simNum"]=0;
  951. }
  952. $channelInfo = Channel::whereIn("uid",$channelList)->select('type')->get();
  953. $output["tranNum"]=0;
  954. $output["nissayaNum"]=0;
  955. $output["commNum"]=0;
  956. $output["originNum"]=0;
  957. foreach ($channelInfo as $key => $value) {
  958. # code...
  959. switch($value->type){
  960. case "translation":
  961. $output["tranNum"]++;
  962. break;
  963. case "nissaya":
  964. $output["nissayaNum"]++;
  965. break;
  966. case "commentary":
  967. $output["commNum"]++;
  968. break;
  969. case "original":
  970. $output["originNum"]++;
  971. break;
  972. }
  973. }
  974. return $output;
  975. }
  976. /**
  977. * 获取某个句子的相关资源的句子数量
  978. */
  979. public static function sentCanReadCount($book,$para,$start,$end,$userUuid=null){
  980. $sentId = "{$book}-{$para}-{$start}-{$end}";
  981. $hKey = "/sentence/res-count/{$sentId}/";
  982. if($userUuid){
  983. $key = $userUuid;
  984. }else{
  985. $key = 'guest';
  986. }
  987. if(Redis::hExists($hKey,$key)){
  988. return json_decode(Redis::hGet($hKey,$key),true);
  989. }else{
  990. $channelCount = CorpusController::_sentCanReadCount($book,$para,$start,$end,$userUuid);
  991. Redis::hSet($hKey,$key,json_encode($channelCount));
  992. return $channelCount;
  993. }
  994. }
  995. private function markdownRender($input){
  996. }
  997. /**
  998. * Update the specified resource in storage.
  999. *
  1000. * @param \Illuminate\Http\Request $request
  1001. * @param \App\Models\Sentence $sentence
  1002. * @return \Illuminate\Http\Response
  1003. */
  1004. public function update(Request $request, Sentence $sentence)
  1005. {
  1006. //
  1007. }
  1008. /**
  1009. * Remove the specified resource from storage.
  1010. *
  1011. * @param \App\Models\Sentence $sentence
  1012. * @return \Illuminate\Http\Response
  1013. */
  1014. public function destroy(Sentence $sentence)
  1015. {
  1016. //
  1017. }
  1018. }