diff --git a/application/api/controller/Article.php b/application/api/controller/Article.php index 1a2b808..e124feb 100644 --- a/application/api/controller/Article.php +++ b/application/api/controller/Article.php @@ -351,7 +351,7 @@ class Article extends Base $aAiReview = array(); if (!empty($res)) { $aArticleId = array_column($res, 'article_id'); - $aAiReview = Db::table('t_article_ai_review')->field('article_id,content')->whereIn('article_id', $aArticleId)->column('article_id,content'); + $aAiReview = Db::table('t_article_ai_review')->field('article_id,content')->whereIn('article_id', $aArticleId)->where('journal_scope_assessment','<>','')->column('article_id,content'); } //查询AI审核内容 chengxiaoling 20250328 end @@ -3215,7 +3215,7 @@ class Article extends Base } $info = $this->article_author_obj->where("art_aut_id", $data['art_aut_id'])->find(); if ($info['sort'] < 2) { - return jsonError("fail"); + return jsonError("Upward adjustment: Failed to obtain the location of the exchanged author"); } $info_1 = $this->article_author_obj->where("article_id", $info['article_id'])->where("state", 0)->where("sort", $info['sort'] - 1)->find(); $this->article_author_obj->where("art_aut_id", $info['art_aut_id'])->setDec("sort"); @@ -3238,7 +3238,7 @@ class Article extends Base $info = $this->article_author_obj->where("art_aut_id", $data['art_aut_id'])->find(); $info_1 = $this->article_author_obj->where("article_id", $info['article_id'])->where("state", 0)->where("sort", $info['sort'] + 1)->find(); if ($info_1 == null) { - return jsonError("fail"); + return jsonError("Downward adjustment:Failed to obtain the location of the exchanged author"); } $this->article_author_obj->where("art_aut_id", $info['art_aut_id'])->setInc("sort"); $this->article_author_obj->where("art_aut_id", $info_1['art_aut_id'])->setDec("sort"); @@ -3699,6 +3699,14 @@ class Article extends Base if ($black) { return jsonError("Your account is currently blacklisted by TMR Publishing Group. Please contact the official email of the journal you wish to submit to for further clarification."); } + + //验证前三步骤是否完成 + $aArticleState = $this->getArticleStateThree($article_info); + $iStatus = empty($aArticleState['status']) ? 0 : $aArticleState['status']; + $sMsg = empty($aArticleState['msg']) ? '' : $aArticleState['msg']; + if($iStatus != 1){ + return jsonError($sMsg); + } //稿件号 $sbbr = empty($journal_info['abbr']) ? '' : $journal_info['abbr']; $sArticleType = empty($article_info['type']) ? '' : $article_info['type']; @@ -3856,18 +3864,20 @@ class Article extends Base $this->article_obj->where('article_id', $data['article_id'])->update($update_l); $this->ai_scor($data['article_id']); - //判断是否有文章领域 进行更新操作 chengxiaoling 20250722 start - $iArticleId = empty($data['article_id']) ? 0 : $data['article_id'];//文章ID - if(!empty($sMajorData) && !empty($iArticleId)){ - $this->updateArticleField(['article_id' => $iArticleId,'article_field' => $sMajorData]); - } - //判断是否有文章领域 进行更新操作 chengxiaoling 20250722 end //AI初审队列 chengxiaoling 20250815 start + $iArticleId = empty($data['article_id']) ? 0 : $data['article_id'];//文章ID if(!empty($iArticleId)){ $aQueueParam = ['article_id' => $iArticleId]; $sQueueId = \think\Queue::push('app\api\job\ArticleReview@fire', $aQueueParam, 'ArticleReview'); } //AI初审队列 chengxiaoling 20250815 end + + //判断是否有文章领域 进行更新操作 chengxiaoling 20250722 start + if(!empty($sMajorData) && !empty($iArticleId)){ + $this->updateArticleField(['article_id' => $iArticleId,'article_field' => $sMajorData]); + } + //判断是否有文章领域 进行更新操作 chengxiaoling 20250722 end + return json(['code' => 0]); } @@ -5394,6 +5404,7 @@ class Article extends Base //第二步 查询作者 $iSecondStatus = 1; $is_super = $is_report = 2; + $sSecondMsg = $sSecondReportMsg = ''; // if($iFirstStatus == 1){ $aWhere = ['article_id' => $iArticleId,'state' => 0]; $aAuthorData = DB::name('article_author')->where($aWhere)->select(); @@ -5404,11 +5415,18 @@ class Article extends Base foreach ($aAuthorData as $key => $value) { if(empty($value['email']) || empty($value['author_title']) || empty($value['company']) || empty($value['firstname']) || empty($value['lastname'])){ $iSecondStatus = 2; + $sSecondMsg = 'Step 2: The author\'s information is incomplete'; break; } if($is_report != 1){ - if($value['is_report'] == 1 && !empty($value['orcid'])){ - $is_report = 1; + if($value['is_report'] == 1){ + if(empty($value['orcid'])){ + $sSecondReportMsg = 'Step 2: Please fill in the ORCID of at least one corresponding author'; + } + if(!empty($value['orcid'])){ + $is_report = 1; + $sSecondReportMsg = ''; + } } } if($is_super != 1){ @@ -5421,7 +5439,7 @@ class Article extends Base } // } if($iSecondStatus == 2){ - $sSecondMsg = 'Step 2: The author\'s required information is incomplete'; + $sSecondMsg = empty($sSecondMsg) ? 'Step 2: The author\'s information is incomplete' : $sSecondMsg; } if($iSecondStatus == 1){ if($is_super == 2){ @@ -5429,7 +5447,7 @@ class Article extends Base $iSecondStatus = 2; } if($is_report == 2){ - $sSecondMsg = empty($sSecondMsg) ? 'Step 2: Please select the first author' : 'Step 2: Please select the corresponding/first author'; + $sSecondMsg = empty($sSecondReportMsg) ? 'Step 2: Please select the corresponding author' : $sSecondReportMsg; $iSecondStatus = 2; } } @@ -5702,14 +5720,14 @@ class Article extends Base } //参数判空 - $sMajorData = empty($aParam['major']) ? '' : $aParam['major'];//文章领域 + $sMajorData = isset($aParam['major']) ? $aParam['major'] : '-1' ;//文章领域 // if(empty($sMajorData) && empty($aUpdate)){ // return json_encode(['status' => 6,'msg' => 'Update data to empty']); // } //文章领域处理 $aMajorInsert = $aMajorDelete = []; - if(!empty($sMajorData)){ + if(!empty($sMajorData) && is_string($sMajorData) && $sMajorData != -1){ $aField = explode(',', $sMajorData); $aWhere = ['state' => 0, 'article_id' => $iArticleId]; $aArticleMajor = Db::name('major_to_article')->where($aWhere)->order('major_id asc')->column('major_id'); @@ -5720,34 +5738,41 @@ class Article extends Base //数据处理 if (!empty($aInsert)) {//新增 foreach ($aInsert as $key => $value) { + $value = intval($value); + if(empty($value)){ + continue; + } $aMajorInsert[] = ['major_id' => $value, 'article_id' => $iArticleId, 'ctime' => time(), 'state' => 0]; } } } - - //转投信息 - $aTransferJournalId = empty($aParam['checkedjours']) ? [] : $aParam['checkedjours']; - if(!empty($aTransferJournalId) && is_string($aTransferJournalId)){ - $aTransferJournalId = explode(',', $aTransferJournalId); - } - $aJournalInsert = $aJournalDelete = []; - if(!empty($aTransferJournalId)){ - //查询转投期刊ID + if($sMajorData == ''){//为空删除 $aWhere = ['state' => 0, 'article_id' => $iArticleId]; - $aTransferJournal = Db::name('article_transfer')->where($aWhere)->order('journal_id asc')->column('journal_id'); - //新增 - $aInsert = array_diff($aTransferJournalId, $aTransferJournal); - //删除 - $aJournalDelete = array_diff($aTransferJournal, $aTransferJournalId); - if (!empty($aInsert)) {//新增 - foreach ($aInsert as $key => $value) { - $aJournalInsert[] = ['journal_id' => $value, 'article_id' => $iArticleId, 'ctime' => time(), 'state' => 0]; - } - } + $aMajorDelete = Db::name('major_to_article')->where($aWhere)->column('major_id'); } + //转投信息 + // $aTransferJournalId = empty($aParam['checkedjours']) ? [] : $aParam['checkedjours']; + // if(!empty($aTransferJournalId) && is_string($aTransferJournalId)){ + // $aTransferJournalId = explode(',', $aTransferJournalId); + // } + // $aJournalInsert = $aJournalDelete = []; + // if(!empty($aTransferJournalId)){ + // //查询转投期刊ID + // $aWhere = ['state' => 0, 'article_id' => $iArticleId]; + // $aTransferJournal = Db::name('article_transfer')->where($aWhere)->order('journal_id asc')->column('journal_id'); + // //新增 + // $aInsert = array_diff($aTransferJournalId, $aTransferJournal); + // //删除 + // $aJournalDelete = array_diff($aTransferJournal, $aTransferJournalId); + // if (!empty($aInsert)) {//新增 + // foreach ($aInsert as $key => $value) { + // $aJournalInsert[] = ['journal_id' => $value, 'article_id' => $iArticleId, 'ctime' => time(), 'state' => 0]; + // } + // } + // } //推荐审稿人 - $aReviewer = isset($aParam['reviewers']) ? $aParam['reviewers'] : []; + $aReviewer = isset($aParam['reviewers']) ? $aParam['reviewers'] : '-1'; // $json = '{ // "article_id": "5997", // "user_id": "54", @@ -5786,7 +5811,7 @@ class Article extends Base // }'; // $aReviewer = json_decode($json,true); // $aReviewer = $aReviewer['reviewers']; - if(!empty($aReviewer)){ + if(!empty($aReviewer) && is_array($aReviewer)){ //根据用户邮箱查询是否注册 $aEmail = array_unique(array_column($aReviewer, 'email')); @@ -5877,6 +5902,10 @@ class Article extends Base $aUserRecommendDelete = array_diff($aUserRecommend,$aUserRecommendId); } } + if($aReviewer == ''){ + $aWhere = ['article_id' => $iArticleId]; + $aUserRecommendDelete = Db::name('user_reviewer_recommend')->where($aWhere)->column('reviewer_id'); + } Db::startTrans(); if(!empty($aMajorInsert)){//新增领域 $add_result = Db::name('major_to_article')->insertAll($aMajorInsert); @@ -5885,13 +5914,13 @@ class Article extends Base $aWhere = ['article_id' => $iArticleId, 'state' => 0, 'major_id' => ['in', $aMajorDelete]]; $delete_result = Db::name('major_to_article')->where($aWhere)->limit(count($aMajorDelete))->update(['state' => 1, 'ctime' => time()]); } - if(!empty($aJournalInsert)){//新增转投期刊 - $add_journal_result = Db::name('article_transfer')->insertAll($aJournalInsert); - } - if(!empty($aJournalDelete)){//删除转投期刊 - $aWhere = ['article_id' => $iArticleId, 'state' => 0, 'journal_id' => ['in', $aJournalDelete]]; - $delete_journal_result = Db::name('article_transfer')->where($aWhere)->limit(count($aJournalDelete))->delete(); - } + // if(!empty($aJournalInsert)){//新增转投期刊 + // $add_journal_result = Db::name('article_transfer')->insertAll($aJournalInsert); + // } + // if(!empty($aJournalDelete)){//删除转投期刊 + // $aWhere = ['article_id' => $iArticleId, 'state' => 0, 'journal_id' => ['in', $aJournalDelete]]; + // $delete_journal_result = Db::name('article_transfer')->where($aWhere)->limit(count($aJournalDelete))->delete(); + // } //更新数据 if(!empty($aUpdate)){//更新文章主表数据 //数据更新 @@ -5915,7 +5944,7 @@ class Article extends Base } if(!empty($aUserRecommendDelete)){ $aWhere = ['urr_state' => 0,'article_id' => $iArticleId,'reviewer_id' => ['in',$aUserRecommendDelete]]; - $sDelete = Db::name('user_reviewer_recommend')->where($aWhere)->limit(count($aUserRecommendDelete))->delete(); + $sDelete = Db::name('user_reviewer_recommend')->where($aWhere)->limit(count($aUserRecommendDelete))->update(['urr_state' => 1]); } Db::commit(); @@ -6043,4 +6072,131 @@ class Article extends Base $p = self::getMajorShuList($res['pid']); return $p . ',' . $res['major_id']; } + /** + * 按步骤获取文章的状态 + */ + private function getArticleStateThree($aArticle = []){ + + if(empty($aArticle)){ + return ['status' => 2,'msg' => 'The article does not exist']; + } + + //第一步验证 + $aFirst = ['title','type','abstrart','journal_id']; + $iFirstStatus = 1; + $sFirstMsg = $sSecondMsg = $sThreeMsg = $sFourMsg = ''; + foreach ($aFirst as $key => $value) { + if(empty($aArticle[$value])){ + $iFirstStatus = 2; + $sFirstMsg = 'Step 1: Required fields incomplete'.$value; + break; + } + } + if($iFirstStatus == 2){ + return ['status' => 3,'msg' => $sFirstMsg]; + } + //判断伦理 + if(!empty($aArticle['approval']) && $aArticle['approval'] == 1 && empty($aArticle['approval_file'])){ + $iFirstStatus = 2; + $sFirstMsg = 'Step 1: Ethics documents not uploaded'; + return ['status' => 3,'msg' => $sFirstMsg]; + } + if(isset($aArticle['approval']) && $aArticle['approval'] == 0 && empty($aArticle['approval_content'])){ + $iFirstStatus = 2; + $sFirstMsg = 'Step 1: Please explain the lack of ethical recognition'; + return ['status' => 3,'msg' => $sFirstMsg]; + } + + //查询manuscirpt文件是否上传 + $iArticleId = empty($aArticle['article_id']) ? 0 : $aArticle['article_id']; + $aWhere = ['article_id' => $iArticleId,'state' => 0,'type_name' => 'manuscirpt']; + $aFile = Db::name('article_file')->where($aWhere)->order('ctime desc')->find(); + if(empty($aFile)){ + $iFirstStatus = 2; + $sFirstMsg = 'Step 1: The manuscirpt file has not been uploaded'; + return ['status' => 3,'msg' => $sFirstMsg]; + } + + + //第二步 查询作者 + $iSecondStatus = 1; + $is_super = 2; + $is_report = 2; + $sSecondMsg = $sSecondReportMsg = ''; + $aWhere = ['article_id' => $iArticleId,'state' => 0]; + $aAuthorData = DB::name('article_author')->where($aWhere)->select(); + if(empty($aAuthorData)){ + $iSecondStatus = 2; + } + if(!empty($aAuthorData)){ + foreach ($aAuthorData as $key => $value) { + if(empty($value['email']) || empty($value['author_title']) || empty($value['company']) || empty($value['firstname']) || empty($value['lastname'])){ + $iSecondStatus = 2; + break; + } + if($is_report != 1){ + if($value['is_report'] == 1){ + if(empty($value['orcid'])){ + $sSecondReportMsg = 'Step 2: Please fill in the ORCID of at least one corresponding author'; + } + if(!empty($value['orcid'])){ + $is_report = 1; + $sSecondReportMsg = ''; + } + } + } + if($is_super != 1){ + if($value['is_super'] == 1){ + $is_super = 1; + } + } + } + + } + if($iSecondStatus == 2){ + $sSecondMsg = 'Step 2: The author\'s information is incomplete'; + return ['status' => 3,'msg' => $sSecondMsg]; + } + if($iSecondStatus == 1){ + if($is_super == 2){ + $sSecondMsg = 'Step 2: Please select the first author'; + $iSecondStatus = 2; + return ['status' => 3,'msg' => $sSecondMsg]; + } + if($is_report == 2){ + $sSecondMsg = empty($sSecondReportMsg) ? 'Step 2: Please select the corresponding author' : $sSecondReportMsg; + $iSecondStatus = 2; + return ['status' => 3,'msg' => $sSecondMsg]; + } + } + + //第三步 + $iThreeStatus = 1; + $sThreeMsg = ''; + if($aArticle['is_use_ai'] == 3){//验证是否使用AI + $sThreeMsg = 'Step 3: Please check whether to use AI technology'; + $iThreeStatus = 2; + return ['status' => 3,'msg' => $sThreeMsg]; + } + if($aArticle['is_use_ai'] == 1 && empty($aArticle['use_ai_explain'])){//验证是否填写AI说明 + $sThreeMsg = 'Step 3: Please enter supplementary instructions'; + $iThreeStatus = 2; + return ['status' => 3,'msg' => $sThreeMsg]; + } + if($aArticle['is_figure_copyright'] == 3){//验证是否上传版权声明 + $sThreeMsg = 'Step 3: Please check whether to upload the figure copyright statement'; + $iThreeStatus = 2; + return ['status' => 3,'msg' => $sThreeMsg]; + } + if($aArticle['is_figure_copyright'] == 1){//验证是否上传版权声明 + $aWhere = ['article_id' => $iArticleId,'state' => 0,'type_name' => 'figurecopyright']; + $aFile = Db::name('article_file')->where($aWhere)->order('ctime desc')->find(); + if(empty($aFile)){ + $sThreeMsg = 'Step 3: Please upload the figure copyright declaration file'; + $iThreeStatus = 2; + return ['status' => 3,'msg' => $sThreeMsg]; + } + } + return ['status' => 1,'msg' => 'success']; + } } diff --git a/application/api/controller/Contribute.php b/application/api/controller/Contribute.php index 7241510..43aa59e 100644 --- a/application/api/controller/Contribute.php +++ b/application/api/controller/Contribute.php @@ -82,7 +82,7 @@ class Contribute extends Base //获取数据 $aDealData = json_decode(ArticleParserService::uploadAndParse($sFileUrl),true); $iStatus = empty($aDealData['status']) ? 0 : $aDealData['status']; - $sMsg = empty($aDealData['msg']) ? 'fail' : $aDealData['msg']; + $sMsg = empty($aDealData['msg']) ? 'Content parsing failed' : $aDealData['msg']; if($iStatus != 1){ return json_encode(['status' => 5, 'msg' => $sMsg]); } @@ -96,7 +96,6 @@ class Contribute extends Base if(!empty($aArticle)){ return json_encode(['code' => 7, 'msg' => 'Warning: you are re-submitting the article!']); } - //数据入库 $aData += $aParam; $result = $this->_addData($aData); @@ -121,34 +120,36 @@ class Contribute extends Base //插入基础表 t_article $aInsert = []; //标题 - if(empty($aArticle['title'])){ + // if(empty($aArticle['title'])){ $sTitile = empty($aParam['title']) ? '' : $aParam['title']; if(!empty($sTitile)){ - $aInsert['title'] = $sTitile; + $aInsert['title'] = is_string($sTitile) ? strip_tags($sTitile) : ''; } - } + // } //关键词 - if(empty($aArticle['keywords'])){ + // if(empty($aArticle['keywords'])){ $sKeyWords = empty($aParam['keywords']) ? '' : $aParam['keywords']; if(!empty($sKeyWords)){ $aInsert['keywords'] = is_array($sKeyWords) ? implode(',', $sKeyWords) : $sKeyWords; + $aInsert['keywords'] = is_string($aInsert['keywords']) ? str_replace(';', ',', $aInsert['keywords']) : ''; + } - } + // } //摘要 - if(empty($aArticle['abstrart'])){ + // if(empty($aArticle['abstrart'])){ $sAbstrart = empty($aParam['abstrart']) ? '' : $aParam['abstrart']; if(!empty($sAbstrart)){ - $aInsert['abstrart'] = $sAbstrart; + $aInsert['abstrart'] = is_string($sAbstrart) ? strip_tags($sAbstrart) : ''; } - } + // } //基金 - if(empty($aArticle['fund'])){ + // if(empty($aArticle['fund'])){ $sFund = empty($aParam['fund']) ? '' : $aParam['fund']; - if(!empty($sAbstrart)){ - $aInsert['fund'] = $sFund; + if(!empty($sFund)){ + $aInsert['fund'] = is_string($sFund) ? strip_tags($sFund) : ''; } - } + // } //期刊ID $iJournalId = empty($aParam['journal_id']) ? 0 : $aParam['journal_id']; if(!empty($iJournalId)){ @@ -182,19 +183,14 @@ class Contribute extends Base $aInsert['state'] = -1; $aInsert['is_use_ai'] = 3; $aInsert['is_figure_copyright'] = 3; - $aInsert['is_transfer'] = 3; + // $aInsert['is_transfer'] = 3; $aInsert['is_become_reviewer'] = 3; + $sType = empty($aParam['type']) ? 'D' : $aParam['type']; + $aInsert['accept_sn'] = getArticleSN('Draft',$sType); $iArticleId = Db::name('article')->insertGetId($aInsert); if(empty($iArticleId)){ return json_encode(['status' => 3,'msg' => 'Article added successfully']); } - //获取accept_sn并更新 - $sAcceptSn = $this->getArticleAcceptSn(['article_id' => $iArticleId]); - if(!empty($sAcceptSn)){ - $aWhere = ['article_id' => $iArticleId]; - $aUpdate = ['accept_sn' => $sAcceptSn]; - $update_result = Db::name('article')->where($aWhere)->limit(1)->update($aUpdate); - } } if(!empty($aArticle) && !empty($aInsert)){//更新 $aWhere = ['article_id' => $iUpdateArticleId,'state' => ['in',[-1,3]]]; @@ -210,11 +206,58 @@ class Contribute extends Base } } - //作者单位 - $iArticleId = empty($iArticleId) ? $iUpdateArticleId : $iArticleId; - $aCompany = empty($aParam['company']) ? [] : $aParam['company']; + //处理作者 + $aCompanyId = $aAuthor = [];//作者机构ID + if(!empty($aParam['author'])){ + $aAuthor = $aParam['author']; + foreach ($aAuthor as $key => $value) { + if(empty($iArticleId)){ + break; + } + //处理作者名字 + $aName = empty($value['name']) ? [] : explode(' ', $value['name']); + if(empty($aName)){ + continue; + } + //名字拆分 + $sOldLastName = array_pop($aName); + $letters = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z' + ]; + if(in_array(strtolower($sOldLastName), $letters)){ + $value['company_id'] = [$sOldLastName]; + $sLastName = array_pop($aName); + if(!empty($value['superscript'])){ + $value['is_super'] = strpos($value['superscript'], '1') !== false ? 1 : 0; + $value['superscript'] = $sOldLastName.$value['superscript']; + } - if(!empty($aCompany)){ + }else{ + $sLastName = $sOldLastName; + } + $value['firstname'] = empty($aName) ? '' : trim(implode(' ', $aName)); + if(empty($value['firstname'])){ + continue; + } + if(ctype_upper($value['firstname'][0]) == false){ + continue; + } + $value['lastname'] = $sLastName; + $aAuthor[$key] = $value; + if(!empty($value['company_id'])){ + $aCompanyId[] = implode(',', $value['company_id']); + } + } + } + $aCompanyId = empty($aCompanyId) ? [] : array_unique(explode(',', implode(',', $aCompanyId))); + + //文章机构 + $iArticleId = empty($iArticleId) ? $iUpdateArticleId : $iArticleId; + if(!empty($aCompanyId)){ + $aCompany = empty($aParam['company']) ? [] : $aParam['company']; $aWhere = ['article_id' => $iArticleId,'state' => 0]; $aOrgan = Db::name('article_organ')->field('organ_id,organ_name,sort')->where($aWhere)->select(); $aOrganName = empty($aOrgan) ? [] : array_column($aOrgan, 'organ_name'); @@ -225,17 +268,19 @@ class Contribute extends Base if(empty($value)){ continue; } + if(!in_array($key, $aCompanyId)){ + continue; + } $sName = trim(trim(trim($value,'*'),'#')); if(in_array($sName, $aOrganName)){ continue; } - if(empty($iMaxSort)){ - $iSort = $key; - $aSort[$key] = $key; - }else{ - $iMaxSort++; - $iSort = $iMaxSort; - $aSort[$key] = $iMaxSort; + $iMaxSort++; + $iSort = $iMaxSort; + $aSort[$key] = $iMaxSort; + $sName = is_string($sName) ? strip_tags($sName) : ''; + if(empty($sName)){ + continue; } $aCompanyInsert[] = ['article_id' => $iArticleId,'organ_name' =>$sName,'create_time' => time(),'sort' => $iSort]; } @@ -246,34 +291,66 @@ class Contribute extends Base return json_encode(['status' => 3,'msg' => 'Article institution insertion failed']); } $aWhere = ['article_id' => $iArticleId,'state' => 0]; - $aCompanyData = Db::name('article_organ')->where($aWhere)->column('sort,organ_id'); + $aCompanyData = Db::name('article_organ')->field('sort,organ_id,organ_name')->where($aWhere)->select(); + $aCompanyData = empty($aCompanyData) ? [] : array_column($aCompanyData, null,'sort'); } //处理作者 $aAuthorData = $aAuthorOrgn = []; - if(!empty($aParam['author'])){ - $aAuthor = $aParam['author']; + if(!empty($aAuthor)){ //通讯作者 $aCorresponding = empty($aParam['corresponding']) ? [] : array_column($aParam['corresponding'], null,'name'); //查询文章作者 $aWhere = ['article_id' => $iArticleId,'state' => 0]; - $aAuthorList = Db::name('article_author')->field('art_aut_id,firstname')->where($aWhere)->select(); + $aAuthorList = Db::name('article_author')->field('art_aut_id,firstname,sort')->where($aWhere)->select(); $aAuthorNameList = empty($aAuthorList) ? [] : array_column($aAuthorList, 'firstname'); + $iMaxSort = empty($aAuthorList) ? 0 : max(array_column($aAuthorList, 'sort')); foreach ($aAuthor as $key => $value) { - if(empty($iArticleId)){ - break; + //处理作者名 + if(empty($value['firstname'])){ + continue; } + if(ctype_upper($value['firstname'][0]) == false){ + continue; + } //处理作者机构单位关联 + $aCountry = []; if(!empty($value['company_id'])){ foreach ($value['company_id'] as $k => $v) { $iNewSort = empty($aSort[$v]) ? $v : $aSort[$v]; - $iOrgnId = empty($aCompanyData[$iNewSort]) ? 0 : $aCompanyData[$iNewSort]; + $iOrgnId = empty($aCompanyData[$iNewSort]['organ_id']) ? 0 : $aCompanyData[$iNewSort]['organ_id']; if(empty($iOrgnId)){ continue; } - $aAuthorOrgn[] = ['article_id' => $iArticleId,'organ_id' => $iOrgnId,'art_aut_id' => $value['name']]; + + //获取 + $sOrganName = empty($aCompanyData[$iNewSort]['organ_name']) ? 0 : $aCompanyData[$iNewSort]['organ_name']; + if(!empty($sOrganName)){ + $sOrganName = str_replace([', ',','], ',', $sOrganName); + $aOrganName = explode(',',$sOrganName); + + $aCountry[] = empty($aOrganName) ? '' : strtolower(end($aOrganName)); + } + $aAuthorOrgn[] = ['article_id' => $iArticleId,'organ_id' => $iOrgnId,'art_aut_id' => $value['firstname']]; } } - $value['firstname'] = empty($value['name']) ? '' : trim($value['name']); + + //处理城市 + $aCountry = empty($aCountry) ? [] : array_unique($aCountry); + $value['country'] = ''; + if(count($aCountry) == 1){ + $value['country'] = empty($aCountry[0]) ? '' : ucfirst($aCountry[0]); + } + if(count($aCountry) > 1){ + // 1. 找到目标值的键名 + $key = array_search('china', $aCountry); + // 2. 确认找到后删除(避免键名为 0 时被误判为 false) + if ($key !== false) { + unset($aCountry[$key]); + } + $aCountry = empty($aCountry) ? [] : array_values($aCountry); + $value['country'] = empty($aCountry[0]) ? '' : ucfirst($aCountry[0]); + } + //已添加 if(!empty($value['firstname']) && in_array($value['firstname'], $aAuthorList)){ continue; @@ -281,7 +358,10 @@ class Contribute extends Base $value['email'] = empty($aCorresponding[$value['name']]['email']) ? '' : $aCorresponding[$value['name']]['email']; $value['article_id'] = $iArticleId; unset($value['name'],$value['company_id']); - $aAuthorData[$key] = $value; + $iMaxSort++; + $value['sort'] = $iMaxSort; + $value['country'] = empty($value['country']) ? '' : ucfirst(str_replace(['Pr '], '', $value['country'])); + $aAuthorData[] = $value; } } if(!empty($aAuthorData)){ @@ -318,42 +398,31 @@ class Contribute extends Base continue; } $value['art_aut_id'] = $iAuthorId; + $value['create_time'] = time(); $aAuthorOrgnInsert[] = $value; } } + if(!empty($aAuthorOrgnInsert)){ $author_orgn_result = DB::name('article_author_organ')->insertAll($aAuthorOrgnInsert); if(empty($author_orgn_result)){ return json_encode(['status' => 3,'msg' => 'Adding article author orgn failed']); } } - // //处理上传文件 - // if(!empty($sFileUrl) && !empty($iUserId)){ - // $aInsertFile['article_id'] = $iArticleId; - // $aInsertFile['user_id'] = $iUserId; - // $aInsertFile['username'] = empty($aUser['account']) ? '' : $aUser['account']; - // $aInsertFile['file_url'] = trim($sFileUrl,'/'); - // $aInsertFile['type_name'] = 'manuscirpt'; - // $aInsertFile['ctime'] = time(); - // $iFileId = Db::name('article_file')->insertGetId($aInsertFile); - // } + //处理上传文件 + + if(!empty($aInsert['manuscirpt_url']) && !empty($iUserId)){ + $aInsertFile['article_id'] = $iArticleId; + $aInsertFile['user_id'] = $iUserId; + $aInsertFile['username'] = empty($aUser['account']) ? '' : $aUser['account']; + $aInsertFile['file_url'] = $aInsert['manuscirpt_url']; + $aInsertFile['type_name'] = 'manuscirpt'; + $aInsertFile['ctime'] = time(); + $iFileId = Db::name('article_file')->insertGetId($aInsertFile); + } Db::commit(); $aInsert['article_id'] = $iArticleId; return json_encode(['status' => 1,'msg' => 'Successfully added article','article' => $aInsert]); } - - /** - * 生成文章sn号 - */ - private function getArticleAcceptSn($aParam = [],$sFlag = 'Draft') - { - $iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id']; - if(empty($iArticleId)){ - return ''; - } - $sDate = date('Y'); - $sType = $sDate; - return $sFlag.$sType.str_pad($iArticleId,6,'0',STR_PAD_LEFT).rand(100,999); - } } diff --git a/application/api/controller/Finalreview.php b/application/api/controller/Finalreview.php index 33a31b6..0baa80e 100644 --- a/application/api/controller/Finalreview.php +++ b/application/api/controller/Finalreview.php @@ -1137,4 +1137,59 @@ class Finalreview extends Base } return json_encode(['status' => 1,'msg' => 'success','data' => $aReviewerFinal]); } + + /** + * @title 获取文章的终审意见 + * @param article_id + */ + public function getRecord($aParam = []){ + + //获取参数 + $aParam = empty($aParam) ? $this->request->post() : $aParam; + //参数验证-文章ID + $iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id']; + if(empty($iArticleId)){ + return json_encode(['status' => 2,'msg' => 'Please select a article']); + } + + //查询文章 + $aWhere = ['article_id' => $iArticleId]; + $aArticle = Db::name('article')->field('ctime')->where($aWhere)->find(); + if(empty($aArticle)){ + return json_encode(['status' => 3,'msg' => 'The query article does not exist']); + } + //查询终审-审稿记录 + $aWhere = ['article_id' => $iArticleId,'state' => ['in',[1,2,3]]]; + $aReviewerFinal = Db::name('article_reviewer_final')->field('reviewer_id')->order('review_time desc')->where($aWhere)->find(); + //查询审稿人姓名 + $iUserId = empty($aReviewerFinal['reviewer_id']) ? 0 : $aReviewerFinal['reviewer_id']; + if(!empty($iUserId)){ + $aWhere = ['user_id' => $iUserId,'state' => 0]; + $aUser = Db::name('user')->field('realname')->where($aWhere)->find(); + $aReviewerFinal['realname'] = empty($aUser['realname']) ? '' : $aUser['realname']; + } + + //获取文章记录 + $aWhere = ['article_id' => $iArticleId,'state' => 0,'state_to' => ['in',[0,4,5]]]; + $aArticleMsg = Db::name('article_msg')->field('state_from,state_to,ctime')->where($aWhere)->order('ctime deac')->select(); + if(!empty($aArticleMsg)){ + $iReceivedTime = $iRevisionTime = $iAcceptedTime = 0; + foreach ($aArticleMsg as $key => $value) { + if(empty($iReceivedTime) && $value['state_to'] == 0){ + $iReceivedTime = $value['ctime']; + } + if(empty($iRevisionTime) && $value['state_to'] == 4){ + $iRevisionTime = $value['ctime']; + } + if(empty($iAcceptedTime) && $value['state_to'] == 5){ + $iAcceptedTime = $value['ctime']; + } + } + } + $iReceivedTime = empty($iReceivedTime) ? $aArticle['ctime'] : $iReceivedTime; + $aReviewerFinal['received_time'] = empty($iReceivedTime) ? '' :date('Y-m-d H:i:s',$iReceivedTime); + $aReviewerFinal['revision_time'] = empty($iRevisionTime) ? '' :date('Y-m-d H:i:s',$iRevisionTime); + $aReviewerFinal['accepted_time'] = empty($iAcceptedTime) ? '' :date('Y-m-d H:i:s',$iAcceptedTime); + return json_encode(['status' => 1,'msg' => 'success','data' => $aReviewerFinal]); + } } diff --git a/application/api/controller/Production.php b/application/api/controller/Production.php index 036d26e..5d9cac8 100644 --- a/application/api/controller/Production.php +++ b/application/api/controller/Production.php @@ -481,7 +481,8 @@ class Production extends Base 'acknowledgment' => 'require', 'keywords' => 'require', 'author_contribution' => 'require', - 'abbr' => 'require' + 'abbr' => 'require', + 'abstract' => 'require' ]); if (!$rule->check($data)) { return jsonError($rule->getError()); @@ -521,7 +522,7 @@ class Production extends Base // $update['doi'] = $doi; - + $update['abstract'] = isset($data['abstract']) ? trim($data['abstract']) : ''; $this->production_article_obj->where('p_article_id', $data['p_article_id'])->update($update); return jsonSuccess($update); } @@ -664,14 +665,16 @@ class Production extends Base $data = $this->request->post(); $rule = new Validate([ 'p_article_id' => 'require|number', - 'abstract' => 'require', + // 'abstract' => 'require', 'doi' => 'require', 'pub_date' => 'require' ]); if (!$rule->check($data)) { return jsonError($rule->getError()); } - $updata['abstract'] = trim($data['abstract']); + if(isset($data['abstract'])){ + $updata['abstract'] = trim($data['abstract']); + } $updata['doi'] = $data['doi']; $updata['pub_date'] = $data['pub_date']; $this->production_article_obj->where('p_article_id', $data['p_article_id'])->update($updata); @@ -1807,8 +1810,8 @@ private function escapeLatexText($text) $typesetInfo['images'] = null; $typesetInfo['tables'] = null; -// $url = $this->ts_base_url."api/typeset/createDocx"; - $url = "http://192.168.110.110:8081/typeset/createDocx"; + $url = $this->ts_base_url."api/typeset/createDocx"; +// $url = "http://192.168.110.110:8081/typeset/createDocx"; // $url = "http://192.168.110.110:8081/typeset/testqt"; $res = object_to_array(json_decode(myPost1($url, $typesetInfo))); diff --git a/application/api/controller/Reviewer.php b/application/api/controller/Reviewer.php index 08d1bff..c101465 100644 --- a/application/api/controller/Reviewer.php +++ b/application/api/controller/Reviewer.php @@ -184,10 +184,26 @@ class Reviewer extends Base // ->order('t_article_reviewer.state') ->limit($limit_start, $data['pageSize']) ->select(); + + //查询复审记录 chengxiaoling 20251110 start + $aReviewerRepeatLists = []; + if(!empty($res)){ + //查询复审 + $aArtRevId = array_column($res, 'art_rev_id'); + $aWhere = ['art_rev_id' => ['in',$aArtRevId],'recommend' => ['between',[1,3]]]; + $aReviewerRepeat = Db::name('article_reviewer_repeat')->field('art_rev_rep_id,art_rev_id,recommend,ctime,stime')->where($aWhere)->select(); + if(!empty($aReviewerRepeat)){ + foreach ($aReviewerRepeat as $key => $value) { + $aReviewerRepeatLists[$value['art_rev_id']][] = $value; + } + } + } + //查询复审记录 chengxiaoling 20251110 end foreach ($res as $k => $v) { if ($v['type']) { $res[$k]['type'] = translateType($v['type']); } + $res[$k]['repeat'] = empty($aReviewerRepeatLists[$v['art_rev_id']]) ? [] : $aReviewerRepeatLists[$v['art_rev_id']]; } //加上文章领域 @@ -968,12 +984,12 @@ class Reviewer extends Base //生成pdf文件 // $reviewer_pdf = self::pdftest($journal_info['title']); $reviewer_ZS = self::createReviewerZS($data['art_rev_id']); - + $sArticleSn = empty($article_info['accept_sn']) ? '' : $article_info['accept_sn']; //发送email->编辑 $tt = 'Dear editor,
'; $tt .= 'Please check the new comments from the reviewer.
'; $tt .= 'Journal:' . $journal_info['title'] . ' and article title:' . $article_info['title']; - + $tt .= '
sn:'.$sArticleSn; $sendEditor = [ 'title' => $journal_info['title'], // 邮件标题 'content' => $tt, //邮件内容 @@ -2631,10 +2647,11 @@ class Reviewer extends Base $reviewer_ZS = self::createReviewerZS($data['art_rev_id']); //发送email->编辑 + $sArticleSn = empty($article_info['accept_sn']) ? '' : $article_info['accept_sn']; $tt = 'Dear editor,
'; - $tt .= 'Please check the new comments from the reviewer.sn:'.$article_info['accept_sn'].'
'; + $tt .= 'Please check the new comments from the reviewer.
'; $tt .= 'Journal:' . $journal_info['title'] . ' and article title:' . $article_info['title']; - + $tt .= '
sn:'.$sArticleSn; $sendEditor = [ 'title' => $journal_info['title'], // 邮件标题 'content' => $tt, //邮件内容 diff --git a/application/api/controller/Ucenter.php b/application/api/controller/Ucenter.php index 915bc16..ef05e41 100644 --- a/application/api/controller/Ucenter.php +++ b/application/api/controller/Ucenter.php @@ -163,6 +163,14 @@ class Ucenter extends Base{ if($check){ return jsonError("Your application for Editorial Board is processing. Please do not repeat it."); } + + //判断是否上传CV + $aWhere = ['user_id' => $data['user_id'],'state' => 0]; + $aCv = $this->user_cv_obj->field('user_cv_id')->where($aWhere)->find(); + if(empty($aCv)){ + return jsonError("Please upload CV"); + } + $insert['user_id'] = $data['user_id']; $insert['journal_id'] = $data['journal_id']; $insert['ctime'] = time(); diff --git a/application/api/controller/User.php b/application/api/controller/User.php index 674a3ac..25fc120 100644 --- a/application/api/controller/User.php +++ b/application/api/controller/User.php @@ -3133,4 +3133,167 @@ class User extends Base die; // return $output; } + + /** + * 获取用户信息 + */ + public function getUser(){ + //获取参数 + $aParam = empty($aParam) ? $this->request->post() : $aParam; + + //获取用户ID + $iUserId = empty($aParam['user_id']) ? 0 : $aParam['user_id']; + if(empty($iUserId)){ + return json_encode(['status' => 2,'msg' => 'Please select the user']); + } + + //获取主信息 + $aWhere = ['user_id' => $iUserId,'state' => 0]; + $aUser = Db::name('user')->field('user_id,account,email,phone,realname')->where($aWhere)->find(); + if(empty($aUser)){ + return json_encode(['status' => 2,'msg' => 'User does not exist','data' => []]); + } + + //查询用户CV + $aCv = Db::name('user_cv')->field('user_cv_id,cv,ctime')->where($aWhere)->select(); + $aUser['cv'] = empty($aCv) ? [] : $aCv; + + //获取附属信息 + $aWhere = ['reviewer_id' => $iUserId,'state' => 0]; + $aUserInfo = Db::name('user_reviewer_info')->field('technical,country,introduction,company,website,field')->where($aWhere)->find(); + if(!empty($aUserInfo)){ + $aUser += $aUserInfo; + } + return json_encode(['status' => 1,'data' => $aUser]); + } + + /** + * 同意青年科学家申请 + */ + public function agreeYboardApplyNew(){ + //获取参数 + $data = empty($aParam) ? $this->request->post() : $aParam; + //必填信息验证 + $aField = ['ap_yboard_id','year','realname','technical','country','field','company']; + $sMsg = ''; + + $aUserUpdate = $aUserReviewer = []; + foreach ($aField as $key => $value) { + if(empty($data[$value])){ + $sMsg = 'Incomplete information'; + break; + } + if(!in_array($value, ['ap_yboard_id','year','realname'])){ + $aUserReviewer[$value] = $data[$value]; + } + if($value == 'realname'){ + $aUser['realname'] = $data[$value]; + } + } + if(!empty($sMsg)){ + return jsonError($sMsg); + } + + //查询申请记录 + $start_time = time(); + $app_info = $this->apply_yboard_obj->where('ap_yboard_id',$data['ap_yboard_id'])->find(); + if(empty($app_info)){ + return jsonError('No application record found'); + } + $user_info = $this->user_obj->where('user_id',$app_info['user_id'])->find(); + if(empty($user_info)){ + return jsonError('Basic user information not found'); + } + //获取期刊信息 + $journal_info = $this->journal_obj->where('journal_id',$app_info['journal_id'])->find(); + if(empty($journal_info)){ + return jsonError('No journal information found or journal closed'); + } + //查询是否申请过 + $check = $this->user_to_yboard_obj->where('user_id',$app_info['user_id'])->where('journal_id',$app_info['journal_id'])->where('start_date',"<=",$start_time)->where('end_date',">=",$start_time)->where('state',0)->find(); + if($check){ + return jsonError("Already exists"); + } + + //数据处理组装 + if(isset($data['phone'])){ + $aUser['phone'] = $data['phone']; + } + if(isset($data['website'])){ + $aUserReviewer['website'] = $data['website']; + } + if(isset($data['introduction'])){ + $aUserReviewer['introduction'] = $data['introduction']; + } + + + Db::startTrans(); + //更新用户表user + if(!empty($aUser)){ + $aWhere = ['user_id' => $app_info['user_id']]; + $user_result = Db::name('user')->where($aWhere)->limit(1)->update($aUser); + } + //更新用户附属表user_reviewer_info + if(!empty($aUserReviewer)){ + $aWhere = ['reviewer_id' => $app_info['user_id']]; + $aUserReviewerInfo = Db::name('user_reviewer_info')->field('reviewer_id')->where($aWhere)->find(); + if(empty($aUserReviewerInfo)){ + $user_reviewer_result = Db::name('user_reviewer_info')->insert($aUserReviewer); + } + if(!empty($aUserReviewerInfo)){ + $user_reviewer_result = Db::name('user_reviewer_info')->where($aWhere)->limit(1)->update($aUserReviewer); + } + } + $icon = self::createYboardCert($user_info,$journal_info,$start_time,$data['year']); + $insert['user_id'] = $app_info['user_id']; + $insert['journal_id'] = $app_info['journal_id']; + $insert['start_date'] = $start_time; + $insert['end_date'] = strtotime("+ ".$data['year']." year",$start_time); + $insert['icon'] = $icon; + $insert['ctime'] = $start_time; + $this->user_to_yboard_obj->insert($insert); + $this->apply_yboard_obj->where('ap_yboard_id',$data['ap_yboard_id'])->update(['state'=>1]); + //同意成为青年科学家后,将其添加到审稿人 + $check_reviewer = $this->reviewer_to_journal_obj->where('journal_id',$app_info['journal_id'])->where('reviewer_id',$app_info['user_id'])->find(); + if(!$check_reviewer){//如果不存在那么添加审稿人到对应期刊下面 + $insert_reviewer['reviewer_id'] = $app_info['user_id']; + $insert_reviewer['journal_id'] = $app_info['journal_id']; + $insert_reviewer['account'] = $user_info['account']; + $insert_reviewer['journal_title'] = $journal_info['title']; + $insert_reviewer['ctime'] = time(); + $this->reviewer_to_journal_obj->insert($insert_reviewer); + } + Db::commit(); + + //发送邮件提醒用户//发送邮件给用户 + $realname = empty($data['realname']) ? $user_info['realname'] : $data['realname']; + $dd = "Dear Dr. ".$realname.",

