CorpusController.php 38 KB

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