Pārlūkot izejas kodu

Merge pull request #1059 from visuddhinanda/laravel

导出wbw 优化语法信息
visuddhinanda 3 gadi atpakaļ
vecāks
revīzija
aabbff0d10

+ 7 - 4
app/Console/Commands/ExportChannel.php

@@ -1,5 +1,7 @@
 <?php
-
+/**
+ * 导出离线用的channel数据
+ */
 namespace App\Console\Commands;
 
 use Illuminate\Console\Command;
@@ -20,7 +22,7 @@ class ExportChannel extends Command
      *
      * @var string
      */
-    protected $description = 'Command description';
+    protected $description = '导出离线用的channel数据';
 
     /**
      * Create a new command instance.
@@ -39,8 +41,9 @@ class ExportChannel extends Command
      */
     public function handle()
     {
-        Storage::disk('local')->put("public/export/channel.csv", "");
-        $file = fopen(storage_path('app/public/export/channel.csv'),"w");
+        $filename = "public/export/offline/channel.csv";
+        Storage::disk('local')->put($filename, "");
+        $file = fopen(storage_path("app/{$filename}"),"w");
         fputcsv($file,['id','name','type','language','summary','owner_id','setting','created_at']);
         $bar = $this->output->createProgressBar(Channel::where('status',30)->count());
         foreach (Channel::where('status',30)->select(['uid','name','type','lang','summary','owner_uid','setting','created_at'])->cursor() as $chapter) {

+ 3 - 2
app/Console/Commands/ExportChapterIndex.php

@@ -39,8 +39,9 @@ class ExportChapterIndex extends Command
      */
     public function handle()
     {
-        Storage::disk('local')->put("public/export/chapter.csv", "");
-        $file = fopen(storage_path('app/public/export/chapter.csv'),"w");
+        $filename = "public/export/offline/chapter.csv";
+        Storage::disk('local')->put($filename, "");
+        $file = fopen(storage_path("app/{$filename}"),"w");
         fputcsv($file,['id','book','paragraph','language','title','channel_id','progress','updated_at']);
         $bar = $this->output->createProgressBar(ProgressChapter::count());
         foreach (ProgressChapter::select(['uid','book','para','lang','title','channel_id','progress','updated_at'])->cursor() as $chapter) {

+ 3 - 2
app/Console/Commands/ExportNissaya.php

@@ -49,8 +49,9 @@ class ExportNissaya extends Command
             $channels[] = $value->uid;
         }
         $this->info('channel:'.count($channels));
-        Storage::disk('local')->put("public/export/nissaya.csv", "");
-        $file = fopen(storage_path('app/public/export/nissaya.csv'),"w");
+        $filename = "public/export/nissaya.csv";
+        Storage::disk('local')->put($filename, "");
+        $file = fopen(storage_path("app/$filename"),"w");
         $bar = $this->output->createProgressBar(Sentence::whereIn('channel_uid',$channels)->count());
         foreach (Sentence::whereIn('channel_uid',$channels)->select('content')->cursor() as $sent) {
             $lines = explode("\n",$sent->content);

+ 50 - 0
app/Console/Commands/ExportOffline.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+
+class ExportOffline extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'export:offline';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        //导出channel
+        $this->call('export:channel');
+        //导出章节索引
+        $this->call('export:chapter.index');
+        //导出译文
+        $this->call('export:sentence');
+        //导出原文
+        $this->call('export:sentence',['channel'=>'28f2e33a-794f-11ed-9481-1395f6ece2de']);
+        return 0;
+    }
+}

+ 71 - 0
app/Console/Commands/ExportPalitext.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Storage;
+use App\Models\PaliText;
+
+class ExportPalitext extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'export:palitext';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '导出离线用的巴利段落数据';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $filename = "public/export/offline/pali_text.csv";
+        Storage::disk('local')->put($filename, "");
+        $file = fopen(storage_path("app/{$filename}"),"w");
+        fputcsv($file,['id','book','paragraph','level','toc','length','chapter_len','next_chapter','prev_chapter','parent','chapter_strlen']);
+        $bar = $this->output->createProgressBar(PaliText::count());
+        foreach (PaliText::select(['uid','book','paragraph','level','toc','lenght','chapter_len','next_chapter','prev_chapter','parent','chapter_strlen'])
+                    ->orderBy('book')
+                    ->orderBy('paragraph')
+                    ->cursor() as $chapter) {
+            fputcsv($file,[
+                            $chapter->uid,
+                            $chapter->book,
+                            $chapter->paragraph,
+                            $chapter->level,
+                            $chapter->toc,
+                            $chapter->lenght,
+                            $chapter->chapter_len,
+                            $chapter->next_chapter,
+                            $chapter->prev_chapter,
+                            $chapter->parent,
+                            $chapter->chapter_strlen,
+                            ]);
+            $bar->advance();
+        }
+        fclose($file);
+        $bar->finish();
+
+        return 0;
+    }
+}

+ 25 - 7
app/Console/Commands/ExportSentence.php

@@ -5,6 +5,7 @@ namespace App\Console\Commands;
 use Illuminate\Console\Command;
 use Illuminate\Support\Facades\Storage;
 use App\Models\Sentence;