"; + $dd .= "It is my great pleasure to extend my warmest congratulations to you on being selected as a Young Scientist Board Member for ".$journal_info['title']." Journal.

"; + $dd .= "As a member of the Young Scientist Board, you will be an integral part of the peer-review process and will play a crucial role in ensuring the quality and rigor of the journal's content. Your contributions will be invaluable in shaping the direction of the journal and advancing scholarship in your field.

"; + $dd .= "You can download the Young Scientist Board certificate from the personal center system: https://submission.tmrjournals.com/

"; + $dd .= "The rights and duties of the members of the Young Scientist Board are as follows:

"; + $dd .= "Rights:

"; + $dd .= "1. Electronic Certificate: As a Young Scientist Board Member, you will receive an electronic certificate to acknowledge your position in our publication.
"; + $dd .= "2. Opportunity for Advancement: Outstanding Youth Editorial Board Members may be considered as potential candidates for the Editorial Board.
"; + $dd .= "3. Special Issues: You will have the opportunity to apply for a special Issue to expand your influence and that of your team.

"; + $dd .= "Obligations:

"; + $dd .= "1. Peer Review: As a Youth Scientist Board Member, you will be required to review and assess 3-5 manuscripts per year, based on your area of research.
"; + $dd .= "2. Active Promotion: You should actively promote our publication in your academic and professional circles and recommend quality manuscripts to scholars in your network.
"; + $dd .= "3. Provide Feedback: You should provide constructive feedback and suggestions to improve the direction and style of our publication. If feasible, you may also attend small forums or conferences to represent our publication.

