<?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'); } /** * 不查询哪些发件人的邮件 */ $form_not_in = app()->request('from_not_in'); if($form_not_in){ $form_not_in = is_array($form_not_in) ? $form_not_in : [$form_not_in]; $form_not_in = array_filter($form_not_in,function ($v){ if(is_string($v) && Verify::sEmail($v)){ return true; } return false; }); if($form_not_in){ if(isset($where['from.notin'])){ $where['from.notin'] = array_merge($where['from.notin'],$form_not_in); }else{ $where['from.notin'] = $form_not_in; } } } if(!empty($where['from.notin'])){ $where['from.notin'] = array_unique($where['from.notin']); } $lists = db()->all( listsSql::lists( dbWhere($where), $page, $limit ) ); $lists = $lists ? $lists : []; // map $lists = array_map(function ($v){ $v['uuid'] = get_email_uuid($v['subject'],$v['udate'],$v['from'],$v['to'],$v['size']); if(!empty($v['description'])){ $v['description'] = @html_entity_decode($v['description'], ENT_COMPAT, 'UTF-8'); } $v['to_name'] = @json_decode($v['to_name'],true); $v['to_name'] = $v['to_name']?:[]; if($v['to_name']){ if(!empty($v['to_name'][0]['email'])){ $v['to'] = $v['to_name'][0]['email']; } $v['to_name'] = $v['to_name'][0]['name']??''; } if(is_array($v['to_name'])){ $v['to_name'] = ''; } 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`,`password`,`imap`')); foreach ($lists as $k=>$list){ if(!$list['pwd_error']){ $mail = new \Lib\Mail\Mail($list['email'],base64_decode($list['password']),$list['imap']); // 2次登录请求 if($mail->login()!==1){ if($mail->login()!==1){ $lists[$k]['pwd_error'] = 1; } } $mail = null; } } 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'=>500], '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(!empty($formData['aicc-hot'])){ $sendData['mail-header'] = [ 'Aicc-Hot-Mail' => 'hot' // 预热邮件 ]; } 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']); // 计算 // $folders = db()->all(folderSql::all($v['id'],'`id`')); // foreach ($folders as $folder){ // $w = [ // 'email_id' => $v['id'], // 'folder_id' => $folder['id'], // ]; // db()->update(folderSql::$table,[ // 'exsts' => db()->count(listsSql::listCount(dbWhere($w))), // 'unseen' => db()->count(listsSql::listCount(dbWhere(array_merge($w,['seen'=>0])))), // ],'`id` = '.$folder['id'],false); // } } // 返回成功的参数值 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'); } /** * 星标 * @throws \Lib\Err * @author:dc * @time 2024/6/21 16:35 */ public function star_2_unstar(){ $this->setFlags('flagged'); } /** * 邮件移动 * @author:dc * @time 2023/3/21 11:41 */ public function move(){ $this->moveCopy(function (Mail $mailInstance,$uid,$origin_folder,$to_origin_folder){ // try { // return $mailInstance->move($uid,$origin_folder,$to_origin_folder); // }catch (\Throwable $e){ // if(app()->request('move_err_copy',1)){ // 复制成功 if($mailInstance->copy($uid,$origin_folder,$to_origin_folder)){ return $mailInstance->deleted($uid,$origin_folder); } // } // } return false; }); } private function moveCopy(\Closure $call){ $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()==1){ $ret = $call($mailInstance,array_column($uid,'uid'),$folder['origin_folder'],$to_origin_folder['origin_folder']); // TODO:: 这个过程无法保证原子性。没办法 // 先复制 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 ]); } /** * 复制邮件 * @throws \Lib\Err * @author:dc * @time 2024/3/9 13:50 */ public function copy(){ $this->moveCopy(function (Mail $mailInstance,$uid,$origin_folder,$to_origin_folder){ return $mailInstance->copy($uid,$origin_folder,$to_origin_folder); }); } /** * 执行清空邮件操作 * @throws \Lib\Err * @author:dc * @time 2024/3/14 14:18 */ public function expunge(){ $email = $this->getEmail(); $mailInstance = new Mail($email['email'],base64_decode($email['password']),$email['imap']); if($mailInstance->login()==1 && $mailInstance->expunge()){ app()->_json([]); } app()->e('执行失败'); } /** * 远程标签 * @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){ $folder = app()->request('folder','收件箱'); // 查询 当前的 文件夹 如果有选中文件夹 就 查询出 选中文件夹的id $fids = db()->all(folderSql::all(array_column($emails,'id'),'`id`,`folder`')); foreach ($fids as $fk=>$fid){ if($fid['folder'] != $folder){ unset($fids[$fk]); } } if($fids){ // 查询要标记的 邮件id $mail_ids = db()->all(listsSql::all(dbWhere(['folder_id'=>array_column($fids,'id'),'seen'=>0]),'`id`')); $mail_ids = array_column($mail_ids,'id'); } } if($mail_ids){ $mail_ids = is_array($mail_ids) ? $mail_ids : explode(',',$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::all(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()==1){ 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 'flagged':{ $mailInstance->flagged(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['description'] = @html_entity_decode($data['description']??'', ENT_COMPAT, 'UTF-8'); $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()==1){ $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()==1){ $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; } } } } logs('超过读取body次数 '.$data['id']); if(isset($mail)){ if(!$mail->client->hasMail($data['uid'])){ app()->e('mail_not'); } } }else{ logs('读取body 没有查询到数据 '.$id.'-'.($email['email']??'')); } app()->e('mail_body_error'); } /** * 解析订阅数据 * @author:dc * @time 2024/8/7 14:12 */ public function desubscribe(){ $key = app()->request('key',''); app()->_json(MailFun::deSubscribeUrl($key)); } }