强制提交
This commit is contained in:
@@ -3476,6 +3476,17 @@ class Article extends Base
|
||||
return jsonError("Submission failed");
|
||||
}
|
||||
$article_id = $data['article_id'];
|
||||
|
||||
//查询标题是否存在
|
||||
$oArticle = new \app\common\Article;
|
||||
$aCheckTitle = $oArticle->checkTitle(['title' => $data['title'],'article_id' => $article_id,'user_id' => $data['user_id']]);
|
||||
$iStatus = empty($aCheckTitle['status']) ? -1 : $aCheckTitle['status'];
|
||||
$sMsg = empty($aCheckTitle['msg']) ? '': $aCheckTitle['msg'];
|
||||
$iDraftId = empty($aCheckTitle['draft_id']) ? 0 : $aCheckTitle['draft_id'];
|
||||
if($iStatus != 1){
|
||||
return json(['code' => $iStatus, 'msg' => $sMsg,'draft_id' => $iDraftId]);
|
||||
}
|
||||
|
||||
//添加文章基础信息
|
||||
if ($data['article_id'] == 0) {
|
||||
$checkArticle = $this->article_obj->where("title", trim($data['title']))->select();
|
||||
@@ -5489,6 +5500,17 @@ class Article extends Base
|
||||
}
|
||||
}
|
||||
|
||||
//查询标题是否存在
|
||||
$oArticle = new \app\common\Article;
|
||||
$aCheckTitle = $oArticle->checkTitle(['title' => $aArticle['title'],'article_id' => $iArticleId,'user_id' => $iUserId]);
|
||||
$iStatus = empty($aCheckTitle['status']) ? -1 : $aCheckTitle['status'];
|
||||
$sMsg = empty($aCheckTitle['msg']) ? '': $aCheckTitle['msg'];
|
||||
$iDraftId = empty($aCheckTitle['draft_id']) ? 0 : $aCheckTitle['draft_id'];
|
||||
if($iStatus != 1){
|
||||
$iFirstStatus = 2;
|
||||
$sFirstMsg = 'Step 1: '.$sMsg;
|
||||
}
|
||||
|
||||
//判断伦理
|
||||
if(!empty($aArticle['approval']) && $aArticle['approval'] == 1 && empty($aArticle['approval_file'])){
|
||||
$iFirstStatus = 2;
|
||||
|
||||
407
application/api/controller/Cronreview.php
Normal file
407
application/api/controller/Cronreview.php
Normal file
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
namespace app\api\controller;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
class Cronreview extends Controller
|
||||
{
|
||||
|
||||
//定义邮件模版
|
||||
protected $aEmailConfig = [
|
||||
'three' => [
|
||||
'email_subject' => 'Invitation to Review Manuscript for [{accept_sn}]-Reminder',
|
||||
'email_content' => '
|
||||
Dear Dr. {realname},<br><br>
|
||||
I hope this email finds you well.<br><br>
|
||||
On {invite_time}, we sent you the following review request for <i>{journal_title}</i>, <br>
|
||||
Manuscript ID:{accept_sn}<br>
|
||||
Title:{article_title}<br><br>
|
||||
We have not yet received a response from you, and we understand that the original invitation may not have reached you. We would greatly appreciate it if you could kindly inform us whether you are available to undertake this review.<br><br>
|
||||
For your convenience, please find the relevant links below:<br>
|
||||
<a href="{creatLoginUrlForreviewer}">Accept the review invitation</a><br>
|
||||
<a href="{creatRejectUrlForReviewer}">Reject the review invitation</a><br>
|
||||
Your username: {account}<br>
|
||||
Your original password:123456qwe, if you have reset the password, please sign in with the new one or click the "<a href="https://submission.tmrjournals.com/retrieve">forgot password</a>".<br><br>
|
||||
Thank you once again for considering our invitation. Your input is invaluable to us, and we truly appreciate your time and effort.<br><br>
|
||||
Please feel free to reply to this email or contact me directly with any questions.<br><br>
|
||||
Sincerely,<br>
|
||||
Editorial Office<br>
|
||||
<a href="https://www.tmrjournals.com/draw_up.html?issn={journal_issn}">Subscribe to this journal</a><br><i>{journal_title}</i><br>
|
||||
Email: {journal_email}<br>
|
||||
Website: {website}'
|
||||
],
|
||||
'five' => [
|
||||
'email_subject' => 'Gentle Reminder: Review Invitation for Manuscript {accept_sn}',
|
||||
'email_content' => '
|
||||
Dear Dr. {realname},<br><br>
|
||||
This is a brief follow-up regarding our review invitation sent on {invite_time} for the following manuscript submitted to <i>{journal_title}</i>:<br><br>
|
||||
Manuscript ID:{accept_sn}<br>
|
||||
Title:{article_title}<br><br>
|
||||
We would appreciate it if you could inform us whether you are able to undertake this review. If you are unavailable or require additional time, please feel free to let us know so that we may make appropriate arrangements.<br><br>
|
||||
For your convenience, please find the relevant links below:<br>
|
||||
<a href="{creatLoginUrlForreviewer}">Accept the review invitation</a><br>
|
||||
<a href="{creatRejectUrlForReviewer}">Reject the review invitation</a><br>
|
||||
Your username: {account}<br>
|
||||
Your original password:123456qwe, if you have reset the password, please sign in with the new one or click the "<a href="https://submission.tmrjournals.com/retrieve">forgot password</a>".<br><br>
|
||||
Thank you very much for your time and consideration.<br><br>
|
||||
Sincerely,<br>
|
||||
Editorial Office<br>
|
||||
<a href="https://www.tmrjournals.com/draw_up.html?issn={journal_issn}">Subscribe to this journal</a><br><i>{journal_title}</i><br>
|
||||
Email: {journal_email}<br>
|
||||
Website: {website}'
|
||||
],
|
||||
|
||||
'ten' => [
|
||||
'email_subject' => 'Reminder: Review Report for Manuscript [{accept_sn}]',
|
||||
'email_content' => '
|
||||
Dear Dr. {realname},<br><br>
|
||||
I hope this message finds you well.<br><br>
|
||||
I am writing to kindly follow up regarding the review report for the following manuscript, for which you were so kind to agree to serve as a reviewer for <i>{journal_title}</i>.<br>
|
||||
Manuscript ID:{accept_sn}<br>
|
||||
Title:{article_title}<br><br>
|
||||
We sincerely appreciate the time and expertise you are dedicating to this review. We fully understand that academic and professional commitments can be demanding, and should you require additional time or experience any difficulty in accessing the manuscript, please do not hesitate to let us know. We would be more than happy to accommodate your schedule or provide any assistance needed.<br><br>
|
||||
We would greatly appreciate your expert feedback at your earliest convenience, as it will help us proceed smoothly with the editorial process. For your convenience, please find the relevant link below:<br>
|
||||
<a href="{creatLoginUrlForreviewer}">Click here to submit the review report</a><br>
|
||||
Your username: {account}<br>
|
||||
Your original password:123456qwe, if you have reset the password, please sign in with the new one or click the "<a href="https://submission.tmrjournals.com/retrieve">forgot password</a>".<br><br>
|
||||
Thank you once again for your valued contribution to <i>{journal_title}</i> and for your continued support of our peer-review process.<br><br>
|
||||
Sincerely,<br>
|
||||
Editorial Office<br>
|
||||
<a href="https://www.tmrjournals.com/draw_up.html?issn={journal_issn}">Subscribe to this journal</a><br><i>{journal_title}</i><br>
|
||||
Email: {journal_email}<br>
|
||||
Website: {website}'
|
||||
],
|
||||
'twelve' => [
|
||||
'email_subject' => 'Gentle Reminder: Review Report for Manuscript [{accept_sn}]',
|
||||
'email_content' => '
|
||||
Dear Dr. {realname},<br><br>
|
||||
This is a gentle reminder regarding the review report for the manuscript listed below, for which you kindly agreed to serve as a reviewer:<br><br>
|
||||
Manuscript ID:{accept_sn}<br>
|
||||
Title:{article_title}<br><br>
|
||||
For your convenience, please find the relevant links below:<br>
|
||||
<a href="{creatLoginUrlForreviewer}">Click here to submit review report</a><br>
|
||||
Your username: {account}<br>
|
||||
Your original password:123456qwe, if you have reset the password, please login with the new one or click the "<a href="https://submission.tmrjournals.com/retrieve">forgot password</a>".<br><br>
|
||||
We would greatly appreciate it if you could submit your review at your convenience before <b>{agree_deadline}</b>. If you require additional time or encounter any difficulties, please feel free to let us know.<br><br>
|
||||
Thank you very much for your valuable time and contribution to the peer-review process.<br><br>
|
||||
Sincerely,<br>
|
||||
Editorial Office<br>
|
||||
<a href="https://www.tmrjournals.com/draw_up.html?issn={journal_issn}">Subscribe to this journal</a><br><i>{journal_title}</i><br>
|
||||
Email: {journal_email}<br>
|
||||
Website: {website}'
|
||||
],
|
||||
];
|
||||
private $iDayTime = 86400;//一天秒数
|
||||
/**
|
||||
* 文章审稿阶段-邀请审稿超过三天/五天发送提醒邮件
|
||||
* @return void
|
||||
*/
|
||||
public function inviteReminder(){
|
||||
|
||||
//获取当前时间
|
||||
$sCurrentDate = date('Y-m-d', time());
|
||||
$iTime = $this->iDayTime;
|
||||
//获取文章信息
|
||||
$aResult = $this->getArticle();
|
||||
$iStatus = empty($aResult['status']) ? 0 : $aResult['status'];
|
||||
$sMsg = empty($aResult['msg']) ? 'No data obtained' : $aResult['msg'];
|
||||
if($iStatus != 1){
|
||||
$this->showMessage($sMsg,2);
|
||||
exit;
|
||||
}
|
||||
|
||||
//数据处理
|
||||
$aArticle = empty($aResult['data']) ? [] : $aResult['data'];
|
||||
if(empty($aArticle)){
|
||||
$this->showMessage('Article or journal information is empty',2);
|
||||
exit;
|
||||
}
|
||||
|
||||
//查询文章邀请的审稿人
|
||||
$aArticleId = array_column($aArticle, 'article_id');
|
||||
$aWhere = ['article_id' => ['in',$aArticleId],'state' => 5];
|
||||
$aReviewer = Db::name('article_reviewer')->field('art_rev_id,reviewer_id,article_id,ctime')->where($aWhere)->order('article_id desc')->select();
|
||||
if(empty($aReviewer)){
|
||||
$this->showMessage('No qualified reviewers were found',2);
|
||||
exit;
|
||||
}
|
||||
|
||||
//数据处理
|
||||
foreach ($aReviewer as $key => $value) {
|
||||
if(empty($value['ctime'])){
|
||||
continue;
|
||||
}
|
||||
//时间处理
|
||||
$sTargetDate = date('Y-m-d', $value['ctime']);
|
||||
//日期转时间戳
|
||||
$iTargetTime = strtotime($sTargetDate);//邀请时间戳
|
||||
$iCurrentTime = strtotime($sCurrentDate);//当前时间戳
|
||||
$iThreeCtime = intval($iTargetTime + (3 * $iTime));//三天
|
||||
$iFiveCtime = intval($iTargetTime + (5 * $iTime));//五天
|
||||
//对比
|
||||
if($iCurrentTime != $iThreeCtime && $iCurrentTime != $iFiveCtime){
|
||||
continue;
|
||||
}
|
||||
if($iThreeCtime == $iCurrentTime){ //超过三天
|
||||
$value['email_type'] = 'three';
|
||||
}
|
||||
if($iFiveCtime == $iCurrentTime){ //超过五天
|
||||
$value['email_type'] = 'five';
|
||||
}
|
||||
\think\Queue::push('app\api\job\ReminderEmailToReviewer@fire',$value, 'ReminderEmailToReviewer');
|
||||
}
|
||||
$this->showMessage('邀请审稿超过三天/五天发送提醒邮件处理完成',1);
|
||||
}
|
||||
/**
|
||||
* 文章审稿阶段-同意审稿超过十天/十二天发送提醒邮件
|
||||
* @return void
|
||||
*/
|
||||
public function agreeReminder(){
|
||||
|
||||
//获取当前时间
|
||||
$sCurrentDate = date('Y-m-d', time());
|
||||
$iTime = $this->iDayTime;
|
||||
//获取文章信息
|
||||
$aResult = $this->getArticle();
|
||||
$iStatus = empty($aResult['status']) ? 0 : $aResult['status'];
|
||||
$sMsg = empty($aResult['msg']) ? 'No data obtained' : $aResult['msg'];
|
||||
if($iStatus != 1){
|
||||
$this->showMessage($sMsg,2);
|
||||
exit;
|
||||
}
|
||||
|
||||
//数据处理
|
||||
$aArticle = empty($aResult['data']) ? [] : $aResult['data'];
|
||||
if(empty($aArticle)){
|
||||
$this->showMessage('Article or journal information is empty',2);
|
||||
exit;
|
||||
}
|
||||
|
||||
//查询文章邀请的审稿人
|
||||
$aArticleId = array_column($aArticle, 'article_id');
|
||||
$aWhere = ['article_id' => ['in',$aArticleId],'state' => 0];
|
||||
$aReviewer = Db::name('article_reviewer')->field('art_rev_id,reviewer_id,article_id,agree_review_time')->where($aWhere)->order('article_id desc')->select();
|
||||
if(empty($aReviewer)){
|
||||
$this->showMessage('No qualified reviewers were found',2);
|
||||
exit;
|
||||
}
|
||||
|
||||
//数据处理
|
||||
foreach ($aReviewer as $key => $value) {
|
||||
if(empty($value['agree_review_time'])){
|
||||
continue;
|
||||
}
|
||||
//时间处理
|
||||
$sTargetDate = date('Y-m-d', $value['agree_review_time']);
|
||||
//日期转时间戳
|
||||
$iTargetTime = strtotime($sTargetDate);//邀请时间戳
|
||||
$iCurrentTime = strtotime($sCurrentDate);//当前时间戳
|
||||
$iTenCtime = intval($iTargetTime + (10 * $iTime));//十天
|
||||
$iTwelveCtime = intval($iTargetTime + (12 * $iTime));//十二天
|
||||
//对比
|
||||
if($iCurrentTime != $iTenCtime && $iCurrentTime != $iTwelveCtime){
|
||||
continue;
|
||||
}
|
||||
if($iTenCtime == $iCurrentTime){ //超过十天
|
||||
$value['email_type'] = 'ten';
|
||||
}
|
||||
if($iTwelveCtime == $iCurrentTime){ //超过十二天
|
||||
$value['email_type'] = 'twelve';
|
||||
}
|
||||
\think\Queue::push('app\api\job\ReminderEmailToReviewer@fire',$value, 'ReminderEmailToReviewer');
|
||||
}
|
||||
$this->showMessage('同意审稿超过十天/十二天发送提醒邮件处理完成',1);
|
||||
}
|
||||
/**
|
||||
* 发送邮件提醒
|
||||
* @return void
|
||||
*/
|
||||
public function reminder($aParam = []){
|
||||
//文章ID
|
||||
$iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id'];
|
||||
if(empty($iArticleId)){
|
||||
return json_encode(['status' => 2,'msg' => 'Please select the article to query']);
|
||||
}
|
||||
//审稿人ID
|
||||
$iReviewerId = empty($aParam['reviewer_id']) ? 0 : $aParam['reviewer_id'];
|
||||
if(empty($iReviewerId)){
|
||||
return json_encode(['status' => 2,'msg' => 'Reviewers who meet the criteria of the article were not selected']);
|
||||
}
|
||||
//邮件类型
|
||||
$sEmailType = empty($aParam['email_type']) ? '' : $aParam['email_type'];
|
||||
if(empty($sEmailType)){
|
||||
return json_encode(['status' => 2,'msg' => 'Email type cannot be empty']);
|
||||
}
|
||||
//判断文章是否送审
|
||||
$aWhere = ['state' => 2,'article_id' => $iArticleId];
|
||||
$aArticle = Db::name('article')->field('article_id,user_id,journal_id,accept_sn,title,abstrart')->where($aWhere)->find();
|
||||
if(empty($aArticle)){
|
||||
return json_encode(array('status' => 3,'msg' => 'No articles requiring review were found' ));
|
||||
}
|
||||
//查询是否存在审稿记录
|
||||
$aState = ['three' => 5,'five' => 5,'ten' => 0,'twelve' => 0];
|
||||
$iState = isset($aState[$sEmailType]) ? $aState[$sEmailType] : -1 ;
|
||||
$aWhere = ['reviewer_id' => $iReviewerId,'article_id' => $iArticleId,'state' => $iState];
|
||||
$aReviewer = Db::name('article_reviewer')->field('art_rev_id,reviewer_id,ctime,agree_review_time,state')->where($aWhere)->find();
|
||||
if(empty($aReviewer)){
|
||||
return json_encode(['status' => 4,'msg' => 'No qualified reviewers were found']);
|
||||
}
|
||||
|
||||
//查询期刊信息
|
||||
if(empty($aArticle['journal_id'])){
|
||||
return json_encode(array('status' => 5,'msg' => 'The article is not associated with a journal' ));
|
||||
}
|
||||
$aWhere = ['state' => 0,'journal_id' => $aArticle['journal_id']];
|
||||
$aJournal = Db::name('journal')->where($aWhere)->find();
|
||||
if(empty($aJournal)){
|
||||
return json_encode(array('status' => 6,'msg' => 'No journal information found' ));
|
||||
}
|
||||
|
||||
//查询审稿人邮箱
|
||||
$aWhere = ['user_id' => $iReviewerId,'state' => 0,'email' => ['<>','']];
|
||||
$aUser = Db::name('user')->field('user_id,email,realname,account')->where($aWhere)->find();
|
||||
//收件人
|
||||
$sEmail = empty($aUser['email']) ? '' : $aUser['email'];//'1172937051@qq.com';//'publisher@tmrjournals.com';//'1172937051@qq.com';//
|
||||
if(empty($sEmail)){
|
||||
return json_encode(['status' => 7,'msg' => "Reviewer's email information not found"]);
|
||||
}
|
||||
|
||||
//处理发邮件
|
||||
//获取邮件模版
|
||||
$aEmailConfig= empty($this->aEmailConfig[$sEmailType]) ? [] : $this->aEmailConfig[$sEmailType];
|
||||
if(empty($aEmailConfig)){
|
||||
return json_encode(['status' => 8,'msg' => "Email template not obtained"]);
|
||||
}
|
||||
//邮件内容
|
||||
$aSearch = [
|
||||
'{accept_sn}' => empty($aArticle['accept_sn']) ? '' : $aArticle['accept_sn'],//accept_sn
|
||||
'{article_title}' => empty($aArticle['title']) ? '' : $aArticle['title'],//文章标题
|
||||
'{abstrart}' => empty($aArticle['abstrart']) ? '' : $aArticle['abstrart'],//文章摘要
|
||||
'{journal_title}' => empty($aJournal['title']) ? '' : $aJournal['title'],//期刊名
|
||||
'{journal_issn}' => empty($aJournal['issn']) ? '' : $aJournal['issn'],
|
||||
'{journal_email}' => empty($aJournal['email']) ? '' : $aJournal['email'],
|
||||
'{website}' => empty($aJournal['website']) ? '' : $aJournal['website'],
|
||||
];
|
||||
//用户名
|
||||
$realname = empty($aUser['account']) ? '' : $aUser['account'];
|
||||
$realname = empty($aUser['realname']) ? $realname : $aUser['realname'];
|
||||
$aSearch['{realname}'] = $realname;
|
||||
//用户账号
|
||||
$aSearch['{account}'] = empty($aUser['account']) ? '' : $aUser['account'];
|
||||
//审稿链接
|
||||
$oArticle = new \app\api\controller\Article;
|
||||
$aSearch['{creatLoginUrlForreviewer}'] = $oArticle->creatLoginUrlForreviewer(['user_id' => $iReviewerId],$aReviewer['art_rev_id']);
|
||||
if($aReviewer['state'] == 5){
|
||||
$aSearch['{creatRejectUrlForReviewer}'] = $oArticle->creatRejectUrlForReviewer(['user_id' => $iReviewerId],$aReviewer['art_rev_id']);
|
||||
|
||||
}
|
||||
//邀请时间
|
||||
$aSearch['{invite_time}'] = empty($aReviewer['ctime']) ? '' : $this->timestampToEnglishDate($aReviewer['ctime']);
|
||||
//同意审稿截止时间
|
||||
$iAgreeTime = empty($aReviewer['agree_review_time']) ? 0 : $aReviewer['agree_review_time'];
|
||||
$iAgreeTime = empty($iAgreeTime) ? '' : intval($iAgreeTime + (14 * $this->iDayTime));//十四天
|
||||
$aSearch['{agree_deadline}'] = empty($iAgreeTime) ? '' : $this->timestampToEnglishDate($iAgreeTime);
|
||||
//邮件标题
|
||||
$title = str_replace(array_keys($aSearch), array_values($aSearch),$aEmailConfig['email_subject']);
|
||||
//邮件内容变量替换
|
||||
$content = str_replace(array_keys($aSearch), array_values($aSearch), $aEmailConfig['email_content']);
|
||||
//判断标题和内容是否为空
|
||||
if(empty($title) || empty($content)){
|
||||
return json_encode(['status' => 9,'msg' => "The email content and title are empty"]);
|
||||
}
|
||||
//拼接样式
|
||||
$pre = \think\Env::get('emailtemplete.pre');
|
||||
$net = \think\Env::get('emailtemplete.net');
|
||||
$net1 = str_replace("{{email}}",trim($sEmail),$net);
|
||||
$content=$pre.$content.$net1;
|
||||
|
||||
//发送邮件
|
||||
$memail = empty($aJournal['email']) ? '' : $aJournal['email'];
|
||||
$mpassword = empty($aJournal['epassword']) ? '' : $aJournal['epassword'];
|
||||
//期刊标题
|
||||
$from_name = empty($aJournal['title']) ? '' : $aJournal['title'];
|
||||
|
||||
//查询是否发送过邮件
|
||||
$aStateType = ['three' => 10,'five' => 11,'ten' => 12,'twelve' => 13];
|
||||
//邮件日志类型
|
||||
$iLogType = empty($aStateType[$sEmailType]) ? 0 : $aStateType[$sEmailType];
|
||||
$oReviewer = new \app\common\Reviewer;
|
||||
$aEmailLog = ['article_id' => $iArticleId,'art_rev_id' => $aReviewer['art_rev_id'],'reviewer_id' => $iReviewerId,'type' => $iLogType,'is_success' => 1];
|
||||
$aLog = DB::name('email_reviewer')->field('id')->where($aEmailLog)->find();
|
||||
$sMsg = '邮件已发送';
|
||||
if(empty($aLog)){
|
||||
$aResult = sendEmail($sEmail,$title,$from_name,$content,$memail,$mpassword);
|
||||
$iStatus = empty($aResult['status']) ? 1 : $aResult['status'];
|
||||
$iIsSuccess = 2;
|
||||
$sMsg = empty($aResult['data']) ? '失败' : $aResult['data'];
|
||||
if($iStatus == 1){
|
||||
$iIsSuccess = 1;
|
||||
$sMsg = '成功';
|
||||
}
|
||||
//记录邮件发送日志
|
||||
$aEmailLog['email'] = $sEmail;
|
||||
$aEmailLog['content'] = $content;
|
||||
$aEmailLog['create_time'] = time();
|
||||
$aEmailLog['is_success'] = $iIsSuccess;
|
||||
$aEmailLog['msg'] = $sMsg;
|
||||
//添加邮件发送日志
|
||||
$iId = $oReviewer->addLog($aEmailLog);
|
||||
}
|
||||
return json_encode(['status' => 1,'msg' => $sMsg]);
|
||||
}
|
||||
/**
|
||||
* 获取审稿状态的文章
|
||||
* @return void
|
||||
*/
|
||||
private function getArticle(){
|
||||
|
||||
//查询送审中的文章
|
||||
$aWhere = ['state' => 2];
|
||||
$aArticle = Db::name('article')->field('article_id')->where($aWhere)->select();
|
||||
if(empty($aArticle)){
|
||||
return ['status' => 2,'msg' => 'No articles requiring review were found'];
|
||||
}
|
||||
|
||||
//数据返回
|
||||
return ['status' => 1,'msg' => 'Successfully obtained information','data' => $aArticle];
|
||||
}
|
||||
private function timestampToEnglishDate($timestamp) {
|
||||
//验证时间戳有效性
|
||||
if (!is_numeric($timestamp) || $timestamp < 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
//设置本地化(确保月份为英文全称,兼容Linux/Windows)
|
||||
$locale = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'
|
||||
? 'English_United States.1252'
|
||||
: 'en_US.UTF-8';
|
||||
setlocale(LC_TIME, $locale);
|
||||
|
||||
//格式化:d=日(无前导零)、F=英文月份全称、Y=四位年 strftime的%e在Linux下是无前导零的日,Windows用%#d
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
||||
$dateStr = strftime('%#d %B %Y', $timestamp); // Windows
|
||||
} else {
|
||||
$dateStr = strftime('%e %B %Y', $timestamp); // Linux/Mac
|
||||
}
|
||||
return trim($dateStr);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* 格式化信息输出
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
* @date 2018.09.28
|
||||
* @param $[message] [<显示信息>]
|
||||
* @param $[status] [<输出信息1成功,2失败>]
|
||||
*/
|
||||
private function showMessage($message, $status = 1) {
|
||||
if ($status == 1) {
|
||||
echo "[SUCCESS]";
|
||||
} else {
|
||||
echo "[ERROR]";
|
||||
}
|
||||
echo date("Y-m-d H:i:s") . " " . $message . "\n";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1385,7 +1385,7 @@ class EmailClient extends Base
|
||||
$factoryId = intval($this->request->param('promotion_factory_id', 0));
|
||||
$sendDate = trim($this->request->param('send_date', date('Y-m-d', strtotime('+1 day'))));
|
||||
$taskName = trim($this->request->param('task_name', ''));
|
||||
$noRepeatDays = intval($this->request->param('no_repeat_days', 30));
|
||||
$noRepeatDays = intval($this->request->param('no_repeat_days', 15));
|
||||
$minInterval = intval($this->request->param('min_interval', 30));
|
||||
$maxInterval = intval($this->request->param('max_interval', 60));
|
||||
$maxBounceRate = intval($this->request->param('max_bounce_rate', 5));
|
||||
@@ -2282,7 +2282,7 @@ class EmailClient extends Base
|
||||
set_time_limit(120);
|
||||
|
||||
$sendDate = date('Y-m-d', strtotime('+1 day'));
|
||||
$noRepeatDaysDefault = 30;
|
||||
$noRepeatDaysDefault = 15;
|
||||
|
||||
$factories = Db::name('promotion_factory')
|
||||
->alias('f')
|
||||
@@ -2354,7 +2354,7 @@ class EmailClient extends Base
|
||||
$expertType = intval($factory['expert_type']);
|
||||
|
||||
// 内部受众(type∈{1..4}):默认 60 天频次(约稿场景);外部 expert 库(type=5)沿用 30 天
|
||||
$noRepeatDays = $expertType === 5 ? $noRepeatDaysDefault : 60;
|
||||
$noRepeatDays = $expertType === 5 ? $noRepeatDaysDefault : 20;
|
||||
|
||||
if ($expertType === 5) {
|
||||
$fields = $this->resolveFieldsByFetchIds($factory['fetch_ids']);
|
||||
@@ -2624,8 +2624,8 @@ class EmailClient extends Base
|
||||
$expertType = intval($factory['expert_type']);
|
||||
$dailyLimit = max(1, intval($factory['send_count']));
|
||||
|
||||
// 默认频次:expert=30天,内部=60天
|
||||
$noRepeatDaysDefault = $expertType === 5 ? 30 : 60;
|
||||
// 默认频次:expert=15天,内部=20天
|
||||
$noRepeatDaysDefault = $expertType === 5 ? 15 : 20;
|
||||
$noRepeatDays = intval($this->request->param('no_repeat_days', $noRepeatDaysDefault));
|
||||
|
||||
if ($expertType === 5) {
|
||||
@@ -2734,7 +2734,7 @@ class EmailClient extends Base
|
||||
|
||||
$query = Db::name('expert')->alias('e')
|
||||
->join('t_expert_field ef', 'e.expert_id = ef.expert_id', 'inner')
|
||||
->where('e.state', 0)
|
||||
// ->where('e.state', 0)
|
||||
->where('e.unsubscribed', 0)
|
||||
->where('ef.state', 0);
|
||||
|
||||
@@ -2776,11 +2776,16 @@ class EmailClient extends Base
|
||||
$query->where('e.country_id', 'in', $countryIds);
|
||||
}
|
||||
|
||||
return $query
|
||||
$res1 = $query
|
||||
->field('e.*')
|
||||
->group('e.expert_id')
|
||||
->limit($limit)
|
||||
->select();
|
||||
|
||||
|
||||
// echo $query->getLastSql();
|
||||
|
||||
return $res1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -298,7 +298,7 @@ class ExpertFinder extends Base
|
||||
*/
|
||||
public function dailyFetchAll()
|
||||
{
|
||||
$perPage = max(10, intval($this->request->param('per_page', 10)));
|
||||
$perPage = max(10, intval($this->request->param('per_page', 50)));
|
||||
$source = $this->request->param('source', 'pubmed');
|
||||
$minYear = intval($this->request->param('min_year', date('Y') - 3));
|
||||
|
||||
|
||||
@@ -126,6 +126,24 @@ class Journal extends Base {
|
||||
return jsonSuccess($program);
|
||||
}
|
||||
|
||||
public function citeMate(){
|
||||
$data = $this->request->post();
|
||||
$rule = new Validate([
|
||||
"journal_id"=>"require",
|
||||
"year"=>"require"
|
||||
]);
|
||||
if(!$rule->check($data)){
|
||||
return jsonError($rule->getError());
|
||||
}
|
||||
$journal_info = $this->journal_obj->where("journal_id",$data['journal_id'])->find();
|
||||
$url = "http://journalapi.tmrjournals.com/public/index.php/api/Main/citeMate";
|
||||
$program['journal_issn'] = $journal_info['issn'];
|
||||
$program['year'] = $data['year'];
|
||||
$res = object_to_array(json_decode(myPost($url,$program)));
|
||||
|
||||
return json($res);
|
||||
}
|
||||
|
||||
public function delJournalStage(){
|
||||
$data = $this->request->post();
|
||||
$rule = new Validate([
|
||||
|
||||
@@ -782,7 +782,8 @@ class Preaccept extends Base
|
||||
|
||||
$title = trim((string)($summary['title'] ?? ''));
|
||||
$jouraRaw = trim((string)($summary['joura'] ?? ''));
|
||||
$authorStr = trim((string)($summary['author_str'] ?? ''));
|
||||
// 姓全写 + 名首字母,超过 3 个作者取前 3 个 + et al
|
||||
$authorCitation = $svc->getAuthorsCitation($summary['raw'] ?? [], 3);
|
||||
$dateno = trim((string)($summary['dateno'] ?? ''));
|
||||
$doilink = trim((string)($summary['doilink'] ?? ''));
|
||||
if ($doilink === '') {
|
||||
@@ -790,7 +791,7 @@ class Preaccept extends Base
|
||||
}
|
||||
|
||||
$f = [
|
||||
'author' => $authorStr !== '' ? prgeAuthor($authorStr) : '',
|
||||
'author' => $authorCitation !== '' ? $authorCitation . '.' : '',
|
||||
'title' => $title,
|
||||
'joura' => $jouraRaw !== '' ? formateJournal($jouraRaw) : '',
|
||||
'dateno' => str_replace(' ', '', str_replace('-', '–', $dateno)),
|
||||
|
||||
@@ -3208,7 +3208,7 @@ class Production extends Base
|
||||
$z = count($list);
|
||||
$m = 0;
|
||||
foreach ($list as $v) {
|
||||
if ($v['refer_frag'] != '' || $v['author'] != '') {
|
||||
if ($v['refer_frag'] != '' || $v['author'] != ''|| $v['title'] != '') {
|
||||
$m++;
|
||||
}
|
||||
}
|
||||
@@ -3361,7 +3361,7 @@ class Production extends Base
|
||||
$tt .= "Please carefully check the proof, including the text, figures, tables, references, author information, affiliations, spelling, and formatting. If any corrections are needed, please mark them clearly on the proof or submit comments through the system.<br/>";
|
||||
$tt .= "If we do not receive your confirmation by ".date("Y-m-d", strtotime("+3 days")).", the proof will be considered approved in its current form. Please note that no further revisions will be accepted after online confirmation.<br/><br/>";
|
||||
$tt .= "Thank you for your time and cooperation. Should you have any questions, please feel free to contact us.<br/><br/>";
|
||||
$tt .= "Best regards,<br/>Biomedical Engineering Communications<br/>Email: bmec@tmrjournals.com<br/>Website: https://www.tmrjournals.com/bmec/";
|
||||
$tt .= "Best regards,<br/>".$journal_info['title']."<br/>Email: ".$journal_info['email']."<br/>Website: ".$journal_info['website'];
|
||||
|
||||
// $maidata['email'] = '751475802@qq.com';
|
||||
$maidata['email'] = $user_info['email'];
|
||||
|
||||
78
application/api/job/AiCheckRefer.php
Normal file
78
application/api/job/AiCheckRefer.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
namespace app\api\job;
|
||||
use think\queue\Job;
|
||||
use app\common\QueueJob;
|
||||
use app\common\QueueRedis;
|
||||
use think\Db;
|
||||
class AiCheckRefer
|
||||
{
|
||||
private $oQueueJob;
|
||||
private $QueueRedis;
|
||||
private $completedExprie = 3600;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->oQueueJob = new QueueJob;
|
||||
$this->QueueRedis = QueueRedis::getInstance();
|
||||
}
|
||||
|
||||
public function fire(Job $job, $data)
|
||||
{
|
||||
//任务开始判断
|
||||
$this->oQueueJob->init($job);
|
||||
|
||||
// 获取 Redis 任务的原始数据
|
||||
$rawBody = empty($job->getRawBody()) ? '' : $job->getRawBody();
|
||||
$jobData = empty($rawBody) ? [] : json_decode($rawBody, true);
|
||||
$jobId = empty($jobData['id']) ? 'unknown' : $jobData['id'];
|
||||
|
||||
$this->oQueueJob->log("-----------队列任务开始-----------");
|
||||
$this->oQueueJob->log("当前任务ID: {$jobId}, 尝试次数: {$job->attempts()}");
|
||||
|
||||
|
||||
// 获取生产文章ID
|
||||
$iPArticleId = empty($data['p_article_id']) ? 0 : $data['p_article_id'];
|
||||
if (empty($iPArticleId)) {
|
||||
$this->oQueueJob->log("无效的p_article_id,删除任务");
|
||||
$job->delete();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
// 生成Redis键并尝试获取锁
|
||||
$sClassName = get_class($this);
|
||||
$sRedisKey = "queue_job:{$sClassName}:{$iPArticleId}";
|
||||
$sRedisValue = uniqid() . '_' . getmypid();
|
||||
if (!$this->oQueueJob->acquireLock($sRedisKey, $sRedisValue, $job)) {
|
||||
return; // 未获取到锁,已处理
|
||||
}
|
||||
|
||||
//生成内容
|
||||
$oProductionArticleRefer = new \app\api\controller\References;
|
||||
$response = $oProductionArticleRefer->checkByAi($data);
|
||||
// 验证API响应
|
||||
if (empty($response)) {
|
||||
throw new \RuntimeException("OpenAI API返回空结果");
|
||||
}
|
||||
// 检查JSON解析错误
|
||||
$aResult = json_decode($response, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \RuntimeException("解析OpenAI响应失败: " . json_last_error_msg() . " | 原始响应: {$response}");
|
||||
}
|
||||
$sMsg = empty($aResult['msg']) ? 'success' : $aResult['msg'];
|
||||
//更新完成标识
|
||||
$this->QueueRedis->finishJob($sRedisKey, 'completed', $this->completedExprie,$sRedisValue);
|
||||
$job->delete();
|
||||
$this->oQueueJob->log("任务执行成功 | 日志ID: {$sRedisKey} | 执行日志:{$sMsg}");
|
||||
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->oQueueJob->handleRetryableException($e,$sRedisKey,$sRedisValue, $job);
|
||||
} catch (\LogicException $e) {
|
||||
$this->oQueueJob->handleNonRetryableException($e,$sRedisKey,$sRedisValue, $job);
|
||||
} catch (\Exception $e) {
|
||||
$this->oQueueJob->handleRetryableException($e,$sRedisKey,$sRedisValue, $job);
|
||||
} finally {
|
||||
$this->oQueueJob->finnal();
|
||||
}
|
||||
}
|
||||
}
|
||||
82
application/api/job/ProofReadQueue.php
Normal file
82
application/api/job/ProofReadQueue.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
namespace app\api\job;
|
||||
|
||||
use think\queue\Job;
|
||||
use app\common\QueueJob;
|
||||
use app\common\QueueRedis;
|
||||
use app\common\ProofRead;
|
||||
class ProofReadQueue
|
||||
{
|
||||
private $oQueueJob;
|
||||
private $QueueRedis;
|
||||
private $completedExprie = 3600;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->oQueueJob = new QueueJob;
|
||||
$this->QueueRedis = QueueRedis::getInstance();
|
||||
}
|
||||
|
||||
public function fire(Job $job, $data)
|
||||
{
|
||||
//任务开始判断
|
||||
$this->oQueueJob->init($job);
|
||||
|
||||
// 获取 Redis 任务的原始数据
|
||||
$rawBody = empty($job->getRawBody()) ? '' : $job->getRawBody();
|
||||
$jobData = empty($rawBody) ? [] : json_decode($rawBody, true);
|
||||
$jobId = empty($jobData['id']) ? 'unknown' : $jobData['id'];
|
||||
|
||||
$this->oQueueJob->log("-----------队列任务开始-----------");
|
||||
$this->oQueueJob->log("当前任务ID: {$jobId}, 尝试次数: {$job->attempts()}");
|
||||
|
||||
// 获取文章ID
|
||||
$iArticleId = empty($data['article_id']) ? 0 : $data['article_id'];
|
||||
$sChunkIndex = empty($data['chunkIndex']) ? 0 : $data['chunkIndex'];
|
||||
$sPrompt = empty($data['prompt']) ? '' : $data['prompt'];
|
||||
if (empty($iArticleId)) {
|
||||
$this->oQueueJob->log("无效的article_id,删除任务");
|
||||
$job->delete();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
// 生成Redis键并尝试获取锁
|
||||
$sClassName = get_class($this);
|
||||
$sRedisKey = "queue_job:{$sClassName}:{$iArticleId}:{$sPrompt}_{$sChunkIndex}";
|
||||
$sRedisValue = uniqid() . '_' . getmypid();
|
||||
if (!$this->oQueueJob->acquireLock($sRedisKey, $sRedisValue, $job)) {
|
||||
return; // 未获取到锁,已处理
|
||||
}
|
||||
|
||||
//生成内容
|
||||
$oAireview = new ProofRead;
|
||||
$response = $oAireview->proofReadQueue($data);
|
||||
|
||||
// 验证API响应
|
||||
if (empty($response)) {
|
||||
throw new \RuntimeException("OpenAI API返回空结果");
|
||||
}
|
||||
// 检查JSON解析错误
|
||||
$aResult = json_decode($response, true);
|
||||
echo '<pre>';var_dump($aResult);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \RuntimeException("解析OpenAI响应失败: " . json_last_error_msg() . " | 原始响应: {$response}");
|
||||
}
|
||||
$sMsg = empty($aResult['msg']) ? 'success' : $aResult['msg'];
|
||||
//更新完成标识
|
||||
$this->QueueRedis->finishJob($sRedisKey, 'completed', $this->completedExprie,$sRedisValue);
|
||||
$job->delete();
|
||||
$this->oQueueJob->log("任务执行成功 | 日志ID: {$sRedisKey} | 执行日志:{$sMsg}");
|
||||
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->oQueueJob->handleRetryableException($e,$sRedisKey,$sRedisValue, $job);
|
||||
} catch (\LogicException $e) {
|
||||
$this->oQueueJob->handleNonRetryableException($e,$sRedisKey,$sRedisValue, $job);
|
||||
} catch (\Exception $e) {
|
||||
$this->oQueueJob->handleRetryableException($e,$sRedisKey,$sRedisValue, $job);
|
||||
} finally {
|
||||
$this->oQueueJob->finnal();
|
||||
}
|
||||
}
|
||||
}
|
||||
89
application/api/job/SendAuthorEmail.php
Normal file
89
application/api/job/SendAuthorEmail.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
namespace app\api\job;
|
||||
|
||||
use think\queue\Job;
|
||||
use app\common\QueueJob;
|
||||
use app\common\QueueRedis;
|
||||
class SendAuthorEmail
|
||||
{
|
||||
private $oQueueJob;
|
||||
private $QueueRedis;
|
||||
private $completedExprie = 3600; // 完成状态过期时间
|
||||
public function __construct()
|
||||
{
|
||||
$this->oQueueJob = new QueueJob;
|
||||
$this->QueueRedis = QueueRedis::getInstance();
|
||||
}
|
||||
|
||||
public function fire(Job $job, $data)
|
||||
{
|
||||
//任务开始判断
|
||||
$this->oQueueJob->init($job);
|
||||
|
||||
// 获取 Redis 任务的原始数据
|
||||
$rawBody = empty($job->getRawBody()) ? '' : $job->getRawBody();
|
||||
$jobData = empty($rawBody) ? [] : json_decode($rawBody, true);
|
||||
$jobId = empty($jobData['id']) ? 'unknown' : $jobData['id'];
|
||||
|
||||
$this->oQueueJob->log("-----------队列任务开始-----------");
|
||||
$this->oQueueJob->log("当前任务ID: {$jobId}, 尝试次数: {$job->attempts()}");
|
||||
|
||||
try {
|
||||
|
||||
// 验证任务数据完整性
|
||||
// 获取文章ID
|
||||
$iArticleId = empty($data['article_id']) ? 0 : $data['article_id'];
|
||||
//作者邮箱
|
||||
$email = empty($data['email']) ? '' : $data['email'];
|
||||
//邮件主题
|
||||
$subject = empty($data['subject']) ? '' : $data['subject'];
|
||||
//发送来源
|
||||
$title = empty($data['title']) ? '' : $data['title'];
|
||||
$subject = empty($subject) ? $title : $subject;
|
||||
//邮件内容
|
||||
$content = empty($data['content']) ? '' : $data['content'];
|
||||
//邮箱
|
||||
$temail = empty($data['temail']) ? '' : $data['temail'];
|
||||
//密码
|
||||
$tpassword = empty($data['tpassword']) ? '' : $data['tpassword'];
|
||||
//邮件类型
|
||||
$type = empty($data['type']) ? 1 : $data['type'];
|
||||
if (empty($iArticleId) || empty($email)) {
|
||||
$this->oQueueJob->log("无效的article_id/email,删除任务");
|
||||
$job->delete();
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成唯一任务标识
|
||||
$sClassName = get_class($this);
|
||||
$sRedisKey = "queue_job:{$sClassName}:{$iArticleId}:{$email}";
|
||||
$sRedisValue = uniqid() . '_' . getmypid();
|
||||
if (!$this->oQueueJob->acquireLock($sRedisKey, $sRedisValue, $job)) {
|
||||
return; // 未获取到锁,已处理
|
||||
}
|
||||
|
||||
// 执行核心任务-发送邮件
|
||||
$aResult = sendEmail($email,$subject,$title,$content,$temail,$tpassword);
|
||||
$iStatus = empty($aResult['status']) ? 1 : $aResult['status'];
|
||||
$iIsSuccess = 2;
|
||||
$sMsg = empty($aResult['data']) ? '失败' : $aResult['data'];
|
||||
if($iStatus == 1){
|
||||
$iIsSuccess = 1;
|
||||
$sMsg = '成功';
|
||||
}
|
||||
// 更新完成标识
|
||||
$this->QueueRedis->finishJob($sRedisKey, 'completed', $this->completedExprie, $sRedisValue);
|
||||
$job->delete();
|
||||
$this->oQueueJob->log("任务执行成功 | 日志ID: {$sRedisKey} | 执行日志:{$sMsg}");
|
||||
|
||||
} catch (RuntimeException $e) {
|
||||
$this->oQueueJob->handleRetryableException($e,$sRedisKey,$sRedisValue, $job);
|
||||
} catch (LogicException $e) {
|
||||
$this->oQueueJob->handleNonRetryableException($e,$sRedisKey,$sRedisValue, $job);
|
||||
} catch (Exception $e) {
|
||||
$this->oQueueJob->handleRetryableException($e,$sRedisKey,$sRedisValue, $job);
|
||||
} finally {
|
||||
$this->oQueueJob->finnal();
|
||||
}
|
||||
}
|
||||
}
|
||||
23
application/command/agreeReviewReminder.sh
Executable file
23
application/command/agreeReviewReminder.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
# agreeReviewReminder.sh
|
||||
# 批量处理文章审稿阶段-同意审稿超过十天/十二天发送提醒邮件
|
||||
# 调用接口获取需要提醒的审稿人记录
|
||||
# 此文件需要在crontab中配置每天【凌晨0:30】运行一次
|
||||
# @author chengxiaoling
|
||||
# @date 2025-12-29
|
||||
|
||||
# 基础配置
|
||||
DOMAIN="http://api.tmrjournals.com/public/index.php/" # 项目域名
|
||||
# DOMAIN="http://zmzm.tougao.dev.com" # 项目域名
|
||||
ROUTE="/api/Cronreview/agreeReminder" # 控制器路由
|
||||
BASE_PATH=$(cd `dirname $0`; pwd)
|
||||
# 如果日志目录不存在则创建
|
||||
logDir=${BASE_PATH}/log/$(date "+%Y")/$(date "+%m")
|
||||
if [ ! -d $logDir ];then
|
||||
mkdir -p $logDir
|
||||
fi
|
||||
|
||||
# 执行请求并记录日志
|
||||
curl "${DOMAIN}${ROUTE}" >> ${logDir}/agreeReminder_$(date "+%Y%m%d").log 2>&1
|
||||
# 添加时间戳
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 定时任务已执行" >> ${logDir}/agreeReminder_$(date "+%Y%m%d").log
|
||||
23
application/command/inviteReviewReminder.sh
Executable file
23
application/command/inviteReviewReminder.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
# inviteReviewReminder.sh
|
||||
# 批量处理文章审稿阶段-邀请审稿超过三天/五天发送提醒邮件
|
||||
# 调用接口获取需要提醒的审稿人记录
|
||||
# 此文件需要在crontab中配置每天【凌晨0:30】运行一次
|
||||
# @author chengxiaoling
|
||||
# @date 2025-12-29
|
||||
|
||||
# 基础配置
|
||||
DOMAIN="http://api.tmrjournals.com/public/index.php/" # 项目域名
|
||||
# DOMAIN="http://zmzm.tougao.dev.com" # 项目域名
|
||||
ROUTE="/api/Cronreview/inviteReminder" # 控制器路由
|
||||
BASE_PATH=$(cd `dirname $0`; pwd)
|
||||
# 如果日志目录不存在则创建
|
||||
logDir=${BASE_PATH}/log/$(date "+%Y")/$(date "+%m")
|
||||
if [ ! -d $logDir ];then
|
||||
mkdir -p $logDir
|
||||
fi
|
||||
|
||||
# 执行请求并记录日志
|
||||
curl "${DOMAIN}${ROUTE}" >> ${logDir}/inviteReminder_$(date "+%Y%m%d").log 2>&1
|
||||
# 添加时间戳
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 定时任务已执行" >> ${logDir}/inviteReminder_$(date "+%Y%m%d").log
|
||||
@@ -16,6 +16,10 @@ class CrossrefService
|
||||
private $timeout = 15; // 请求超时(秒)
|
||||
private $maxRetry = 2; // 单个DOI最大重试次数
|
||||
private $crossrefUrl = "https://api.crossref.org/works/"; // 接口地址
|
||||
private $pubmedAbbr = true; // CrossRef 无期刊缩写时,是否回退到 PubMed/NLM 规范缩写
|
||||
|
||||
/** @var PubmedService|null 懒加载 */
|
||||
private $pubmedService = null;
|
||||
|
||||
public function __construct($config = [])
|
||||
{
|
||||
@@ -24,6 +28,7 @@ class CrossrefService
|
||||
if (isset($config['timeout'])) $this->timeout = intval($config['timeout']);
|
||||
if (isset($config['maxRetry'])) $this->maxRetry = intval($config['maxRetry']);
|
||||
if (isset($config['crossrefUrl'])) $this->crossrefUrl = (string)$config['crossrefUrl'];
|
||||
if (isset($config['pubmed_abbr'])) $this->pubmedAbbr = (bool)$config['pubmed_abbr'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +196,15 @@ class CrossrefService
|
||||
|
||||
$title = $this->getTitle($msg);
|
||||
$publisher = $this->getPublisher($msg);
|
||||
$joura = !empty($publisher['title']) ? $publisher['title'] : ($publisher['short_title'] ?? '');
|
||||
$validDoi = $this->filterValidDoi($doi);
|
||||
// 期刊缩写优先级:CrossRef short-container-title → PubMed/NLM 规范缩写 → CrossRef 全称
|
||||
$shortTitle = trim((string)($publisher['short_title'] ?? ''));
|
||||
$fullTitle = trim((string)($publisher['title'] ?? ''));
|
||||
$joura = $shortTitle;
|
||||
if ($joura === '') {
|
||||
$pubmedAbbr = $this->lookupPubmedJournalAbbr($validDoi);
|
||||
$joura = $pubmedAbbr !== '' ? $pubmedAbbr : $fullTitle;
|
||||
}
|
||||
$authors = $this->getAuthors($msg);
|
||||
$dateno = $this->getVolumeIssuePages($msg);
|
||||
$retractInfo = $this->checkRetracted($msg);
|
||||
@@ -280,6 +293,34 @@ class CrossrefService
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 用 PubMed/NLM 反查期刊规范缩写(CrossRef 无缩写时的兜底)。
|
||||
* 任何异常都吞掉并返回空串,保证不影响主流程。
|
||||
*
|
||||
* @param string $doi 已规整的裸 DOI
|
||||
* @return string 缩写或空串
|
||||
*/
|
||||
private function lookupPubmedJournalAbbr($doi)
|
||||
{
|
||||
$doi = trim((string)$doi);
|
||||
if (!$this->pubmedAbbr || $doi === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->pubmedService === null) {
|
||||
$this->pubmedService = new PubmedService([
|
||||
'email' => $this->mailto,
|
||||
'timeout' => $this->timeout,
|
||||
]);
|
||||
}
|
||||
$abbr = $this->pubmedService->journalAbbrByDoi($doi);
|
||||
return is_string($abbr) ? trim($abbr) : '';
|
||||
} catch (\Throwable $e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取作者列表
|
||||
*/
|
||||
@@ -300,6 +341,73 @@ class CrossrefService
|
||||
return $authors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 引用格式作者串:姓全写 + 名首字母,超过 $maxAuthors 个取前 N 个 + et al
|
||||
* 例:Smith JA, Jones B, Lee C, et al
|
||||
*
|
||||
* @param array $aDoiInfo Crossref message
|
||||
* @param int $maxAuthors 最多展示作者数,超过则截断加 et al
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthorsCitation($aDoiInfo = [], $maxAuthors = 3)
|
||||
{
|
||||
$list = [];
|
||||
if (!empty($aDoiInfo['author'])) {
|
||||
foreach ($aDoiInfo['author'] as $author) {
|
||||
$family = trim((string)($author['family'] ?? ''));
|
||||
$given = trim((string)($author['given'] ?? ''));
|
||||
|
||||
if ($family === '' && $given === '') {
|
||||
// 机构作者等无姓名结构的情况
|
||||
$orgName = trim((string)($author['name'] ?? ''));
|
||||
if ($orgName !== '') {
|
||||
$list[] = $orgName;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$initials = $this->givenToInitials($given);
|
||||
$name = $initials !== '' ? trim($family . ' ' . $initials) : $family;
|
||||
if ($name !== '') {
|
||||
$list[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($list)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$maxAuthors = max(1, (int)$maxAuthors);
|
||||
if (count($list) > $maxAuthors) {
|
||||
$list = array_slice($list, 0, $maxAuthors);
|
||||
return implode(', ', $list) . ', et al';
|
||||
}
|
||||
|
||||
return implode(', ', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 名转首字母:取每个组成部分(空格/连字符/点分隔)首字母大写并拼接。
|
||||
* 例:"John A." -> "JA","Mary-Jane" -> "MJ"
|
||||
*/
|
||||
private function givenToInitials($given)
|
||||
{
|
||||
$given = trim((string)$given);
|
||||
if ($given === '') {
|
||||
return '';
|
||||
}
|
||||
$parts = preg_split('/[\s\-\.]+/u', $given, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$initials = '';
|
||||
foreach ($parts as $p) {
|
||||
$first = mb_substr($p, 0, 1);
|
||||
if ($first !== '') {
|
||||
$initials .= mb_strtoupper($first);
|
||||
}
|
||||
}
|
||||
return $initials;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取发表年份
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace app\common;
|
||||
|
||||
use app\common\service\LocalModelService;
|
||||
use think\Db;
|
||||
use think\Env;
|
||||
use think\Exception;
|
||||
@@ -27,9 +28,17 @@ class ExpertFieldAiService
|
||||
|
||||
private $logFile;
|
||||
|
||||
/** @var bool|null */
|
||||
private static $schemaReady = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->logFile = ROOT_PATH . 'runtime' . DS . 'expert_field_ai.log';
|
||||
try {
|
||||
$this->ensureSchema();
|
||||
} catch (\Throwable $e) {
|
||||
$this->log('[ExpertFieldAi] ensureSchema fail: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 链式队列 =====================
|
||||
@@ -366,10 +375,16 @@ class ExpertFieldAiService
|
||||
$papers = array_slice($papers, 0, $maxPapers);
|
||||
$searchKeywords = array_values(array_unique(array_filter($searchKeywords)));
|
||||
|
||||
$countryName = '';
|
||||
$countryId = intval($expert['country_id'] ?? 0);
|
||||
if ($countryId > 0) {
|
||||
$countryName = (string)Db::name('country')->where('country_id', $countryId)->value('title');
|
||||
// t_expert.country 已存国家英文名,无需再查 country 表
|
||||
$countryName = trim((string)($expert['country'] ?? ''));
|
||||
if ($countryName === '') {
|
||||
$countryId = intval($expert['country_id'] ?? 0);
|
||||
if ($countryId > 0) {
|
||||
$row = Db::name('country')->where('country_id', $countryId)->find();
|
||||
if ($row) {
|
||||
$countryName = (string)($row['en_name'] ?? ($row['zh_name'] ?? ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -453,69 +468,27 @@ class ExpertFieldAiService
|
||||
|
||||
private function summarizeWithLlm(array $context)
|
||||
{
|
||||
$url = $this->resolveLlmChatUrl();
|
||||
$model = $this->resolveLlmModel();
|
||||
$apiKey = trim((string)Env::get(
|
||||
'expert_field_ai.chat_api_key',
|
||||
Env::get('user_field_ai.chat_api_key', Env::get('expert_country_chat_api_key', Env::get('citation_chat_api_key', '')))
|
||||
));
|
||||
|
||||
if ($url === '' || $model === '') {
|
||||
throw new Exception('LLM not configured (set base.model_url / expert_field_ai.chat_model)');
|
||||
}
|
||||
|
||||
$payloadJson = json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$messages = [
|
||||
[
|
||||
'role' => 'system',
|
||||
'content' => '你是学术领域分类助手。根据专家的单位、论文标题与 PubMed 检索上下文,用简体中文总结该专家最主要的研究领域。'
|
||||
. '注意:search_keywords 只是检索词,不可直接当作领域结论,应结合 paper 标题与 affiliation 判断。'
|
||||
. '要求:精确、简洁,1~3 个中文领域词或短短语,用顿号分隔;不要解释、不要英文。'
|
||||
. '只输出 JSON:{"field_ai":"..."}。',
|
||||
],
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => "请根据以下 JSON 资料总结该专家的主要研究领域:\n" . $payloadJson,
|
||||
],
|
||||
];
|
||||
$systemPrompt = '你是学术领域分类助手。根据专家的单位、论文标题与 PubMed 检索上下文,用简体中文总结该专家最主要的研究领域。'
|
||||
. '注意:search_keywords 只是检索词,不可直接当作领域结论,应结合 paper 标题与 affiliation 判断。'
|
||||
. '要求:精确、简洁,1~3 个中文领域词或短短语,用顿号分隔;不要解释、不要英文。'
|
||||
. '只输出 JSON:{"field_ai":"..."}。';
|
||||
$userPrompt = "请根据以下 JSON 资料总结该专家的主要研究领域:\n" . $payloadJson;
|
||||
|
||||
$body = [
|
||||
'model' => $model,
|
||||
'temperature' => 0.2,
|
||||
'messages' => $messages,
|
||||
];
|
||||
// 按上下文长度动态选模型(小: base.model_url1 / 大: base.model_url)
|
||||
$svc = new LocalModelService();
|
||||
$res = $svc->chat([
|
||||
['role' => 'system', 'content' => $systemPrompt],
|
||||
['role' => 'user', 'content' => $userPrompt],
|
||||
], ['temperature' => 0.2]);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode($body, JSON_UNESCAPED_UNICODE),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CONNECTTIMEOUT => 15,
|
||||
CURLOPT_TIMEOUT => max(30, (int)Env::get('expert_field_ai.timeout', Env::get('user_field_ai.timeout', 90))),
|
||||
CURLOPT_HTTPHEADER => array_filter([
|
||||
'Content-Type: application/json',
|
||||
$apiKey !== '' ? 'Authorization: Bearer ' . $apiKey : null,
|
||||
]),
|
||||
]);
|
||||
$raw = curl_exec($ch);
|
||||
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($raw === false) {
|
||||
throw new Exception('LLM curl error: ' . $err);
|
||||
}
|
||||
if ($code < 200 || $code >= 300) {
|
||||
throw new Exception('LLM HTTP ' . $code . ': ' . mb_substr((string)$raw, 0, 400));
|
||||
if (empty($res['ok'])) {
|
||||
throw new Exception('LLM error: ' . (string)($res['error'] ?? 'unknown'));
|
||||
}
|
||||
|
||||
$data = json_decode($raw, true);
|
||||
$content = '';
|
||||
if (is_array($data) && isset($data['choices'][0]['message']['content'])) {
|
||||
$content = trim((string)$data['choices'][0]['message']['content']);
|
||||
}
|
||||
$this->log('[ExpertFieldAi] llm tier=' . ($res['tier'] ?? '') . ' ctx_len=' . ($res['context_len'] ?? 0) . ' url=' . ($res['url'] ?? ''));
|
||||
|
||||
$content = trim((string)($res['content'] ?? ''));
|
||||
$fieldAi = $this->parseFieldAiFromContent($content);
|
||||
if ($fieldAi === '' && $content !== '') {
|
||||
$fieldAi = $this->cleanFieldAiText($content);
|
||||
@@ -523,44 +496,6 @@ class ExpertFieldAiService
|
||||
return $fieldAi;
|
||||
}
|
||||
|
||||
private function resolveLlmChatUrl()
|
||||
{
|
||||
$candidates = [
|
||||
// Env::get('expert_field_ai.chat_url', ''),
|
||||
// Env::get('user_field_ai.chat_url', ''),
|
||||
Env::get('base.model_url1', ''),
|
||||
];
|
||||
foreach ($candidates as $u) {
|
||||
$u = trim((string)$u);
|
||||
if ($u === '') {
|
||||
continue;
|
||||
}
|
||||
if (stripos($u, 'chat/completions') !== false) {
|
||||
return $u;
|
||||
}
|
||||
return rtrim($u, '/') . '/v1/chat/completions';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private function resolveLlmModel()
|
||||
{
|
||||
$candidates = [
|
||||
Env::get('expert_field_ai.chat_model', ''),
|
||||
Env::get('user_field_ai.chat_model', ''),
|
||||
Env::get('base.model', ''),
|
||||
Env::get('expert_country_chat_model', ''),
|
||||
'gpt-4.1',
|
||||
];
|
||||
foreach ($candidates as $m) {
|
||||
$m = trim((string)$m);
|
||||
if ($m !== '' && strtolower($m) !== 'your-model-name') {
|
||||
return $m;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private function parseFieldAiFromContent($content)
|
||||
{
|
||||
$content = trim((string)$content);
|
||||
@@ -637,18 +572,73 @@ class ExpertFieldAiService
|
||||
|
||||
private function updateFieldAi($expertId, $fieldAi, $status, $source, $note)
|
||||
{
|
||||
$this->ensureSchema();
|
||||
|
||||
$data = [
|
||||
'field_ai' => mb_substr(trim((string)$fieldAi), 0, 512),
|
||||
'field_ai_status' => intval($status),
|
||||
'field_ai_utime' => time(),
|
||||
'field_ai_source' => mb_substr(trim((string)$source), 0, 32),
|
||||
];
|
||||
if ($this->hasColumn('field_ai_source')) {
|
||||
$data['field_ai_source'] = mb_substr(trim((string)$source), 0, 32);
|
||||
}
|
||||
|
||||
Db::name('expert')->where('expert_id', intval($expertId))->update($data);
|
||||
if ($note !== '') {
|
||||
$this->log('[ExpertFieldAi] expert_id=' . $expertId . ' status=' . $status . ' note=' . $note);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动补全 t_expert 上缺失的 field_ai 字段(可重复执行)。
|
||||
*/
|
||||
public function ensureSchema()
|
||||
{
|
||||
if (self::$schemaReady === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = config('database.prefix') . 'expert';
|
||||
$columns = Db::query('SHOW COLUMNS FROM `' . $table . '`');
|
||||
$existing = [];
|
||||
foreach ($columns as $col) {
|
||||
$existing[$col['Field']] = true;
|
||||
}
|
||||
|
||||
$alters = [];
|
||||
if (!isset($existing['field_ai'])) {
|
||||
$alters[] = "ADD COLUMN `field_ai` VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'AI总结的主要研究领域(中文)' AFTER `affiliation`";
|
||||
$existing['field_ai'] = true;
|
||||
}
|
||||
if (!isset($existing['field_ai_status'])) {
|
||||
$alters[] = "ADD COLUMN `field_ai_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待处理 1已生成 2资料不足 3失败 4无user待AI' AFTER `field_ai`";
|
||||
$existing['field_ai_status'] = true;
|
||||
}
|
||||
if (!isset($existing['field_ai_utime'])) {
|
||||
$alters[] = "ADD COLUMN `field_ai_utime` INT NOT NULL DEFAULT 0 COMMENT 'field_ai更新时间' AFTER `field_ai_status`";
|
||||
$existing['field_ai_utime'] = true;
|
||||
}
|
||||
if (!isset($existing['field_ai_source'])) {
|
||||
$alters[] = "ADD COLUMN `field_ai_source` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '来源: user_link / ai' AFTER `field_ai_utime`";
|
||||
$existing['field_ai_source'] = true;
|
||||
}
|
||||
|
||||
if (!empty($alters)) {
|
||||
Db::execute('ALTER TABLE `' . $table . '` ' . implode(', ', $alters));
|
||||
$this->log('[ExpertFieldAi] schema patched: ' . implode('; ', $alters));
|
||||
}
|
||||
|
||||
self::$schemaReady = true;
|
||||
}
|
||||
|
||||
private function hasColumn($column)
|
||||
{
|
||||
$this->ensureSchema();
|
||||
$table = config('database.prefix') . 'expert';
|
||||
$columns = Db::query('SHOW COLUMNS FROM `' . $table . '` LIKE \'' . addslashes($column) . '\'');
|
||||
return !empty($columns);
|
||||
}
|
||||
|
||||
public function statusLabel($status)
|
||||
{
|
||||
$map = [
|
||||
|
||||
@@ -13,6 +13,9 @@ class ExpertFinderService
|
||||
private $ncbiBaseUrl = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/';
|
||||
private $logFile;
|
||||
|
||||
/** @var bool|null */
|
||||
private static $schemaReady = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->httpClient = new Client([
|
||||
@@ -21,6 +24,54 @@ class ExpertFinderService
|
||||
'verify' => false,
|
||||
]);
|
||||
$this->logFile = ROOT_PATH . 'runtime' . DS . 'expert_finder.log';
|
||||
|
||||
try {
|
||||
$this->ensureSchema();
|
||||
} catch (\Throwable $e) {
|
||||
$this->log('[ExpertFinder] ensureSchema fail: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 历史遗留数据迁移用:旧版每天按页抓取时使用的 per_page。
|
||||
* 用于把旧的 last_page 换算成新的 last_offset(last_offset = last_page × 此值)。
|
||||
*/
|
||||
const MIGRATE_LEGACY_PER_PAGE = 10;
|
||||
|
||||
/**
|
||||
* 自动补全 expert_fetch 上缺失的 last_offset 列,并一次性回填历史进度(可重复执行)。
|
||||
* last_offset 为累计抓取偏移量(已扫到第几篇),与 per_page 解耦,
|
||||
* 改 per_page 不会再导致翻页错位。
|
||||
*/
|
||||
public function ensureSchema()
|
||||
{
|
||||
if (self::$schemaReady === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = config('database.prefix') . 'expert_fetch';
|
||||
$columns = Db::query('SHOW COLUMNS FROM `' . $table . '`');
|
||||
$existing = [];
|
||||
foreach ($columns as $col) {
|
||||
$existing[$col['Field']] = true;
|
||||
}
|
||||
|
||||
if (!isset($existing['last_offset'])) {
|
||||
Db::execute('ALTER TABLE `' . $table . '` ADD COLUMN `last_offset` INT NOT NULL DEFAULT 0 COMMENT \'累计抓取偏移量(与per_page解耦)\' AFTER `last_page`');
|
||||
$this->log('[ExpertFinder] schema patched: add last_offset');
|
||||
}
|
||||
|
||||
// 一次性迁移:把旧 last_page 按历史 per_page 换算成 last_offset。
|
||||
// 只命中"未迁移"的遗留行(last_offset=0 且 last_page>0),幂等,不会重复执行。
|
||||
$affected = Db::execute(
|
||||
'UPDATE `' . $table . '` SET `last_offset` = `last_page` * ' . intval(self::MIGRATE_LEGACY_PER_PAGE)
|
||||
. ' WHERE `last_offset` = 0 AND `last_page` > 0'
|
||||
);
|
||||
if ($affected > 0) {
|
||||
$this->log('[ExpertFinder] migrated last_offset from last_page for ' . $affected . ' rows (×' . self::MIGRATE_LEGACY_PER_PAGE . ')');
|
||||
}
|
||||
|
||||
self::$schemaReady = true;
|
||||
}
|
||||
|
||||
public function doFetchForField($field, $source = 'pubmed', $perPage = 100, $minYear = null)
|
||||
@@ -30,12 +81,13 @@ class ExpertFinderService
|
||||
}
|
||||
|
||||
$fetchLog = $this->getFetchLog($field, $source);
|
||||
$page = $fetchLog['last_page'] + 1;
|
||||
// 基于累计偏移量(offset)的游标:改 per_page 也不会错位
|
||||
$offset = intval($fetchLog['last_offset'] ?? 0);
|
||||
|
||||
if ($source === 'pmc') {
|
||||
$result = $this->searchViaPMC($field, $perPage, $minYear, $page);
|
||||
$result = $this->searchViaPMC($field, $perPage, $minYear, $offset);
|
||||
} else {
|
||||
$result = $this->searchViaPubMed($field, $perPage, $minYear, $page);
|
||||
$result = $this->searchViaPubMed($field, $perPage, $minYear, $offset);
|
||||
}
|
||||
|
||||
if(!isset($result['total'])){
|
||||
@@ -45,13 +97,15 @@ class ExpertFinderService
|
||||
}
|
||||
$saveResult = $this->saveExperts($result['experts'], $field, $source);
|
||||
|
||||
$nextPage = $result['has_more'] ? $page : $fetchLog['last_page'];
|
||||
$totalPages = $result['total_pages'] ?? $fetchLog['total_pages'];
|
||||
$this->updateFetchLog($field, $source, $nextPage, $totalPages);
|
||||
// 抓到下一篇则前移一个窗口;抓完则保持当前 offset
|
||||
$nextOffset = $result['has_more'] ? ($offset + $perPage) : $offset;
|
||||
$totalPages = $result['total_pages'] ?? ($fetchLog['total_pages'] ?? 0);
|
||||
$this->updateFetchLog($field, $source, $nextOffset, $totalPages, $perPage);
|
||||
|
||||
return [
|
||||
'keyword' => $field,
|
||||
'page' => $page,
|
||||
'page' => $result['page'] ?? 1,
|
||||
'offset' => $offset,
|
||||
'experts_found' => $result['total'],
|
||||
'saved_new' => $saveResult['inserted'],
|
||||
'saved_exist' => $saveResult['existing'],
|
||||
@@ -63,10 +117,12 @@ class ExpertFinderService
|
||||
|
||||
public function searchExperts($keyword, $perPage, $minYear, $page, $source)
|
||||
{
|
||||
// 交互式按页搜索:把页码换算成偏移量后走统一的 offset 逻辑
|
||||
$retstart = max(0, (intval($page) - 1) * intval($perPage));
|
||||
if ($source === 'pmc') {
|
||||
return $this->searchViaPMC($keyword, $perPage, $minYear, $page);
|
||||
return $this->searchViaPMC($keyword, $perPage, $minYear, $retstart);
|
||||
}
|
||||
return $this->searchViaPubMed($keyword, $perPage, $minYear, $page);
|
||||
return $this->searchViaPubMed($keyword, $perPage, $minYear, $retstart);
|
||||
}
|
||||
|
||||
public function saveExperts($experts, $field, $source)
|
||||
@@ -184,14 +240,25 @@ class ExpertFinderService
|
||||
->find();
|
||||
|
||||
if (!$log) {
|
||||
return ['last_page' => 0, 'total_pages' => 0, 'last_time' => 0];
|
||||
return ['last_page' => 0, 'last_offset' => 0, 'total_pages' => 0, 'last_time' => 0];
|
||||
}
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
public function updateFetchLog($field, $source, $lastPage, $totalPages)
|
||||
/**
|
||||
* 回写抓取进度。
|
||||
* @param int $lastOffset 累计偏移量(权威游标)
|
||||
* @param int $totalPages 总页数(仅展示)
|
||||
* @param int $perPage 本次窗口大小,用于换算展示用 last_page
|
||||
*/
|
||||
public function updateFetchLog($field, $source, $lastOffset, $totalPages, $perPage = 0)
|
||||
{
|
||||
$lastOffset = max(0, intval($lastOffset));
|
||||
$perPage = intval($perPage);
|
||||
// last_page 仅作展示:由偏移量换算(per_page 未知时退化为偏移量本身)
|
||||
$lastPage = $perPage > 0 ? intval(floor($lastOffset / $perPage)) : $lastOffset;
|
||||
|
||||
$exists = Db::name('expert_fetch')
|
||||
->where('field', $field)
|
||||
->where('source', $source)
|
||||
@@ -201,6 +268,7 @@ class ExpertFinderService
|
||||
Db::name('expert_fetch')
|
||||
->where('expert_fetch_id', $exists['expert_fetch_id'])
|
||||
->update([
|
||||
'last_offset' => $lastOffset,
|
||||
'last_page' => $lastPage,
|
||||
'total_pages' => $totalPages,
|
||||
'last_time' => time(),
|
||||
@@ -209,6 +277,7 @@ class ExpertFinderService
|
||||
Db::name('expert_fetch')->insert([
|
||||
'field' => mb_substr($field, 0, 128),
|
||||
'source' => mb_substr($source, 0, 128),
|
||||
'last_offset' => $lastOffset,
|
||||
'last_page' => $lastPage,
|
||||
'total_pages' => $totalPages,
|
||||
'last_time' => time(),
|
||||
@@ -218,16 +287,16 @@ class ExpertFinderService
|
||||
|
||||
// ==================== PubMed Search ====================
|
||||
|
||||
private function searchViaPubMed($keyword, $perPage, $minYear, $page = 1)
|
||||
private function searchViaPubMed($keyword, $perPage, $minYear, $retstart = 0)
|
||||
{
|
||||
set_time_limit(600);
|
||||
|
||||
$searchResult = $this->esearch('pubmed', $keyword, $perPage, $minYear, $page);
|
||||
$searchResult = $this->esearch('pubmed', $keyword, $perPage, $minYear, $retstart);
|
||||
$ids = $searchResult['ids'];
|
||||
$totalArticles = $searchResult['total'];
|
||||
|
||||
if (empty($ids)) {
|
||||
return $this->buildPagedResult([], 0, 0, $totalArticles, $page, $perPage, 'pubmed');
|
||||
return $this->buildPagedResult([], 0, 0, $totalArticles, $retstart, $perPage, 'pubmed');
|
||||
}
|
||||
|
||||
$allAuthors = [];
|
||||
@@ -243,21 +312,21 @@ class ExpertFinderService
|
||||
|
||||
$experts = $this->aggregateExperts($allAuthors);
|
||||
|
||||
return $this->buildPagedResult($experts, count($experts), count($ids), $totalArticles, $page, $perPage, 'pubmed');
|
||||
return $this->buildPagedResult($experts, count($experts), count($ids), $totalArticles, $retstart, $perPage, 'pubmed');
|
||||
}
|
||||
|
||||
// ==================== PMC Search ====================
|
||||
|
||||
private function searchViaPMC($keyword, $perPage, $minYear, $page = 1)
|
||||
private function searchViaPMC($keyword, $perPage, $minYear, $retstart = 0)
|
||||
{
|
||||
set_time_limit(600);
|
||||
|
||||
$searchResult = $this->esearch('pmc', $keyword, $perPage, $minYear, $page);
|
||||
$searchResult = $this->esearch('pmc', $keyword, $perPage, $minYear, $retstart);
|
||||
$ids = $searchResult['ids'];
|
||||
$totalArticles = $searchResult['total'];
|
||||
|
||||
if (empty($ids)) {
|
||||
return $this->buildPagedResult([], 0, 0, $totalArticles, $page, $perPage, 'pmc');
|
||||
return $this->buildPagedResult([], 0, 0, $totalArticles, $retstart, $perPage, 'pmc');
|
||||
}
|
||||
|
||||
$allAuthors = [];
|
||||
@@ -273,15 +342,15 @@ class ExpertFinderService
|
||||
|
||||
$experts = $this->aggregateExperts($allAuthors);
|
||||
|
||||
return $this->buildPagedResult($experts, count($experts), count($ids), $totalArticles, $page, $perPage, 'pmc');
|
||||
return $this->buildPagedResult($experts, count($experts), count($ids), $totalArticles, $retstart, $perPage, 'pmc');
|
||||
}
|
||||
|
||||
// ==================== NCBI API ====================
|
||||
|
||||
private function esearch($db, $keyword, $perPage, $minYear, $page = 1)
|
||||
private function esearch($db, $keyword, $perPage, $minYear, $retstart = 0)
|
||||
{
|
||||
$term = $keyword . ' AND ' . $minYear . ':' . date('Y') . '[pdat]';
|
||||
$retstart = ($page - 1) * $perPage;
|
||||
$retstart = max(0, intval($retstart));
|
||||
|
||||
$response = $this->httpClient->get($this->ncbiBaseUrl . 'esearch.fcgi', [
|
||||
'query' => [
|
||||
@@ -563,18 +632,23 @@ class ExpertFinderService
|
||||
return $experts;
|
||||
}
|
||||
|
||||
private function buildPagedResult($experts, $expertCount, $articlesScanned, $totalArticles, $page, $perPage, $source)
|
||||
private function buildPagedResult($experts, $expertCount, $articlesScanned, $totalArticles, $retstart, $perPage, $source)
|
||||
{
|
||||
$perPage = max(1, intval($perPage));
|
||||
$retstart = max(0, intval($retstart));
|
||||
$totalPages = $totalArticles > 0 ? ceil($totalArticles / $perPage) : 0;
|
||||
$page = intval(floor($retstart / $perPage)) + 1;
|
||||
return [
|
||||
'experts' => $experts,
|
||||
'total' => $expertCount,
|
||||
'articles_scanned' => $articlesScanned,
|
||||
'total_articles' => $totalArticles,
|
||||
'page' => $page,
|
||||
'offset' => $retstart,
|
||||
'per_page' => $perPage,
|
||||
'total_pages' => $totalPages,
|
||||
'has_more' => $page < $totalPages,
|
||||
// 偏移量驱动:下一个窗口还在范围内才有更多
|
||||
'has_more' => ($retstart + $perPage) < $totalArticles,
|
||||
'source' => $source,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace app\common;
|
||||
use think\Db;
|
||||
use think\Env;
|
||||
use app\common\CrossrefService;
|
||||
class ProductionArticleRefer
|
||||
{
|
||||
|
||||
@@ -78,6 +79,41 @@ class ProductionArticleRefer
|
||||
return json_encode(['status' => 4,'msg' => 'Reference DOI is empty'.json_encode($aParam)]);
|
||||
}
|
||||
|
||||
|
||||
//开始用crossref接口的方式处理数据
|
||||
$doiNorm = preg_replace('#^https?://(dx\.)?doi\.org/#i', '', $aRefer['refer_doi']);
|
||||
$doiNorm = trim($doiNorm, " \t\n\r\0\x0B/");
|
||||
|
||||
$svc = new CrossrefService([
|
||||
'mailto' => trim((string)Env::get('crossref_mailto', '')),
|
||||
]);
|
||||
$summary = $svc->fetchWorkSummary($doiNorm);
|
||||
if ($summary !== null && !empty($summary['doi'])) {
|
||||
$update_a = [];
|
||||
$title = trim((string)($summary['title'] ?? ''));
|
||||
$jouraRaw = trim((string)($summary['joura'] ?? ''));
|
||||
// 姓全写 + 名首字母,超过 3 个作者取前 3 个 + et al
|
||||
$authorCitation = $svc->getAuthorsCitation($summary['raw'] ?? [], 3);
|
||||
$dateno = trim((string)($summary['dateno'] ?? ''));
|
||||
$doilink = trim((string)($summary['doilink'] ?? ''));
|
||||
$update_a['title'] = $title;
|
||||
$update_a['author'] = $authorCitation !== '' ? $authorCitation . '.' : '';
|
||||
$update_a['joura'] = $jouraRaw;
|
||||
$update_a['dateno'] = $dateno;
|
||||
$update_a['refer_type'] = "journal";
|
||||
$update_a['is_ja'] = 1;
|
||||
$update_a['doilink'] = $doilink;
|
||||
$update_a['cs'] = 1;
|
||||
$update_a['update_time'] = time();
|
||||
$update_a['is_deal'] = 1;
|
||||
Db::name('production_article_refer')->where(['p_refer_id' => $iPReferId])->limit(1)->update($update_a);
|
||||
return json_encode(['status' => 1,'msg' => 'Update successful']);
|
||||
}
|
||||
|
||||
//结束---用crossref接口的方式处理数据
|
||||
|
||||
|
||||
|
||||
//数据处理
|
||||
$doi = str_replace('/', '%2F', $aRefer['refer_doi']);
|
||||
$url = "https://citation.doi.org/format?doi=$doi&style=cancer-translational-medicine&lang=en-US";
|
||||
|
||||
@@ -253,9 +253,13 @@ class PromotionService
|
||||
'send_time' => $now,
|
||||
]);
|
||||
Db::name('journal_email')->where('j_email_id', $account['j_email_id'])->setInc('today_sent');
|
||||
// 仅外部 expert 库回写最近一次推广时间;内部 user 用 promotion_email_log.send_time 计频次
|
||||
// 仅外部 expert 库回写最近一次推广时间与累计推广次数;内部 user 用 promotion_email_log.send_time 计频次
|
||||
if ($audienceKind === 'expert' && intval($expert['expert_id']) > 0) {
|
||||
Db::name('expert')->where('expert_id', $expert['expert_id'])->update(['state' => 1, 'ltime' => $now]);
|
||||
Db::name('expert')->where('expert_id', $expert['expert_id'])->update([
|
||||
'state' => 1,
|
||||
'ltime' => $now,
|
||||
'times' => Db::raw('times+1'),
|
||||
]);
|
||||
}
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->setInc('sent_count');
|
||||
} else {
|
||||
|
||||
@@ -60,7 +60,8 @@ class PubmedService
|
||||
$pmid = trim($pmid);
|
||||
if ($pmid === '') return null;
|
||||
|
||||
$cacheKey = 'pmid_' . $pmid;
|
||||
// v2:解析结果新增 journal_iso_abbr / journal_medline_ta,换 key 避免命中旧缓存
|
||||
$cacheKey = 'pmid_v2_' . $pmid;
|
||||
$cached = $this->cacheGet($cacheKey, 30 * 86400);
|
||||
if (is_array($cached)) return $cached;
|
||||
|
||||
@@ -96,6 +97,22 @@ class PubmedService
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* DOI -> 期刊规范缩写(NLM/ISO 形式,如 "J Clin Oncol")
|
||||
* 优先 ISOAbbreviation,回退 MedlineTA;查不到返回 null。
|
||||
*/
|
||||
public function journalAbbrByDoi(string $doi): ?string
|
||||
{
|
||||
$info = $this->fetchByDoi($doi);
|
||||
if (!is_array($info)) return null;
|
||||
|
||||
$abbr = trim((string)($info['journal_iso_abbr'] ?? ''));
|
||||
if ($abbr === '') {
|
||||
$abbr = trim((string)($info['journal_medline_ta'] ?? ''));
|
||||
}
|
||||
return $abbr !== '' ? $abbr : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按书目信息检索 PubMed(标题 + 第一作者 + 年份)
|
||||
*/
|
||||
@@ -224,6 +241,9 @@ class PubmedService
|
||||
$pubTypes = array_values(array_unique($pubTypes));
|
||||
|
||||
$journal = $this->xpText($xp, '//PubmedArticle//Journal//Title');
|
||||
// 期刊规范缩写:ISOAbbreviation(Journal 下)与 MedlineTA(MedlineJournalInfo 下)
|
||||
$journalIsoAbbr = $this->xpText($xp, '//PubmedArticle//Journal//ISOAbbreviation');
|
||||
$journalMedlineTa = $this->xpText($xp, '//PubmedArticle//MedlineJournalInfo//MedlineTA');
|
||||
|
||||
$year = '';
|
||||
$year = $this->xpText($xp, '//PubmedArticle//JournalIssue//PubDate//Year');
|
||||
@@ -244,6 +264,8 @@ class PubmedService
|
||||
'mesh_terms' => $mesh,
|
||||
'publication_types' => $pubTypes,
|
||||
'journal' => $journal,
|
||||
'journal_iso_abbr' => $journalIsoAbbr,
|
||||
'journal_medline_ta' => $journalMedlineTa,
|
||||
'year' => $year,
|
||||
];
|
||||
}
|
||||
|
||||
219
application/common/service/LocalModelService.php
Normal file
219
application/common/service/LocalModelService.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use think\Env;
|
||||
|
||||
/**
|
||||
* 本地模型服务:按上下文长度自动选择模型
|
||||
*
|
||||
* - 短上下文 -> 小模型(显存为大模型一半),对应 base.model_url1
|
||||
* - 长上下文 -> 大模型,对应 base.model_url
|
||||
*
|
||||
* 选择规则:上下文字符数 <= 阈值 用小模型;超过阈值 用大模型。
|
||||
* 两个端点模型名相同(base.model)。
|
||||
*
|
||||
* 用法:
|
||||
* $svc = new LocalModelService();
|
||||
* $res = $svc->chat([
|
||||
* ['role' => 'system', 'content' => '...'],
|
||||
* ['role' => 'user', 'content' => '...'],
|
||||
* ]);
|
||||
* // $res['ok'], $res['content'], $res['tier'](small|large), $res['context_len']
|
||||
*
|
||||
* // 只要文本结果:
|
||||
* $text = $svc->complete($systemPrompt, $userPrompt);
|
||||
*/
|
||||
class LocalModelService
|
||||
{
|
||||
/** 上下文长度阈值(字符数):<= 用小模型,> 用大模型 */
|
||||
const CONTEXT_THRESHOLD = 1000;
|
||||
|
||||
/** 请求超时(秒) */
|
||||
const TIMEOUT = 120;
|
||||
|
||||
/** 小模型端点(短上下文,显存一半) */
|
||||
private $smallUrl;
|
||||
|
||||
/** 大模型端点(长上下文) */
|
||||
private $largeUrl;
|
||||
|
||||
/** 模型名(两端点相同) */
|
||||
private $model;
|
||||
|
||||
/** 上下文长度阈值(字符数) */
|
||||
private $threshold;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// 小模型 -> base.model_url1,大模型 -> base.model_url,模型名同为 base.model
|
||||
$this->smallUrl = $this->normalizeChatUrl((string)Env::get('base.model_url1', ''));
|
||||
$this->largeUrl = $this->normalizeChatUrl((string)Env::get('base.model_url', ''));
|
||||
$this->model = trim((string)Env::get('base.model', ''));
|
||||
$this->threshold = self::CONTEXT_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起一次对话,按上下文长度自动选模型。
|
||||
*
|
||||
* @param array $messages OpenAI 格式 messages
|
||||
* @param array $options 可选:
|
||||
* - temperature (float, 默认 0.2)
|
||||
* - max_tokens (int, 可选)
|
||||
* - force_tier ('small'|'large') 强制指定模型,跳过长度判断
|
||||
* - extra (array) 透传到请求体的额外字段
|
||||
* @return array{ok:bool, content:string, tier:string, model:string, url:string, context_len:int, error:string}
|
||||
*/
|
||||
public function chat(array $messages, array $options = [])
|
||||
{
|
||||
$contextLen = $this->measureMessages($messages);
|
||||
|
||||
$tier = isset($options['force_tier']) && in_array($options['force_tier'], ['small', 'large'], true)
|
||||
? $options['force_tier']
|
||||
: $this->pickTier($contextLen);
|
||||
|
||||
$endpoint = $this->resolveEndpoint($tier);
|
||||
|
||||
$result = [
|
||||
'ok' => false,
|
||||
'content' => '',
|
||||
'tier' => $tier,
|
||||
'model' => $endpoint['model'],
|
||||
'url' => $endpoint['url'],
|
||||
'context_len' => $contextLen,
|
||||
'error' => '',
|
||||
];
|
||||
|
||||
if ($endpoint['url'] === '' || $endpoint['model'] === '') {
|
||||
$result['error'] = $tier . ' 模型未配置(检查 .env [base] model_url / model_url1 / model)';
|
||||
return $result;
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'model' => $endpoint['model'],
|
||||
'temperature' => isset($options['temperature']) ? (float)$options['temperature'] : 0.2,
|
||||
'messages' => $messages,
|
||||
];
|
||||
if (isset($options['max_tokens']) && intval($options['max_tokens']) > 0) {
|
||||
$payload['max_tokens'] = intval($options['max_tokens']);
|
||||
}
|
||||
if (isset($options['extra']) && is_array($options['extra'])) {
|
||||
$payload = array_merge($payload, $options['extra']);
|
||||
}
|
||||
|
||||
$content = $this->postChat($endpoint['url'], $payload, $err);
|
||||
if ($content === null) {
|
||||
$result['error'] = $err !== '' ? $err : 'LLM 请求失败';
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result['ok'] = true;
|
||||
$result['content'] = $content;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷方法:传 system + user,返回纯文本内容(失败返回空字符串)。
|
||||
*/
|
||||
public function complete($systemPrompt, $userPrompt, array $options = [])
|
||||
{
|
||||
$messages = [];
|
||||
if (trim((string)$systemPrompt) !== '') {
|
||||
$messages[] = ['role' => 'system', 'content' => (string)$systemPrompt];
|
||||
}
|
||||
$messages[] = ['role' => 'user', 'content' => (string)$userPrompt];
|
||||
|
||||
$res = $this->chat($messages, $options);
|
||||
return $res['ok'] ? $res['content'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据上下文长度选择 tier。
|
||||
*/
|
||||
public function pickTier($contextLen)
|
||||
{
|
||||
return $contextLen > $this->threshold ? 'large' : 'small';
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计 messages 的上下文长度(所有 content 字符数之和)。
|
||||
*/
|
||||
public function measureMessages(array $messages)
|
||||
{
|
||||
$len = 0;
|
||||
foreach ($messages as $m) {
|
||||
if (isset($m['content']) && is_string($m['content'])) {
|
||||
$len += mb_strlen($m['content']);
|
||||
}
|
||||
}
|
||||
return $len;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回某 tier 的端点配置(模型名两端点相同)。
|
||||
*/
|
||||
private function resolveEndpoint($tier)
|
||||
{
|
||||
$url = $tier === 'large' ? $this->largeUrl : $this->smallUrl;
|
||||
return ['url' => $url, 'model' => $this->model];
|
||||
}
|
||||
|
||||
private function postChat($url, array $payload, &$err = '')
|
||||
{
|
||||
$err = '';
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, JSON_UNESCAPED_UNICODE));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, self::TIMEOUT);
|
||||
|
||||
$headers = ['Content-Type: application/json'];
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
$raw = curl_exec($ch);
|
||||
if ($raw === false) {
|
||||
$err = 'curl error: ' . curl_error($ch);
|
||||
curl_close($ch);
|
||||
return null;
|
||||
}
|
||||
$httpCode = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode < 200 || $httpCode >= 300) {
|
||||
$err = 'http ' . $httpCode . ': ' . mb_substr((string)$raw, 0, 300);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($raw, true);
|
||||
if (!is_array($data)) {
|
||||
$err = 'invalid json response';
|
||||
return null;
|
||||
}
|
||||
if (isset($data['choices'][0]['message']['content'])) {
|
||||
return (string)$data['choices'][0]['message']['content'];
|
||||
}
|
||||
if (isset($data['content'])) {
|
||||
return (string)$data['content'];
|
||||
}
|
||||
$err = 'no content in response: ' . mb_substr((string)$raw, 0, 300);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根地址自动补 /v1/chat/completions。
|
||||
*/
|
||||
private function normalizeChatUrl($url)
|
||||
{
|
||||
$url = trim((string)$url);
|
||||
if ($url === '') {
|
||||
return '';
|
||||
}
|
||||
if (stripos($url, 'chat/completions') !== false) {
|
||||
return $url;
|
||||
}
|
||||
return rtrim($url, '/') . '/v1/chat/completions';
|
||||
}
|
||||
}
|
||||
3
sql/add_field_ai_source_to_expert.sql
Normal file
3
sql/add_field_ai_source_to_expert.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- 若已执行过 add_field_ai_to_expert.sql 但缺少 field_ai_source,单独补这一列
|
||||
ALTER TABLE `t_expert`
|
||||
ADD COLUMN `field_ai_source` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '来源: user_link / ai' AFTER `field_ai_utime`;
|
||||
46
sql/patch_expert_field_ai_columns.php
Normal file
46
sql/patch_expert_field_ai_columns.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* 补全 t_expert 缺失的 field_ai 相关字段(可重复执行)
|
||||
* 用法: php sql/patch_expert_field_ai_columns.php
|
||||
*/
|
||||
$config = require __DIR__ . '/../application/database.php';
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
|
||||
$config['hostname'],
|
||||
$config['hostport'],
|
||||
$config['database'],
|
||||
$config['charset']
|
||||
);
|
||||
$pdo = new PDO($dsn, $config['username'], $config['password'], [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
]);
|
||||
|
||||
$table = $config['prefix'] . 'expert';
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM `{$table}`")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$colSet = array_flip($cols);
|
||||
|
||||
$alters = [];
|
||||
if (!isset($colSet['field_ai'])) {
|
||||
$alters[] = "ADD COLUMN `field_ai` VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'AI总结的主要研究领域(中文)' AFTER `affiliation`";
|
||||
}
|
||||
if (!isset($colSet['field_ai_status'])) {
|
||||
$after = isset($colSet['field_ai']) || !empty($alters) ? 'field_ai' : 'affiliation';
|
||||
$alters[] = "ADD COLUMN `field_ai_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待处理 1已生成 2资料不足 3失败 4无user待AI' AFTER `{$after}`";
|
||||
}
|
||||
if (!isset($colSet['field_ai_utime'])) {
|
||||
$alters[] = "ADD COLUMN `field_ai_utime` INT NOT NULL DEFAULT 0 COMMENT 'field_ai更新时间' AFTER `field_ai_status`";
|
||||
}
|
||||
if (!isset($colSet['field_ai_source'])) {
|
||||
$alters[] = "ADD COLUMN `field_ai_source` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '来源: user_link / ai' AFTER `field_ai_utime`";
|
||||
}
|
||||
|
||||
if (empty($alters)) {
|
||||
echo "OK: all field_ai columns exist on {$table}\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$sql = "ALTER TABLE `{$table}` " . implode(', ', $alters);
|
||||
echo "Running: {$sql}\n";
|
||||
$pdo->exec($sql);
|
||||
echo "Done.\n";
|
||||
5
vendor/composer/autoload_classmap.php
vendored
5
vendor/composer/autoload_classmap.php
vendored
@@ -6,5 +6,10 @@ $vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
||||
'Stringable' => $vendorDir . '/myclabs/php-enum/stubs/Stringable.php',
|
||||
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
||||
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
|
||||
);
|
||||
|
||||
2
vendor/composer/autoload_real.php
vendored
2
vendor/composer/autoload_real.php
vendored
@@ -22,6 +22,8 @@ class ComposerAutoloaderInit2bc4f313dba415539e266f7ac2c87dcd
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit2bc4f313dba415539e266f7ac2c87dcd', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit2bc4f313dba415539e266f7ac2c87dcd', 'loadClassLoader'));
|
||||
|
||||
154
vendor/composer/installed.php
vendored
154
vendor/composer/installed.php
vendored
@@ -3,7 +3,7 @@
|
||||
'name' => 'topthink/think',
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => 'bbd690ca0f68c671ece05e82edc88ee7a68b82ed',
|
||||
'reference' => '1d54946fef97376f7c2789af83a1616dd6f7a380',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
@@ -11,9 +11,9 @@
|
||||
),
|
||||
'versions' => array(
|
||||
'apimatic/core' => array(
|
||||
'pretty_version' => '0.3.16',
|
||||
'version' => '0.3.16.0',
|
||||
'reference' => 'ae4ab4ca26a41be41718f33c703d67b7a767c07b',
|
||||
'pretty_version' => '0.3.17',
|
||||
'version' => '0.3.17.0',
|
||||
'reference' => 'a48a583f686ee3786432b976c795a2817ec095b3',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../apimatic/core',
|
||||
'aliases' => array(),
|
||||
@@ -55,15 +55,6 @@
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'composer/pcre' => array(
|
||||
'pretty_version' => '3.3.2',
|
||||
'version' => '3.3.2.0',
|
||||
'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/./pcre',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'ezyang/htmlpurifier' => array(
|
||||
'pretty_version' => 'v4.19.0',
|
||||
'version' => '4.19.0.0',
|
||||
@@ -92,18 +83,18 @@
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'guzzlehttp/psr7' => array(
|
||||
'pretty_version' => '2.8.0',
|
||||
'version' => '2.8.0.0',
|
||||
'reference' => '21dc724a0583619cd1652f673303492272778051',
|
||||
'pretty_version' => '2.9.0',
|
||||
'version' => '2.9.0.0',
|
||||
'reference' => '7d0ed42f28e42d61352a7a79de682e5e67fec884',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'maennchen/zipstream-php' => array(
|
||||
'pretty_version' => '3.1.2',
|
||||
'version' => '3.1.2.0',
|
||||
'reference' => 'aeadcf5c412332eb426c0f9b4485f6accba2a99f',
|
||||
'pretty_version' => '2.1.0',
|
||||
'version' => '2.1.0.0',
|
||||
'reference' => 'c4c5803cc1f93df3d2448478ef79394a5981cc58',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../maennchen/zipstream-php',
|
||||
'aliases' => array(),
|
||||
@@ -127,6 +118,15 @@
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'myclabs/php-enum' => array(
|
||||
'pretty_version' => '1.8.5',
|
||||
'version' => '1.8.5.0',
|
||||
'reference' => 'e7be26966b7398204a234f8673fdad5ac6277802',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../myclabs/php-enum',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'nyholm/psr7' => array(
|
||||
'pretty_version' => '1.8.2',
|
||||
'version' => '1.8.2.0',
|
||||
@@ -137,9 +137,9 @@
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'paragonie/constant_time_encoding' => array(
|
||||
'pretty_version' => 'v3.1.3',
|
||||
'version' => '3.1.3.0',
|
||||
'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77',
|
||||
'pretty_version' => 'v2.8.2',
|
||||
'version' => '2.8.2.0',
|
||||
'reference' => 'e30811f7bc69e4b5b6d5783e712c06c8eabf0226',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../paragonie/constant_time_encoding',
|
||||
'aliases' => array(),
|
||||
@@ -194,9 +194,9 @@
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'php-http/message' => array(
|
||||
'pretty_version' => '1.16.1',
|
||||
'version' => '1.16.1.0',
|
||||
'reference' => '5997f3289332c699fa2545c427826272498a2088',
|
||||
'pretty_version' => '1.16.2',
|
||||
'version' => '1.16.2.0',
|
||||
'reference' => '06dd5e8562f84e641bf929bfe699ee0f5ce8080a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../php-http/message',
|
||||
'aliases' => array(),
|
||||
@@ -209,9 +209,9 @@
|
||||
),
|
||||
),
|
||||
'php-http/multipart-stream-builder' => array(
|
||||
'pretty_version' => '1.3.1',
|
||||
'version' => '1.3.1.0',
|
||||
'reference' => 'ed56da23b95949ae4747378bed8a5b61a2fdae24',
|
||||
'pretty_version' => '1.4.2',
|
||||
'version' => '1.4.2.0',
|
||||
'reference' => '10086e6de6f53489cca5ecc45b6f468604d3460e',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../php-http/multipart-stream-builder',
|
||||
'aliases' => array(),
|
||||
@@ -227,18 +227,18 @@
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'phpmailer/phpmailer' => array(
|
||||
'pretty_version' => 'v6.11.1',
|
||||
'version' => '6.11.1.0',
|
||||
'reference' => 'd9e3b36b47f04b497a0164c5a20f92acb4593284',
|
||||
'pretty_version' => 'v6.12.0',
|
||||
'version' => '6.12.0.0',
|
||||
'reference' => 'd1ac35d784bf9f5e61b424901d5a014967f15b12',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpmailer/phpmailer',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'phpoffice/math' => array(
|
||||
'pretty_version' => '0.2.0',
|
||||
'version' => '0.2.0.0',
|
||||
'reference' => 'fc2eb6d1a61b058d5dac77197059db30ee3c8329',
|
||||
'pretty_version' => '0.3.0',
|
||||
'version' => '0.3.0.0',
|
||||
'reference' => 'fc31c8f57a7a81f962cbf389fd89f4d9d06fc99a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpoffice/math',
|
||||
'aliases' => array(),
|
||||
@@ -254,18 +254,18 @@
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'phpoffice/phpspreadsheet' => array(
|
||||
'pretty_version' => '1.30.1',
|
||||
'version' => '1.30.1.0',
|
||||
'reference' => 'fa8257a579ec623473eabfe49731de5967306c4c',
|
||||
'pretty_version' => '1.25.2',
|
||||
'version' => '1.25.2.0',
|
||||
'reference' => 'a317a09e7def49852400a4b3eca4a4b0790ceeb5',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpoffice/phpspreadsheet',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'phpoffice/phpword' => array(
|
||||
'pretty_version' => '1.3.0',
|
||||
'version' => '1.3.0.0',
|
||||
'reference' => '8392134ce4b5dba65130ba956231a1602b848b7f',
|
||||
'pretty_version' => '1.4.0',
|
||||
'version' => '1.4.0.0',
|
||||
'reference' => '6d75328229bc93790b37e93741adf70646cea958',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpoffice/phpword',
|
||||
'aliases' => array(),
|
||||
@@ -297,9 +297,9 @@
|
||||
),
|
||||
),
|
||||
'psr/http-factory' => array(
|
||||
'pretty_version' => '1.0.2',
|
||||
'version' => '1.0.2.0',
|
||||
'reference' => 'e616d01114759c4c489f93b099585439f795fe35',
|
||||
'pretty_version' => '1.1.0',
|
||||
'version' => '1.1.0.0',
|
||||
'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-factory',
|
||||
'aliases' => array(),
|
||||
@@ -313,9 +313,9 @@
|
||||
),
|
||||
),
|
||||
'psr/http-message' => array(
|
||||
'pretty_version' => '2.0',
|
||||
'version' => '2.0.0.0',
|
||||
'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71',
|
||||
'pretty_version' => '1.1',
|
||||
'version' => '1.1.0.0',
|
||||
'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-message',
|
||||
'aliases' => array(),
|
||||
@@ -329,18 +329,18 @@
|
||||
),
|
||||
),
|
||||
'psr/log' => array(
|
||||
'pretty_version' => '3.0.1',
|
||||
'version' => '3.0.1.0',
|
||||
'reference' => '79dff0b268932c640297f5208d6298f71855c03e',
|
||||
'pretty_version' => '1.1.4',
|
||||
'version' => '1.1.4.0',
|
||||
'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/log',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/simple-cache' => array(
|
||||
'pretty_version' => '3.0.0',
|
||||
'version' => '3.0.0.0',
|
||||
'reference' => '764e0b3939f5ca87cb904f570ef9be2d78a07865',
|
||||
'pretty_version' => '1.0.1',
|
||||
'version' => '1.0.1.0',
|
||||
'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/simple-cache',
|
||||
'aliases' => array(),
|
||||
@@ -355,6 +355,15 @@
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'smalot/pdfparser' => array(
|
||||
'pretty_version' => 'v2.9.0',
|
||||
'version' => '2.9.0.0',
|
||||
'reference' => '6b53144fcb24af77093d4150dd7d0dd571f25761',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../smalot/pdfparser',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'spatie/data-transfer-object' => array(
|
||||
'pretty_version' => '1.14.1',
|
||||
'version' => '1.14.1.0',
|
||||
@@ -365,32 +374,41 @@
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/deprecation-contracts' => array(
|
||||
'pretty_version' => 'v3.6.0',
|
||||
'version' => '3.6.0.0',
|
||||
'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62',
|
||||
'pretty_version' => 'v2.5.4',
|
||||
'version' => '2.5.4.0',
|
||||
'reference' => '605389f2a7e5625f273b53960dc46aeaf9c62918',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/http-foundation' => array(
|
||||
'pretty_version' => 'v8.0.1',
|
||||
'version' => '8.0.1.0',
|
||||
'reference' => '3690740e2e8b19d877f20d4f10b7a489cddf0fe2',
|
||||
'pretty_version' => 'v5.4.50',
|
||||
'version' => '5.4.50.0',
|
||||
'reference' => '1a0706e8b8041046052ea2695eb8aeee04f97609',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/http-foundation',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-mbstring' => array(
|
||||
'pretty_version' => 'v1.32.0',
|
||||
'version' => '1.32.0.0',
|
||||
'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493',
|
||||
'pretty_version' => 'v1.37.0',
|
||||
'version' => '1.37.0.0',
|
||||
'reference' => '6a21eb99c6973357967f6ce3708cd55a6bec6315',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-php80' => array(
|
||||
'pretty_version' => 'v1.37.0',
|
||||
'version' => '1.37.0.0',
|
||||
'reference' => 'dfb55726c3a76ea3b6459fcfda1ec2d80a682411',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'tectalic/openai' => array(
|
||||
'pretty_version' => 'v1.6.0',
|
||||
'version' => '1.6.0.0',
|
||||
@@ -412,7 +430,7 @@
|
||||
'topthink/think' => array(
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => 'bbd690ca0f68c671ece05e82edc88ee7a68b82ed',
|
||||
'reference' => '1d54946fef97376f7c2789af83a1616dd6f7a380',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
@@ -428,18 +446,18 @@
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'topthink/think-helper' => array(
|
||||
'pretty_version' => 'v3.1.11',
|
||||
'version' => '3.1.11.0',
|
||||
'reference' => '1d6ada9b9f3130046bf6922fe1bd159c8d88a33c',
|
||||
'pretty_version' => 'v3.1.12',
|
||||
'version' => '3.1.12.0',
|
||||
'reference' => 'fe277121112a8f1c872e169a733ca80bb11c4acb',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../topthink/think-helper',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'topthink/think-image' => array(
|
||||
'pretty_version' => 'v1.0.7',
|
||||
'version' => '1.0.7.0',
|
||||
'reference' => '8586cf47f117481c6d415b20f7dedf62e79d5512',
|
||||
'pretty_version' => 'v1.0.8',
|
||||
'version' => '1.0.8.0',
|
||||
'reference' => 'd1d748cbb2fe2f29fca6138cf96cb8b5113892f1',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../topthink/think-image',
|
||||
'aliases' => array(),
|
||||
|
||||
6
vendor/phpmailer/phpmailer/composer.json
vendored
6
vendor/phpmailer/phpmailer/composer.json
vendored
@@ -49,15 +49,14 @@
|
||||
},
|
||||
"suggest": {
|
||||
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
|
||||
"ext-imap": "Needed to support advanced email address parsing according to RFC822",
|
||||
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
||||
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
||||
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
|
||||
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
|
||||
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
|
||||
"psr/log": "For optional PSR-3 debug logging",
|
||||
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
|
||||
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
|
||||
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication",
|
||||
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -72,7 +71,6 @@
|
||||
"license": "LGPL-2.1-only",
|
||||
"scripts": {
|
||||
"check": "./vendor/bin/phpcs",
|
||||
"style": "./vendor/bin/phpcbf",
|
||||
"test": "./vendor/bin/phpunit --no-coverage",
|
||||
"coverage": "./vendor/bin/phpunit",
|
||||
"lint": [
|
||||
|
||||
374
vendor/phpmailer/phpmailer/src/PHPMailer.php
vendored
374
vendor/phpmailer/phpmailer/src/PHPMailer.php
vendored
@@ -561,9 +561,9 @@ class PHPMailer
|
||||
* string $body the email body
|
||||
* string $from email address of sender
|
||||
* string $extra extra information of possible use
|
||||
* 'smtp_transaction_id' => last smtp transaction id
|
||||
* "smtp_transaction_id' => last smtp transaction id
|
||||
*
|
||||
* @var callable|callable-string
|
||||
* @var string
|
||||
*/
|
||||
public $action_function = '';
|
||||
|
||||
@@ -711,7 +711,7 @@ class PHPMailer
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $language = [];
|
||||
protected $language = [];
|
||||
|
||||
/**
|
||||
* The number of errors encountered.
|
||||
@@ -768,7 +768,7 @@ class PHPMailer
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VERSION = '6.11.1';
|
||||
const VERSION = '6.12.0';
|
||||
|
||||
/**
|
||||
* Error severity: message only, continue processing.
|
||||
@@ -1102,7 +1102,7 @@ class PHPMailer
|
||||
//At-sign is missing.
|
||||
$error_message = sprintf(
|
||||
'%s (%s): %s',
|
||||
self::lang('invalid_address'),
|
||||
$this->lang('invalid_address'),
|
||||
$kind,
|
||||
$address
|
||||
);
|
||||
@@ -1187,7 +1187,7 @@ class PHPMailer
|
||||
if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
|
||||
$error_message = sprintf(
|
||||
'%s: %s',
|
||||
self::lang('Invalid recipient kind'),
|
||||
$this->lang('Invalid recipient kind'),
|
||||
$kind
|
||||
);
|
||||
$this->setError($error_message);
|
||||
@@ -1201,7 +1201,7 @@ class PHPMailer
|
||||
if (!static::validateAddress($address)) {
|
||||
$error_message = sprintf(
|
||||
'%s (%s): %s',
|
||||
self::lang('invalid_address'),
|
||||
$this->lang('invalid_address'),
|
||||
$kind,
|
||||
$address
|
||||
);
|
||||
@@ -1220,16 +1220,12 @@ class PHPMailer
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
foreach ($this->ReplyTo as $replyTo) {
|
||||
if (0 === strcasecmp($replyTo[0], $address)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$this->ReplyTo[] = [$address, $name];
|
||||
} elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
|
||||
$this->ReplyTo[strtolower($address)] = [$address, $name];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1242,18 +1238,15 @@ class PHPMailer
|
||||
* @see https://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
|
||||
*
|
||||
* @param string $addrstr The address list string
|
||||
* @param null $useimap Deprecated argument since 6.11.0.
|
||||
* @param bool $useimap Whether to use the IMAP extension to parse the list
|
||||
* @param string $charset The charset to use when decoding the address list string.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function parseAddresses($addrstr, $useimap = null, $charset = self::CHARSET_ISO88591)
|
||||
public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591)
|
||||
{
|
||||
if ($useimap !== null) {
|
||||
trigger_error(self::lang('deprecated_argument'), E_USER_DEPRECATED);
|
||||
}
|
||||
$addresses = [];
|
||||
if (function_exists('imap_rfc822_parse_adrlist')) {
|
||||
if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
|
||||
//Use this built-in parser if it's available
|
||||
$list = imap_rfc822_parse_adrlist($addrstr, '');
|
||||
// Clear any potential IMAP errors to get rid of notices being thrown at end of script.
|
||||
@@ -1263,13 +1256,20 @@ class PHPMailer
|
||||
'.SYNTAX-ERROR.' !== $address->host &&
|
||||
static::validateAddress($address->mailbox . '@' . $address->host)
|
||||
) {
|
||||
//Decode the name part if it's present and maybe encoded
|
||||
//Decode the name part if it's present and encoded
|
||||
if (
|
||||
property_exists($address, 'personal')
|
||||
&& is_string($address->personal)
|
||||
&& $address->personal !== ''
|
||||
property_exists($address, 'personal') &&
|
||||
//Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
|
||||
defined('MB_CASE_UPPER') &&
|
||||
preg_match('/^=\?.*\?=$/s', $address->personal)
|
||||
) {
|
||||
$address->personal = static::decodeHeader($address->personal, $charset);
|
||||
$origCharset = mb_internal_encoding();
|
||||
mb_internal_encoding($charset);
|
||||
//Undo any RFC2047-encoded spaces-as-underscores
|
||||
$address->personal = str_replace('_', '=20', $address->personal);
|
||||
//Decode the name
|
||||
$address->personal = mb_decode_mimeheader($address->personal);
|
||||
mb_internal_encoding($origCharset);
|
||||
}
|
||||
|
||||
$addresses[] = [
|
||||
@@ -1280,51 +1280,40 @@ class PHPMailer
|
||||
}
|
||||
} else {
|
||||
//Use this simpler parser
|
||||
$addresses = static::parseSimplerAddresses($addrstr, $charset);
|
||||
}
|
||||
|
||||
return $addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string containing one or more RFC822-style comma-separated email addresses
|
||||
* with the form "display name <address>" into an array of name/address pairs.
|
||||
* Uses a simpler parser that does not require the IMAP extension but doesnt support
|
||||
* the full RFC822 spec. For full RFC822 support, use the PHP IMAP extension.
|
||||
*
|
||||
* @param string $addrstr The address list string
|
||||
* @param string $charset The charset to use when decoding the address list string.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function parseSimplerAddresses($addrstr, $charset)
|
||||
{
|
||||
// Emit a runtime notice to recommend using the IMAP extension for full RFC822 parsing
|
||||
trigger_error(self::lang('imap_recommended'), E_USER_NOTICE);
|
||||
|
||||
$addresses = [];
|
||||
$list = explode(',', $addrstr);
|
||||
foreach ($list as $address) {
|
||||
$address = trim($address);
|
||||
//Is there a separate name part?
|
||||
if (strpos($address, '<') === false) {
|
||||
//No separate name, just use the whole thing
|
||||
if (static::validateAddress($address)) {
|
||||
$addresses[] = [
|
||||
'name' => '',
|
||||
'address' => $address,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$parsed = static::parseEmailString($address);
|
||||
$email = $parsed['email'];
|
||||
if (static::validateAddress($email)) {
|
||||
$name = static::decodeHeader($parsed['name'], $charset);
|
||||
$addresses[] = [
|
||||
//Remove any surrounding quotes and spaces from the name
|
||||
'name' => trim($name, '\'" '),
|
||||
'address' => $email,
|
||||
];
|
||||
$list = explode(',', $addrstr);
|
||||
foreach ($list as $address) {
|
||||
$address = trim($address);
|
||||
//Is there a separate name part?
|
||||
if (strpos($address, '<') === false) {
|
||||
//No separate name, just use the whole thing
|
||||
if (static::validateAddress($address)) {
|
||||
$addresses[] = [
|
||||
'name' => '',
|
||||
'address' => $address,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
list($name, $email) = explode('<', $address);
|
||||
$email = trim(str_replace('>', '', $email));
|
||||
$name = trim($name);
|
||||
if (static::validateAddress($email)) {
|
||||
//Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
|
||||
//If this name is encoded, decode it
|
||||
if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) {
|
||||
$origCharset = mb_internal_encoding();
|
||||
mb_internal_encoding($charset);
|
||||
//Undo any RFC2047-encoded spaces-as-underscores
|
||||
$name = str_replace('_', '=20', $name);
|
||||
//Decode the name
|
||||
$name = mb_decode_mimeheader($name);
|
||||
mb_internal_encoding($origCharset);
|
||||
}
|
||||
$addresses[] = [
|
||||
//Remove any surrounding quotes and spaces from the name
|
||||
'name' => trim($name, '\'" '),
|
||||
'address' => $email,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1332,42 +1321,6 @@ class PHPMailer
|
||||
return $addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string containing an email address with an optional name
|
||||
* and divide it into a name and email address.
|
||||
*
|
||||
* @param string $input The email with name.
|
||||
*
|
||||
* @return array{name: string, email: string}
|
||||
*/
|
||||
private static function parseEmailString($input)
|
||||
{
|
||||
$input = trim((string)$input);
|
||||
|
||||
if ($input === '') {
|
||||
return ['name' => '', 'email' => ''];
|
||||
}
|
||||
|
||||
$pattern = '/^\s*(?:(?:"([^"]*)"|\'([^\']*)\'|([^<]*?))\s*)?<\s*([^>]+)\s*>\s*$/';
|
||||
if (preg_match($pattern, $input, $matches)) {
|
||||
$name = '';
|
||||
// Double quotes including special scenarios.
|
||||
if (isset($matches[1]) && $matches[1] !== '') {
|
||||
$name = $matches[1];
|
||||
// Single quotes including special scenarios.
|
||||
} elseif (isset($matches[2]) && $matches[2] !== '') {
|
||||
$name = $matches[2];
|
||||
// Simplest scenario, name and email are in the format "Name <email>".
|
||||
} elseif (isset($matches[3])) {
|
||||
$name = trim($matches[3]);
|
||||
}
|
||||
|
||||
return ['name' => $name, 'email' => trim($matches[4])];
|
||||
}
|
||||
|
||||
return ['name' => '', 'email' => $input];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the From and FromName properties.
|
||||
*
|
||||
@@ -1381,10 +1334,6 @@ class PHPMailer
|
||||
*/
|
||||
public function setFrom($address, $name = '', $auto = true)
|
||||
{
|
||||
if (is_null($name)) {
|
||||
//Helps avoid a deprecation warning in the preg_replace() below
|
||||
$name = '';
|
||||
}
|
||||
$address = trim((string)$address);
|
||||
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
|
||||
//Don't validate now addresses with IDN. Will be done in send().
|
||||
@@ -1396,7 +1345,7 @@ class PHPMailer
|
||||
) {
|
||||
$error_message = sprintf(
|
||||
'%s (From): %s',
|
||||
self::lang('invalid_address'),
|
||||
$this->lang('invalid_address'),
|
||||
$address
|
||||
);
|
||||
$this->setError($error_message);
|
||||
@@ -1652,7 +1601,7 @@ class PHPMailer
|
||||
&& ini_get('mail.add_x_header') === '1'
|
||||
&& stripos(PHP_OS, 'WIN') === 0
|
||||
) {
|
||||
trigger_error(self::lang('buggy_php'), E_USER_WARNING);
|
||||
trigger_error($this->lang('buggy_php'), E_USER_WARNING);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1682,7 +1631,7 @@ class PHPMailer
|
||||
call_user_func_array([$this, 'addAnAddress'], $params);
|
||||
}
|
||||
if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
|
||||
throw new Exception(self::lang('provide_address'), self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);
|
||||
}
|
||||
|
||||
//Validate From, Sender, and ConfirmReadingTo addresses
|
||||
@@ -1699,7 +1648,7 @@ class PHPMailer
|
||||
if (!static::validateAddress($this->{$address_kind})) {
|
||||
$error_message = sprintf(
|
||||
'%s (%s): %s',
|
||||
self::lang('invalid_address'),
|
||||
$this->lang('invalid_address'),
|
||||
$address_kind,
|
||||
$this->{$address_kind}
|
||||
);
|
||||
@@ -1721,7 +1670,7 @@ class PHPMailer
|
||||
$this->setMessageType();
|
||||
//Refuse to send an empty message unless we are specifically allowing it
|
||||
if (!$this->AllowEmpty && empty($this->Body)) {
|
||||
throw new Exception(self::lang('empty_message'), self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
|
||||
}
|
||||
|
||||
//Trim subject consistently
|
||||
@@ -1860,10 +1809,8 @@ class PHPMailer
|
||||
} else {
|
||||
$sendmailFmt = '%s -oi -f%s -t';
|
||||
}
|
||||
} elseif ($this->Mailer === 'qmail') {
|
||||
$sendmailFmt = '%s';
|
||||
} else {
|
||||
//Allow sendmail to choose a default envelope sender. It may
|
||||
//allow sendmail to choose a default envelope sender. It may
|
||||
//seem preferable to force it to use the From header as with
|
||||
//SMTP, but that introduces new problems (see
|
||||
//<https://github.com/PHPMailer/PHPMailer/issues/2298>), and
|
||||
@@ -1881,35 +1828,33 @@ class PHPMailer
|
||||
foreach ($this->SingleToArray as $toAddr) {
|
||||
$mail = @popen($sendmail, 'w');
|
||||
if (!$mail) {
|
||||
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
|
||||
}
|
||||
$this->edebug("To: {$toAddr}");
|
||||
fwrite($mail, 'To: ' . $toAddr . "\n");
|
||||
fwrite($mail, $header);
|
||||
fwrite($mail, $body);
|
||||
$result = pclose($mail);
|
||||
$addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
|
||||
foreach ($addrinfo as $addr) {
|
||||
$this->doCallback(
|
||||
($result === 0),
|
||||
[[$addr['address'], $addr['name']]],
|
||||
$this->cc,
|
||||
$this->bcc,
|
||||
$this->Subject,
|
||||
$body,
|
||||
$this->From,
|
||||
[]
|
||||
);
|
||||
}
|
||||
$addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
|
||||
$this->doCallback(
|
||||
($result === 0),
|
||||
[[$addrinfo['address'], $addrinfo['name']]],
|
||||
$this->cc,
|
||||
$this->bcc,
|
||||
$this->Subject,
|
||||
$body,
|
||||
$this->From,
|
||||
[]
|
||||
);
|
||||
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
|
||||
if (0 !== $result) {
|
||||
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$mail = @popen($sendmail, 'w');
|
||||
if (!$mail) {
|
||||
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
|
||||
}
|
||||
fwrite($mail, $header);
|
||||
fwrite($mail, $body);
|
||||
@@ -1926,7 +1871,7 @@ class PHPMailer
|
||||
);
|
||||
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
|
||||
if (0 !== $result) {
|
||||
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2065,19 +2010,17 @@ class PHPMailer
|
||||
if ($this->SingleTo && count($toArr) > 1) {
|
||||
foreach ($toArr as $toAddr) {
|
||||
$result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
|
||||
$addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
|
||||
foreach ($addrinfo as $addr) {
|
||||
$this->doCallback(
|
||||
$result,
|
||||
[[$addr['address'], $addr['name']]],
|
||||
$this->cc,
|
||||
$this->bcc,
|
||||
$this->Subject,
|
||||
$body,
|
||||
$this->From,
|
||||
[]
|
||||
);
|
||||
}
|
||||
$addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
|
||||
$this->doCallback(
|
||||
$result,
|
||||
[[$addrinfo['address'], $addrinfo['name']]],
|
||||
$this->cc,
|
||||
$this->bcc,
|
||||
$this->Subject,
|
||||
$body,
|
||||
$this->From,
|
||||
[]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
|
||||
@@ -2087,7 +2030,7 @@ class PHPMailer
|
||||
ini_set('sendmail_from', $old_from);
|
||||
}
|
||||
if (!$result) {
|
||||
throw new Exception(self::lang('instantiate'), self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -2173,12 +2116,12 @@ class PHPMailer
|
||||
$header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
|
||||
$bad_rcpt = [];
|
||||
if (!$this->smtpConnect($this->SMTPOptions)) {
|
||||
throw new Exception(self::lang('smtp_connect_failed'), self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
|
||||
}
|
||||
//If we have recipient addresses that need Unicode support,
|
||||
//but the server doesn't support it, stop here
|
||||
if ($this->UseSMTPUTF8 && !$this->smtp->getServerExt('SMTPUTF8')) {
|
||||
throw new Exception(self::lang('no_smtputf8'), self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('no_smtputf8'), self::STOP_CRITICAL);
|
||||
}
|
||||
//Sender already validated in preSend()
|
||||
if ('' === $this->Sender) {
|
||||
@@ -2190,7 +2133,7 @@ class PHPMailer
|
||||
$this->smtp->xclient($this->SMTPXClient);
|
||||
}
|
||||
if (!$this->smtp->mail($smtp_from)) {
|
||||
$this->setError(self::lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
|
||||
$this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
|
||||
throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
|
||||
}
|
||||
|
||||
@@ -2212,7 +2155,7 @@ class PHPMailer
|
||||
|
||||
//Only send the DATA command if we have viable recipients
|
||||
if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {
|
||||
throw new Exception(self::lang('data_not_accepted'), self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
|
||||
}
|
||||
|
||||
$smtp_transaction_id = $this->smtp->getLastTransactionID();
|
||||
@@ -2243,7 +2186,7 @@ class PHPMailer
|
||||
foreach ($bad_rcpt as $bad) {
|
||||
$errstr .= $bad['to'] . ': ' . $bad['error'];
|
||||
}
|
||||
throw new Exception(self::lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
|
||||
throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -2297,7 +2240,7 @@ class PHPMailer
|
||||
$hostinfo
|
||||
)
|
||||
) {
|
||||
$this->edebug(self::lang('invalid_hostentry') . ' ' . trim($hostentry));
|
||||
$this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));
|
||||
//Not a valid host entry
|
||||
continue;
|
||||
}
|
||||
@@ -2309,7 +2252,7 @@ class PHPMailer
|
||||
|
||||
//Check the host name is a valid name or IP address before trying to use it
|
||||
if (!static::isValidHost($hostinfo[2])) {
|
||||
$this->edebug(self::lang('invalid_host') . ' ' . $hostinfo[2]);
|
||||
$this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
|
||||
continue;
|
||||
}
|
||||
$prefix = '';
|
||||
@@ -2329,7 +2272,7 @@ class PHPMailer
|
||||
if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {
|
||||
//Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
|
||||
if (!$sslext) {
|
||||
throw new Exception(self::lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
|
||||
}
|
||||
}
|
||||
$host = $hostinfo[2];
|
||||
@@ -2381,7 +2324,7 @@ class PHPMailer
|
||||
$this->oauth
|
||||
)
|
||||
) {
|
||||
throw new Exception(self::lang('authenticate'));
|
||||
throw new Exception($this->lang('authenticate'));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -2431,7 +2374,7 @@ class PHPMailer
|
||||
*
|
||||
* @return bool Returns true if the requested language was loaded, false otherwise.
|
||||
*/
|
||||
public static function setLanguage($langcode = 'en', $lang_path = '')
|
||||
public function setLanguage($langcode = 'en', $lang_path = '')
|
||||
{
|
||||
//Backwards compatibility for renamed language codes
|
||||
$renamed_langcodes = [
|
||||
@@ -2480,9 +2423,6 @@ class PHPMailer
|
||||
'smtp_error' => 'SMTP server error: ',
|
||||
'variable_set' => 'Cannot set or reset variable: ',
|
||||
'no_smtputf8' => 'Server does not support SMTPUTF8 needed to send to Unicode addresses',
|
||||
'imap_recommended' => 'Using simplified address parser is not recommended. ' .
|
||||
'Install the PHP IMAP extension for full RFC822 parsing.',
|
||||
'deprecated_argument' => 'Argument $useimap is deprecated',
|
||||
];
|
||||
if (empty($lang_path)) {
|
||||
//Calculate an absolute path so it can work if CWD is not here
|
||||
@@ -2549,7 +2489,7 @@ class PHPMailer
|
||||
}
|
||||
}
|
||||
}
|
||||
self::$language = $PHPMAILER_LANG;
|
||||
$this->language = $PHPMAILER_LANG;
|
||||
|
||||
return $foundlang; //Returns false if language not found
|
||||
}
|
||||
@@ -2561,11 +2501,11 @@ class PHPMailer
|
||||
*/
|
||||
public function getTranslations()
|
||||
{
|
||||
if (empty(self::$language)) {
|
||||
self::setLanguage(); // Set the default language.
|
||||
if (empty($this->language)) {
|
||||
$this->setLanguage(); // Set the default language.
|
||||
}
|
||||
|
||||
return self::$language;
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2988,6 +2928,10 @@ class PHPMailer
|
||||
//Create unique IDs and preset boundaries
|
||||
$this->setBoundaries();
|
||||
|
||||
if ($this->sign_key_file) {
|
||||
$body .= $this->getMailMIME() . static::$LE;
|
||||
}
|
||||
|
||||
$this->setWordWrap();
|
||||
|
||||
$bodyEncoding = $this->Encoding;
|
||||
@@ -3019,12 +2963,6 @@ class PHPMailer
|
||||
if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
|
||||
$altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
|
||||
}
|
||||
|
||||
if ($this->sign_key_file) {
|
||||
$this->Encoding = $bodyEncoding;
|
||||
$body .= $this->getMailMIME() . static::$LE;
|
||||
}
|
||||
|
||||
//Use this as a preamble in all multipart message types
|
||||
$mimepre = '';
|
||||
switch ($this->message_type) {
|
||||
@@ -3206,12 +3144,12 @@ class PHPMailer
|
||||
if ($this->isError()) {
|
||||
$body = '';
|
||||
if ($this->exceptions) {
|
||||
throw new Exception(self::lang('empty_message'), self::STOP_CRITICAL);
|
||||
throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
|
||||
}
|
||||
} elseif ($this->sign_key_file) {
|
||||
try {
|
||||
if (!defined('PKCS7_TEXT')) {
|
||||
throw new Exception(self::lang('extension_missing') . 'openssl');
|
||||
throw new Exception($this->lang('extension_missing') . 'openssl');
|
||||
}
|
||||
|
||||
$file = tempnam(sys_get_temp_dir(), 'srcsign');
|
||||
@@ -3249,7 +3187,7 @@ class PHPMailer
|
||||
$body = $parts[1];
|
||||
} else {
|
||||
@unlink($signed);
|
||||
throw new Exception(self::lang('signing') . openssl_error_string());
|
||||
throw new Exception($this->lang('signing') . openssl_error_string());
|
||||
}
|
||||
} catch (Exception $exc) {
|
||||
$body = '';
|
||||
@@ -3394,7 +3332,7 @@ class PHPMailer
|
||||
) {
|
||||
try {
|
||||
if (!static::fileIsAccessible($path)) {
|
||||
throw new Exception(self::lang('file_access') . $path, self::STOP_CONTINUE);
|
||||
throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
|
||||
}
|
||||
|
||||
//If a MIME type is not specified, try to work it out from the file name
|
||||
@@ -3407,7 +3345,7 @@ class PHPMailer
|
||||
$name = $filename;
|
||||
}
|
||||
if (!$this->validateEncoding($encoding)) {
|
||||
throw new Exception(self::lang('encoding') . $encoding);
|
||||
throw new Exception($this->lang('encoding') . $encoding);
|
||||
}
|
||||
|
||||
$this->attachment[] = [
|
||||
@@ -3568,11 +3506,11 @@ class PHPMailer
|
||||
{
|
||||
try {
|
||||
if (!static::fileIsAccessible($path)) {
|
||||
throw new Exception(self::lang('file_open') . $path, self::STOP_CONTINUE);
|
||||
throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
|
||||
}
|
||||
$file_buffer = file_get_contents($path);
|
||||
if (false === $file_buffer) {
|
||||
throw new Exception(self::lang('file_open') . $path, self::STOP_CONTINUE);
|
||||
throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
|
||||
}
|
||||
$file_buffer = $this->encodeString($file_buffer, $encoding);
|
||||
|
||||
@@ -3625,9 +3563,9 @@ class PHPMailer
|
||||
$encoded = $this->encodeQP($str);
|
||||
break;
|
||||
default:
|
||||
$this->setError(self::lang('encoding') . $encoding);
|
||||
$this->setError($this->lang('encoding') . $encoding);
|
||||
if ($this->exceptions) {
|
||||
throw new Exception(self::lang('encoding') . $encoding);
|
||||
throw new Exception($this->lang('encoding') . $encoding);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3733,42 +3671,6 @@ class PHPMailer
|
||||
return trim(static::normalizeBreaks($encoded));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an RFC2047-encoded header value
|
||||
* Attempts multiple strategies so it works even when the mbstring extension is disabled.
|
||||
*
|
||||
* @param string $value The header value to decode
|
||||
* @param string $charset The target charset to convert to, defaults to ISO-8859-1 for BC
|
||||
*
|
||||
* @return string The decoded header value
|
||||
*/
|
||||
public static function decodeHeader($value, $charset = self::CHARSET_ISO88591)
|
||||
{
|
||||
if (!is_string($value) || $value === '') {
|
||||
return '';
|
||||
}
|
||||
// Detect the presence of any RFC2047 encoded-words
|
||||
$hasEncodedWord = (bool) preg_match('/=\?.*\?=/s', $value);
|
||||
if ($hasEncodedWord && defined('MB_CASE_UPPER')) {
|
||||
$origCharset = mb_internal_encoding();
|
||||
// Always decode to UTF-8 to provide a consistent, modern output encoding.
|
||||
mb_internal_encoding($charset);
|
||||
if (PHP_VERSION_ID < 80300) {
|
||||
// Undo any RFC2047-encoded spaces-as-underscores.
|
||||
$value = str_replace('_', '=20', $value);
|
||||
} else {
|
||||
// PHP 8.3+ already interprets underscores as spaces. Remove additional
|
||||
// linear whitespace between adjacent encoded words to avoid double spacing.
|
||||
$value = preg_replace('/(\?=)\s+(=\?)/', '$1$2', $value);
|
||||
}
|
||||
// Decode the header value
|
||||
$value = mb_decode_mimeheader($value);
|
||||
mb_internal_encoding($origCharset);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string contains multi-byte characters.
|
||||
*
|
||||
@@ -3938,7 +3840,7 @@ class PHPMailer
|
||||
}
|
||||
|
||||
if (!$this->validateEncoding($encoding)) {
|
||||
throw new Exception(self::lang('encoding') . $encoding);
|
||||
throw new Exception($this->lang('encoding') . $encoding);
|
||||
}
|
||||
|
||||
//Append to $attachment array
|
||||
@@ -3997,7 +3899,7 @@ class PHPMailer
|
||||
) {
|
||||
try {
|
||||
if (!static::fileIsAccessible($path)) {
|
||||
throw new Exception(self::lang('file_access') . $path, self::STOP_CONTINUE);
|
||||
throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
|
||||
}
|
||||
|
||||
//If a MIME type is not specified, try to work it out from the file name
|
||||
@@ -4006,7 +3908,7 @@ class PHPMailer
|
||||
}
|
||||
|
||||
if (!$this->validateEncoding($encoding)) {
|
||||
throw new Exception(self::lang('encoding') . $encoding);
|
||||
throw new Exception($this->lang('encoding') . $encoding);
|
||||
}
|
||||
|
||||
$filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
|
||||
@@ -4072,7 +3974,7 @@ class PHPMailer
|
||||
}
|
||||
|
||||
if (!$this->validateEncoding($encoding)) {
|
||||
throw new Exception(self::lang('encoding') . $encoding);
|
||||
throw new Exception($this->lang('encoding') . $encoding);
|
||||
}
|
||||
|
||||
//Append to $attachment array
|
||||
@@ -4329,7 +4231,7 @@ class PHPMailer
|
||||
}
|
||||
if (strpbrk($name . $value, "\r\n") !== false) {
|
||||
if ($this->exceptions) {
|
||||
throw new Exception(self::lang('invalid_header'));
|
||||
throw new Exception($this->lang('invalid_header'));
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -4353,15 +4255,15 @@ class PHPMailer
|
||||
if ('smtp' === $this->Mailer && null !== $this->smtp) {
|
||||
$lasterror = $this->smtp->getError();
|
||||
if (!empty($lasterror['error'])) {
|
||||
$msg .= ' ' . self::lang('smtp_error') . $lasterror['error'];
|
||||
$msg .= ' ' . $this->lang('smtp_error') . $lasterror['error'];
|
||||
if (!empty($lasterror['detail'])) {
|
||||
$msg .= ' ' . self::lang('smtp_detail') . $lasterror['detail'];
|
||||
$msg .= ' ' . $this->lang('smtp_detail') . $lasterror['detail'];
|
||||
}
|
||||
if (!empty($lasterror['smtp_code'])) {
|
||||
$msg .= ' ' . self::lang('smtp_code') . $lasterror['smtp_code'];
|
||||
$msg .= ' ' . $this->lang('smtp_code') . $lasterror['smtp_code'];
|
||||
}
|
||||
if (!empty($lasterror['smtp_code_ex'])) {
|
||||
$msg .= ' ' . self::lang('smtp_code_ex') . $lasterror['smtp_code_ex'];
|
||||
$msg .= ' ' . $this->lang('smtp_code_ex') . $lasterror['smtp_code_ex'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4486,21 +4388,21 @@ class PHPMailer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function lang($key)
|
||||
protected function lang($key)
|
||||
{
|
||||
if (count(self::$language) < 1) {
|
||||
self::setLanguage(); //Set the default language
|
||||
if (count($this->language) < 1) {
|
||||
$this->setLanguage(); //Set the default language
|
||||
}
|
||||
|
||||
if (array_key_exists($key, self::$language)) {
|
||||
if (array_key_exists($key, $this->language)) {
|
||||
if ('smtp_connect_failed' === $key) {
|
||||
//Include a link to troubleshooting docs on SMTP connection failure.
|
||||
//This is by far the biggest cause of support questions
|
||||
//but it's usually not PHPMailer's fault.
|
||||
return self::$language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
|
||||
return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
|
||||
}
|
||||
|
||||
return self::$language[$key];
|
||||
return $this->language[$key];
|
||||
}
|
||||
|
||||
//Return the key as a fallback
|
||||
@@ -4515,7 +4417,7 @@ class PHPMailer
|
||||
*/
|
||||
private function getSmtpErrorMessage($base_key)
|
||||
{
|
||||
$message = self::lang($base_key);
|
||||
$message = $this->lang($base_key);
|
||||
$error = $this->smtp->getError();
|
||||
if (!empty($error['error'])) {
|
||||
$message .= ' ' . $error['error'];
|
||||
@@ -4559,7 +4461,7 @@ class PHPMailer
|
||||
//Ensure name is not empty, and that neither name nor value contain line breaks
|
||||
if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {
|
||||
if ($this->exceptions) {
|
||||
throw new Exception(self::lang('invalid_header'));
|
||||
throw new Exception($this->lang('invalid_header'));
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -4952,7 +4854,7 @@ class PHPMailer
|
||||
|
||||
return true;
|
||||
}
|
||||
$this->setError(self::lang('variable_set') . $name);
|
||||
$this->setError($this->lang('variable_set') . $name);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -5090,7 +4992,7 @@ class PHPMailer
|
||||
{
|
||||
if (!defined('PKCS7_TEXT')) {
|
||||
if ($this->exceptions) {
|
||||
throw new Exception(self::lang('extension_missing') . 'openssl');
|
||||
throw new Exception($this->lang('extension_missing') . 'openssl');
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
2
vendor/phpmailer/phpmailer/src/POP3.php
vendored
2
vendor/phpmailer/phpmailer/src/POP3.php
vendored
@@ -46,7 +46,7 @@ class POP3
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VERSION = '6.11.1';
|
||||
const VERSION = '6.12.0';
|
||||
|
||||
/**
|
||||
* Default POP3 port number.
|
||||
|
||||
4
vendor/phpoffice/math/mkdocs.yml
vendored
4
vendor/phpoffice/math/mkdocs.yml
vendored
@@ -57,7 +57,9 @@ nav:
|
||||
- Writers: 'usage/writers.md'
|
||||
- Credits: 'credits.md'
|
||||
- Releases:
|
||||
- '0.1.0 (WIP)': 'changes/0.1.0.md'
|
||||
- '0.3.0 (WIP)': 'changes/0.3.0.md'
|
||||
- '0.2.0': 'changes/0.2.0.md'
|
||||
- '0.1.0': 'changes/0.1.0.md'
|
||||
- Developers:
|
||||
- 'Coveralls': 'https://coveralls.io/github/PHPOffice/Math'
|
||||
- 'Code Coverage': 'coverage/index.html'
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Tests\PhpOffice\Math\Reader;
|
||||
use PhpOffice\Math\Element;
|
||||
use PhpOffice\Math\Exception\InvalidInputException;
|
||||
use PhpOffice\Math\Exception\NotImplementedException;
|
||||
use PhpOffice\Math\Exception\SecurityException;
|
||||
use PhpOffice\Math\Math;
|
||||
use PhpOffice\Math\Reader\MathML;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -294,4 +295,15 @@ class MathMLTest extends TestCase
|
||||
$reader = new MathML();
|
||||
$math = $reader->read($content);
|
||||
}
|
||||
|
||||
public function testReadSecurity(): void
|
||||
{
|
||||
$this->expectException(SecurityException::class);
|
||||
$this->expectExceptionMessage('Detected use of ENTITY in XML, loading aborted to prevent XXE/XEE attacks');
|
||||
|
||||
$content = '<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE x SYSTEM "php://filter/convert.base64-decode/zlib.inflate/resource=data:,7Ztdb9owFIbv%2bRVZJ9armNjOZ2k7QUaL%2bRYO2nqFUnBFNQaMptP272cnNFuTsBbSskg1iATZzvGxn/ccX3A4fdfoecS7UsrK1A98hV5Rr9FVjlaz1UmlcnM7D9i6MlkufrB1AK79O2bqKltMllMWt96KL6ADwci7sJ4Yu0vr9/tlwKbqan27CPzrOXvevFGrbRvOGIseaCa7TAxok1x44xahXzQEcdKPKZPevap3RZw920I0VscWGLlU1efPsy0c5cbV1AoI7ZuOMCZW12nkcP9Q2%2bQObBNmL6ajg8s6xJqmJTrq5NIArX6zVk8Zcwwt4fPuLvHnbeBSvpdIQ6g93MvUv3CHqKNrmtEW4EYmCr5gDT5QzyNWE4x6xO1/aqQmgMhGYgaVDFUnScKltbFnaJoKHRuHK0L1pIkuaYselMe9cPUqRmm5C51u00kkhy1S3aBougkl7e4d6RGaTYeSehdCjAG/O/p%2bYfKyQsoLmgdlmsFYQFDjh6GWJyGE0ZfMX08EZtwNTdAYud7nLcksnwppA2UnqpCzgyDo1QadAU3vLOQZ82EHMxAi0KVcq7rzas5xD6AQoeqkYkgk02abukkJ/z%2bNvkj%2bjUy16Ba5d/S8anhBLwt44EgGkoFkIBlIBpKBZCAZSAaSgWQgGUgGkoFkIBlIBpKBZCAZSAaSgWQgGUgGxWOwW2nF7kt%2by7/Kb3ag2GUTUgBvXAAxiKxt4Is3sB4WniVrOvhwzB0CXerg5GN9esGRQv7RgQdMmMO9sIwtc/sIJUOCsY4ee7f7FIWu2Si4euKan8wg58nFsEIXxYGntgZqMog3Z2FrgPhgyzIOlsmijowqwb0jyMqMoGEbarqdOpP/iqFISMkSVFG1Z5p8f3OK%2bxAZ7gClpgUPg70rq0T2RIkcup/0newQ7NbcUXv/DPl4LL/N7hdfn2dp07pmd8v79YSdVVgwqcyWd8HC/8aOzkunf6r%2b2c8bpSxK/6uPmlf%2br/nSnyrHcduH99iqKiz7HwLxTLMgEM0QWUDjb3ji8NdHPslZmV%2bqR%2bfH56Xyxni1VGbV0m8=" []><foo></foo>M';
|
||||
|
||||
$reader = new MathML();
|
||||
$math = $reader->read($content);
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/phpoffice/phpword/LICENSE
vendored
2
vendor/phpoffice/phpword/LICENSE
vendored
@@ -1,6 +1,6 @@
|
||||
PHPWord, a pure PHP library for reading and writing word processing documents.
|
||||
|
||||
Copyright (c) 2010-2016 PHPWord.
|
||||
Copyright (c) 2010-2025 PHPWord.
|
||||
|
||||
PHPWord is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License version 3 as published by
|
||||
|
||||
11
vendor/phpoffice/phpword/README.md
vendored
11
vendor/phpoffice/phpword/README.md
vendored
@@ -1,11 +1,11 @@
|
||||
# 
|
||||
|
||||
[](https://packagist.org/packages/phpoffice/phpword)
|
||||
[](https://packagist.org/packages/phpoffice/phpword)
|
||||
[](https://coveralls.io/github/PHPOffice/PHPWord?branch=master)
|
||||
[](https://packagist.org/packages/phpoffice/phpword)
|
||||
[](https://packagist.org/packages/phpoffice/phpword)
|
||||
[](https://github.com/PHPOffice/PHPWord/actions/workflows/ci.yml)
|
||||
[](https://gitter.im/PHPOffice/PHPWord)
|
||||
[](https://packagist.org/packages/phpoffice/phpword)
|
||||
[](https://packagist.org/packages/phpoffice/phpword)
|
||||
|
||||
Branch Master : [](https://github.com/PHPOffice/PHPWord/actions/workflows/php.yml)
|
||||
|
||||
PHPWord is a library written in pure PHP that provides a set of classes to write to and read from different document file formats. The current version of PHPWord supports Microsoft [Office Open XML](http://en.wikipedia.org/wiki/Office_Open_XML) (OOXML or OpenXML), OASIS [Open Document Format for Office Applications](http://en.wikipedia.org/wiki/OpenDocument) (OpenDocument or ODF), [Rich Text Format](http://en.wikipedia.org/wiki/Rich_Text_Format) (RTF), HTML, and PDF.
|
||||
|
||||
@@ -81,7 +81,6 @@ The following is a basic usage example of the PHPWord library.
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
// Creating the new document...
|
||||
$phpWord = new \PhpOffice\PhpWord\PhpWord();
|
||||
|
||||
5
vendor/phpoffice/phpword/mkdocs.yml
vendored
5
vendor/phpoffice/phpword/mkdocs.yml
vendored
@@ -63,6 +63,7 @@ nav:
|
||||
- OLE Object: 'usage/elements/oleobject.md'
|
||||
- Page Break: 'usage/elements/pagebreak.md'
|
||||
- Preserve Text: 'usage/elements/preservetext.md'
|
||||
- Ruby: 'usage/elements/ruby.md'
|
||||
- Text: 'usage/elements/text.md'
|
||||
- TextBox: 'usage/elements/textbox.md'
|
||||
- Text Break: 'usage/elements/textbreak.md'
|
||||
@@ -87,7 +88,9 @@ nav:
|
||||
- Credits: 'credits.md'
|
||||
- Releases:
|
||||
- '1.x':
|
||||
- '1.3.0 (WIP)': 'changes/1.x/1.3.0.md'
|
||||
- '1.5.0 (WIP)': 'changes/1.x/1.5.0.md'
|
||||
- '1.4.0': 'changes/1.x/1.4.0.md'
|
||||
- '1.3.0': 'changes/1.x/1.3.0.md'
|
||||
- '1.2.0': 'changes/1.x/1.2.0.md'
|
||||
- '1.1.0': 'changes/1.x/1.1.0.md'
|
||||
- '1.0.0': 'changes/1.x/1.0.0.md'
|
||||
|
||||
1
vendor/phpoffice/phpword/phpword.ini.dist
vendored
1
vendor/phpoffice/phpword/phpword.ini.dist
vendored
@@ -14,6 +14,7 @@ outputEscapingEnabled = false
|
||||
|
||||
defaultFontName = Arial
|
||||
defaultFontSize = 10
|
||||
defaultFontColor = 000000
|
||||
|
||||
[Paper]
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -21,6 +22,7 @@ namespace PhpOffice\PhpWord\Collection;
|
||||
* Collection abstract class.
|
||||
*
|
||||
* @since 0.10.0
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
abstract class AbstractCollection
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -23,6 +24,7 @@ use PhpOffice\PhpWord\Element\Bookmark;
|
||||
* Bookmarks collection.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @extends AbstractCollection<Bookmark>
|
||||
*/
|
||||
class Bookmarks extends AbstractCollection
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -50,7 +51,7 @@ class Chart extends AbstractElement
|
||||
/**
|
||||
* Chart style.
|
||||
*
|
||||
* @var ?\PhpOffice\PhpWord\Style\Chart
|
||||
* @var ?ChartStyle
|
||||
*/
|
||||
private $style;
|
||||
|
||||
@@ -120,7 +121,7 @@ class Chart extends AbstractElement
|
||||
/**
|
||||
* Get chart style.
|
||||
*
|
||||
* @return ?\PhpOffice\PhpWord\Style\Chart
|
||||
* @return ?ChartStyle
|
||||
*/
|
||||
public function getStyle()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -89,7 +90,7 @@ class Comment extends TrackChange
|
||||
/**
|
||||
* Get the element where this comment starts.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\AbstractElement
|
||||
* @return AbstractElement
|
||||
*/
|
||||
public function getStartElement()
|
||||
{
|
||||
@@ -108,7 +109,7 @@ class Comment extends TrackChange
|
||||
/**
|
||||
* Get the element where this comment ends.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\AbstractElement
|
||||
* @return AbstractElement
|
||||
*/
|
||||
public function getEndElement()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -27,7 +28,7 @@ class Line extends AbstractElement
|
||||
/**
|
||||
* Line style.
|
||||
*
|
||||
* @var ?\PhpOffice\PhpWord\Style\Line
|
||||
* @var ?LineStyle
|
||||
*/
|
||||
private $style;
|
||||
|
||||
@@ -44,7 +45,7 @@ class Line extends AbstractElement
|
||||
/**
|
||||
* Get line style.
|
||||
*
|
||||
* @return ?\PhpOffice\PhpWord\Style\Line
|
||||
* @return ?LineStyle
|
||||
*/
|
||||
public function getStyle()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -43,14 +44,14 @@ class Link extends AbstractElement
|
||||
/**
|
||||
* Font style.
|
||||
*
|
||||
* @var null|\PhpOffice\PhpWord\Style\Font|string
|
||||
* @var null|Font|string
|
||||
*/
|
||||
private $fontStyle;
|
||||
|
||||
/**
|
||||
* Paragraph style.
|
||||
*
|
||||
* @var null|\PhpOffice\PhpWord\Style\Paragraph|string
|
||||
* @var null|Paragraph|string
|
||||
*/
|
||||
private $paragraphStyle;
|
||||
|
||||
@@ -98,10 +99,8 @@ class Link extends AbstractElement
|
||||
|
||||
/**
|
||||
* Get link text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getText()
|
||||
public function getText(): string
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
@@ -109,7 +108,7 @@ class Link extends AbstractElement
|
||||
/**
|
||||
* Get Text style.
|
||||
*
|
||||
* @return null|\PhpOffice\PhpWord\Style\Font|string
|
||||
* @return null|Font|string
|
||||
*/
|
||||
public function getFontStyle()
|
||||
{
|
||||
@@ -119,7 +118,7 @@ class Link extends AbstractElement
|
||||
/**
|
||||
* Get Paragraph style.
|
||||
*
|
||||
* @return null|\PhpOffice\PhpWord\Style\Paragraph|string
|
||||
* @return null|Paragraph|string
|
||||
*/
|
||||
public function getParagraphStyle()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -36,14 +37,14 @@ class PreserveText extends AbstractElement
|
||||
/**
|
||||
* Text style.
|
||||
*
|
||||
* @var null|\PhpOffice\PhpWord\Style\Font|string
|
||||
* @var null|Font|string
|
||||
*/
|
||||
private $fontStyle;
|
||||
|
||||
/**
|
||||
* Paragraph style.
|
||||
*
|
||||
* @var null|\PhpOffice\PhpWord\Style\Paragraph|string
|
||||
* @var null|Paragraph|string
|
||||
*/
|
||||
private $paragraphStyle;
|
||||
|
||||
@@ -69,7 +70,7 @@ class PreserveText extends AbstractElement
|
||||
/**
|
||||
* Get Text style.
|
||||
*
|
||||
* @return null|\PhpOffice\PhpWord\Style\Font|string
|
||||
* @return null|Font|string
|
||||
*/
|
||||
public function getFontStyle()
|
||||
{
|
||||
@@ -79,7 +80,7 @@ class PreserveText extends AbstractElement
|
||||
/**
|
||||
* Get Paragraph style.
|
||||
*
|
||||
* @return null|\PhpOffice\PhpWord\Style\Paragraph|string
|
||||
* @return null|Paragraph|string
|
||||
*/
|
||||
public function getParagraphStyle()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -31,7 +32,7 @@ class Section extends AbstractContainer
|
||||
/**
|
||||
* Section style.
|
||||
*
|
||||
* @var ?\PhpOffice\PhpWord\Style\Section
|
||||
* @var ?SectionStyle
|
||||
*/
|
||||
private $style;
|
||||
|
||||
@@ -87,7 +88,7 @@ class Section extends AbstractContainer
|
||||
/**
|
||||
* Get section style.
|
||||
*
|
||||
* @return ?\PhpOffice\PhpWord\Style\Section
|
||||
* @return ?SectionStyle
|
||||
*/
|
||||
public function getStyle()
|
||||
{
|
||||
@@ -196,14 +197,14 @@ class Section extends AbstractContainer
|
||||
*/
|
||||
private function addHeaderFooter($type = Header::AUTO, $header = true)
|
||||
{
|
||||
$containerClass = substr(static::class, 0, strrpos(static::class, '\\')) . '\\' .
|
||||
$containerClass = substr(static::class, 0, strrpos(static::class, '\\') ?: 0) . '\\' .
|
||||
($header ? 'Header' : 'Footer');
|
||||
$collectionArray = $header ? 'headers' : 'footers';
|
||||
$collection = &$this->$collectionArray;
|
||||
|
||||
if (in_array($type, [Header::AUTO, Header::FIRST, Header::EVEN])) {
|
||||
$index = count($collection);
|
||||
/** @var \PhpOffice\PhpWord\Element\AbstractContainer $container Type hint */
|
||||
/** @var AbstractContainer $container Type hint */
|
||||
$container = new $containerClass($this->sectionId, ++$index, $type);
|
||||
$container->setPhpWord($this->phpWord);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -36,7 +37,7 @@ class Shape extends AbstractElement
|
||||
/**
|
||||
* Shape style.
|
||||
*
|
||||
* @var ?\PhpOffice\PhpWord\Style\Shape
|
||||
* @var ?ShapeStyle
|
||||
*/
|
||||
private $style;
|
||||
|
||||
@@ -80,7 +81,7 @@ class Shape extends AbstractElement
|
||||
/**
|
||||
* Get shape style.
|
||||
*
|
||||
* @return ?\PhpOffice\PhpWord\Style\Shape
|
||||
* @return ?ShapeStyle
|
||||
*/
|
||||
public function getStyle()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -27,14 +28,14 @@ class Table extends AbstractElement
|
||||
/**
|
||||
* Table style.
|
||||
*
|
||||
* @var ?\PhpOffice\PhpWord\Style\Table
|
||||
* @var ?TableStyle
|
||||
*/
|
||||
private $style;
|
||||
|
||||
/**
|
||||
* Table rows.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Element\Row[]
|
||||
* @var Row[]
|
||||
*/
|
||||
private $rows = [];
|
||||
|
||||
@@ -61,7 +62,7 @@ class Table extends AbstractElement
|
||||
* @param int $height
|
||||
* @param mixed $style
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\Row
|
||||
* @return Row
|
||||
*/
|
||||
public function addRow($height = null, $style = null)
|
||||
{
|
||||
@@ -78,7 +79,7 @@ class Table extends AbstractElement
|
||||
* @param int $width
|
||||
* @param mixed $style
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\Cell
|
||||
* @return Cell
|
||||
*/
|
||||
public function addCell($width = null, $style = null)
|
||||
{
|
||||
@@ -92,7 +93,7 @@ class Table extends AbstractElement
|
||||
/**
|
||||
* Get all rows.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\Row[]
|
||||
* @return Row[]
|
||||
*/
|
||||
public function getRows()
|
||||
{
|
||||
@@ -102,7 +103,7 @@ class Table extends AbstractElement
|
||||
/**
|
||||
* Get table style.
|
||||
*
|
||||
* @return null|\PhpOffice\PhpWord\Style\Table|string
|
||||
* @return null|string|TableStyle
|
||||
*/
|
||||
public function getStyle()
|
||||
{
|
||||
@@ -140,7 +141,7 @@ class Table extends AbstractElement
|
||||
|
||||
$rowCount = count($this->rows);
|
||||
for ($i = 0; $i < $rowCount; ++$i) {
|
||||
/** @var \PhpOffice\PhpWord\Element\Row $row Type hint */
|
||||
/** @var Row $row Type hint */
|
||||
$row = $this->rows[$i];
|
||||
$cellCount = count($row->getCells());
|
||||
if ($columnCount < $cellCount) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -28,14 +29,14 @@ class TextBreak extends AbstractElement
|
||||
/**
|
||||
* Paragraph style.
|
||||
*
|
||||
* @var null|\PhpOffice\PhpWord\Style\Paragraph|string
|
||||
* @var null|Paragraph|string
|
||||
*/
|
||||
private $paragraphStyle;
|
||||
|
||||
/**
|
||||
* Text style.
|
||||
*
|
||||
* @var null|\PhpOffice\PhpWord\Style\Font|string
|
||||
* @var null|Font|string
|
||||
*/
|
||||
private $fontStyle;
|
||||
|
||||
@@ -61,7 +62,7 @@ class TextBreak extends AbstractElement
|
||||
* @param mixed $style
|
||||
* @param mixed $paragraphStyle
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Font|string
|
||||
* @return Font|string
|
||||
*/
|
||||
public function setFontStyle($style = null, $paragraphStyle = null)
|
||||
{
|
||||
@@ -82,7 +83,7 @@ class TextBreak extends AbstractElement
|
||||
/**
|
||||
* Get Text style.
|
||||
*
|
||||
* @return null|\PhpOffice\PhpWord\Style\Font|string
|
||||
* @return null|Font|string
|
||||
*/
|
||||
public function getFontStyle()
|
||||
{
|
||||
@@ -92,9 +93,9 @@ class TextBreak extends AbstractElement
|
||||
/**
|
||||
* Set Paragraph style.
|
||||
*
|
||||
* @param array|\PhpOffice\PhpWord\Style\Paragraph|string $style
|
||||
* @param array|Paragraph|string $style
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Paragraph|string
|
||||
* @return Paragraph|string
|
||||
*/
|
||||
public function setParagraphStyle($style = null)
|
||||
{
|
||||
@@ -113,7 +114,7 @@ class TextBreak extends AbstractElement
|
||||
/**
|
||||
* Get Paragraph style.
|
||||
*
|
||||
* @return null|\PhpOffice\PhpWord\Style\Paragraph|string
|
||||
* @return null|Paragraph|string
|
||||
*/
|
||||
public function getParagraphStyle()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -85,6 +86,9 @@ class TextRun extends AbstractContainer
|
||||
foreach ($this->getElements() as $element) {
|
||||
if ($element instanceof Text) {
|
||||
$outstr .= $element->getText();
|
||||
} elseif ($element instanceof Ruby) {
|
||||
$outstr .= $element->getBaseTextRun()->getText() .
|
||||
' (' . $element->getRubyTextRun()->getText() . ')';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -35,7 +36,7 @@ abstract class IOFactory
|
||||
*/
|
||||
public static function createWriter(PhpWord $phpWord, $name = 'Word2007')
|
||||
{
|
||||
if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF'], true)) {
|
||||
if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF', 'EPub3'], true)) {
|
||||
throw new Exception("\"{$name}\" is not a valid writer.");
|
||||
}
|
||||
|
||||
@@ -61,9 +62,9 @@ abstract class IOFactory
|
||||
*
|
||||
* @param string $type
|
||||
* @param string $name
|
||||
* @param \PhpOffice\PhpWord\PhpWord $phpWord
|
||||
* @param PhpWord $phpWord
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Reader\ReaderInterface|\PhpOffice\PhpWord\Writer\WriterInterface
|
||||
* @return ReaderInterface|WriterInterface
|
||||
*/
|
||||
private static function createObject($type, $name, $phpWord = null)
|
||||
{
|
||||
@@ -81,11 +82,11 @@ abstract class IOFactory
|
||||
* @param string $filename The name of the file
|
||||
* @param string $readerName
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\PhpWord $phpWord
|
||||
* @return PhpWord $phpWord
|
||||
*/
|
||||
public static function load($filename, $readerName = 'Word2007')
|
||||
{
|
||||
/** @var \PhpOffice\PhpWord\Reader\ReaderInterface $reader */
|
||||
/** @var ReaderInterface $reader */
|
||||
$reader = self::createReader($readerName);
|
||||
|
||||
return $reader->load($filename);
|
||||
@@ -100,7 +101,7 @@ abstract class IOFactory
|
||||
*/
|
||||
public static function extractVariables(string $filename, string $readerName = 'Word2007'): array
|
||||
{
|
||||
/** @var \PhpOffice\PhpWord\Reader\ReaderInterface $reader */
|
||||
/** @var ReaderInterface $reader */
|
||||
$reader = self::createReader($readerName);
|
||||
$document = $reader->load($filename);
|
||||
$extractedVariables = [];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
57
vendor/phpoffice/phpword/src/PhpWord/PhpWord.php
vendored
57
vendor/phpoffice/phpword/src/PhpWord/PhpWord.php
vendored
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -47,7 +48,7 @@ class PhpWord
|
||||
/**
|
||||
* Collection of sections.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Element\Section[]
|
||||
* @var Section[]
|
||||
*/
|
||||
private $sections = [];
|
||||
|
||||
@@ -151,7 +152,7 @@ class PhpWord
|
||||
/**
|
||||
* Get document properties object.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Metadata\DocInfo
|
||||
* @return Metadata\DocInfo
|
||||
*/
|
||||
public function getDocInfo()
|
||||
{
|
||||
@@ -161,7 +162,7 @@ class PhpWord
|
||||
/**
|
||||
* Get compatibility.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Metadata\Compatibility
|
||||
* @return Metadata\Compatibility
|
||||
*
|
||||
* @since 0.12.0
|
||||
*/
|
||||
@@ -173,7 +174,7 @@ class PhpWord
|
||||
/**
|
||||
* Get compatibility.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Metadata\Settings
|
||||
* @return Metadata\Settings
|
||||
*
|
||||
* @since 0.14.0
|
||||
*/
|
||||
@@ -185,7 +186,7 @@ class PhpWord
|
||||
/**
|
||||
* Get all sections.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\Section[]
|
||||
* @return Section[]
|
||||
*/
|
||||
public function getSections()
|
||||
{
|
||||
@@ -197,7 +198,7 @@ class PhpWord
|
||||
*
|
||||
* @param int $index
|
||||
*
|
||||
* @return null|\PhpOffice\PhpWord\Element\Section
|
||||
* @return null|Section
|
||||
*/
|
||||
public function getSection($index)
|
||||
{
|
||||
@@ -213,7 +214,7 @@ class PhpWord
|
||||
*
|
||||
* @param null|array|string $style
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\Section
|
||||
* @return Section
|
||||
*/
|
||||
public function addSection($style = null)
|
||||
{
|
||||
@@ -256,6 +257,40 @@ class PhpWord
|
||||
Settings::setDefaultFontName($fontName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default asian font name.
|
||||
*/
|
||||
public function getDefaultAsianFontName(): string
|
||||
{
|
||||
return Settings::getDefaultAsianFontName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default asian font name.
|
||||
*
|
||||
* @param string $fontName
|
||||
*/
|
||||
public function setDefaultAsianFontName($fontName): void
|
||||
{
|
||||
Settings::setDefaultAsianFontName($fontName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default font color.
|
||||
*/
|
||||
public function setDefaultFontColor(string $fontColor): void
|
||||
{
|
||||
Settings::setDefaultFontColor($fontColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default font color.
|
||||
*/
|
||||
public function getDefaultFontColor(): string
|
||||
{
|
||||
return Settings::getDefaultFontColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default font size.
|
||||
*
|
||||
@@ -281,7 +316,7 @@ class PhpWord
|
||||
*
|
||||
* @param array $styles Paragraph style definition
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Paragraph
|
||||
* @return Style\Paragraph
|
||||
*/
|
||||
public function setDefaultParagraphStyle($styles)
|
||||
{
|
||||
@@ -333,7 +368,7 @@ class PhpWord
|
||||
*
|
||||
* @param array $settings
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\Section
|
||||
* @return Section
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
@@ -347,7 +382,7 @@ class PhpWord
|
||||
*
|
||||
* @deprecated 0.12.0
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Metadata\DocInfo
|
||||
* @return Metadata\DocInfo
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
@@ -361,7 +396,7 @@ class PhpWord
|
||||
*
|
||||
* @deprecated 0.12.0
|
||||
*
|
||||
* @param \PhpOffice\PhpWord\Metadata\DocInfo $documentProperties
|
||||
* @param Metadata\DocInfo $documentProperties
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -32,7 +33,7 @@ class ODText extends AbstractReader implements ReaderInterface
|
||||
*
|
||||
* @param string $docFile
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\PhpWord
|
||||
* @return PhpWord
|
||||
*/
|
||||
public function load($docFile)
|
||||
{
|
||||
@@ -58,7 +59,7 @@ class ODText extends AbstractReader implements ReaderInterface
|
||||
{
|
||||
$partClass = "PhpOffice\\PhpWord\\Reader\\ODText\\{$partName}";
|
||||
if (class_exists($partClass)) {
|
||||
/** @var \PhpOffice\PhpWord\Reader\ODText\AbstractPart $part Type hint */
|
||||
/** @var ODText\AbstractPart $part Type hint */
|
||||
$part = new $partClass($docFile, $xmlFile);
|
||||
$part->setRels($relationships);
|
||||
$part->read($phpWord);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
282
vendor/phpoffice/phpword/src/PhpWord/Shared/Html.php
vendored
282
vendor/phpoffice/phpword/src/PhpWord/Shared/Html.php
vendored
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -22,9 +23,11 @@ use DOMDocument;
|
||||
use DOMNode;
|
||||
use DOMXPath;
|
||||
use Exception;
|
||||
use PhpOffice\PhpWord\ComplexType\RubyProperties;
|
||||
use PhpOffice\PhpWord\Element\AbstractContainer;
|
||||
use PhpOffice\PhpWord\Element\Row;
|
||||
use PhpOffice\PhpWord\Element\Table;
|
||||
use PhpOffice\PhpWord\Element\TextRun;
|
||||
use PhpOffice\PhpWord\Settings;
|
||||
use PhpOffice\PhpWord\SimpleType\Jc;
|
||||
use PhpOffice\PhpWord\SimpleType\NumberFormat;
|
||||
@@ -57,7 +60,7 @@ class Html
|
||||
* Warning: Do not pass user-generated HTML here, as that would allow an attacker to read arbitrary
|
||||
* files or perform server-side request forgery by passing local file paths or URLs in <img>.
|
||||
*
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element Where the parts need to be added
|
||||
* @param AbstractContainer $element Where the parts need to be added
|
||||
* @param string $html The code to parse
|
||||
* @param bool $fullHTML If it's a full HTML, no need to add 'body' tag
|
||||
* @param bool $preserveWhiteSpace If false, the whitespaces between nodes will be removed
|
||||
@@ -127,21 +130,21 @@ class Html
|
||||
break;
|
||||
case 'width':
|
||||
// tables, cells
|
||||
$val = $val === 'auto' ? '100%' : $val;
|
||||
if (false !== strpos($val, '%')) {
|
||||
// e.g. <table width="100%"> or <td width="50%">
|
||||
$styles['width'] = (int) $val * 50;
|
||||
$styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT;
|
||||
} else {
|
||||
// e.g. <table width="250> where "250" = 250px (always pixels)
|
||||
$styles['width'] = Converter::pixelToTwip($val);
|
||||
$styles['width'] = Converter::pixelToTwip(self::convertHtmlSize($val));
|
||||
$styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::TWIP;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'cellspacing':
|
||||
// tables e.g. <table cellspacing="2">, where "2" = 2px (always pixels)
|
||||
$val = (int) $val . 'px';
|
||||
$styles['cellSpacing'] = Converter::cssToTwip($val);
|
||||
$styles['cellSpacing'] = Converter::pixelToTwip(self::convertHtmlSize($val));
|
||||
|
||||
break;
|
||||
case 'bgcolor':
|
||||
@@ -185,7 +188,7 @@ class Html
|
||||
* Parse a node and add a corresponding element to the parent element.
|
||||
*
|
||||
* @param DOMNode $node node to parse
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element object to add an element corresponding with the node
|
||||
* @param AbstractContainer $element object to add an element corresponding with the node
|
||||
* @param array $styles Array with all styles
|
||||
* @param array $data Array to transport data to a next level in the DOM tree, for example level of listitems
|
||||
*/
|
||||
@@ -208,16 +211,16 @@ class Html
|
||||
|
||||
// Node mapping table
|
||||
$nodes = [
|
||||
// $method $node $element $styles $data $argument1 $argument2
|
||||
'p' => ['Paragraph', $node, $element, $styles, null, null, null],
|
||||
'h1' => ['Heading', null, $element, $styles, null, 'Heading1', null],
|
||||
'h2' => ['Heading', null, $element, $styles, null, 'Heading2', null],
|
||||
'h3' => ['Heading', null, $element, $styles, null, 'Heading3', null],
|
||||
'h4' => ['Heading', null, $element, $styles, null, 'Heading4', null],
|
||||
'h5' => ['Heading', null, $element, $styles, null, 'Heading5', null],
|
||||
'h6' => ['Heading', null, $element, $styles, null, 'Heading6', null],
|
||||
'#text' => ['Text', $node, $element, $styles, null, null, null],
|
||||
'strong' => ['Property', null, null, $styles, null, 'bold', true],
|
||||
// $method $node $element $styles $data $argument1 $argument2
|
||||
'p' => ['Paragraph', $node, $element, $styles, null, null, null],
|
||||
'h1' => ['Heading', $node, $element, $styles, null, 'Heading1', null],
|
||||
'h2' => ['Heading', $node, $element, $styles, null, 'Heading2', null],
|
||||
'h3' => ['Heading', $node, $element, $styles, null, 'Heading3', null],
|
||||
'h4' => ['Heading', $node, $element, $styles, null, 'Heading4', null],
|
||||
'h5' => ['Heading', $node, $element, $styles, null, 'Heading5', null],
|
||||
'h6' => ['Heading', $node, $element, $styles, null, 'Heading6', null],
|
||||
'#text' => ['Text', $node, $element, $styles, null, null, null],
|
||||
'strong' => ['Property', null, null, $styles, null, 'bold', true],
|
||||
'b' => ['Property', null, null, $styles, null, 'bold', true],
|
||||
'em' => ['Property', null, null, $styles, null, 'italic', true],
|
||||
'i' => ['Property', null, null, $styles, null, 'italic', true],
|
||||
@@ -238,6 +241,7 @@ class Html
|
||||
'a' => ['Link', $node, $element, $styles, null, null, null],
|
||||
'input' => ['Input', $node, $element, $styles, null, null, null],
|
||||
'hr' => ['HorizRule', $node, $element, $styles, null, null, null],
|
||||
'ruby' => ['Ruby', $node, $element, $styles, null, null, null],
|
||||
];
|
||||
|
||||
$newElement = null;
|
||||
@@ -276,7 +280,7 @@ class Html
|
||||
* Parse child nodes.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer|Row|Table $element
|
||||
* @param AbstractContainer|Row|Table $element
|
||||
* @param array $styles
|
||||
* @param array $data
|
||||
*/
|
||||
@@ -298,10 +302,10 @@ class Html
|
||||
* Parse paragraph node.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
|
||||
* @param AbstractContainer $element
|
||||
* @param array &$styles
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\PageBreak|\PhpOffice\PhpWord\Element\TextRun
|
||||
* @return \PhpOffice\PhpWord\Element\PageBreak|TextRun
|
||||
*/
|
||||
protected static function parseParagraph($node, $element, &$styles)
|
||||
{
|
||||
@@ -317,7 +321,7 @@ class Html
|
||||
* Parse input node.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
|
||||
* @param AbstractContainer $element
|
||||
* @param array &$styles
|
||||
*/
|
||||
protected static function parseInput($node, $element, &$styles): void
|
||||
@@ -341,28 +345,25 @@ class Html
|
||||
/**
|
||||
* Parse heading node.
|
||||
*
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
|
||||
* @param array &$styles
|
||||
* @param string $argument1 Name of heading style
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\TextRun
|
||||
*
|
||||
* @todo Think of a clever way of defining header styles, now it is only based on the assumption, that
|
||||
* Heading1 - Heading6 are already defined somewhere
|
||||
*/
|
||||
protected static function parseHeading($element, &$styles, $argument1)
|
||||
protected static function parseHeading(DOMNode $node, AbstractContainer $element, array &$styles, string $argument1): TextRun
|
||||
{
|
||||
$styles['paragraph'] = $argument1;
|
||||
$newElement = $element->addTextRun($styles['paragraph']);
|
||||
$style = new Paragraph();
|
||||
$style->setStyleName($argument1);
|
||||
$style->setStyleByArray(self::parseInlineStyle($node, $styles['paragraph']));
|
||||
|
||||
return $newElement;
|
||||
return $element->addTextRun($style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse text node.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
|
||||
* @param AbstractContainer $element
|
||||
* @param array &$styles
|
||||
*/
|
||||
protected static function parseText($node, $element, &$styles): void
|
||||
@@ -406,7 +407,7 @@ class Html
|
||||
* Parse table node.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
|
||||
* @param AbstractContainer $element
|
||||
* @param array &$styles
|
||||
*
|
||||
* @return Table $element
|
||||
@@ -437,7 +438,7 @@ class Html
|
||||
* Parse a table row.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\Table $element
|
||||
* @param Table $element
|
||||
* @param array &$styles
|
||||
*
|
||||
* @return Row $element
|
||||
@@ -460,10 +461,10 @@ class Html
|
||||
* Parse table cell.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\Table $element
|
||||
* @param Table $element
|
||||
* @param array &$styles
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\Cell|\PhpOffice\PhpWord\Element\TextRun $element
|
||||
* @return \PhpOffice\PhpWord\Element\Cell|TextRun $element
|
||||
*/
|
||||
protected static function parseCell($node, $element, &$styles)
|
||||
{
|
||||
@@ -554,7 +555,7 @@ class Html
|
||||
* Parse list node.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
|
||||
* @param AbstractContainer $element
|
||||
* @param array &$styles
|
||||
* @param array &$data
|
||||
*/
|
||||
@@ -627,15 +628,15 @@ class Html
|
||||
return [
|
||||
'type' => 'hybridMultilevel',
|
||||
'levels' => [
|
||||
['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '◦', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '◦', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '◦', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'],
|
||||
['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'],
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -644,7 +645,7 @@ class Html
|
||||
* Parse list item node.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
|
||||
* @param AbstractContainer $element
|
||||
* @param array &$styles
|
||||
* @param array $data
|
||||
*
|
||||
@@ -704,6 +705,10 @@ class Html
|
||||
case 'text-align':
|
||||
$styles['alignment'] = self::mapAlign($value, $bidi);
|
||||
|
||||
break;
|
||||
case 'ruby-align':
|
||||
$styles['rubyAlignment'] = self::mapRubyAlign($value);
|
||||
|
||||
break;
|
||||
case 'display':
|
||||
$styles['hidden'] = $value === 'none' || $value === 'hidden';
|
||||
@@ -733,7 +738,7 @@ class Html
|
||||
break;
|
||||
case 'line-height':
|
||||
$matches = [];
|
||||
if ($value === 'normal') {
|
||||
if ($value === 'normal' || $value === 'inherit') {
|
||||
$spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO;
|
||||
$spacing = 0;
|
||||
} elseif (preg_match('/([0-9]+\.?[0-9]*[a-z]+)/', $value, $matches)) {
|
||||
@@ -803,6 +808,58 @@ class Html
|
||||
$styles['spaceAfter'] = Converter::cssToTwip($value);
|
||||
|
||||
break;
|
||||
|
||||
case 'padding':
|
||||
$valueTop = $valueRight = $valueBottom = $valueLeft = null;
|
||||
$cValue = preg_replace('# +#', ' ', trim($value));
|
||||
$paddingArr = explode(' ', $cValue);
|
||||
$countParams = count($paddingArr);
|
||||
if ($countParams == 1) {
|
||||
$valueTop = $valueRight = $valueBottom = $valueLeft = $paddingArr[0];
|
||||
} elseif ($countParams == 2) {
|
||||
$valueTop = $valueBottom = $paddingArr[0];
|
||||
$valueRight = $valueLeft = $paddingArr[1];
|
||||
} elseif ($countParams == 3) {
|
||||
$valueTop = $paddingArr[0];
|
||||
$valueRight = $valueLeft = $paddingArr[1];
|
||||
$valueBottom = $paddingArr[2];
|
||||
} elseif ($countParams == 4) {
|
||||
$valueTop = $paddingArr[0];
|
||||
$valueRight = $paddingArr[1];
|
||||
$valueBottom = $paddingArr[2];
|
||||
$valueLeft = $paddingArr[3];
|
||||
}
|
||||
if ($valueTop !== null) {
|
||||
$styles['paddingTop'] = Converter::cssToTwip($valueTop);
|
||||
}
|
||||
if ($valueRight !== null) {
|
||||
$styles['paddingRight'] = Converter::cssToTwip($valueRight);
|
||||
}
|
||||
if ($valueBottom !== null) {
|
||||
$styles['paddingBottom'] = Converter::cssToTwip($valueBottom);
|
||||
}
|
||||
if ($valueLeft !== null) {
|
||||
$styles['paddingLeft'] = Converter::cssToTwip($valueLeft);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'padding-top':
|
||||
$styles['paddingTop'] = Converter::cssToTwip($value);
|
||||
|
||||
break;
|
||||
case 'padding-right':
|
||||
$styles['paddingRight'] = Converter::cssToTwip($value);
|
||||
|
||||
break;
|
||||
case 'padding-bottom':
|
||||
$styles['paddingBottom'] = Converter::cssToTwip($value);
|
||||
|
||||
break;
|
||||
case 'padding-left':
|
||||
$styles['paddingLeft'] = Converter::cssToTwip($value);
|
||||
|
||||
break;
|
||||
|
||||
case 'border-color':
|
||||
self::mapBorderColor($styles, $value);
|
||||
|
||||
@@ -886,7 +943,7 @@ class Html
|
||||
* Parse image node.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
|
||||
* @param AbstractContainer $element
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Element\Image
|
||||
*/
|
||||
@@ -901,36 +958,12 @@ class Html
|
||||
|
||||
break;
|
||||
case 'width':
|
||||
$width = $attribute->value;
|
||||
|
||||
// pt
|
||||
if (false !== strpos($width, 'pt')) {
|
||||
$width = Converter::pointToPixel((float) str_replace('pt', '', $width));
|
||||
}
|
||||
|
||||
// px
|
||||
if (false !== strpos($width, 'px')) {
|
||||
$width = str_replace('px', '', $width);
|
||||
}
|
||||
|
||||
$style['width'] = $width;
|
||||
$style['width'] = self::convertHtmlSize($attribute->value);
|
||||
$style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX;
|
||||
|
||||
break;
|
||||
case 'height':
|
||||
$height = $attribute->value;
|
||||
|
||||
// pt
|
||||
if (false !== strpos($height, 'pt')) {
|
||||
$height = Converter::pointToPixel((float) str_replace('pt', '', $height));
|
||||
}
|
||||
|
||||
// px
|
||||
if (false !== strpos($height, 'px')) {
|
||||
$height = str_replace('px', '', $height);
|
||||
}
|
||||
|
||||
$style['height'] = $height;
|
||||
$style['height'] = self::convertHtmlSize($attribute->value);
|
||||
$style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX;
|
||||
|
||||
break;
|
||||
@@ -970,14 +1003,15 @@ class Html
|
||||
|
||||
$match = [];
|
||||
preg_match('/data:image\/(\w+);base64,(.+)/', $src, $match);
|
||||
if (!empty($match)) {
|
||||
$src = $imgFile = $tmpDir . uniqid() . '.' . $match[1];
|
||||
|
||||
$src = $imgFile = $tmpDir . uniqid() . '.' . $match[1];
|
||||
$ifp = fopen($imgFile, 'wb');
|
||||
|
||||
$ifp = fopen($imgFile, 'wb');
|
||||
|
||||
if ($ifp !== false) {
|
||||
fwrite($ifp, base64_decode($match[2]));
|
||||
fclose($ifp);
|
||||
if ($ifp !== false) {
|
||||
fwrite($ifp, base64_decode($match[2]));
|
||||
fclose($ifp);
|
||||
}
|
||||
}
|
||||
}
|
||||
$src = urldecode($src);
|
||||
@@ -1073,6 +1107,23 @@ class Html
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a HTML/CSS ruby alignment into a \PhpOffice\PhpWord\SimpleType\Jc.
|
||||
*/
|
||||
protected static function mapRubyAlign(string $cssRubyAlignment): string
|
||||
{
|
||||
switch ($cssRubyAlignment) {
|
||||
case 'center':
|
||||
return RubyProperties::ALIGNMENT_CENTER;
|
||||
case 'start':
|
||||
return RubyProperties::ALIGNMENT_LEFT;
|
||||
case 'space-between':
|
||||
return RubyProperties::ALIGNMENT_DISTRIBUTE_SPACE;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a HTML/CSS vertical alignment.
|
||||
*
|
||||
@@ -1129,7 +1180,7 @@ class Html
|
||||
/**
|
||||
* Parse line break.
|
||||
*
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
|
||||
* @param AbstractContainer $element
|
||||
*/
|
||||
protected static function parseLineBreak($element): void
|
||||
{
|
||||
@@ -1140,7 +1191,7 @@ class Html
|
||||
* Parse link node.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
|
||||
* @param AbstractContainer $element
|
||||
* @param array $styles
|
||||
*/
|
||||
protected static function parseLink($node, $element, &$styles)
|
||||
@@ -1172,7 +1223,7 @@ class Html
|
||||
* Note: Word rule is not the same as HTML's <hr> since it does not support width and thus neither alignment.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param \PhpOffice\PhpWord\Element\AbstractContainer $element
|
||||
* @param AbstractContainer $element
|
||||
*/
|
||||
protected static function parseHorizRule($node, $element): void
|
||||
{
|
||||
@@ -1201,6 +1252,59 @@ class Html
|
||||
// - repeated text, e.g. underline "_", because of unpredictable line wrapping
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse ruby node.
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @param AbstractContainer $element
|
||||
* @param array $styles
|
||||
*/
|
||||
protected static function parseRuby($node, $element, &$styles)
|
||||
{
|
||||
$rubyProperties = new RubyProperties();
|
||||
$baseTextRun = new TextRun($styles['paragraph']);
|
||||
$rubyTextRun = new TextRun(null);
|
||||
if ($node->hasAttributes()) {
|
||||
$langAttr = $node->attributes->getNamedItem('lang');
|
||||
if ($langAttr !== null) {
|
||||
$rubyProperties->setLanguageId($langAttr->textContent);
|
||||
}
|
||||
$styleAttr = $node->attributes->getNamedItem('style');
|
||||
if ($styleAttr !== null) {
|
||||
$styles = self::parseStyle($styleAttr, $styles['paragraph']);
|
||||
if (isset($styles['rubyAlignment']) && $styles['rubyAlignment'] !== '') {
|
||||
$rubyProperties->setAlignment($styles['rubyAlignment']);
|
||||
}
|
||||
if (isset($styles['size']) && $styles['size'] !== '') {
|
||||
$rubyProperties->setFontSizeForBaseText($styles['size']);
|
||||
}
|
||||
$baseTextRun->setParagraphStyle($styles);
|
||||
}
|
||||
}
|
||||
foreach ($node->childNodes as $child) {
|
||||
if ($child->nodeName === '#text') {
|
||||
$content = trim($child->textContent);
|
||||
if ($content !== '') {
|
||||
$baseTextRun->addText($content);
|
||||
}
|
||||
} elseif ($child->nodeName === 'rt') {
|
||||
$rubyTextRun->addText(trim($child->textContent));
|
||||
if ($child->hasAttributes()) {
|
||||
$styleAttr = $child->attributes->getNamedItem('style');
|
||||
if ($styleAttr !== null) {
|
||||
$styles = self::parseStyle($styleAttr, []);
|
||||
if (isset($styles['size']) && $styles['size'] !== '') {
|
||||
$rubyProperties->setFontFaceSize($styles['size']);
|
||||
}
|
||||
$rubyTextRun->setParagraphStyle($styles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $element->addRuby($baseTextRun, $rubyTextRun, $rubyProperties);
|
||||
}
|
||||
|
||||
private static function convertRgb(string $rgb): string
|
||||
{
|
||||
if (preg_match(self::RGB_REGEXP, $rgb, $matches) === 1) {
|
||||
@@ -1209,4 +1313,22 @@ class Html
|
||||
|
||||
return trim($rgb, '# ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform HTML sizes (pt, px) in pixels.
|
||||
*/
|
||||
protected static function convertHtmlSize(string $size): float
|
||||
{
|
||||
// pt
|
||||
if (false !== strpos($size, 'pt')) {
|
||||
return Converter::pointToPixel((float) str_replace('pt', '', $size));
|
||||
}
|
||||
|
||||
// px
|
||||
if (false !== strpos($size, 'px')) {
|
||||
return (float) str_replace('px', '', $size);
|
||||
}
|
||||
|
||||
return (float) $size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -293,8 +294,10 @@ class ZipArchive
|
||||
|
||||
// Write $contents to a temp file
|
||||
$handle = fopen($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename'], 'wb');
|
||||
fwrite($handle, $contents);
|
||||
fclose($handle);
|
||||
if ($handle) {
|
||||
fwrite($handle, $contents);
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
// Add temp file to zip
|
||||
$filename = $this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename'];
|
||||
@@ -420,4 +423,15 @@ class ZipArchive
|
||||
|
||||
return ($listIndex > -1) ? $listIndex : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an empty directory to the zip archive (emulate \ZipArchive).
|
||||
*
|
||||
* @param string $dirname Directory name to add to the zip archive
|
||||
*/
|
||||
public function addEmptyDir(string $dirname): bool
|
||||
{
|
||||
// Create a directory entry by adding an empty file with trailing slash
|
||||
return $this->addFromString(rtrim($dirname, '/') . '/', '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -108,7 +109,7 @@ class Font extends AbstractStyle
|
||||
/**
|
||||
* Font color.
|
||||
*
|
||||
* @var string
|
||||
* @var null|string
|
||||
*/
|
||||
private $color;
|
||||
|
||||
@@ -219,14 +220,14 @@ class Font extends AbstractStyle
|
||||
/**
|
||||
* Paragraph style.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\Paragraph
|
||||
* @var Paragraph
|
||||
*/
|
||||
private $paragraph;
|
||||
|
||||
/**
|
||||
* Shading.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\Shading
|
||||
* @var Shading
|
||||
*/
|
||||
private $shading;
|
||||
|
||||
@@ -248,7 +249,7 @@ class Font extends AbstractStyle
|
||||
/**
|
||||
* Languages.
|
||||
*
|
||||
* @var null|\PhpOffice\PhpWord\Style\Language
|
||||
* @var null|Language
|
||||
*/
|
||||
private $lang;
|
||||
|
||||
@@ -288,7 +289,7 @@ class Font extends AbstractStyle
|
||||
* Create new font style.
|
||||
*
|
||||
* @param string $type Type of font
|
||||
* @param array|\PhpOffice\PhpWord\Style\AbstractStyle|string $paragraph Paragraph styles definition
|
||||
* @param AbstractStyle|array|string $paragraph Paragraph styles definition
|
||||
*/
|
||||
public function __construct($type = 'text', $paragraph = null)
|
||||
{
|
||||
@@ -425,10 +426,8 @@ class Font extends AbstractStyle
|
||||
|
||||
/**
|
||||
* Get font color.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getColor()
|
||||
public function getColor(): ?string
|
||||
{
|
||||
return $this->color;
|
||||
}
|
||||
@@ -682,7 +681,7 @@ class Font extends AbstractStyle
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Table
|
||||
* @return Table
|
||||
*/
|
||||
public function setBgColor($value = null)
|
||||
{
|
||||
@@ -812,7 +811,7 @@ class Font extends AbstractStyle
|
||||
/**
|
||||
* Get paragraph style.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Paragraph
|
||||
* @return Paragraph
|
||||
*/
|
||||
public function getParagraph()
|
||||
{
|
||||
@@ -860,7 +859,7 @@ class Font extends AbstractStyle
|
||||
/**
|
||||
* Get shading.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Shading
|
||||
* @return Shading
|
||||
*/
|
||||
public function getShading()
|
||||
{
|
||||
@@ -884,7 +883,7 @@ class Font extends AbstractStyle
|
||||
/**
|
||||
* Get language.
|
||||
*
|
||||
* @return null|\PhpOffice\PhpWord\Style\Language
|
||||
* @return null|Language
|
||||
*/
|
||||
public function getLang()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -28,30 +29,37 @@ class Indentation extends AbstractStyle
|
||||
/**
|
||||
* Left indentation (twip).
|
||||
*
|
||||
* @var float|int
|
||||
* @var null|float
|
||||
*/
|
||||
private $left = 0;
|
||||
|
||||
/**
|
||||
* Right indentation (twip).
|
||||
*
|
||||
* @var float|int
|
||||
* @var null|float
|
||||
*/
|
||||
private $right = 0;
|
||||
|
||||
/**
|
||||
* Additional first line indentation (twip).
|
||||
*
|
||||
* @var float|int
|
||||
* @var null|float
|
||||
*/
|
||||
private $firstLine = 0;
|
||||
|
||||
/**
|
||||
* Additional first line chars indentation (twip).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $firstLineChars = 0;
|
||||
|
||||
/**
|
||||
* Indentation removed from first line (twip).
|
||||
*
|
||||
* @var float|int
|
||||
* @var null|float
|
||||
*/
|
||||
private $hanging;
|
||||
private $hanging = 0;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
@@ -65,96 +73,90 @@ class Indentation extends AbstractStyle
|
||||
|
||||
/**
|
||||
* Get left.
|
||||
*
|
||||
* @return float|int
|
||||
*/
|
||||
public function getLeft()
|
||||
public function getLeft(): ?float
|
||||
{
|
||||
return $this->left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set left.
|
||||
*
|
||||
* @param float|int $value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setLeft($value)
|
||||
public function setLeft(?float $value): self
|
||||
{
|
||||
$this->left = $this->setNumericVal($value, $this->left);
|
||||
$this->left = $this->setNumericVal($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get right.
|
||||
*
|
||||
* @return float|int
|
||||
*/
|
||||
public function getRight()
|
||||
public function getRight(): ?float
|
||||
{
|
||||
return $this->right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set right.
|
||||
*
|
||||
* @param float|int $value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setRight($value)
|
||||
public function setRight(?float $value): self
|
||||
{
|
||||
$this->right = $this->setNumericVal($value, $this->right);
|
||||
$this->right = $this->setNumericVal($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first line.
|
||||
*
|
||||
* @return float|int
|
||||
*/
|
||||
public function getFirstLine()
|
||||
public function getFirstLine(): ?float
|
||||
{
|
||||
return $this->firstLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set first line.
|
||||
*
|
||||
* @param float|int $value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setFirstLine($value)
|
||||
public function setFirstLine(?float $value): self
|
||||
{
|
||||
$this->firstLine = $this->setNumericVal($value, $this->firstLine);
|
||||
$this->firstLine = $this->setNumericVal($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first line chars.
|
||||
*/
|
||||
public function getFirstLineChars(): int
|
||||
{
|
||||
return $this->firstLineChars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set first line chars.
|
||||
*/
|
||||
public function setFirstLineChars(int $value): self
|
||||
{
|
||||
$this->firstLineChars = $this->setIntVal($value, $this->firstLineChars);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hanging.
|
||||
*
|
||||
* @return float|int
|
||||
*/
|
||||
public function getHanging()
|
||||
public function getHanging(): ?float
|
||||
{
|
||||
return $this->hanging;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set hanging.
|
||||
*
|
||||
* @param float|int $value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setHanging($value = null)
|
||||
public function setHanging(?float $value = null): self
|
||||
{
|
||||
$this->hanging = $this->setNumericVal($value, $this->hanging);
|
||||
$this->hanging = $this->setNumericVal($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -54,22 +55,16 @@ class Numbering extends AbstractStyle
|
||||
|
||||
/**
|
||||
* Get Id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getNumId()
|
||||
public function getNumId(): ?int
|
||||
{
|
||||
return $this->numId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Id.
|
||||
*
|
||||
* @param int $value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setNumId($value)
|
||||
public function setNumId(int $value): self
|
||||
{
|
||||
$this->numId = $this->setIntVal($value, $this->numId);
|
||||
|
||||
@@ -78,22 +73,16 @@ class Numbering extends AbstractStyle
|
||||
|
||||
/**
|
||||
* Get multilevel type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
public function getType(): ?string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multilevel type.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setType($value)
|
||||
public function setType(string $value): self
|
||||
{
|
||||
$enum = ['singleLevel', 'multilevel', 'hybridMultilevel'];
|
||||
$this->type = $this->setEnumVal($value, $enum, $this->type);
|
||||
@@ -106,19 +95,15 @@ class Numbering extends AbstractStyle
|
||||
*
|
||||
* @return NumberingLevel[]
|
||||
*/
|
||||
public function getLevels()
|
||||
public function getLevels(): array
|
||||
{
|
||||
return $this->levels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multilevel type.
|
||||
*
|
||||
* @param array $values
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setLevels($values)
|
||||
public function setLevels(array $values): self
|
||||
{
|
||||
if (is_array($values)) {
|
||||
foreach ($values as $key => $value) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -86,14 +87,14 @@ class Paragraph extends Border
|
||||
/**
|
||||
* Indentation.
|
||||
*
|
||||
* @var null|\PhpOffice\PhpWord\Style\Indentation
|
||||
* @var null|Indentation
|
||||
*/
|
||||
private $indentation;
|
||||
|
||||
/**
|
||||
* Spacing.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\Spacing
|
||||
* @var Spacing
|
||||
*/
|
||||
private $spacing;
|
||||
|
||||
@@ -149,14 +150,14 @@ class Paragraph extends Border
|
||||
/**
|
||||
* Set of Custom Tab Stops.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\Tab[]
|
||||
* @var Tab[]
|
||||
*/
|
||||
private $tabs = [];
|
||||
|
||||
/**
|
||||
* Shading.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\Shading
|
||||
* @var Shading
|
||||
*/
|
||||
private $shading;
|
||||
|
||||
@@ -321,78 +322,144 @@ class Paragraph extends Border
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hanging.
|
||||
*/
|
||||
public function getHanging(): ?float
|
||||
{
|
||||
return $this->getChildStyleValue($this->indentation, 'hanging');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get indentation.
|
||||
*
|
||||
* @return null|\PhpOffice\PhpWord\Style\Indentation
|
||||
* @deprecated 1.4.0 Use getIndentLeft
|
||||
*/
|
||||
public function getIndentation()
|
||||
public function getIndent(): ?float
|
||||
{
|
||||
return $this->getChildStyleValue($this->indentation, 'left');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get indentation.
|
||||
*/
|
||||
public function getIndentation(): ?Indentation
|
||||
{
|
||||
return $this->indentation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set shading.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return self
|
||||
* Get firstLine.
|
||||
*/
|
||||
public function setIndentation($value = null)
|
||||
public function getIndentFirstLine(): ?float
|
||||
{
|
||||
return $this->getChildStyleValue($this->indentation, 'firstLine');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get left indentation.
|
||||
*/
|
||||
public function getIndentLeft(): ?float
|
||||
{
|
||||
return $this->getChildStyleValue($this->indentation, 'left');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get right indentation.
|
||||
*/
|
||||
public function getIndentRight(): ?float
|
||||
{
|
||||
return $this->getChildStyleValue($this->indentation, 'right');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set hanging.
|
||||
*
|
||||
* @deprecated 1.4.0 Use setIndentHanging
|
||||
*/
|
||||
public function setHanging(?float $value = null): self
|
||||
{
|
||||
return $this->setIndentation(['hanging' => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set indentation.
|
||||
*
|
||||
* @deprecated 1.4.0 Use setIndentLeft
|
||||
*/
|
||||
public function setIndent(?float $value = null): self
|
||||
{
|
||||
return $this->setIndentation(['left' => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set indentation.
|
||||
*
|
||||
* @param array{
|
||||
* left?:null|float|int|numeric-string,
|
||||
* right?:null|float|int|numeric-string,
|
||||
* hanging?:null|float|int|numeric-string,
|
||||
* firstLine?:null|float|int|numeric-string
|
||||
* } $value
|
||||
*/
|
||||
public function setIndentation(array $value = []): self
|
||||
{
|
||||
$value = array_map(function ($indent) {
|
||||
if (is_string($indent) || is_numeric($indent)) {
|
||||
$indent = $this->setFloatVal($indent);
|
||||
}
|
||||
|
||||
return $indent;
|
||||
}, $value);
|
||||
$this->setObjectVal($value, 'Indentation', $this->indentation);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get indentation.
|
||||
*
|
||||
* @return int
|
||||
* Set hanging indentation.
|
||||
*/
|
||||
public function getIndent()
|
||||
{
|
||||
return $this->getChildStyleValue($this->indentation, 'left');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set indentation.
|
||||
*
|
||||
* @param int $value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setIndent($value = null)
|
||||
{
|
||||
return $this->setIndentation(['left' => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hanging.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHanging()
|
||||
{
|
||||
return $this->getChildStyleValue($this->indentation, 'hanging');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set hanging.
|
||||
*
|
||||
* @param int $value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setHanging($value = null)
|
||||
public function setIndentHanging(?float $value = null): self
|
||||
{
|
||||
return $this->setIndentation(['hanging' => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set firstline indentation.
|
||||
*/
|
||||
public function setIndentFirstLine(?float $value = null): self
|
||||
{
|
||||
return $this->setIndentation(['firstLine' => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set firstlineChars indentation.
|
||||
*/
|
||||
public function setIndentFirstLineChars(int $value = 0): self
|
||||
{
|
||||
return $this->setIndentation(['firstLineChars' => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set left indentation.
|
||||
*/
|
||||
public function setIndentLeft(?float $value = null): self
|
||||
{
|
||||
return $this->setIndentation(['left' => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set right indentation.
|
||||
*/
|
||||
public function setIndentRight(?float $value = null): self
|
||||
{
|
||||
return $this->setIndentation(['right' => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get spacing.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Spacing
|
||||
* @return Spacing
|
||||
*
|
||||
* @todo Rename to getSpacing in 1.0
|
||||
*/
|
||||
@@ -498,7 +565,7 @@ class Paragraph extends Border
|
||||
*
|
||||
* @param string $value Possible values are defined in LineSpacingRule
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Paragraph
|
||||
* @return Paragraph
|
||||
*/
|
||||
public function setSpacingLineRule($value)
|
||||
{
|
||||
@@ -686,7 +753,7 @@ class Paragraph extends Border
|
||||
/**
|
||||
* Get tabs.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Tab[]
|
||||
* @return Tab[]
|
||||
*/
|
||||
public function getTabs()
|
||||
{
|
||||
@@ -712,7 +779,7 @@ class Paragraph extends Border
|
||||
/**
|
||||
* Get shading.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Shading
|
||||
* @return Shading
|
||||
*/
|
||||
public function getShading()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -58,7 +59,7 @@ class Section extends Border
|
||||
/**
|
||||
* Paper size.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\Paper
|
||||
* @var Paper
|
||||
*/
|
||||
private $paper;
|
||||
|
||||
@@ -137,7 +138,7 @@ class Section extends Border
|
||||
/**
|
||||
* Line numbering.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\LineNumbering
|
||||
* @var LineNumbering
|
||||
*
|
||||
* @see http://www.schemacentral.com/sc/ooxml/e-w_lnNumType-1.html
|
||||
*/
|
||||
@@ -278,7 +279,7 @@ class Section extends Border
|
||||
/**
|
||||
* @param null|float|int $value
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Section
|
||||
* @return Section
|
||||
*
|
||||
* @since 0.12.0
|
||||
*/
|
||||
@@ -304,7 +305,7 @@ class Section extends Border
|
||||
/**
|
||||
* @param null|float|int $value
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Section
|
||||
* @return Section
|
||||
*
|
||||
* @since 0.12.0
|
||||
*/
|
||||
@@ -486,7 +487,7 @@ class Section extends Border
|
||||
/**
|
||||
* Get line numbering.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\LineNumbering
|
||||
* @return LineNumbering
|
||||
*/
|
||||
public function getLineNumbering()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -51,35 +52,35 @@ class Shape extends AbstractStyle
|
||||
/**
|
||||
* Frame.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\Frame
|
||||
* @var Frame
|
||||
*/
|
||||
private $frame;
|
||||
|
||||
/**
|
||||
* Fill.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\Fill
|
||||
* @var Fill
|
||||
*/
|
||||
private $fill;
|
||||
|
||||
/**
|
||||
* Outline.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\Outline
|
||||
* @var Outline
|
||||
*/
|
||||
private $outline;
|
||||
|
||||
/**
|
||||
* Shadow.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\Shadow
|
||||
* @var Shadow
|
||||
*/
|
||||
private $shadow;
|
||||
|
||||
/**
|
||||
* 3D extrusion.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\Style\Extrusion
|
||||
* @var Extrusion
|
||||
*/
|
||||
private $extrusion;
|
||||
|
||||
@@ -144,7 +145,7 @@ class Shape extends AbstractStyle
|
||||
/**
|
||||
* Get frame.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Frame
|
||||
* @return Frame
|
||||
*/
|
||||
public function getFrame()
|
||||
{
|
||||
@@ -168,7 +169,7 @@ class Shape extends AbstractStyle
|
||||
/**
|
||||
* Get fill.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Fill
|
||||
* @return Fill
|
||||
*/
|
||||
public function getFill()
|
||||
{
|
||||
@@ -192,7 +193,7 @@ class Shape extends AbstractStyle
|
||||
/**
|
||||
* Get outline.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Outline
|
||||
* @return Outline
|
||||
*/
|
||||
public function getOutline()
|
||||
{
|
||||
@@ -216,7 +217,7 @@ class Shape extends AbstractStyle
|
||||
/**
|
||||
* Get shadow.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Shadow
|
||||
* @return Shadow
|
||||
*/
|
||||
public function getShadow()
|
||||
{
|
||||
@@ -240,7 +241,7 @@ class Shape extends AbstractStyle
|
||||
/**
|
||||
* Get 3D extrusion.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Style\Extrusion
|
||||
* @return Extrusion
|
||||
*/
|
||||
public function getExtrusion()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -33,7 +34,7 @@ abstract class AbstractWriter implements WriterInterface
|
||||
/**
|
||||
* PHPWord object.
|
||||
*
|
||||
* @var \PhpOffice\PhpWord\PhpWord
|
||||
* @var PhpWord
|
||||
*/
|
||||
protected $phpWord;
|
||||
|
||||
@@ -96,7 +97,7 @@ abstract class AbstractWriter implements WriterInterface
|
||||
/**
|
||||
* Get PhpWord object.
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\PhpWord
|
||||
* @return PhpWord
|
||||
*/
|
||||
public function getPhpWord()
|
||||
{
|
||||
@@ -110,8 +111,6 @@ abstract class AbstractWriter implements WriterInterface
|
||||
/**
|
||||
* Set PhpWord object.
|
||||
*
|
||||
* @param \PhpOffice\PhpWord\PhpWord
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPhpWord(?PhpWord $phpWord = null)
|
||||
@@ -267,7 +266,7 @@ abstract class AbstractWriter implements WriterInterface
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return \PhpOffice\PhpWord\Shared\ZipArchive
|
||||
* @return ZipArchive
|
||||
*/
|
||||
protected function getZipArchive($filename)
|
||||
{
|
||||
@@ -360,7 +359,7 @@ abstract class AbstractWriter implements WriterInterface
|
||||
*
|
||||
* Get the actual source from an archive image.
|
||||
*
|
||||
* @param \PhpOffice\PhpWord\Shared\ZipArchive $zipPackage
|
||||
* @param ZipArchive $zipPackage
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -72,7 +73,7 @@ class Text extends AbstractElement
|
||||
/** @var \PhpOffice\PhpWord\Element\Text $element Type hint */
|
||||
$element = $this->element;
|
||||
|
||||
$text = $this->parentWriter->escapeHTML($element->getText());
|
||||
$text = $this->parentWriter->escapeHTML($element->getText() ?? '');
|
||||
if (!$this->withoutP && !trim($text)) {
|
||||
$text = ' ';
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -22,7 +23,7 @@ use PhpOffice\PhpWord\Writer\HTML\Element\TextRun as TextRunWriter;
|
||||
use PhpOffice\PhpWord\Writer\PDF\TCPDF;
|
||||
|
||||
/**
|
||||
* RTF body part writer.
|
||||
* HTML body part writer.
|
||||
*
|
||||
* @since 0.11.0
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -55,7 +56,7 @@ class ODText extends AbstractWriter implements WriterInterface
|
||||
foreach (array_keys($this->parts) as $partName) {
|
||||
$partClass = static::class . '\\Part\\' . $partName;
|
||||
if (class_exists($partClass)) {
|
||||
/** @var \PhpOffice\PhpWord\Writer\ODText\Part\AbstractPart $partObject Type hint */
|
||||
/** @var AbstractPart $partObject Type hint */
|
||||
$partObject = new $partClass();
|
||||
$partObject->setParentWriter($this);
|
||||
$this->writerParts[strtolower($partName)] = $partObject;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -57,37 +58,29 @@ class Text extends AbstractElement
|
||||
$xmlWriter->writeAttribute('text:change-id', $element->getTrackChange()->getElementId());
|
||||
$xmlWriter->endElement();
|
||||
} else {
|
||||
if (empty($fontStyle)) {
|
||||
if (empty($paragraphStyle)) {
|
||||
if (!$this->withoutP) {
|
||||
$xmlWriter->writeAttribute('text:style-name', 'Normal');
|
||||
}
|
||||
} elseif (is_string($paragraphStyle)) {
|
||||
if (!$this->withoutP) {
|
||||
$xmlWriter->writeAttribute('text:style-name', $paragraphStyle);
|
||||
}
|
||||
if (empty($paragraphStyle)) {
|
||||
if (!$this->withoutP) {
|
||||
$xmlWriter->writeAttribute('text:style-name', 'Normal');
|
||||
}
|
||||
$this->writeChangeInsertion(true, $element->getTrackChange());
|
||||
$this->replaceTabs($element->getText(), $xmlWriter);
|
||||
$this->writeChangeInsertion(false, $element->getTrackChange());
|
||||
} else {
|
||||
if (empty($paragraphStyle)) {
|
||||
if (!$this->withoutP) {
|
||||
$xmlWriter->writeAttribute('text:style-name', 'Normal');
|
||||
}
|
||||
} elseif (is_string($paragraphStyle)) {
|
||||
if (!$this->withoutP) {
|
||||
$xmlWriter->writeAttribute('text:style-name', $paragraphStyle);
|
||||
}
|
||||
} elseif (is_string($paragraphStyle)) {
|
||||
if (!$this->withoutP) {
|
||||
$xmlWriter->writeAttribute('text:style-name', $paragraphStyle);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($fontStyle)) {
|
||||
// text:span
|
||||
$xmlWriter->startElement('text:span');
|
||||
if (is_string($fontStyle)) {
|
||||
$xmlWriter->writeAttribute('text:style-name', $fontStyle);
|
||||
}
|
||||
$this->writeChangeInsertion(true, $element->getTrackChange());
|
||||
$this->replaceTabs($element->getText(), $xmlWriter);
|
||||
$this->writeChangeInsertion(false, $element->getTrackChange());
|
||||
}
|
||||
|
||||
$this->writeChangeInsertion(true, $element->getTrackChange());
|
||||
$this->replaceTabs($element->getText(), $xmlWriter);
|
||||
$this->writeChangeInsertion(false, $element->getTrackChange());
|
||||
|
||||
if (!empty($fontStyle)) {
|
||||
$xmlWriter->endElement();
|
||||
}
|
||||
}
|
||||
@@ -96,35 +89,6 @@ class Text extends AbstractElement
|
||||
}
|
||||
}
|
||||
|
||||
private function replacetabs($text, $xmlWriter): void
|
||||
{
|
||||
if (preg_match('/^ +/', $text, $matches)) {
|
||||
$num = strlen($matches[0]);
|
||||
$xmlWriter->startElement('text:s');
|
||||
$xmlWriter->writeAttributeIf($num > 1, 'text:c', "$num");
|
||||
$xmlWriter->endElement();
|
||||
$text = preg_replace('/^ +/', '', $text);
|
||||
}
|
||||
preg_match_all('/([\\s\\S]*?)(\\t| +| ?$)/', $text, $matches, PREG_SET_ORDER);
|
||||
foreach ($matches as $match) {
|
||||
$this->writeText($match[1]);
|
||||
if ($match[2] === '') {
|
||||
break;
|
||||
} elseif ($match[2] === "\t") {
|
||||
$xmlWriter->writeElement('text:tab');
|
||||
} elseif ($match[2] === ' ') {
|
||||
$xmlWriter->writeElement('text:s');
|
||||
|
||||
break;
|
||||
} else {
|
||||
$num = strlen($match[2]);
|
||||
$xmlWriter->startElement('text:s');
|
||||
$xmlWriter->writeAttributeIf($num > 1, 'text:c', "$num");
|
||||
$xmlWriter->endElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function writeChangeInsertion($start = true, ?TrackChange $trackChange = null): void
|
||||
{
|
||||
if ($trackChange == null || $trackChange->getChangeType() != TrackChange::INSERTED) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
@@ -91,11 +92,12 @@ class Styles extends AbstractPart
|
||||
|
||||
// Font
|
||||
$xmlWriter->startElement('style:text-properties');
|
||||
$xmlWriter->writeAttribute('style:use-window-font-color', 'true');
|
||||
$xmlWriter->writeAttribute('style:use-window-font-color', 'false');
|
||||
$xmlWriter->writeAttribute('style:font-name', Settings::getDefaultFontName());
|
||||
$xmlWriter->writeAttribute('fo:font-size', Settings::getDefaultFontSize() . 'pt');
|
||||
$xmlWriter->writeAttribute('fo:language', $latinLang[0]);
|
||||
$xmlWriter->writeAttribute('fo:country', $latinLang[1]);
|
||||
$xmlWriter->writeAttribute('fo:color', '#' . Settings::getDefaultFontColor());
|
||||
$xmlWriter->writeAttribute('style:letter-kerning', 'true');
|
||||
$xmlWriter->writeAttribute('style:font-name-asian', Settings::getDefaultFontName() . '2');
|
||||
$xmlWriter->writeAttribute('style:font-size-asian', Settings::getDefaultFontSize() . 'pt');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of PHPWord - A pure PHP library for reading and writing
|
||||
* word processing documents.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user