"; + $dd .= "We look forward to working with you and benefiting from your insights, expertise, and dedication. We are confident that your contributions will make a significant impact on the journal and the academic community as a whole.

"; + $dd .= "Once again, congratulations on your appointment as a Young Scientist Board Member for ".$journal_info['title'].". We are honored to have you on board and look forward to collaborating with you.

"; + $dd .= 'Sincerely,
Editorial Office
'; + $dd .= $journal_info['title'] . '
'; + $dd .= 'Email: ' . $journal_info['email'] . '
'; + $dd .= 'Website: ' . $journal_info['website'] . '
'; + + $maidata['email'] = $user_info['email']; + $maidata['title'] = $journal_info['title']; + $maidata['content'] = $dd; + $maidata['tmail'] = $journal_info['email']; + $maidata['tpassword'] = $journal_info['epassword']; + Queue::push('app\api\job\mail@fire', $maidata, "tmail"); + + return jsonSuccess([]); + } } diff --git a/application/common/ArticleParserService.php b/application/common/ArticleParserService.php index 7cd305e..9b93a37 100644 --- a/application/common/ArticleParserService.php +++ b/application/common/ArticleParserService.php @@ -18,7 +18,7 @@ class ArticleParserService public function __construct($filePath = '') { if (!file_exists($filePath)) { - throw new Exception("文档不存在:{$filePath}"); + return json_encode(['status' => 5, 'msg' => '"文档不存在:{$filePath}"']); } try { // 关键配置:关闭“仅读数据”,保留完整节结构 @@ -32,11 +32,162 @@ class ArticleParserService // $this->log("✅ 文档直接加载成功,节数量:{$sectionCount}"); $this->phpWord = $reader->load($filePath); $this->sections = $this->phpWord->getSections(); - } catch (\Exception $e) { - return json(['status' => 'error', 'msg' => $e->getMessage()]); + // 预处理:移除 DOCX 中的 EMF 图片 + $processedFilePath = $this->removeEmfFromDocx($filePath); + // 加载处理后的文档 + $reader = IOFactory::createReader(); + $reader->setReadDataOnly(false); + Settings::setCompatibility(false); + Settings::setOutputEscapingEnabled(true); + + $this->phpWord = $reader->load($processedFilePath); + $this->sections = $this->phpWord->getSections(); + + // 可选:删除临时处理文件(避免冗余) + unlink($processedFilePath); + return json_encode(['status' => 5, 'msg' => $e->getMessage()]); } } + /** + * 移除 DOCX 压缩包内的所有 EMF 图片 + * @param string $docxPath 原 DOCX 文件路径 + * @return string 处理后的临时 DOCX 路径 + */ + private function removeEmfFromDocx($docxPath){ + $zip = new ZipArchive(); + if ($zip->open($docxPath) !== true) { + throw new \Exception("无法打开 DOCX 文件:{$docxPath}"); + } + + // 1. 创建临时目录用于解压 + $tempDir = rtrim(ROOT_PATH,'/').'/runtime/'.uniqid('docx_temp_'); + + mkdir($tempDir, 0700, true); + + // 2. 解压 DOCX 到临时目录 + $zip->extractTo($tempDir); + $zip->close(); + + // 3. 递归删除所有 EMF 文件 + $dirIterator = new RecursiveDirectoryIterator($tempDir); + $iterator = new RecursiveIteratorIterator($dirIterator); + foreach ($iterator as $file) { + if ($file->isFile() && strtolower(pathinfo($file, PATHINFO_EXTENSION)) === 'emf') { + unlink($file->getPathname()); + } + } + // 4. 重新打包为 DOCX + $processedPath = $tempDir . '_processed.docx'; + $newZip = new ZipArchive(); + if ($newZip->open($processedPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { + throw new \Exception("无法创建处理后的 DOCX 文件"); + } + + // 遍历临时目录,添加所有文件到新压缩包 + $this->addFilesToZip($tempDir, $newZip); + $newZip->close(); + + // 5. 删除临时解压目录 + $this->deleteDir($tempDir); + + return $processedPath; + } + + /** + * 递归添加目录文件到 ZipArchive + * @param string $dir 目录路径 + * @param ZipArchive $zip ZipArchive 实例 + */ + private function addFilesToZip($dir, $zip) + { + $files = scandir($dir); + foreach ($files as $file) { + if ($file === '.' || $file === '..') continue; + + $filePath = $dir . '/' . $file; + if (is_dir($filePath)) { + $this->addFilesToZip($filePath, $zip); + } else { + // 计算压缩包内的相对路径(避免冗余目录层级) + $relativePath = str_replace(dirname($dir) . '/', '', $filePath); + $zip->addFile($filePath, $relativePath); + } + } + } + + /** + * 递归删除目录 + * @param string $dir 目录路径 + */ + private function deleteDir($dir){ + // 1. 基础校验:非空字符串且为有效目录 + if (trim($dir) === '' || !is_dir($dir)) { + return false; + } + + // 2. 统一路径格式(去除尾部分隔符,避免跨系统差异) + $dir = rtrim($dir, DIRECTORY_SEPARATOR); + $dirName = basename($dir); + + // 3. 前缀强校验:仅处理docx_temp_开头的目录 + if (strpos($dirName, 'docx_temp_') !== 0) { + return false; + } + + // 4. 路径归属校验(缓存realpath结果,减少I/O) + $runtimeDir = rtrim(ROOT_PATH, '/') . '/runtime'; + $realDir = realpath($dir); + $realRuntimeDir = realpath($runtimeDir); + if ($realDir === false || $realRuntimeDir === false || strpos($realDir, $realRuntimeDir) !== 0) { + return false; + } + + // 5. 扫描目录(带错误抑制,处理权限问题) + $files = @scandir($dir); + if ($files === false) { + return false; + } + + $isFullyDeleted = true; // 标记是否完全删除 + + // 6. 递归处理子项 + foreach ($files as $file) { + if ($file === '.' || $file === '..') { + continue; + } + + $filePath = $dir . DIRECTORY_SEPARATOR . $file; + $realFilePath = realpath($filePath); + + // 子路径校验:必须是当前目录的子项(防符号链接跳转) + if ($realFilePath === false || strpos($realFilePath, $realDir) !== 0) { + $isFullyDeleted = false; + continue; + } + + if (is_dir($realFilePath)) { + // 递归删除子目录,继承校验逻辑 + if (!$this->deleteDir($realFilePath)) { + $isFullyDeleted = false; + } + } else { + // 尝试删除文件(失败则标记未完全删除) + if (!@unlink($realFilePath)) { + $isFullyDeleted = false; + } + } + } + + // 7. 最终删除目录(确保空目录才删除) + $remainingFiles = @scandir($dir); + if ($remainingFiles !== false && count($remainingFiles) <= 2) { + @rmdir($dir); + return $isFullyDeleted; // 若子项完全删除,则返回true + } + + return false; + } // 上传并解析文档的入口方法 public static function uploadAndParse($sFileUrl){ @@ -80,9 +231,11 @@ class ArticleParserService // 提取文章标题 private function getTitle(){ + if(empty($this->sections)){ + return ''; + } $title = ''; $maxLength = 0; - foreach ($this->sections as $section) { foreach ($section->getElements() as $element) { $text = $this->getTextFromElement($element); @@ -94,6 +247,9 @@ class ArticleParserService } } } + if(!empty($title) && !mb_check_encoding($title, 'UTF-8')){ + $title = mb_convert_encoding($title, 'UTF-8', 'GBK'); + } return $title; } // 提取作者 @@ -260,6 +416,168 @@ class ArticleParserService // var_dump($aAuthorData);exit; // return ['author' => $aAuthorData,'report' => array_unique($aReport)]; // } + + // 提取作者 + private function parseAuthorsWithoutRegex($str = '') { + if (empty($str)) { + return []; + } + // 清理乱码和特殊字符(扩展全角数字处理) + $str = mb_convert_encoding($str, 'UTF-8', 'auto'); + $str = str_replace(["\xC2\xA0", 'ï¼', '�', ',', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'], + [' ', ' ', ' ', ' ', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'], $str); + $str = trim(str_replace([' and ', ' AND ', ' And '], ', ', $str)); + + // 合并上标中数字与逗号间的空格(如"2, 3"→"2,3") + $len = mb_strlen($str); + $processed = ''; + for ($i = 0; $i < $len; $i++) { + $char = mb_substr($str, $i, 1); + if ($char === ',' && $i - 1 >= 0 && $i + 2 < $len) { + $prevChar = mb_substr($str, $i - 1, 1); + $next1 = mb_substr($str, $i + 1, 1); + $next2 = mb_substr($str, $i + 2, 1); + // 兼容全角数字转半角后的判断 + if ((ctype_digit($prevChar) || is_numeric($prevChar)) && $next1 === ' ' && (ctype_digit($next2) || is_numeric($next2))) { + $processed .= $char; + $i += 1; + continue; + } + } + $processed .= $char; + } + $str = $processed; + + // 合并数字与符号间的空格(如"1 *"→"1*") + $len = mb_strlen($str); + $processed = ''; + for ($i = 0; $i < $len; $i++) { + $char = mb_substr($str, $i, 1); + if ((ctype_digit($char) || is_numeric($char)) && $i + 2 < $len) { // 支持数字判断 + $next1 = mb_substr($str, $i + 1, 1); + $next2 = mb_substr($str, $i + 2, 1); + if ($next1 === ' ' && in_array($next2, ['#', '*', '†', '‡', '§'])) { // 扩展符号支持 + $processed .= $char; + $i += 2; + $processed .= $next2; + continue; + } + } + $processed .= $char; + } + $str = $processed; + + // 合并连续空格 + $len = mb_strlen($str); + $processed = ''; + $prevSpace = false; + for ($i = 0; $i < $len; $i++) { + $char = mb_substr($str, $i, 1); + if ($char === ' ') { + if (!$prevSpace) { + $processed .= $char; + $prevSpace = true; + } + } else { + $processed .= $char; + $prevSpace = false; + } + } + $str = trim($processed); + + // 作者处理 + $authors = []; + $currentName = ''; + $currentSuperscript = ''; + $inName = true; + $len = mb_strlen($str); + for ($i = 0; $i < $len; $i++) { + $char = mb_substr($str, $i, 1); + + // 处理作者分隔符:逗号+空格 + if ($char === ',' && $i + 1 < $len) { + $nextChar = mb_substr($str, $i + 1, 1); + if ($nextChar === ' ') { + if (!empty($currentName)) { + $currentSuperscript = rtrim($currentSuperscript, ','); + $authors[] = [ + 'name' => trim($currentName), + 'superscript' => trim($currentSuperscript) + ]; + } + $currentName = ''; + $currentSuperscript = ''; + $inName = true; + $i++; + continue; + } + } + + // 支持姓名中的点、连字符、特殊字母(如带重音的字母) + if (ctype_alpha($char) || in_array($char, [' ', '.', '-', 'à', 'á', 'â', 'ã', 'ä', 'ç', 'è', 'é', 'ê', 'ë'])) { + if ($inName) { + $currentName .= $char; + } else { + $currentSuperscript = rtrim($currentSuperscript, ','); + $authors[] = [ + 'name' => trim($currentName), + 'superscript' => trim($currentSuperscript) + ]; + $currentName = $char; + $currentSuperscript = ''; + $inName = true; + } + } + // 解析上标(数字、逗号、#、*、†等) + elseif ((ctype_digit($char) || is_numeric($char)) || in_array($char, ['#', '*', '†', ',', '‡', '§'])) { + $inName = false; + $currentSuperscript .= $char; + } + // 忽略其他字符 + else { + continue; + } + } + + // 处理最后一个作者 + if (!empty($currentName)) { + $currentSuperscript = rtrim($currentSuperscript, ','); + $authors[] = [ + 'name' => trim($currentName), + 'superscript' => trim($currentSuperscript) + ]; + } + + // 提取机构编号为数组、判断通讯作者和第一作者 + foreach ($authors as $index => &$author) { + // 提取机构编号(兼容多字节数字) + $institutionIds = []; + $superscript = $author['superscript']; + $numStr = ''; + for ($i = 0; $i < mb_strlen($superscript); $i++) { + $c = mb_substr($superscript, $i, 1); + if (ctype_digit($c) || is_numeric($c)) { // 支持数字判断 + $numStr .= $c; + } else { + if (!empty($numStr)) { + $institutionIds[] = (int)$numStr; + $numStr = ''; + } + } + } + if (!empty($numStr)) { + $institutionIds[] = (int)$numStr; + } + $institutionIds = array_values(array_unique($institutionIds)); + $author['company_id'] = $institutionIds; + + // 判断第一作者(#标记)和通讯作者(*、†标记) + $author['is_super'] = strpos($superscript, '#') !== false ? 1 : 0; + $author['is_report'] = (strpos($superscript, '*') !== false || strpos($superscript, '†') !== false) ? 1 : 0; + } + unset($author); // 释放引用 + return $authors; + } private function getAuthors($aParam = []) { $title = empty($aParam['title']) ? $this->getTitle() : $aParam['title']; $sAuthorContent = $this->getNextParagraphAfterText($title); @@ -291,95 +609,195 @@ class ArticleParserService $sAuthorContent = preg_replace('/\s+and\s+/i', ', ', $sAuthorContent); // and转逗号 $sAuthorContent = preg_replace('/\s+/', ' ', $sAuthorContent); // 合并多余空格 $sAuthorContent = trim($sAuthorContent); - - // 处理作者 - $content = mb_convert_encoding($sAuthorContent, 'UTF-8', 'auto'); // 确保编码正确 - $content = str_replace("\xC2\xA0", ' ', $content); // 替换非-breaking空格为普通空格 - $content = preg_replace('/(\d+)\s*([*#†])/', '$1$2', $content); // 合并"1 *"为"1*"、"1 #"为"1#" - $content = preg_replace('/,†/', ',†', $content); // 保留"1,†"格式(防止被拆分) - //标记上标内的逗号+空格(多编号) - $tempStr = preg_replace('/(\d+)\s*,\s*(\d+)/', '$1$2', $content); - // 原有步骤2:正则匹配(扩展上标符号支持,保持原有逻辑) - $pattern = '/ - ([A-Za-z\s\.\-]+?) # 姓名(支持缩写、空格) - \s* # 姓名与上标间空格 - ( # 上标组(扩展符号支持) - \d+ # 起始数字 - (?:[†#*,]|\d+)* # 允许:†#*符号、逗号、+数字(兼容1,†、1,*等) - ) - \s*,? # 作者间逗号(可选) - (?=\s|$) # 确保后面是空格或结尾 - /ux'; - - preg_match_all($pattern, $tempStr, $matches); - $authorList = []; - if(!empty($matches[1])){ - foreach ($matches[1] as $i => $name) { - $name = trim($name); - $superscript = trim($matches[2][$i]); - $superscript = str_replace('', ',', $superscript); // 恢复多编号逗号 - $superscript = preg_replace('/,$/', '', $superscript); // 清理末尾逗号 - // 修复符号与数字间的空格(如原始"1 *"被误处理为"1*"的情况,保持原样) - $superscript = preg_replace('/(\d)([*#†])/', '$1$2', $superscript); - if (!empty($name)) { - $authorList[] = [ - 'name' => $name, - 'superscript' => $superscript - ]; - } - } - }else { - // 按“两个或多个连续空格”拆分(姓名之间的分隔) - $authorList = array_filter( - array_map('trim', - preg_split('/(,\p{Z}*|\p{Z}{2,})/u', $sAuthorContent) - ) - ); + $aAuthor = $this->parseAuthorsWithoutRegex($sAuthorContent); + if(empty($aAuthor)){ + return ['author' => [],'report' => []]; } - + $aReport = $aAuthorData = []; - // //处理作者 - $aAuthorData = []; - $aReport = []; - $namePattern = '/ - (?:[A-Za-z\s·\-\']+| # 英文姓名(支持空格、连字符) - [\x{4e00}-\x{9fa5}]+| # 中文姓名 - [\x{1800}-\x{18AF}]+| # 蒙古文姓名 - [A-Z]\.) # 单字母缩写(如 J.) - /ux'; - - foreach ($authorList as $authorStr){ - if (empty($authorStr)) continue; - - //获取下标 - $superscript = empty($authorStr['superscript']) ? $authorStr : $authorStr['superscript']; - $nameStr = empty($authorStr['name']) ? $authorStr : $authorStr['name']; - - $companyId = []; - $isSuper = 0; - $isReport = 0; - if (!empty($superscript)) { - // 提取机构编号(忽略上标中的逗号,如1,† → 提取1) - preg_match_all('/\d+/', $superscript, $numMatch); - // 识别特殊符号(#为超级作者,*†为通讯作者) - $isSuper = strpos($superscript, '#') !== false ? 1 : 0; - $isReport = (strpos($superscript, '*') !== false || strpos($superscript, '†') !== false) ? 1 : 0; + foreach ($aAuthor as $key => $value) { + if(empty($value['name']) && empty($value['superscript'])){ + continue; } - if (preg_match("/^([A-Za-z\s'\.-]+)/u", $nameStr, $match)) { - $nameStr = trim($match[1]); + if(!mb_check_encoding($value['name'], 'UTF-8')){ + $value['name'] = mb_convert_encoding($value['name'], 'UTF-8', 'GBK'); } - $aAuthorData[] = [ - 'name' => $nameStr, - 'company_id' => empty($numMatch[0]) ? [] : $numMatch[0], - 'is_super' => $isSuper, - 'is_report' => $isReport - ]; - if ($isReport) { - $aReport[] = $nameStr; + if(!empty($value['name']) && !empty($value['is_report']) && $value['is_report'] == 1){ + $aReport[] = $value['name']; } + $aAuthorData[] = $value; } return ['author' => $aAuthorData,'report' => array_unique($aReport)]; } +// private function getAuthors($aParam = []) { +// $title = empty($aParam['title']) ? $this->getTitle() : $aParam['title']; +// $sAuthorContent = $this->getNextParagraphAfterText($title); +// if (empty($sAuthorContent)) { +// return ['author' => [], 'report' => []]; +// } + +// //编码修复 +// $possibleEncodings = [ +// 'Windows-1252', 'UTF-8', 'GBK', 'GB2312', +// 'Latin-1', 'ISO-8859-1', 'CP1252' +// ]; +// $encodedContent = @mb_convert_encoding($sAuthorContent, 'UTF-8', implode(',', $possibleEncodings)); +// $sAuthorContent = $encodedContent ?: $sAuthorContent; + +// //清理不可见字符 +// $sAuthorContent = preg_replace('/[\x00-\x1F\x7F\x{200B}-\x{200F}]/u', '', $sAuthorContent); + +// //修复特殊符号乱码 +// $symbolMap = [ +// '†' => '†', 'â ' => '†', 'â' => '†', '?†' => '†', +// ':' => ':', ',' => ',', '—' => '-', +// '啊' => '' // 针对性移除异常字符“啊”(若为固定乱码) +// ]; +// $sAuthorContent = strtr($sAuthorContent, $symbolMap); + +// //格式标准化 +// $sAuthorContent = str_replace([',', ';', ';', '、'], ',', $sAuthorContent); // 统一分隔符 +// $sAuthorContent = preg_replace('/\s+and\s+/i', ', ', $sAuthorContent); // and转逗号 +// $sAuthorContent = preg_replace('/\s+/', ' ', $sAuthorContent); // 合并多余空格 +// $sAuthorContent = trim($sAuthorContent); +// var_dump($this->parseAuthorsWithoutRegex($sAuthorContent));exit; +// // 关键预处理:兼容"and"分隔符、清理乱码、统一空格 +// $content = mb_convert_encoding($sAuthorContent, 'UTF-8', 'auto'); +// $content = str_replace(["\xC2\xA0", 'ï¼', '�', ','], ' ', $content); // 清理乱码和全角符号 +// $content = preg_replace('/\band\b/i', ',', $content); // 将 "and" 转为逗号(统一分隔符) +// $content = preg_replace('/(\d+)\s*([*#†])/', '$1$2', $content); // 合并数字与符号间的空格(如"1 *"→"1*") +// $content = trim(preg_replace('/\s+/', ' ', $content)); // 合并连续空格 + +// // 标记上标内的逗号(多编号处理) +// $tempStr = preg_replace('/(\d+)\s*,\s*(\d+)/', '$1$2', $content); + +// // 核心正则(保持原有结构,扩展符号支持) +// $pattern = '/ +// ([A-Za-z\s\.\-]+?) # 姓名(支持缩写、空格、连字符) +// \s* # 姓名与上标间的空格(允许0或多个) +// ( # 上标组(扩展兼容所有符号) +// \d+ # 起始数字(至少1个数字) +// (?:[†#*,]|\d+)* # 允许:符号(†#*)、逗号、+数字(多编号) +// ) +// \s*,? # 作者间的逗号(可选,允许逗号前有空格) +// (?=\s|$) # 确保后面是空格或字符串结尾(避免跨作者匹配) +// /ux'; + +// preg_match_all($pattern, $tempStr, $matches); + +// // 解析结果并格式化 +// $authorList = []; +// if (!empty($matches[1])) { +// foreach ($matches[1] as $i => $name) { +// $name = trim($name); +// $superscript = trim($matches[2][$i]); +// $superscript = str_replace('', ',', $superscript); // 恢复多编号逗号 +// $superscript = preg_replace('/,$/', '', $superscript); // 清理末尾多余逗号 +// if (!empty($name)) { +// $authorList[] = [ +// 'name' => $name, +// 'superscript' => $superscript +// ]; +// } +// } +// } + +// // 输出结果 +// echo "
";
+// print_r($authorList);
+// echo "
"; +// exit; + +// // 处理作者 +// $content = mb_convert_encoding($sAuthorContent, 'UTF-8', 'auto'); // 确保编码正确 +// $content = str_replace("\xC2\xA0", ' ', $content); // 替换非-breaking空格为普通空格 +// $content = preg_replace('/(\d+)\s*([*#†])/', '$1$2', $content); // 合并"1 *"为"1*"、"1 #"为"1#" +// $content = preg_replace('/,†/', ',†', $content); // 保留"1,†"格式(防止被拆分) + +// //标记上标内的逗号+空格(多编号) +// $tempStr = preg_replace('/(\d+)\s*,\s*(\d+)/', '$1$2', $content); +// // 原有步骤2:正则匹配(扩展上标符号支持,保持原有逻辑) +// $pattern = '/ +// ([A-Za-z\s\.\-]+?) # 姓名(支持缩写、空格) +// \s* # 姓名与上标间空格 +// ( # 上标组(扩展符号支持) +// \d+ # 起始数字 +// (?:[†#*,]|\d+)* # 允许:†#*符号、逗号、+数字(兼容1,†、1,*等) +// ) +// \s*,? # 作者间逗号(可选) +// (?=\s|$) # 确保后面是空格或结尾 +// /ux'; + +// preg_match_all($pattern, $tempStr, $matches); +// var_dump($matches);exit; +// $authorList = []; +// if(!empty($matches[1])){ +// foreach ($matches[1] as $i => $name) { +// $name = trim($name); +// $superscript = trim($matches[2][$i]); +// $superscript = str_replace('', ',', $superscript); // 恢复多编号逗号 +// $superscript = preg_replace('/,$/', '', $superscript); // 清理末尾逗号 +// // 修复符号与数字间的空格(如原始"1 *"被误处理为"1*"的情况,保持原样) +// $superscript = preg_replace('/(\d)([*#†])/', '$1$2', $superscript); +// if (!empty($name)) { +// $authorList[] = [ +// 'name' => $name, +// 'superscript' => $superscript +// ]; +// } +// } +// }else { +// // 按“两个或多个连续空格”拆分(姓名之间的分隔) +// $authorList = array_filter( +// array_map('trim', +// preg_split('/(,\p{Z}*|\p{Z}{2,})/u', $sAuthorContent) +// ) +// ); +// } + + +// // //处理作者 +// $aAuthorData = []; +// $aReport = []; +// $namePattern = '/ +// (?:[A-Za-z\s·\-\']+| # 英文姓名(支持空格、连字符) +// [\x{4e00}-\x{9fa5}]+| # 中文姓名 +// [\x{1800}-\x{18AF}]+| # 蒙古文姓名 +// [A-Z]\.) # 单字母缩写(如 J.) +// /ux'; + +// foreach ($authorList as $authorStr){ +// if (empty($authorStr)) continue; + +// //获取下标 +// $superscript = empty($authorStr['superscript']) ? $authorStr : $authorStr['superscript']; +// $nameStr = empty($authorStr['name']) ? $authorStr : $authorStr['name']; + +// $companyId = []; +// $isSuper = 0; +// $isReport = 0; +// if (!empty($superscript)) { +// // 提取机构编号(忽略上标中的逗号,如1,† → 提取1) +// preg_match_all('/\d+/', $superscript, $numMatch); +// // 识别特殊符号(#为超级作者,*†为通讯作者) +// $isSuper = strpos($superscript, '#') !== false ? 1 : 0; +// $isReport = (strpos($superscript, '*') !== false || strpos($superscript, '†') !== false) ? 1 : 0; +// } +// if (preg_match("/^([A-Za-z\s'\.-]+)/u", $nameStr, $match)) { +// $nameStr = trim($match[1]); +// } +// $aAuthorData[] = [ +// 'name' => $nameStr, +// 'company_id' => empty($numMatch[0]) ? [] : $numMatch[0], +// 'is_super' => $isSuper, +// 'is_report' => $isReport +// ]; +// if ($isReport) { +// $aReport[] = $nameStr; +// } +// } +// return ['author' => $aAuthorData,'report' => array_unique($aReport)]; +// } // 获取机构 private function getCompany($aParam = []){ @@ -388,32 +806,68 @@ class ArticleParserService //获取标题下的作者 $sAuthorContent = empty($aParam['authors']) ? $this->getNextParagraphAfterText($title) : $aParam['authors']; //获取作者结构 - $sCompany = $this->getContentAfterText($sAuthorContent); - if(empty($sCompany)){ + $allLines = $this->getContentAfterText($sAuthorContent,1); + if(empty($allLines)){ return []; } - //编码修复 + // 2. 按序号分组,合并同一序号的多行内容 + $grouped = []; + $currentNumber = null; // 当前序号 + foreach ($allLines as $line) { + $line = trim($line); + if (empty($line)) continue; + + // 判断是否是新条目的开头:行首为数字(后续可接任意字符或直接接内容) + $number = ''; + $i = 0; + $lineLen = strlen($line); + // 提取行首的连续数字(作为序号) + while ($i < $lineLen && ctype_digit($line[$i])) { + $number .= $line[$i]; + $i++; + } + + // 若行首有数字,则视为新条目 + if (!empty($number)) { + $currentNumber = $number; + // 提取序号后的内容(跳过数字后的符号/空格,保留核心内容) + // 从数字后的位置开始,跳过可能的符号(./*)或空格 + while ($i < $lineLen && (in_array($line[$i], ['.', '*', ' ']))) { + $i++; + } + $content = trim(substr($line, $i)); // 序号后的内容 + $grouped[$currentNumber] = $content; + continue; + } + + // 非新条目,合并到当前序号的内容中 + if ($currentNumber !== null) { + $grouped[$currentNumber] .= ' ' . $line; + } + } + + //清理结果 $possibleEncodings = [ 'Windows-1252', 'UTF-8', 'GBK', 'GB2312', 'Latin-1', 'ISO-8859-1', 'CP1252' ]; - $encodedContent = @mb_convert_encoding($sCompany, 'UTF-8', implode(',', $possibleEncodings)); - $sCompany = $encodedContent ?: $sCompany; - //按行拆分,保留数字开头的行 - $sCompany = str_replace(["\r\n", "\r"], "\n", $sCompany); - $aCompanyLines = explode("\n", $sCompany); - $aCompanyLines = array_filter(array_map('trim', $aCompanyLines), function($line) { - return preg_match('/^\d+/', $line); // 仅保留数字开头的行 - }); - $aCompany = []; - foreach ($aCompanyLines as $line) { - if (preg_match('/^(\d+)\s*(.+)$/', $line, $match)) { - if(empty($match[1]) || empty($match[2])){ - continue; - } - $aCompany[$match[1]] = ltrim(trim(ltrim($match[2]),'.'),' '); + foreach ($grouped as $number => $institution) { + $encodedContent = @mb_convert_encoding($institution, 'UTF-8', implode(',', $possibleEncodings)); + $sCompany = $encodedContent ?: $sCompany; + $institution = preg_replace('/\s+/', ' ', $institution); // 合并多余空格 + $institution = rtrim($institution, '.'); + $institution = preg_replace('/^\d+\s+/', '', $institution); + $institution = trim($institution); // 清理首尾空格 + preg_match('/(.*?, [A-Za-z]+ \d+, [A-Za-z]+)/', $institution, $institutionmatches);; + $institution = trim($institutionmatches[1] ?? $institution); + if (preg_match('/^(.*?)(?=\s*\*Email)/', $institution, $matches)) { + $institution = trim($matches[1]); // trim() 去除内容前后多余空格 } + if(!empty($institution) && !mb_check_encoding($institution, 'UTF-8')){ + $institution = mb_convert_encoding($institution, 'UTF-8', 'GBK'); + } + $aCompany[$number] = $institution; } return $aCompany; } @@ -451,11 +905,10 @@ class ArticleParserService $corrText = str_replace([':', '@'], [':', '@'], $corrText); $corrText = preg_replace('/\s+/', ' ', $corrText); // 统一空格 $corrText = str_replace(' ', ' ', $corrText); // 去除多余空格 - //按"*"分割通讯作者 $corrBlocks = preg_split('/\s*\*\s*/', $corrText); $corrBlocks = array_filter(array_map('trim', $corrBlocks)); - + $aCorresponding = []; foreach ($corrBlocks as $block) { //匹配通讯作者姓名 @@ -466,7 +919,6 @@ class ArticleParserService preg_match('/(E[\s-]*mail|邮箱)[\s:]*([^\s]+@[^\s]+)/i', $block, $email); preg_match('/(Postal[\s-]*address|地址)[\s:]*([^,;]+)/i', $block, $address); preg_match('/(Tel|电话)[\s:]*([^\s]+)/i', $block, $tel); - $aCorresponding[] = [ 'name' => $sName, 'email' => isset($email[2]) ? trim($email[2]) : '', @@ -474,6 +926,24 @@ class ArticleParserService 'tel' => isset($tel[2]) ? trim($tel[2]) : '' ]; } + if(empty($aCorresponding)){ + $pattern = '/Corresponding Authors: (.*?)(?=$|;)/s'; + preg_match($pattern, $corrText, $match); + if (!empty($match[1])) { + $corrContent = $match[1]; + // 提取每个作者的名称和邮箱(优化正则,支持更多字符) + $authorPattern = '/([A-Za-z\s]+?),\s*E-mail:\s*([\w@\.\-]+)/'; + preg_match_all($authorPattern, $corrContent, $authors); + if(!empty($authors[1])){ + for ($i = 0; $i < count($authors[1]); $i++) { + $aCorresponding[] = [ + 'name' => empty($authors[1][$i]) ? '' : trim($authors[1][$i]), + 'email' => empty($authors[2][$i]) ? '' : trim($authors[2][$i]) + ]; + } + } + } + } return $aCorresponding; } @@ -518,10 +988,10 @@ class ArticleParserService } // 获取目标文本后的所有内容 - private function getContentAfterText($targetText){ + private function getContentAfterText($targetText,$return_type = 2){ $found = false; $content = []; - $stopKeywords = ['关键词', 'Key words', '摘要', 'Abstract']; + $stopKeywords = ['关键词', 'Key words', '摘要', 'Abstract','ABSTRACT']; $maxLines = 200; $lineNumber = 0; foreach ($this->sections as $section) { @@ -559,7 +1029,14 @@ class ArticleParserService } if (count($content) >= $maxLines || (isset($shouldStop) && $shouldStop)) break; } - return implode("\n", $content); + if($return_type == 1){ + return $content; + } + $content = implode("\n", $content); + if(!empty($content) && !mb_check_encoding($content, 'UTF-8')){ + $content = mb_convert_encoding($content, 'UTF-8', 'GBK'); + } + return $content; } // 统一提取元素文本 @@ -635,7 +1112,9 @@ class ArticleParserService $text = preg_replace('/[\x00-\x1F\x7F-\x9F]/', ' ', $text); // 移除控制字符 $text = str_replace(["\t", "\r", "\n"], ' ', $text); // 统一空白字符 $text = preg_replace('/\s+/', ' ', $text); // 合并多个空格 - + if(!empty($text) && !mb_check_encoding($text, 'UTF-8')){ + $text = mb_convert_encoding($text, 'UTF-8', 'GBK'); + } return $text; } @@ -676,13 +1155,17 @@ class ArticleParserService $sContent .= "\n"; } } + + if(!empty($sContent) && !mb_check_encoding($sContent, 'UTF-8')){ + $sContent = mb_convert_encoding($sContent, 'UTF-8', 'GBK'); + } // 2. 基础文本清理(合并多余空格,保留有效换行) $textContent = preg_replace('/(\S)\s+/', '$1 ', $sContent); $textContent = trim($textContent); // 3. 提取摘要 $abstract = ''; - $abstractPattern = '/Abstract\s*([\s\S]*?)(?=Keywords:|$)/i'; + $abstractPattern = '/Abstract\s*([\s\S]*?)(?=Keywords|$)/i'; if (preg_match($abstractPattern, $textContent, $abstractMatches)) { $abstract = trim($abstractMatches[1]); $abstract = preg_replace('/\n+/', ' ', $abstract); @@ -690,7 +1173,8 @@ class ArticleParserService // 4. 提取关键词(核心:仅保留两种强制匹配逻辑) $keywords = []; // $keywordPattern = '/Keywords:\s*([\s\S]*?)(?=\s*\d+\.|[;,]\s*[\r\n]+\s*[\r\n]+|(?i)\bintroduction|abbreviations\b|$)/i'; - $keywordPattern = '/Keywords:\s*(.*?)\s*Keywords-End-Flag/s'; + $keywordPattern = '/Keywords\s*(.*?)\s*Keywords-End-Flag/s'; + if (preg_match($keywordPattern, $textContent, $keywordMatches)) { $keywordStr = trim($keywordMatches[1]); @@ -705,6 +1189,22 @@ class ArticleParserService return !empty($item) && !ctype_space($item); }); } + if(empty($keywords)){ + $keywordPattern = '/Keywords\s*([\s\S]*?)(?=Introduction|$)/i'; + if (preg_match($keywordPattern, $textContent, $keywordMatches)) { + $keywordStr = trim($keywordMatches[1]); + // 清理关键词列表格式(去除换行、末尾多余符号) + $keywordStr = preg_replace('/\n+/', ' ', $keywordStr); + $keywordStr = rtrim($keywordStr, ';,. '); // 去除末尾分号、逗号等 + $keywordStr = trim($keywordStr); + + // 分割并过滤有效关键词 + $keywords = preg_split('/[,;]\s*/', $keywordStr); + $keywords = array_filter(array_map('trim', $keywords), function($item) { + return !empty($item) && !ctype_space($item); + }); + } + } return [ 'status' => 1, 'msg' => '提取成功', diff --git a/application/common/ProofReadService.php b/application/common/ProofReadService.php index 2dcecf3..1a52e26 100644 --- a/application/common/ProofReadService.php +++ b/application/common/ProofReadService.php @@ -301,7 +301,8 @@ class ProofReadService // 3. 核心优先级:运算符规则(精准匹配,排除No.编号干扰) [ - 'pattern' => '~(\S)\s*([<>!]=|===|!==)\s*(\S)~u', + // 'pattern' => '~(\S)\s*([<>!]=|===|!==)\s*(\S)~u', + 'pattern' => '~(?)\s*(\S)\s*([<>!]=|===|!==)\s*(\S)(?!<[a-z]+>)~u', 'replacement' => '$1 $2 $3', 'verbatim_texts' => '复合运算符前后空格不规范', 'explanation' => '复合运算符[>=、<=、==、!=、===、!==]前后应各留一个空格',