+use App\Models\Channel;
 
 class ExportSentence extends Command
 {
@@ -13,7 +14,7 @@ class ExportSentence extends Command
      *
      * @var string
      */
-    protected $signature = 'export:sentence';
+    protected $signature = 'export:sentence {--channel=} {--type=translation}';
 
     /**
      * The console command description.
@@ -39,20 +40,37 @@ class ExportSentence extends Command
      */
     public function handle()
     {
-        Storage::disk('local')->put("public/export/sentence.csv", "");
-        $file = fopen(storage_path('app/public/export/sentence.csv'),"w");
+        $channels = [];
+        $channel_id = $this->option('channel');
+        if($channel_id){
+            $file_suf = $channel_id;
+            $channels[] = $channel_id;
+        }else{
+            $file_suf = $channel_type;
+            $channel_type = $this->option('type');
+            $nissaya_channel = Channel::where('type',$channel_type)->where('status',30)->select('uid')->get();
+            foreach ($nissaya_channel as $key => $value) {
+                # code...
+                $channels[] = $value->uid;
+            }
+        }
+        $db = Sentence::whereIn('channel_uid',$channels);
+        $file_name = "public/export/offline/sentence_{$file_suf}.csv";
+        Storage::disk('local')->put($file_name, "");
+        $file = fopen(storage_path("app/{$file_name}"),"w");
         fputcsv($file,['id','book','paragraph','word_start','word_end','content','content_type','html','channel_id','editor_id','language','updated_at']);
-        $bar = $this->output->createProgressBar(Sentence::where('status',30)->count());
-        foreach (Sentence::where('status',30)->select(['uid','book_id','paragraph','word_start','word_end','content','content_type','channel_uid','editor_uid','language','updated_at'])->cursor() as $chapter) {
+        $bar = $this->output->createProgressBar($db->count());
+        foreach ($db->select(['uid','book_id','paragraph','word_start','word_end','content','content_type','channel_uid','editor_uid','language','updated_at'])->cursor() as $chapter) {
+            $content = str_replace("\n","<br />",$chapter->content);
             fputcsv($file,[
                             $chapter->uid,
                             $chapter->book_id,
                             $chapter->paragraph,
                             $chapter->word_start,
                             $chapter->word_end,
-                            $chapter->content,
+                            $content,
                             $chapter->content_type,
-                            $chapter->content,
+                            $content,
                             $chapter->channel_uid,
                             $chapter->editor_uid,
                             $chapter->language,

+ 61 - 0
app/Console/Commands/ExportTag.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Storage;
+use App\Models\Tag;
+
+class ExportTag extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'export:tag';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $filename = "public/export/offline/tag.csv";
+        Storage::disk('local')->put($filename, "");
+        $file = fopen(storage_path("app/{$filename}"),"w");
+        fputcsv($file,['id','name','description','color','owner_id']);
+        $bar = $this->output->createProgressBar(Tag::count());
+        foreach (Tag::select(['id','name','description','color','owner_id'])->cursor() as $chapter) {
+            fputcsv($file,[
+                            $chapter->id,
+                            $chapter->name,
+                            $chapter->description,
+                            $chapter->color,
+                            $chapter->owner_id,
+                            ]);
+            $bar->advance();
+        }
+        fclose($file);
+        $bar->finish();
+        return 0;
+    }
+}

+ 59 - 0
app/Console/Commands/ExportTagmap.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Storage;
+use App\Models\TagMap;
+class ExportTagmap extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'export:tag.map';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return int
+     */
+    public function handle()
+    {
+        $filename = "public/export/offline/tag_map.csv";
+        Storage::disk('local')->put($filename, "");
+        $file = fopen(storage_path("app/{$filename}"),"w");
+        fputcsv($file,['id','table_name','anchor_id','tag_id']);
+        $bar = $this->output->createProgressBar(TagMap::count());
+        foreach (TagMap::select(['id','table_name','anchor_id','tag_id'])->cursor() as $chapter) {
+            fputcsv($file,[
+                            $chapter->id,
+                            $chapter->table_name,
+                            $chapter->anchor_id,
+                            $chapter->tag_id,
+                            ]);
+            $bar->advance();
+        }
+        fclose($file);
+        $bar->finish();
+        return 0;
+    }
+}

+ 11 - 1
app/Console/Commands/TestMdRender.php

@@ -47,7 +47,17 @@ class TestMdRender extends Command
         $markdown .= "{{168-916-10-37}}";
         $markdown .= "{{exercise|1|((168-916-10-37))}}";
 
-        echo MdRender::render($markdown,'00ae2c48-c204-4082-ae79-79ba2740d506');
+        $markdown2 = "# heading [[isipatana]] \n\n";
+        $markdown2 .= "{{exercise\n|id=1\n|content={{168-916-10-37}}}}";
+        $markdown2 .= "{{exercise\n|id=2\n|content=# ddd}}";
+
+        //echo MdRender::render($markdown,'00ae2c48-c204-4082-ae79-79ba2740d506');
+        $wiki = MdRender::markdown2wiki($markdown2);
+        $xml = MdRender::wiki2xml($wiki);
+        $html = MdRender::xmlQueryId($xml, "1");
+        $sent = MdRender::take_sentence($html);
+        print_r($sent);
+        //echo MdRender::render2($markdown2,'00ae2c48-c204-4082-ae79-79ba2740d506','2');
         return 0;
     }
 }

+ 4 - 0
app/Console/Kernel.php

@@ -19,6 +19,10 @@ class Kernel extends ConsoleKernel
                  ->dailyAt('00:00')
                  ->emailOutputTo(config("app.email.ScheduleEmailOutputTo"))
 				 ->emailOutputOnFailure(config("app.email.ScheduleEmailOutputOnFailure"));
+
+        $schedule->command('export:offline')
+                 ->weekly()
+                 ->emailOutputOnFailure(config("app.email.ScheduleEmailOutputOnFailure"));
     }
 
     /**

+ 168 - 9
app/Http/Api/MdRender.php

@@ -10,18 +10,150 @@ use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\Log;
 
 class MdRender{
-    /**
-     *
-     */
-    public static function render($markdown,$channelId,$isArticle=false){
+    public static function wiki2xml(string $wiki):string{
+        /**
+         * 替换{{}} 到xml之前 要先把换行符号去掉
+         */
+        $html = str_replace("\n","",$wiki);
+
+        $pattern = "/\{\{(.+?)\|/";
+        $replacement = '<MdTpl name="$1"><param>';
+        $html = preg_replace($pattern,$replacement,$html);
+        $html = str_replace("}}","</param></MdTpl>",$html);
+        $html = str_replace("|","</param><param>",$html);
+
+        /**
+         * 替换变量名
+         */
+
+        $pattern = "/<param>([a-z]+?)=/";
+        $replacement = '<param name="$1">';
+        $html = preg_replace($pattern,$replacement,$html);
+
+        $html = str_replace("<p>","<div>",$html);
+        $html = str_replace("</p>","</div>",$html);
+        $html = "<xml>".$html."</xml>";
+        return $html;
+    }
+    public static function xmlQueryId(string $xml, string $id):string{
+        $dom = simplexml_load_string($xml);
+        $tpl_list = $dom->xpath('//MdTpl');
+        foreach ($tpl_list as $key => $tpl) {
+            foreach ($tpl->children() as  $param) {
+                # 处理每个参数
+                if($param->getName() === "param"){
+                    foreach($param->attributes() as $pa => $pa_value){
+                        $pValue = $pa_value->__toString();
+                        if($pa === "name" && $pValue === "id"){
+                            if($param->__toString() === $id){
+                                return $tpl->asXML();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return "<div></div>";
+    }
+    public static function take_sentence(string $xml):array{
+        $output = [];
+        $dom = simplexml_load_string($xml);
+        $tpl_list = $dom->xpath('//MdTpl');
+        foreach ($tpl_list as $key => $tpl) {
+            foreach($tpl->attributes() as $a => $a_value){
+                if($a==="name"){
+                    if($a_value->__toString() ==="sent"){
+                        foreach ($tpl->children() as  $param) {
+                            # 处理每个参数
+                            if($param->getName() === "param"){
+                                $sent = $param->__toString();
+                                if(!empty($sent)){
+                                    $output[] = $sent;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return $output;
+    }
+    public static function xml2tpl(string $xml, $channelId=""):string{
+        /**
+         * 解析xml
+         * 获取模版参数
+         * 生成react 组件参数
+         */
+        $dom = simplexml_load_string($xml);
+        $tpl_list = $dom->xpath('//MdTpl');
+        foreach ($tpl_list as $key => $tpl) {
+            /**
+             * 遍历 MdTpl 处理参数
+             */
+            $props = [];
+            $tpl_name = '';
+            foreach($tpl->attributes() as $a => $a_value){
+                if($a==="name"){
+                    $tpl_name = $a_value;
+                }
+            }
+            $param_id = 0;
+            foreach ($tpl->children() as  $param) {
+                # 处理每个参数
+                if($param->getName() === "param"){
+                    $param_id++;
+                    $props["{$param_id}"] = $param->__toString();
+                    foreach($param->attributes() as $pa => $pa_value){
+                        if($pa === "name"){
+                            $props["{$pa_value}"] = $param->__toString();
+                        }
+                    }
+                }
+            }
+            /**
+             * 生成模版参数
+             */
+            $tplRender = new TemplateRender($props,$channelId,'edit');
+            $tplProps = $tplRender->render($tpl_name);
+            if($tplProps){
+                $tpl->addAttribute("props",$tplProps['props']);
+                $tpl->addAttribute("tpl",$tplProps['tpl']);
+                $tpl->addChild($tplProps['tag'],$tplProps['html']);
+            }
+        }
+        $html = str_replace('<?xml version="1.0"?>','',$dom->asXML()) ;
+        $html = str_replace(['<xml>','</xml>'],['<span>','</span>'],$html);
+        return $html;
+    }
 
+    public static function render2($markdown,$channelId='',$queryId=null){
+        $wiki = MdRender::markdown2wiki($markdown);
+        $html = MdRender::wiki2xml($wiki);
+        if(!is_null($queryId)){
+            $html = MdRender::xmlQueryId($html, $queryId);
+        }
+        $tpl = MdRender::xml2tpl($html,$channelId);
+        return $tpl;
+    }
+    public static function markdown2wiki(string $markdown): string{
+        /**
+         * 替换换行符
+         * react 无法处理 <br> 替换为<div></div>代替换行符作用
+         */
+        $markdown = str_replace('<br>','<div></div>',$markdown);
+
+        /**
+         * markdown -> html
+         */
         $html = Str::markdown($markdown);
+
         #替换术语
         $pattern = "/\[\[(.+?)\]\]/";
         $replacement = '{{term|$1}}';
         $html = preg_replace($pattern,$replacement,$html);
 
-        #替换句子
+        #替换句子模版
         $pattern = "/\{\{([0-9].+?)\}\}/";
         $replacement = '{{sent|$1}}';
         $html = preg_replace($pattern,$replacement,$html);
@@ -29,14 +161,31 @@ class MdRender{
         #替换注释
         #<code>bla</code>
         #{{note:bla}}
-        #替换术语
         $pattern = '/<code>(.+?)<\/code>/';
         $replacement = '{{note|$1}}';
         $html = preg_replace($pattern,$replacement,$html);
+        return $html;
+    }
+
+    /**
+     *
+     */
+    public static function render($markdown,$channelId,$queryId=null){
+        return MdRender::render2($markdown,$channelId,$queryId);
+
+        $html = MdRender::markdown2wiki($markdown);
 
+        /**
+         * 转换为Mustache模版
+         */
         $pattern = "/\{\{(.+?)\}\}/";
         $replacement = "\n{{#function}}\n$1\n{{/function}}\n";
         $html = preg_replace($pattern,$replacement,$html);
+
+        /**
+         * Mustache_Engine 处理Mustache模版
+         * 把Mustache模版内容转换为react组件
+         */
         $m = new \Mustache_Engine(array('entity_flags' => ENT_QUOTES));
         $html = $m->render($html, array(
           'function' => function($text) use($m,$channelId) {
@@ -81,9 +230,18 @@ class MdRender{
                 case 'sent':
                     $tplName = "sentedit";
                     $innerString = "";
-                    $sentId = trim($param[1]);
+                    $sentInfo = explode('@',trim($param[1]));
+                    $sentId = $sentInfo[0];
                     $Sent = new CorpusController();
-                    $html = $Sent->getSentTpl($param[1],[$channelId]);
+                    if(empty($channelId)){
+                        $channels = [];
+                    }else{
+                        $channels = [$channelId];
+                    }
+                    if(isset($sentInfo[1])){
+                        $channels = [$sentInfo[1]];
+                    }
+                    $html = $Sent->getSentTpl($param[1],$channels);
                     return $html;
                     break;
                 case 'quote':
@@ -148,6 +306,7 @@ class MdRender{
             $html = \str_replace(['<p>','</p>'],'',$html);
         }
         //LOG::info($html);
-        return $html;
+        return "<xml>{$html}</xml>";
     }
+
 }

+ 18 - 1
app/Http/Api/StudioApi.php

@@ -5,12 +5,29 @@ require_once __DIR__.'/../../../public/app/ucenter/function.php';
 
 class StudioApi{
     public static function getIdByName($name){
+        //TODO 改为studio table
+        if(empty($name)){
+            return false;
+        }
         $userinfo = new \UserInfo();
-        return $userinfo->getUserByName($name)['userid'];
+        $studio = $userinfo->getUserByName($name);
+        if($studio){
+            return $userinfo->getUserByName($name)['userid'];
+        }else{
+            return false;
+        }
+
     }
     public static function getById($id){
+        //TODO 改为studio table
+        if(empty($name)){
+            return false;
+        }
         $userinfo = new \UserInfo();
         $studio = $userinfo->getName($id);
+        if(!$studio){
+            return false;
+        }
         return [
             'id'=>$id,
             'nickName'=>$studio['nickname'],

+ 200 - 0
app/Http/Api/TemplateRender.php

@@ -0,0 +1,200 @@
+<?php
+namespace App\Http\Api;
+
+use App\Models\DhammaTerm;
+use App\Models\PaliText;
+use App\Http\Controllers\CorpusController;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Log;
+
+class TemplateRender{
+    protected $param = [];
+    protected $mode = "read";
+    protected $channel_id = "";
+
+    /**
+     * Create a new command instance.
+     * int $mode  'read' | 'edit'
+     * @return void
+     */
+    public function __construct($param, $channel_id, $mode)
+    {
+        $this->param = $param;
+        $this->channel_id = $channel_id;
+        $this->mode = $mode;
+    }
+
+    public function render($tpl_name){
+        switch ($tpl_name) {
+            case 'term':
+                # 术语
+                $result = $this->render_term();
+                break;
+            case 'note':
+                $result = $this->render_note();
+                break;
+            case 'sent':
+                $result = $this->render_sent();
+                break;
+            case 'quote':
+                $result = $this->render_quote();
+                break;
+            case 'exercise':
+                $result = $this->render_exercise();
+                break;
+            default:
+                # code...
+                $result = [
+                    'props'=>base64_encode(\json_encode([])),
+                    'html'=>'',
+                    'tag'=>'span',
+                    'tpl'=>'unknown',
+                ];
+                break;
+        }
+        return $result;
+    }
+
+    private function render_term(){
+        $word = $this->get_param($this->param,"word",1);
+        $channelId = $this->channel_id;
+        $props = Cache::remember("/term/{$this->channel_id}/{$word}",
+              60,
+              function() use($word,$channelId){
+                $tplParam = DhammaTerm::where("word",$word)->first();
+                $output = [
+                    "word" => $word,
+                    "channel" => $channelId,
+                    ];
+                    $innerString = $output["word"];
+                if($tplParam){
+                    $output["id"] = $tplParam->guid;
+                    $output["meaning"] = $tplParam->meaning;
+                    $innerString = "{$output["meaning"]}({$output["word"]})";
+                    if(!empty($tplParam->other_meaning)){
+                        $output["meaning2"] = $tplParam->other_meaning;
+                    }
+                }
+                $output['innerHtml'] = $innerString;
+                return $output;
+              });
+        return [
+            'props'=>base64_encode(\json_encode($props)),
+            'html'=>$props['innerHtml'],
+            'tag'=>'span',
+            'tpl'=>'term',
+            ];
+    }
+
+    private  function render_note(){
+
+        $props = ["note" => $this->get_param($this->param,"text",1)];
+        $trigger = $this->get_param($param,"trigger",2);
+        if(!empty($trigger)){
+            $props["trigger"] = $trigger;
+            $innerString = $props["trigger"];
+        }
+        return [
+            'props'=>base64_encode(\json_encode($props)),
+            'html'=>$innerString,
+            'tag'=>'span',
+            'tpl'=>'note',
+            ];
+    }
+
+    private  function render_exercise(){
+
+        $id = $this->get_param($this->param,"id",1);
+        $title = $this->get_param($this->param,"title",1);
+        $props = [
+                    "id" => $id,
+                    "title" => $title,
+                    "channel" => $this->channel_id,
+                ];
+
+        return [
+            'props'=>base64_encode(\json_encode($props)),
+            'html'=>"",
+            'tag'=>'span',
+            'tpl'=>'exercise',
+            ];
+    }
+
+    private  function render_quote(){
+        $paraId = $this->get_param($this->param,"para",1);
+        $channelId = $this->channel_id;
+        $props = Cache::remember("/quote/{$channelId}/{$paraId}",
+              60,
+              function() use($paraId,$channelId){
+                $para = \explode('-',$paraId);
+                $output = [
+                    "paraId" => $paraId,
+                    "channel" => $channelId,
+                    "innerString" => $paraId,
+                    ];
+                if(count($para)<2){
+                    return $output;
+                }
+                $PaliText = PaliText::where("book",$para[0])
+                                    ->where("paragraph",$para[1])
+                                    ->select(['toc','path'])
+                                    ->first();
+
+                if($PaliText){
+                    $output["pali"] = $PaliText->toc;
+                    $output["paliPath"] = \json_decode($PaliText->path);
+                    $output["innerString"]= $PaliText->toc;
+                }
+                return $output;
+              });
+        return [
+            'props'=>base64_encode(\json_encode($props)),
+            'html'=>$props["innerString"],
+            'tag'=>'span',
+            'tpl'=>'quote',
+            ];
+    }
+    private  function render_sent(){
+
+        $sid = $this->get_param($this->param,"sid",1);
+        $channel = $this->get_param($this->param,"channel",2);
+        if(!empty($channel)){
+            $mChannel = $channel;
+        }else{
+            $mChannel = $this->channel_id;
+        }
+        $sentInfo = explode('@',trim($sid));
+        $sentId = $sentInfo[0];
+        if(empty($mChannel)){
+            $channels = [];
+        }else{
+            $channels = [$mChannel];
+        }
+        if(isset($sentInfo[1])){
+            $channels = [$sentInfo[1]];
+        }
+        $Sent = new CorpusController();
+        $props = $Sent->getSentTpl($sentId,$channels,$this->mode,true);
+        if($this->mode==='read'){
+            $tpl = "sentread";
+        }else{
+            $tpl = "sentedit";
+        }
+        return [
+            'props'=>base64_encode(\json_encode($props)),
+            'html'=>"",
+            'tag'=>'span',
+            'tpl'=>$tpl,
+            ];
+    }
+
+    private  function get_param(array $param,string $name,int $id,string $default=''){
+        if(isset($param[$name])){
+            return trim($param[$name]);
+        }else if(isset($param["{$id}"])){
+            return trim($param["1"]);
+        }else{
+            return $default;
+        }
+    }
+}

+ 17 - 18
app/Http/Controllers/ArticleController.php

@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
 use App\Models\Article;
 use Illuminate\Http\Request;
 use Illuminate\Support\Str;
+use App\Http\Resources\ArticleResource;
 
 class ArticleController extends Controller
 {
@@ -130,26 +131,24 @@ class ArticleController extends Controller
     public function show(Request  $request,Article $article)
     {
         //
-        if($article){
-            if($article->status<30){
-                //私有文章,判断权限
-                $user = \App\Http\Api\AuthApi::current($request);
-                if($user){
-                    //判断当前用户是否有指定的studio的权限
-                    if($user['user_uid'] !== $article->owner){
-                        //非所有者
-                        //TODO 判断是否协作
-                        return $this->error(__('auth.failed'));
-                    }
-                }else{
-                    return $this->error(__('auth.failed'));
-                }
-            }
-            return $this->ok($article);
-        }else{
+        if(!$article){
             return $this->error("no recorder");
         }
-
+        if($article->status<30){
+            //私有文章,判断权限
+            $user = \App\Http\Api\AuthApi::current($request);
+            if(!$user){
+                //判断当前用户是否有指定的studio的权限
+                return $this->error(__('auth.failed'));
+            }
+            if($user['user_uid'] !== $article->owner){
+                //非所有者
+                return $this->error(__('auth.failed'));
+            }else{
+                //TODO 判断是否协作
+            }
+        }
+        return $this->ok(new ArticleResource($article));
     }
 
     /**

+ 1 - 0
app/Http/Controllers/AuthController.php

@@ -89,6 +89,7 @@ class AuthController extends Controller
             $userinfo = new \UserInfo();
 		    $username = $userinfo->getName($curr['user_uid']);
             $user = [
+                "id"=>$curr['user_uid'],
                 "nickName"=> $username['nickname'],
                 "realName"=> $username['username'],
                 "avatar"=> "",

+ 6 - 3
app/Http/Controllers/CollectionController.php

@@ -88,8 +88,11 @@ class CollectionController extends Controller
                     $value->childrenNumber = 0;
                 }
 
-                if(isset($value->article_list)){
-                    $result[$key]->article_list = array_slice(\json_decode($value->article_list),0,4);
+                if(isset($value->article_list) && !empty($value->article_list) ){
+                    $arrList = \json_decode($value->article_list);
+                    if(is_array($arrList)){
+                        $result[$key]->article_list = array_slice($arrList,0,4);
+                    }
                 }
                 $value->studio = [
                     'id'=>$value->owner,
@@ -158,7 +161,7 @@ class CollectionController extends Controller
     public function show(Request  $request,$id)
     {
         //
-		$indexCol = ['uid','title','subtitle','summary','article_list','owner','lang','updated_at','created_at'];
+		$indexCol = ['uid','title','subtitle','summary','article_list','status','owner','lang','updated_at','created_at'];
 
 		$result  = Collection::select($indexCol)->where('uid', $id)->first();
 		if($result){

+ 22 - 7
app/Http/Controllers/CorpusController.php

@@ -69,7 +69,7 @@ class CorpusController extends Controller
     {
         //
     }
-    public function getSentTpl($id,$channels){
+    public function getSentTpl($id,$channels,$mode='edit',$onlyProps=false){
         $sent = [];
         $sentId = \explode('-',$id);
         $channelId = ChannelApi::getSysChannel('_System_Wbw_VRI_');
@@ -79,15 +79,25 @@ class CorpusController extends Controller
         $record = Sentence::select($this->selectCol)
         ->where('book_id',$sentId[0])
         ->where('paragraph',$sentId[1])
-        ->where('word_start',$sentId[2])
-        ->where('word_end',$sentId[3])
+        ->where('word_start',(int)$sentId[2])
+        ->where('word_end',(int)$sentId[3])
         ->whereIn('channel_uid',$channels)
         ->get();
         Log::info("sent count:".count($record));
+
+
         $channelIndex = $this->getChannelIndex($channels);
 
-        $content = $this->makeContent($record,"edit",$channelIndex);
-        return $content;
+        //获取wbw channel
+        //目前默认的 wbw channel 是第一个translation channel
+        foreach ($channels as  $channel) {
+            # code...
+            if($channelIndex[$channel]->type==='translation'){
+                $this->wbwChannels[] = $channel;
+                break;
+            }
+        }
+        return $this->makeContent($record,$mode,$channelIndex,[],$onlyProps);
     }
     /**
      * Display the specified resource.
@@ -260,7 +270,7 @@ class CorpusController extends Controller
      * $indexChannel channel索引
      * $indexedHeading 标题索引 用于给段落加标题标签 <h1> ect.
      */
-    private function makeContent($record,$mode,$indexChannel,$indexedHeading=[]){
+    private function makeContent($record,$mode,$indexChannel,$indexedHeading=[],$onlyProps=false){
         $content = [];
 		$lastSent = "0-0";
 		$sentCount = 0;
@@ -382,10 +392,15 @@ class CorpusController extends Controller
 			}
 
 			$sentCount++;
+        }
+        if($onlyProps){
+            return $sent;
         }
 		$content = $this->pushSent($content,$sent,0,$mode);
-        return \implode("",$content);
+        $output = \implode("",$content);
+        return "<xml>{$output}</xml>";
     }
+
 	private function pushSent($result,$sent,$level=0,$mode='read'){
 
 		$sentProps = base64_encode(\json_encode($sent)) ;

+ 39 - 12
app/Http/Controllers/CourseController.php

@@ -20,8 +20,32 @@ class CourseController extends Controller
     {
         //
 		$result=false;
-		$indexCol = ['id','title','subtitle','cover','content','content_type','teacher','start_at','end_at','updated_at','created_at'];
+		$indexCol = ['id','title','subtitle','cover','content','content_type','teacher','start_at','end_at','publicity','updated_at','created_at'];
 		switch ($request->get('view')) {
+            case 'new':
+                //最新公开课程列表
+                $table = Course::where('publicity', 30);
+                break;
+            case 'open':
+                /**
+                 * 开放课程列表
+                 * 开放规则:
+                 * 1. 公开
+                 * 2. 课程开始时间比现在时间晚
+                 */
+                $table = Course::where('publicity', 30)
+                            ->whereDate('start_at',">",date("Y-m-d",strtotime("today")));
+                break;
+            case 'close':
+                /**
+                 * 已经关闭课程列表
+                 * 判定规则:
+                 * 1. 公开
+                 * 2. 课程开始时间比现在时间早
+                 */
+                $table = Course::where('publicity', 30)
+                        ->whereDate('start_at',"<=",date("Y-m-d",strtotime("today")));
+                break;
             case 'create':
 	            # 获取 studio 建立的所有 course
                 $user = AuthApi::current($request);
@@ -42,7 +66,7 @@ class CourseController extends Controller
                 }
                 //我学习的课程
                 $course = CourseMember::where('user_id',$user["user_uid"])
-                                      ->where('role','member')
+                                      ->where('role','student')
                                       ->select('course_id')
                                       ->get();
                 $courseId = [];
@@ -59,7 +83,7 @@ class CourseController extends Controller
                     return $this->error(__('auth.failed'));
                 }
                 $course = CourseMember::where('user_id',$user["user_uid"])
-                ->where('role','manager')
+                ->where('role','assistant')
                 ->select('course_id')
                 ->get();
                 $courseId = [];
@@ -78,11 +102,7 @@ class CourseController extends Controller
         if(isset($_GET["order"]) && isset($_GET["dir"])){
             $table = $table->orderBy($_GET["order"],$_GET["dir"]);
         }else{
-            if($request->get('view') === 'studio_list'){
-                $table = $table->orderBy('count','desc');
-            }else{
-                $table = $table->orderBy('updated_at','desc');
-            }
+            $table = $table->orderBy('updated_at','desc');
         }
 
         if(isset($_GET["limit"])){
@@ -113,9 +133,13 @@ class CourseController extends Controller
         $create = Course::where('studio_id', $user["user_uid"])->count();
         //我学习的课程
         $study = CourseMember::where('user_id',$user["user_uid"])
-        ->where('role','member')
+        ->where('role','student')
+        ->count();
+        //我任教的课程
+        $teach = CourseMember::where('user_id',$user["user_uid"])
+        ->where('role','assistant')
         ->count();
-        return $this->ok(['create'=>$create,'teach'=>0,'study'=>$study]);
+        return $this->ok(['create'=>$create,'teach'=>$teach,'study'=>$study]);
     }
     /**
      * Store a newly created resource in storage.
@@ -186,9 +210,12 @@ class CourseController extends Controller
         }
         $course->title = $request->get('title');
         $course->subtitle = $request->get('subtitle');
-        $course->cover = $request->get('cover');
+        if($request->has('cover')) {$course->cover = $request->get('cover');}
         $course->content = $request->get('content');
-        $course->teacher = $request->get('teacher_id');
+        if($request->has('teacher_id')) {$course->teacher = $request->get('teacher_id');}
+        if($request->has('anthology_id')) {$course->anthology_id = $request->get('anthology_id');}
+        $course->channel_id = $request->get('channel_id');
+        if($request->has('publicity')) {$course->publicity = $request->get('publicity');}
         $course->start_at = $request->get('start_at');
         $course->end_at = $request->get('end_at');
         $course->save();

+ 59 - 0
app/Http/Controllers/CourseMemberController.php

@@ -130,6 +130,43 @@ class CourseMemberController extends Controller
     public function update(Request $request, CourseMember $courseMember)
     {
         //
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+
+        if($request->has('channel_id')) {
+            if($courseMember->user_id !== $user['user_uid']){
+                return $this->error(__('auth.failed'));
+            }
+            $courseMember->channel_id = $request->get('channel_id');
+        }
+        $courseMember->save();
+        return $this->ok(new CourseMemberResource($courseMember));
+
+    }
+    public function set_channel(Request $request)
+    {
+        //
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+
+        if($request->has('channel_id')) {
+            $courseMember = CourseMember::where('course_id',$request->get('course_id'))
+                                        ->where('user_id',$user['user_uid'])
+                                        ->first();
+            if($courseMember){
+                $courseMember->channel_id = $request->get('channel_id');
+                $courseMember->save();
+                return $this->ok(new CourseMemberResource($courseMember));
+            }else{
+                return $this->error(__('auth.failed'));
+            }
+        }
+
+
     }
 
     /**
@@ -166,4 +203,26 @@ class CourseMemberController extends Controller
         $delete = $courseMember->delete();
         return $this->ok($delete);
     }
+
+    /**
+     * 获取当前用户权限
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function curr(Request $request)
+    {
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
+        }
+        $courseUser = CourseMember::where('course_id',$request->get("course_id"))
+                ->where('user_id',$user["user_uid"])
+                ->select(['role','channel_id'])->first();
+        if($courseUser){
+            return $this->ok($courseUser);
+        }else{
+            return $this->error("not member");
+        }
+    }
 }

+ 171 - 0
app/Http/Controllers/ExerciseController.php

@@ -0,0 +1,171 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\Course;
+use App\Models\CourseMember;
+use App\Models\Article;
+use App\Models\WbwBlock;
+use App\Models\Wbw;
+use App\Models\Discussion;
+use App\Models\Sentence;
+use Illuminate\Http\Request;
+use App\Http\Api\MDRender;
+use App\Http\Api\UserApi;
+
+class ExerciseController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        /**
+         * 列出某个练习所有人的提交情况
+         * 情况包括
+         * 1.作业填充百分比
+         * 2.问题数量
+         */
+        $validated = $request->validate([
+            'course_id' => 'required',
+            'article_id' => 'required',
+            'exercise_id' => 'required',
+        ]);
+        $output = [];
+        //课程信息
+        $course = Course::findOrFail($validated['course_id']);
+
+        //查询练习句子编号
+        $article = Article::where('uid',$validated['article_id'])->value('content');
+
+        $wiki = MdRender::markdown2wiki($article);
+        $xml = MdRender::wiki2xml($wiki);
+        $html = MdRender::xmlQueryId($xml, $validated['exercise_id']);
+        $sentences = MdRender::take_sentence($html);
+
+        //获取课程答案逐词解析列表
+        $answerWbw = [];
+        foreach ($sentences as  $sent) {
+            # code...wbw
+            $sentId = explode('-',$sent);
+            if(count($sentId)<4){
+                break;
+            }
+            $courseWb = WbwBlock::where('book_id',$sentId[0])
+                            ->where('paragraph',$sentId[1])
+                            ->where('channel_uid',$course->channel_id)
+                            ->value('uid');
+            if($courseWb){
+                $wbwId = Wbw::where('block_uid',$courseWb)
+                    ->whereBetween('wid',[$sentId[2],$sentId[3]])
+                    ->select('uid')->get();
+                foreach ($wbwId as $id) {
+                    # code...
+                    $answerWbw[] = $id->uid;
+                }
+            }
+        }
+        $members = CourseMember::where('course_id',$validated['course_id'])
+                            ->where('role','student')
+                            ->select(['user_id','channel_id'])
+                            ->get();
+        foreach ($members as  $member) {
+            # code...
+            $data = [
+                'user' => UserApi::getById($member->user_id),
+                'wbw' => 0,
+                'translation' => 0,
+                'question' => 0,
+                'html' => ""
+            ];
+            if(!empty($member->channel_id)){
+                //
+                foreach ($sentences as  $sent) {
+                    # code...wbw
+                    $sentId = explode('-',$sent);
+                    if(count($sentId)<4){
+                        break;
+                    }
+                    $wb = WbwBlock::where('book_id',$sentId[0])
+                            ->where('paragraph',$sentId[1])
+                            ->where('channel_uid',$member->channel_id)
+                            ->value('uid');
+                    if($wb){
+                        $wbwCount = Wbw::where('block_uid',$wb)
+                            ->whereBetween('wid',[$sentId[2],$sentId[3]])
+                            ->where('status','>',4)
+                            ->count();
+                        $data['wbw'] += $wbwCount;
+                    }
+                    //translation
+                    $sentCount = Sentence::where('book_id',$sentId[0])
+                            ->where('paragraph',$sentId[1])
+                            ->where('word_start',$sentId[2])
+                            ->where('word_end',$sentId[3])
+                            ->where('channel_uid',$member->channel_id)
+                            ->count();
+                    $data['translation'] += $sentCount;
+                    //discussion
+                    //查找答案的wbw 对应的discussion
+                    $discussionCount = Discussion::whereIn('res_id',$answerWbw)
+                            ->where('editor_uid',$member->user_id)
+                            ->whereNull('parent')
+                            ->count();
+                    $data['question'] += $discussionCount;
+
+                    $tpl = MdRender::xml2tpl($html,$member->channel_id);
+                    $data['html'] .= $tpl;
+                }
+            }
+            $output[] = $data;
+        }
+        return $this->ok(["rows"=>$output,"count"=>count($output)]);
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\Course  $course
+     * @return \Illuminate\Http\Response
+     */
+    public function show(Course $course)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\Course  $course
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, Course $course)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\Models\Course  $course
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Course $course)
+    {
+        //
+    }
+}

+ 11 - 6
app/Http/Controllers/ExportWbwController.php

@@ -53,18 +53,23 @@ class ExportWbwController extends Controller
 
                 $wordsList = $xmlWord->xpath('//word');
                 foreach ($wordsList as $word) {
-                    $type = $word->type->__toString();
+                    $pali = $word->real->__toString();
+                    $case = explode("#",$word->case->__toString()) ;
+                    $type = $case[0];
+                    $grammar = $case[1];
+                    $grammar = str_replace("null","",$grammar);
                     $style = $word->style->__toString();
-                    if($type !== '.ctl.' && $style !== 'note'){
+                    $factormeaning = str_replace("
","",$word->om->__toString());
+                    $factormeaning = str_replace("↓↓","",$factormeaning);
+                    if($type !== '.ctl.' && $style !== 'note' && !empty($pali)){
                         $sent['data'][]=[
                             'pali'=>$word->real->__toString(),
-                            'mean' => $word->mean->__toString(),
+                            'mean' => str_replace("
","",$word->mean->__toString()),
                             'type' => ltrim($type,'.'),
-                            'grammar' => ltrim(str_replace('$.',',',$word->gramma->__toString()),'.') ,
-                            'case' => ltrim(str_replace(['$.','#.'],[' ',' '],$word->case->__toString()),'.') ,
+                            'grammar' => ltrim(str_replace('$.',',',$grammar),'.') ,
                             'parent' => $word->parent->__toString(),
                             'factors' => $word->org->__toString(),
-                            'factormeaning' => $word->om->__toString()
+                            'factormeaning' => $factormeaning
                         ];
                     }
 

+ 31 - 6
app/Http/Controllers/PaliTextController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
 
 use Illuminate\Support\Facades\DB;
 use App\Models\PaliText;
+use App\Models\BookTitle;
 use App\Models\Tag;
 use App\Models\TagMap;
 use Illuminate\Http\Request;
@@ -139,7 +140,17 @@ class PaliTextController extends Controller
                 break;
 
             case 'book-toc':
-                //获取全书目录
+                /**
+                 * 获取全书目录
+                 * 2023-1-25 改进算法
+                 * 需求:目录显示丛书以及此丛书下面的所有书。比如,选择清净道论的一个章节。显示清净道论两本书的目录
+                 * 算法:
+                 * 1. 查询这个目录的顶级目录
+                 * 2. 查询book-title 获取丛书名
+                 * 3. 根据从书名找到全部的书
+                 * 4. 获取全部书的目录
+                 */
+
                 $path = PaliText::where('book',$request->get('book'))
                                 ->where('paragraph',$request->get('para'))
                                 ->select('path')->first();
@@ -162,12 +173,26 @@ class PaliTextController extends Controller
                 $rootPara = PaliText::where('book',$root->book)
                                 ->where('paragraph',$root->paragraph)
                                 ->first();
+                $book_title = BookTitle::where('book',$rootPara->book)->where('paragraph',$rootPara->paragraph)->value('title');
+                $books = BookTitle::where('title',$book_title)->get();
+                $chapters = [];
+                $chapters[] = ['book'=>0,'paragraph'=>0,'toc'=>$book_title,'level'=>1];
+                foreach ($books as  $book) {
+                    # code...
+                    $rootPara = PaliText::where('book',$book->book)
+                                ->where('paragraph',$book->paragraph)
+                                ->first();
+                    $table = PaliText::where('book',$rootPara->book)
+                                    ->whereBetween('paragraph',[$rootPara->paragraph,($rootPara->paragraph+$rootPara->chapter_len-1)])
+                                    ->where('level','<',8);
+                    $all_count = $table->count();
+                    $curr_chapters = $table->select(['book','paragraph','toc','level'])->orderBy('paragraph')->get();
+                    foreach ($curr_chapters as  $chapter) {
+                        # code...
+                        $chapters[] = ['book'=>$chapter->book,'paragraph'=>$chapter->paragraph,'toc'=>$chapter->toc,'level'=>($chapter->level+1)];
+                    }
+                }
 
-                $table = PaliText::where('book',$rootPara->book)
-                                ->whereBetween('paragraph',[$rootPara->paragraph,($rootPara->paragraph+$rootPara->chapter_len-1)])
-                                ->where('level','<',8);
-                $all_count = $table->count();
-                $chapters = $table->select(['book','paragraph','toc','level'])->orderBy('paragraph')->get();
                 break;
             }
         if($chapters){

+ 80 - 0
app/Http/Resources/ArticleResource.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Http\Api\MdRender;
+use App\Http\Api\UserApi;
+use App\Models\CourseMember;
+use App\Models\Course;
+use Illuminate\Support\Facades\Log;
+
+class ArticleResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        $data = [
+            "uid" => $this->uid,
+            "title" => $this->title,
+            "subtitle" => $this->subtitle,
+            "summary" => $this->summary,
+            "studio"=> \App\Http\Api\StudioApi::getById($this->owner),
+            "editor"=> \App\Http\Api\UserApi::getById($this->editor_id),
+            "status" => $this->status,
+            "lang" => $this->lang,
+            "created_at" => $this->created_at,
+            "updated_at" => $this->updated_at,
+        ];
+        if(isset($this->content) && !empty($this->content)){
+            if($request->has('channel')){
+                $channel = $request->get('channel');
+            }else{
+                $channel = '';
+            }
+            $data["content"] = $this->content;
+            $data["content_type"] = $this->content_type;
+            $query_id = null;
+            if($request->has('course')){
+                if($request->has('exercise')){
+                    $query_id = $request->get('exercise');
+                    if($request->has('user')){
+                        /**
+                         * 显示指定用户作业
+                         * 查询用户在课程中的channel
+                         */
+                        $userId = UserApi::getIdByName($request->get('user'));
+                        Log::info("userId:{$userId}");
+
+                        $userInCourse = CourseMember::where('course_id',$request->get('course'))
+                                    ->where('user_id',$userId)
+                                    ->first();
+                        if($userInCourse){
+                            $channel = $userInCourse->channel_id;
+                        }
+                    }else if($request->get('view')==="answer"){
+                        /**
+                         * 显示答案
+                         * 算法:查询course 答案 channel
+                         */
+                        $channel = Course::where('id',$request->get('course'))->value('channel_id');
+                    }else{
+                        //显示答案
+                        $channel = Course::where('id',$request->get('course'))->value('channel_id');
+                    }
+                }else{
+                    $channel = Course::where('id',$request->get('course'))->value('channel_id');
+                }
+            }
+            Log::info("channel:{$channel}");
+            Log::info("query_id:{$query_id}");
+            $data["html"] = MdRender::render($this->content,$channel,$query_id);
+        }
+        return $data;
+    }
+}

+ 23 - 3
app/Http/Resources/CourseResource.php

@@ -4,6 +4,10 @@ namespace App\Http\Resources;
 
 use Illuminate\Http\Resources\Json\JsonResource;
 use App\Http\Api\UserApi;
+use App\Http\Api\StudioApi;
+use App\Models\Collection;
+use App\Models\Channel;
+use App\Models\CourseMember;
 
 class CourseResource extends JsonResource
 {
@@ -15,21 +19,37 @@ class CourseResource extends JsonResource
      */
     public function toArray($request)
     {
-        return [
+        $data = [
             "id"=>$this->id,
             "title"=> $this->title,
             "subtitle"=> $this->subtitle,
             "teacher"=> UserApi::getById($this->teacher),
             "course_count"=>10,
-            "type"=> 1,
-            "anthology_id"=> '',
+            "member_count"=>CourseMember::where('course_id',$this->id)->count(),
+            "publicity"=> $this->publicity,
             "start_at"=> $this->start_at,
             "end_at"=> $this->end_at,
             "content"=> $this->content,
             "content_type"=> $this->content_type,
             "cover"=> $this->cover,
+            "channel_id"=>$this->channel_id,
             "created_at"=> $this->created_at,
             "updated_at"=> $this->updated_at,
         ];
+        $textbook = Collection::where('uid',$this->anthology_id)->select(['uid','title','owner'])->first();
+        if($textbook){
+            $data['anthology_id'] = $textbook->uid;
+            $data['anthology_title'] = $textbook->title;
+            $data['anthology_owner'] = StudioApi::getById($textbook->owner);
+        }
+        if(!empty($this->channel_id)){
+            $channel = Channel::where('uid',$this->channel_id)->select(['name','owner_uid'])->first();
+            if($channel){
+                $data['channel_name'] = $channel->name;
+                $data['channel_owner'] = StudioApi::getById($channel->owner_uid);
+            }
+        }
+
+        return $data;
     }
 }

+ 0 - 1
app/Http/Resources/SentResource.php

@@ -2,7 +2,6 @@
 
 namespace App\Http\Resources;
 
-use Illuminate\Support\Str;
 use App\Http\Api\MdRender;
 use Illuminate\Http\Resources\Json\JsonResource;
 

+ 38 - 0
database/migrations/2023_01_24_131456_add_anthology_in_courses.php

@@ -0,0 +1,38 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AddAnthologyInCourses extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('courses', function (Blueprint $table) {
+            //
+            $table->uuid('anthology_id')->nullable()->index();
+			$table->integer('publicity')->default(10)->index();
+            $table->uuid('channel_id')->nullable()->index();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('courses', function (Blueprint $table) {
+            //
+            $table->dropColumn('anthology_id');
+            $table->dropColumn('publicity');
+            $table->dropColumn('channel_id');
+        });
+    }
+}

+ 12 - 10
resources/views/export_wbw.blade.php

@@ -19,16 +19,18 @@
             <h3>{{ $sent["sid"] }}</h3>
             <div>
             @foreach ($sent["data"] as $wbw)
-            <b>{{$wbw["pali"]}}</b>
-            <span>{{$wbw["type"]}}</span>
-            <span>{{$wbw["grammar"]}}</span>
-            <span>{{$wbw["case"]}}</span>
-            <span> of </span>
-            <span>{{$wbw["parent"]}}</span>
-            <span>{{$wbw["mean"]}}</span>
-            <span style="color:gray;">
-            <span>({{$wbw["factors"]}}</span>
-            <span>{{$wbw["factormeaning"]}})</span>
+            <b>{{$wbw["pali"]}}:</b>
+            <span class='type' style="font-style: italic;">{{$wbw["type"]}}</span>
+            <span class='grammar' style="font-style: italic;">{{$wbw["grammar"]}}</span>
+            @if(!empty($wbw["grammar"]) && !empty($wbw["parent"]))
+                <span class='of'> of </span>
+            @endif
+            <span class="parent" >{{$wbw["parent"]}} / </span>
+
+            <span class='meaning'>{{$wbw["mean"]}}</span>
+            <span class="factors" style="color:gray;">
+                <span>({{$wbw["factors"]}}</span>
+                <span>{{$wbw["factormeaning"]}})</span>
             </span>
             @endforeach
             </div>

+ 4 - 0
routes/api.php

@@ -31,6 +31,7 @@ use App\Http\Controllers\GroupMemberController;
 use App\Http\Controllers\ShareController;
 use App\Http\Controllers\CourseController;
 use App\Http\Controllers\CourseMemberController;
+use App\Http\Controllers\ExerciseController;
 
 /*
 |--------------------------------------------------------------------------
@@ -90,8 +91,11 @@ Route::group(['prefix' => 'v2'],function(){
     Route::apiResource('wbwlookup',WbwLookupController::class);
     Route::apiResource('course',CourseController::class);
     Route::apiResource('course-member',CourseMemberController::class);
+    Route::put('course-member_set-channel',[CourseMemberController::class,'set_channel']);
     Route::get('course-my-course', [CourseController::class, 'showMyCourseNumber']);
+    Route::get('course-curr', [CourseMemberController::class, 'curr']);
 
+    Route::apiResource('exercise',ExerciseController::class);
 
     Route::get('guide/{lang}/{file}', function ($lang,$file) {
         $filename = public_path("app/users_guide/{$lang}/{$file}.md");