<?php namespace Controller; use Lib\Mail\Mail; use Lib\Mail\MailFun; use Lib\UploadFile; use Lib\Verify; use Model\bodySql; use Model\emailSql; use Model\folderSql; use Model\listsSql; use Model\sendJobsSql; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\SMTP; use function Swoole\Coroutine\Http\request; /** * @author:dc * @time 2023/2/13 11:28 * Class Home * @package Controller */ class Home extends Base { /** * 邮件列表 * @author:dc * @time 2023/2/17 14:12 */ public function lists(){ // 分页 页数 $page = app()->request('page',1,'intval'); $page = $page ? $page : 1; $limit = app()->request('limit',20,'intval'); $limit = $limit ? $limit : 1; // 指定id $ids = app()->request('mail_id'); $ids = is_array($ids) ? $ids : [$ids]; foreach ($ids as $i=>$d){ if(!is_numeric($d)){ unset($ids[$i]); } } // 附件 $attachment = app()->request('attachment',0,'bool_Val'); // 已读/未读 $seen = app()->request('seen',-1,'intval'); // 软删 $deleted = app()->request('deleted',0,'intval'); $where = ['email_id'=>$this->getEmails('id')]; // 目录 $folder = app()->request('folder','收件箱'); $folderList = db()->all(folderSql::all($where['email_id'])); $folder_id = []; // 文件夹id if($folderList){ foreach ($folderList as $item){ if( // 数组文件夹 (is_array($folder) && in_array($item['folder'],$folder)) || $item['folder'] == $folder ){ $folder_id[] = $item['id']; } } } if(!$folder_id){ app()->e('folder_not_fount'); } //目录 $where['folder_id'] = $folder_id; if($ids) $where['id'] = $ids; if(paramHas('attachment')){ $where['is_file'] = $attachment ? 1 : 0; //附件 } // 软删 $where['deleted'] = $deleted; // 已读/未读 if(paramHas('seen')){ if(in_array($seen,[0,1])){ $where['seen'] = $seen; } } $where['_'] = []; // 搜索关键字 $keyword = app()->request('keyword','',['htmlspecialchars','addslashes']); if($keyword){ $where['_'][] = '`subject` like "%'.$keyword.'%"'; } // 那个发的 $address = app()->request('address'); if($address){ if(is_array($address)){ // 发贱人 if(Verify::sEmail($address['from']??'')){ $where['from'] = $address['from']; } // 收件人 if(Verify::sEmail($address['to']??'')){ $where['_'][] = '`to_name` like "%'.$address.'%"'; } }else if(Verify::sEmail($address)){ // 收件人/发件人 $where['_'][] = '(`from` = "'.$address.'" or `to_name` like "%'.$address.'%")'; } } // from 搜索收件人 if(app()->requestHas('from')){ // 如果是发件箱 if($folder == '发件箱'){ $where['to'] = app()->request('from'); if(!$where['to']){ // 不让查询数据 $where['id'] = 0; } }else{ $where['from'] = app()->request('from'); if(!$where['from']){ // 不让查询数据 $where['id'] = 0; } } } // 回复 if (paramHas('answered')){ $where['answered'] = app()->request('answered',0,'bool_Val')?1:0; } $fromto = app()->request('formorto'); if($fromto=='from'){ $where['from'] = $this->getEmails('email'); }elseif ($fromto=='to'){ $where['from.notin'] = $this->getEmails('email'); } $lists = db()->all( listsSql::lists( dbWhere($where), $page, $limit ) ); // map $lists = array_map(function ($v){ $v['uuid'] = get_email_uuid($v['subject'],$v['udate'],$v['from'],$v['to'],$v['size']); return $v; },$lists); // 总数 $total = db()->count( listsSql::listCount(dbWhere($where)) ); app()->_json(listsPage($lists,$total,$page,$limit)); } /** * 检测邮箱状态 * @author:dc * @time 2023/3/28 16:19 */ public function check(){ $lists = db()->all(emailSql::getValues(['email'=>web_request_emails()],'`id`,`pwd_error`,`email`')); return array_column($lists,'pwd_error','email'); } /** * 发送邮件 * @author:dc * @time 2023/2/18 17:32 */ public function send_mail(){ $email = $this->getEmail(); $yzemail = function(&$value,$field){ if($value){ if(!is_array($value)){ if(@json_decode($value,true)){ $value = json_decode($value,true); }else{ $value = [['email'=>$value,'name'=>'']]; } } foreach ($value as $item){ if(!Verify::sEmail($item['email'])){ app()->e([$field.'_verify_error',$item['email']]); } } } }; $formData = Verify::checks([ 'nickname|'.__('nickname') => ['max'=>50], 'subject|'.__('subject') => ['required','max'=>250], 'body|'.__('body_email') => ['required'], 'tos|'.__('to_email') => ['required',$yzemail], 'cc|'.__('to_cc') => [$yzemail], 'bcc|'.__('to_bcc') => [$yzemail], 'priority|'.__('priority_email') => ['in'=>[1,3,5]], 'attachment|'.__('files_email') => [ 'file'=>[ 'ext' => [], 'size' => 1024*1024*5, 'mine' => [] ] ], 'receipt|'.__('receipt_email') => [] ],[ ]); $sendData = []; $sendData['email'] = $email['email']; $sendData['nickname'] = $formData['nickname']??''; $sendData['tos'] = $formData['tos']; if(count($sendData['tos'])>100){ app()->e(['tos_number_error',100]); } // 抄送 $sendData['cc'] = []; if(($formData['isCc']??0) && !empty($formData['cc'])){ $sendData['cc'] = $formData['cc']; } if(count($sendData['cc'])>10){ app()->e(['cc_number_error',10]); } // 密送 $sendData['bcc'] = []; if(($formData['isBcc']??0) && !empty($formData['bcc'])){ $sendData['bcc'] = $formData['bcc']; } if(count($sendData['bcc'])>10){ app()->e(['bcc_number_error',10]); } $sendData['reply_to'] = [];//回复到那个邮件 //Attachments 附件 上传的 $sendData['attachment'] = []; $attachment = app()->file('attachment'); if($attachment){ foreach ($attachment as $file){ if($file->move()){ $sendData['attachment'][] = [ 'name' => $file->name, 'filename' => $file->name, 'signName' => $file->saveName, 'path' => $file->savePath.$file->saveName ]; }else{ app()->e(['attachment_upload_error',$file->name]); } } } // 远程路径,云文件 $attachmentUrl = app()->request('attachmentUrl'); if(is_array($attachmentUrl)){ foreach ($attachmentUrl as $file){ $file = is_array($file) ? $file : json_decode($file,true); if(!empty($file['url']) && !empty($file['name'])){ $file = new UploadFile($file['name'],$file['url']); if($file->move()){ $sendData['attachment'][] = [ 'name' => $file->name, 'filename' => $file->name, 'signName' => $file->saveName, 'path' => $file->savePath.$file->saveName ]; }else{ app()->e(['attachment_upload_error',$file->name]); } } } } $sendData['receipt'] = empty($formData['receipt']) ? '' : 1;// 回执,阅读后收回执的邮箱 $sendData['priority'] = $formData['priority']??3;// 是否紧急邮件 $sendData['subject'] = $formData['subject'];// //Content 主题,标题 // 删除script标记 $sendData['body'] = strip_tags_content($formData['body'],'<script>',true); // 不重要的信息 $sendData['jobName'] = $formData['jobName']??'';//任务标题 $sendData['massSuit'] = $formData['massSuit']??0;// 是否是群发单显 // 定时发送时间 $timer = app()->request('timerValue',0); if(is_numeric($timer) && $timer){ $timer = time()+$timer; }else if (is_string($timer)){ $timer = strtotime($timer); }else{ $timer = 0; } // 是否存草稿 if(app()->request('saveType')=='draft'){ // 保存 $draftid = listsSql::saveDraft($sendData,$email,app()->request('draft_id',0,'intval')); // 保存失败 if($draftid){ app()->_json(['draft_id'=>$draftid]); } app()->e('save_draft_error'); } // 定时发送 或者是单条发送 else if((app()->request('timer') && $timer > time()) || $sendData['massSuit']){ if($sendData['massSuit']){ // 每次发送间隔的时间 $sendData['masssuit_interval_send'] = app()->request('masssuit_interval_send'); $sendData['masssuit_interval_send']['start'] = intval($sendData['masssuit_interval_send']['start']??0); $sendData['masssuit_interval_send']['end'] = intval($sendData['masssuit_interval_send']['end']??1); } // 插入任务 $job_id = db()->insert(sendJobsSql::$table,[ 'email_id' => $email['id'], 'maildata' => $sendData, 'title' => $sendData['jobName'] ? : $sendData['subject'], 'total' => count($sendData['tos']), 'send_time' => $timer?:time() ]); if($job_id){ // 返回任务id app()->_json(['job_id'=>$job_id]); } app()->e('send_timer_job_error'); } else{ // 立即发送 $result = MailFun::sendEmail($sendData,$email); if($result[0]){ app()->_json(['messageId' => $result[1]]); } // 错误 app()->e($result[1]); } } /** * 收到前端的同步请求操作 * @author:dc * @time 2023/3/10 10:38 */ public function sync(){ $emails = web_request_emails(); if(empty($emails)){ app()->e('sync_request_param_error'); }else{ // 查询id if(count($emails)===1){ $emails = $emails[0]; } $datas = db()->all(emailSql::getValues(['email'=>$emails],'`id`,`email`,`pwd_error`')); foreach ($datas as $k=>$v){ if(!$v['pwd_error']){ $blacklist = app()->request('blacklist'); if(is_array($blacklist)){ $blacklist = [ 'emails' => $blacklist['emails']??[], 'domain' => $blacklist['domain']??[], ]; // 黑名单,7天过期时间 redis()->set('blacklist:'.$v['id'],$blacklist,86400*7); } // 删除 if(!$blacklist||(empty($blacklist['emails'])&&empty($blacklist['domain']))){ redis()->delete('blacklist:'.$v['id']); } redis()->rPush('sync_email_lists', $v['id']); } $datas[$k]['have_new'] = redis()->getDel('have_new_mail_'.$v['id']); } // 返回成功的参数值 app()->_json($datas); } } /** * 标记为已读 * @throws \Lib\Err * @author:dc * @time 2023/3/17 16:15 */ public function seen_2_unseen(){ $this->setFlags('seen'); } /** * 是否已回复 * @throws \Lib\Err * @author:dc * @time 2023/4/10 16:30 */ public function answered_2_unanswered(){ $this->setFlags('answered'); } /** * 邮件移动 * @author:dc * @time 2023/3/21 11:41 */ public function move(){ $emails = $this->getEmails(); $mail_ids = app()->request('mail_ids'); if(!($mail_ids && is_array($mail_ids))){ app()->e('param_request_error'); } foreach ($mail_ids as $k=>$id){ if(!is_numeric($id)){ unset($mail_ids[$k]); } } // 移动到的文件夹 $to_folder = app()->request('folder'); if(empty($to_folder)){ app()->e('folder_move_error'); } if($to_folder == '草稿箱'){ app()->e('folder_move_to_draft_error'); } if($to_folder == '发件箱'){ app()->e('folder_move_to_send_error'); } $data = db()->all(listsSql::first(dbWhere(['id'=>$mail_ids,'email_id'=>array_column($emails,'id')]),'`id`,`uid`,`email_id`,`folder_id`')); if($data){ // 查询邮箱 $emails = array_column($emails,null,'id'); $uids = []; foreach ($data as $datum){ // 只有草稿箱才没有uid if(!$datum['uid']){ // 删除 if ($to_folder == '回收站'){ // 删除数据,真实删除 db()->delete(listsSql::$table,[ 'id' => $datum['id'] ]); continue; } } if(empty($uids[$datum['email_id']])){ $uids[$datum['email_id']][$datum['folder_id']] = []; } $uids[$datum['email_id']][$datum['folder_id']][] = [ 'uid' => $datum['uid'], 'id' => $datum['id'], ]; } foreach ($uids as $eid=>$arr){ // 查询需要移动的文件夹 $to_origin_folder = db()->first(folderSql::first(['email_id'=>$eid,'folder'=>$to_folder])); if($to_origin_folder){ foreach ($arr as $fid=>$uid){ // 查询目录 $folder = db()->first(folderSql::first($fid)); if($folder){ // 开始远程 $mailInstance = new Mail($emails[$eid]['email'],base64_decode($emails[$eid]['password']),$emails[$eid]['imap']); if($mailInstance->login()){ // TODO:: 这个过程无法保证原子性。没办法 // 先复制 $ret = $mailInstance->move(array_column($uid,'uid'),$folder['origin_folder'],$to_origin_folder['origin_folder']); if($ret){ $uret = db()->update(listsSql::$table,['deleted'=>1],dbWhere(['id'=>array_column($uid,'id')])); } $mailInstance = null; } } $folder = null; } } } } app()->_json([ 'mail_id' => $mail_ids ]); } /** * 远程标签 * @param $d * @throws \Lib\Err * @author:dc * @time 2023/3/21 14:28 */ private function setFlags($d){ $emails = $this->getEmails(); $mail_ids = app()->request('mail_ids'); if(!($mail_ids && is_array($mail_ids))){ app()->e('param_request_error'); } foreach ($mail_ids as $k=>$id){ if(!is_numeric($id)){ unset($mail_ids[$k]); } } // 已读或未读 $fv = (int) app()->request($d); $fv = $fv ? 1 : 0; $data = db()->all(listsSql::first(dbWhere(['id'=>$mail_ids,'email_id'=>array_column($emails,'id')]),'`id`,`uid`,`email_id`,`folder_id`')); if($data){ // 查询邮箱 $emails = array_column($emails,null,'id'); $uids = []; foreach ($data as $datum){ if(empty($uids[$datum['email_id']])){ $uids[$datum['email_id']][$datum['folder_id']] = []; } $uids[$datum['email_id']][$datum['folder_id']][] = [ 'uid' => $datum['uid'], 'id' => $datum['id'], ]; } foreach ($uids as $eid=>$arr){ foreach ($arr as $fid=>$uid){ // 查询目录 $folder = db()->first(folderSql::first($fid)); if($folder){ // 开始远程 $mailInstance = new Mail($emails[$eid]['email'],base64_decode($emails[$eid]['password']),$emails[$eid]['imap']); if($mailInstance->login()){ switch ($d){ // 已读 未读 case 'seen':{ $mailInstance->seen(array_column($uid,'uid'),$folder['origin_folder'],$fv); break; } // 未回复/已回复 case 'answered':{ $mailInstance->answered(array_column($uid,'uid'),$folder['origin_folder'],$fv); break; } // 回收站,已删 未删,软删 // case 'deleted':{ // $mailInstance->recycle(array_column($uid,'uid'),$folder['origin_folder'],$fv); // break; // } } $mailInstance = null; // 更新数据 db()->update(listsSql::$table,[ $d => $fv ],dbWhere([ 'id' => array_column($uid,'id') ])); } } $folder = null; } } } app()->_json([ 'mail_id' => $mail_ids ]); } /** * @author:dc * @time 2023/4/1 9:24 */ public function info(){ $id = app()->request('id',0,'intval'); // 没有,说明没有同步过来 $email = $this->getEmail(); $data = db()->first(listsSql::first(dbWhere(['id'=>$id,'email_id'=>$email['id']]))); if($data){ $sync_num = 0; $data['uuid'] = get_email_uuid($data['subject'],$data['udate'],$data['from'],$data['to'],$data['size']); $data['to_name'] = $data['to_name'] ? json_decode($data['to_name'],true) : []; $data['cc'] = $data['cc'] ? json_decode($data['cc'],true) : []; $data['bcc'] = $data['bcc'] ? json_decode($data['bcc'],true) : []; // 是否再次 重新获取 $reload = app()->request('reload',0,'intval'); if($reload){ // 删除原有数据 db()->delete(bodySql::$table,['lists_id'=>$id]); //同步基础信息 $mail = new Mail($email['email'],base64_decode($email['password']),$email['imap']); if($mail->login()){ $folder = db()->value(folderSql::first(['id'=>$data['folder_id']],'origin_folder')); if($folder){ $mail->client->selectFolder($folder); $mail->syncUidEmail([$data['uid']],$email['id'],$folder,$data['folder_id'],[],[],db()); } } } HOME_INFO_BODY: $body = db()->first(bodySql::first($id)); if($body){ $data['body'] = json_decode($body['text_html'],true); $charset = 'utf-8'; $htmlbody = ''; foreach ($data['body'] as $bd){ // if(!empty($bd['charset'])){ // $charset = $bd['charset']; // } if(($bd['type']??'') == 'text/html'){ $htmlbody = base64_decode($bd['body']); } } foreach ($data['body'] as $bdk=>$bd){ if(!empty($bd['path'])){ $data['body'][$bdk]['name'] = @base64_decode($bd['name']); $data['body'][$bdk]['filename'] = @base64_decode($bd['filename']); // 进行编码转换 会出现未知bug // if($charset && strtolower($charset) != 'utf-8' && strtolower($charset) != 'utf8'){ // $data['body'][$bdk]['name'] = @iconv($charset,'utf-8',$data['body'][$bdk]['name']); // $data['body'][$bdk]['filename'] = @iconv($charset,'utf-8',$data['body'][$bdk]['filename']); // } $data['body'][$bdk]['size'] = 0; $data['body'][$bdk]['url'] = ''; if(is_file($bd['path'])){ // 文件大小 $data['body'][$bdk]['size'] = filesize($bd['path']); // 文件访问地址 $data['body'][$bdk]['url'] = APP_HOST.str_replace(PUBLIC_PATH,'',$bd['path']); } // 验证编码是否有其他编码字符,这里编辑了未知编码 if(!@json_encode($data['body'][$bdk])){ // 抛弃原有的名字,显示已存储到服务器的名字 $data['body'][$bdk]['name'] = $data['body'][$bdk]['signName']; $data['body'][$bdk]['filename'] = $data['body'][$bdk]['signName']; } unset($data['body'][$bdk]['path']); // 内容区是有有cid if ($htmlbody && !empty($bd['content-id'])){ if(!strpos($htmlbody,"\"cid:{$bd['content-id']}\"")){ unset($data['body'][$bdk]['content-id']); } } // 没有html内容,content-id是不可能有的 else if(!$htmlbody){ unset($data['body'][$bdk]['content-id']); } } } return [ 'data' => $data ]; }// 草稿 else if(!$data['uid'] && $data['draft']){ $data['body'] = []; return [ 'data' => $data ]; } // 循环几次 if($data['uid']&&$sync_num < 1){ $mail = new Mail($email['email'],base64_decode($email['password']),$email['imap']); if($mail->login()){ $folder = db()->value(folderSql::first(['id'=>$data['folder_id']],'origin_folder')); if($folder){ $ret = $mail->syncBody($folder,$data['uid'],$id); $sync_num++; if($ret){ goto HOME_INFO_BODY; } } } } } app()->e('mail_body_error'); } }