|
|
<?php
|
|
|
|
|
|
namespace App\Mail;
|
|
|
|
|
|
use App\Mail\Jobs\SendJob;
|
|
|
use App\Mail\lib\Lang;
|
|
|
use App\Mail\lib\MailFun;
|
|
|
use App\Mail\lib\MailParse\Body;
|
|
|
use App\Models\Email;
|
|
|
use App\Models\EmailBody;
|
|
|
use App\Models\EmailContact;
|
|
|
use App\Models\EmailContactGroup;
|
|
|
use App\Models\EmailFolder;
|
|
|
use App\Models\Host;
|
|
|
use App\Models\EmailList;
|
|
|
use App\Models\EmailLog;
|
|
|
use App\Models\EmailSendJob;
|
|
|
use Illuminate\Http\UploadedFile;
|
|
|
use Illuminate\Support\Facades\Config;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
use Illuminate\Support\Facades\Validator;
|
|
|
|
|
|
/**
|
|
|
* 邮件服务
|
|
|
* @time 2022/7/29 15:05
|
|
|
* Class Mail
|
|
|
* @package app\Mail
|
|
|
*/
|
|
|
class Mail {
|
|
|
|
|
|
/**
|
|
|
* @var Mail
|
|
|
*/
|
|
|
private static $Instance;
|
|
|
|
|
|
/**
|
|
|
* 如 imap.qq.com
|
|
|
* @var array
|
|
|
*/
|
|
|
private $host = [
|
|
|
'imap'=>'',
|
|
|
'smtp'=>''
|
|
|
];
|
|
|
|
|
|
/**
|
|
|
* 用户email表的id
|
|
|
* @var int
|
|
|
*/
|
|
|
private $id = 0;
|
|
|
|
|
|
/**
|
|
|
* 邮箱地址
|
|
|
* @var string
|
|
|
*/
|
|
|
private $username = '';
|
|
|
private $nickname = '';
|
|
|
|
|
|
/**
|
|
|
* 密码
|
|
|
* @var string
|
|
|
*/
|
|
|
private $password = '';
|
|
|
|
|
|
/**
|
|
|
* 端口
|
|
|
* @var int 143 993
|
|
|
*/
|
|
|
private $port = 993;
|
|
|
/**
|
|
|
* 协议
|
|
|
* @var string
|
|
|
*/
|
|
|
private $ssl = 'ssl://';
|
|
|
|
|
|
/**
|
|
|
* 用户id
|
|
|
* @var int
|
|
|
*/
|
|
|
private $user_id = 0;
|
|
|
|
|
|
/**
|
|
|
* 附件保存目录
|
|
|
* @var string
|
|
|
*/
|
|
|
private $filePath;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
* imap服务器连接
|
|
|
* @var \App\Http\Mail\lib\client\Imap[]
|
|
|
*/
|
|
|
private $client;
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 连接目录 INBOX
|
|
|
* @var string
|
|
|
*/
|
|
|
private $folder = 'INBOX';
|
|
|
|
|
|
/**
|
|
|
* 目录编号
|
|
|
* @var int
|
|
|
*/
|
|
|
private $folderId = 0;
|
|
|
|
|
|
/**
|
|
|
* @var array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Http\Request|string|null
|
|
|
*/
|
|
|
private $request;
|
|
|
|
|
|
/**
|
|
|
* 所有绑定邮箱的id
|
|
|
* @var array
|
|
|
*/
|
|
|
private $email_ids = [];
|
|
|
|
|
|
|
|
|
private function __construct(){
|
|
|
$this->request = request();
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* @return Mail
|
|
|
*/
|
|
|
public static function getInstance(int $user_id, string $email=''): Mail
|
|
|
{
|
|
|
$key = md5($user_id.$email);
|
|
|
|
|
|
if(empty(self::$Instance[$key])){
|
|
|
$mail = self::$Instance[$key] = new static();
|
|
|
|
|
|
$mail->user_id = $user_id;
|
|
|
|
|
|
// 绑定的所有邮箱id
|
|
|
$mail->email_ids = array_column(Email::_getById($mail->user_id),'id');
|
|
|
|
|
|
if($email){
|
|
|
$data = Email::_first($email);
|
|
|
// 是否存在并绑定了
|
|
|
if($data && in_array($data['id'],$mail->email_ids)){
|
|
|
$mail->id = $data['id'];
|
|
|
$mail->username = $email;
|
|
|
$mail->nickname = $data['email_name']??'';
|
|
|
$mail->password = $data['password'];
|
|
|
// 当前选择的邮箱
|
|
|
$mail->host = [
|
|
|
'imap' => $data['imap'],
|
|
|
'smtp' => $data['smtp'],
|
|
|
];
|
|
|
// 设置目录
|
|
|
$mail->selectFolder($mail->folder);
|
|
|
}else{
|
|
|
throw new \Exception(Lang::__('email_not_bind',$email));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 设置邮件附件地址
|
|
|
self::$Instance[$key]->setFilePath(storage_path('/imap'));
|
|
|
|
|
|
return self::$Instance[$key];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 设置目录
|
|
|
* @param $path
|
|
|
* @time 2022/8/1 15:39
|
|
|
*/
|
|
|
public function setFilePath($path){
|
|
|
$this->filePath = $path;
|
|
|
// if(!is_dir($this->filePath)){
|
|
|
// @mkdir($this->filePath,0775,true);
|
|
|
// }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @return string
|
|
|
*/
|
|
|
public function getFilePath(): string
|
|
|
{
|
|
|
$path = $this->filePath.'/'.$this->username.'/';
|
|
|
// if(is_dir($path)){
|
|
|
// mkdir($path,0775,true);
|
|
|
// }
|
|
|
return $path;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 选择使用的用户
|
|
|
* @param int $user_id
|
|
|
* @param string $email
|
|
|
* @return Mail
|
|
|
* @author:dc
|
|
|
* @time 2022/7/29 17:42
|
|
|
*/
|
|
|
public static function use(int $user_id, string $email=''):Mail {
|
|
|
$mail = self::getInstance($user_id,$email);
|
|
|
|
|
|
|
|
|
return $mail;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 选择文件夹
|
|
|
* @param string|int $folder
|
|
|
* @time 2022/8/4 14:54
|
|
|
*/
|
|
|
public function selectFolder($folder){
|
|
|
if($folder) {
|
|
|
$lists = EmailFolder::_all($this->id, false);
|
|
|
// 找到目标文件的id,pid
|
|
|
foreach ($lists as $list) {
|
|
|
if ($list[is_numeric($folder)?'id':'folder'] == $folder) {
|
|
|
$id = $list['id'];// id
|
|
|
$pid = $list['pid']; // 上级id
|
|
|
$folder = $list['origin_folder'];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 不存在
|
|
|
if(!$lists){
|
|
|
$folder = 'INBOX';
|
|
|
$pid = 0;
|
|
|
// 这个名字是每个邮箱默认的,不可更改
|
|
|
$id = EmailFolder::_insert($this->user_id,$this->id,'INBOX','INBOX');
|
|
|
if($folder != 'INBOX'){
|
|
|
$id = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 是否存
|
|
|
if ($id ?? 0) {
|
|
|
// 拿到上级,成为 dir/dir/dir
|
|
|
if ($pid ?? 0) {
|
|
|
EmailFolder::_firstTree($lists, $pid, $folder,'origin_folder');
|
|
|
}
|
|
|
$this->folderId = $id;
|
|
|
$this->folder = $folder;
|
|
|
$this->folderInfo = EmailFolder::_first($id);
|
|
|
} else {
|
|
|
throw new \Exception(Lang::__('email_folder_not', $folder));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @return \App\Http\Mail\lib\client\Imap
|
|
|
* @throws \Exception
|
|
|
* @time 2022/8/5 17:22
|
|
|
*/
|
|
|
public function client($email=''){
|
|
|
|
|
|
$email = $email ? : $this->username;
|
|
|
|
|
|
if(empty($this->client[$email])){
|
|
|
if($email == $this->username){
|
|
|
$this->login();
|
|
|
}else{
|
|
|
$data = Email::_first($email);
|
|
|
if($data){
|
|
|
$this->loginImap($data['imap'],$data['email'],$data['password']);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return $this->client[$email];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 登录邮箱
|
|
|
* @param string $password
|
|
|
* @param string $imap
|
|
|
* @param string $smtp
|
|
|
* @return bool
|
|
|
* @throws \Exception
|
|
|
* @time 2022/8/5 17:21
|
|
|
*/
|
|
|
public function login(string $password='',string $imap='',string $smtp=''):bool{
|
|
|
// host
|
|
|
if($imap && $this->username){
|
|
|
$this->hostAdd(explode('@',$this->username)[1],$imap,$smtp);
|
|
|
}
|
|
|
|
|
|
if(!$password && !$this->password){
|
|
|
throw new \Exception(Lang::__('password_required'));
|
|
|
}
|
|
|
|
|
|
if($password){
|
|
|
// 如果密码不一致,更新
|
|
|
if($password != $this->password){
|
|
|
Email::_changePwd($this->username, $password);
|
|
|
}
|
|
|
|
|
|
$this->password = $password;
|
|
|
}
|
|
|
|
|
|
if(!$this->hostMy()){
|
|
|
throw new \Exception(Lang::__('login_host'));
|
|
|
}
|
|
|
|
|
|
// imap imap.qq.com
|
|
|
$this->loginImap($this->hostMy('imap'),$this->username,$this->password);
|
|
|
// 密码没验证成功
|
|
|
// if($this->id){
|
|
|
// Email::_update(['id'=>$this->id],['pwd_error'=>1]);
|
|
|
// }
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 公用链接
|
|
|
* @param $host
|
|
|
* @param $email
|
|
|
* @param $password
|
|
|
* @throws \Exception
|
|
|
* @author:dc
|
|
|
* @time 2022/12/7 9:35
|
|
|
*/
|
|
|
public function loginImap($host,$email,$password):void {
|
|
|
|
|
|
$this->client[$email] = new \App\Http\Mail\lib\client\Imap();
|
|
|
// 是否初始成功
|
|
|
$this->client[$email]->login($this->ssl.$host.':'.$this->port,$email,$password);
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 自动路由
|
|
|
* @return \Illuminate\Http\JsonResponse|string|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
|
|
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
|
|
* @throws \Psr\SimpleCache\InvalidArgumentException
|
|
|
* @throws \Throwable
|
|
|
* @author:dc
|
|
|
* @time 2022/8/1 11:07
|
|
|
*/
|
|
|
public function autoRoute(){
|
|
|
$task = $this->request->get('_task');
|
|
|
$action = $this->request->get('_action');
|
|
|
$result = [];
|
|
|
|
|
|
switch ($task){
|
|
|
case 'sync': {
|
|
|
// 同步操作
|
|
|
switch ($action){
|
|
|
case 'list': {
|
|
|
// 同步邮件列表
|
|
|
$msgno = $this->request->post('msgno',[]);
|
|
|
if(is_string($msgno)){
|
|
|
$msgno = explode(',',$msgno);
|
|
|
}
|
|
|
$result = $this->syncEmailList($msgno);
|
|
|
break;
|
|
|
}
|
|
|
// 同步文件夹
|
|
|
case 'folder': {
|
|
|
$folder = $this->request->post('folder');
|
|
|
$result = $this->syncFolder($folder);
|
|
|
break;
|
|
|
}
|
|
|
// 更新最新的
|
|
|
case 'new':{
|
|
|
$result = $this->syncEmailList();
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
// 设置标签,已读,未读,删除等
|
|
|
case 'flags':{
|
|
|
$ids = $this->request->post('ids');
|
|
|
$ids = is_string($ids) ? explode(',',$ids) : $ids;
|
|
|
$ids = is_array($ids) ? $ids : [$ids];
|
|
|
// 获取到邮件的uid
|
|
|
$uids = EmailList::_getUidsByIds($ids,$this->id);
|
|
|
$filed = '';
|
|
|
$value = 0;
|
|
|
$mod = '';
|
|
|
$flags = '';
|
|
|
// 标记操作
|
|
|
switch ($action){
|
|
|
// 标记已读
|
|
|
case 'setSeen':{
|
|
|
$filed = 'seen';
|
|
|
$value = 1;
|
|
|
$flags = 'seen';
|
|
|
$mod = '+';
|
|
|
break;
|
|
|
}
|
|
|
// 标记未读
|
|
|
case 'delSeen':{
|
|
|
$filed = 'seen';
|
|
|
$value = 0;
|
|
|
$flags = 'seen';
|
|
|
$mod = '-';
|
|
|
break;
|
|
|
}
|
|
|
case 'setFlagged':{
|
|
|
$filed = 'flagged';
|
|
|
$value = 1;
|
|
|
$flags = 'flagged';
|
|
|
$mod = '+';
|
|
|
break;
|
|
|
}
|
|
|
case 'delFlagged':{
|
|
|
$filed = 'flagged';
|
|
|
$value = 0;
|
|
|
$flags = 'flagged';
|
|
|
$mod = '-';
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if($this->setflagged($uids,$flags,$mod)){
|
|
|
// 更新标签
|
|
|
if(EmailList::_setFlags(array_keys($uids),$this->id,$filed,$value)){
|
|
|
$result = array_keys($uids);
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
case 'info': {
|
|
|
$id = (int) $this->request->get('id');
|
|
|
// 读取邮件
|
|
|
$result = $this->emailInfo($id);
|
|
|
|
|
|
if(!$result){
|
|
|
$result = new static();
|
|
|
}else{
|
|
|
// 是否是数组
|
|
|
if(!is_array($result)) $result = $result->toArray();
|
|
|
|
|
|
// 缓存一下
|
|
|
\Cache::set('app_email_info_'.$this->user_id.":".$result['id'],$result,3000);
|
|
|
|
|
|
// 渲染视图
|
|
|
$result['body'] = view('admin/email/info',[
|
|
|
'data' => $result
|
|
|
])->render();
|
|
|
// 有邮件编码bug,必须要转
|
|
|
$result['body'] = base64_encode($result['body']);
|
|
|
|
|
|
}
|
|
|
///////////////////////// 这里是测试
|
|
|
// print_r($result['body']['text_html']);exit();
|
|
|
// foreach ($result['body']['text_html'] as $item){
|
|
|
// if(!empty($item['type']) && ($item['type'] == 'text/html' || $item['type'] == 'text/plain')){
|
|
|
// header("content-type:text/html;charset=".($item['charset']??'utf-8'));
|
|
|
// echo $item['body'];
|
|
|
// }
|
|
|
// }
|
|
|
// exit();
|
|
|
///////////////////////
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
case 'list': {
|
|
|
if($this->id){
|
|
|
$eids = [$this->id];
|
|
|
}else{
|
|
|
$eids = array_column(Email::_get($this->user_id),'id');
|
|
|
}
|
|
|
|
|
|
$total = 0;
|
|
|
|
|
|
if($this->folder=='INBOX'){
|
|
|
$fids = EmailFolder::_user_folders($this->email_ids);
|
|
|
// $total = array_sum(array_column($fids,'exsts'));
|
|
|
$fids = array_column($fids,'id');
|
|
|
|
|
|
$unseen = array_sum(array_column($fids,'unseen')); // 未读数量
|
|
|
// $unseen = EmailList::_getUnseenNum($eids); // 未读数量
|
|
|
}else{
|
|
|
$fids = [$this->folderId];
|
|
|
|
|
|
$total = $this->folderInfo['exsts']??0;
|
|
|
}
|
|
|
|
|
|
// 搜索
|
|
|
$search = [];
|
|
|
$search['search'] = $this->request->get('search');
|
|
|
$search['seen'] = $this->request->get('seen');
|
|
|
|
|
|
|
|
|
$result = $this->emailLists($eids,$fids,$total,$search)->toArray();
|
|
|
|
|
|
|
|
|
// if($this->folder == 'INBOX'){
|
|
|
$this->allemail = [];
|
|
|
foreach ($result['data'] as $k=>$datum){
|
|
|
if(!$datum['uid']){
|
|
|
// 邮件
|
|
|
if (empty($this->allemail[$datum['email_id']])){
|
|
|
$this->allemail[$datum['email_id']] = Email::_firstById($datum['email_id']);
|
|
|
}
|
|
|
if(empty($this->allemail[$datum['email_id']])){
|
|
|
continue;
|
|
|
}
|
|
|
// 同步
|
|
|
$id = $this->syncEmailList(
|
|
|
[$datum['msgno']],
|
|
|
$datum['email_id'],
|
|
|
$this->allemail[$datum['email_id']]['email'],
|
|
|
$datum['folder_id'],
|
|
|
$this->folder
|
|
|
);
|
|
|
if($id['ids']){
|
|
|
// 重新获取
|
|
|
$result['data'][$k] = EmailList::_first($id['ids'][0]);
|
|
|
}
|
|
|
}
|
|
|
// 干掉 -snv 和 (Failure)
|
|
|
if(preg_match("/(\-\ssnv)|(\(Failure\))|(\(Delay\))$/",$datum['subject'])){
|
|
|
unset($result['data'][$k]);
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
$result['data'] = array_values($result['data']);
|
|
|
// }
|
|
|
|
|
|
// 未读邮件数量
|
|
|
$result['unseen'] = $unseen??0;
|
|
|
break;
|
|
|
}
|
|
|
case 'mail': {
|
|
|
$result = $this->emails();
|
|
|
break;
|
|
|
}
|
|
|
case 'add': {
|
|
|
$result = $this->emailAdd();
|
|
|
break;
|
|
|
}
|
|
|
case 'folder': {
|
|
|
|
|
|
$email = $this->request->post('email');
|
|
|
if($email){
|
|
|
$ids = Email::_getIds($email);
|
|
|
}else{
|
|
|
$ids = $this->id;
|
|
|
}
|
|
|
|
|
|
$result = $this->folders($ids);
|
|
|
break;
|
|
|
}
|
|
|
case 'contact': {
|
|
|
switch ($action){
|
|
|
case 'info': {
|
|
|
$id = $this->request->get('id');
|
|
|
$result = $this->contactInfo($id);
|
|
|
break;
|
|
|
}
|
|
|
case 'add': {
|
|
|
$result = $this->contactAdd();
|
|
|
break;
|
|
|
}
|
|
|
case 'del': {
|
|
|
$result = $this->contactDel();
|
|
|
break;
|
|
|
}
|
|
|
case 'group':{
|
|
|
$is_contact = $this->request->get('is_contact');
|
|
|
$result = $this->contactGroup($is_contact);
|
|
|
break;
|
|
|
}
|
|
|
case 'group_save': {
|
|
|
$name = $this->request->post('group_name');
|
|
|
$id = $this->request->post('id',0);
|
|
|
$result = $this->contactGroupSave($name,$id);
|
|
|
break;
|
|
|
}
|
|
|
case 'group_del': {
|
|
|
$group_id = (int) $this->request->get('group_id');
|
|
|
$result = $this->contactGroupDel($group_id);
|
|
|
break;
|
|
|
}
|
|
|
default: {
|
|
|
$is_group = $this->request->get('is_group',false);
|
|
|
$result = $this->contact($is_group);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
// 下载附件
|
|
|
case 'download':{
|
|
|
$name = $this->request->get('name');
|
|
|
$originname = $this->request->get('originname');
|
|
|
return $this->download($name,$originname);
|
|
|
}
|
|
|
case 'contact_view':{
|
|
|
return $this->contact_view();
|
|
|
}
|
|
|
// 发送邮件
|
|
|
case 'send_email':{
|
|
|
$data = $this->request->post('to');
|
|
|
|
|
|
// 文件收件人
|
|
|
$to = $this->request->file()['to']['to_email_file']??[];
|
|
|
if($to && $to instanceof UploadedFile){
|
|
|
$data['to'] = ($data['to']??'')."\n".$to->getContent();
|
|
|
}
|
|
|
|
|
|
|
|
|
// 附件
|
|
|
$data['file'] = $this->request->file()['to']['file']??[];
|
|
|
if($data['file']){
|
|
|
|
|
|
$upconfig = [
|
|
|
// 上传文件的大小范围
|
|
|
'size' => [
|
|
|
'max' => 1024 * 1024 * 100, // 100M大了服务器不知道会发生什么
|
|
|
'min' => 1, // 0k
|
|
|
],
|
|
|
// 扩展名
|
|
|
'ext' => [
|
|
|
'xlsx', 'xltx', 'potx', 'ppsx', 'pptx', 'sldx', 'docx', 'dotx', 'xlam', 'xlsb',
|
|
|
'apk', 'doc', 'pdf', 'xls', 'ppt', /*'jar', 'js', 'json', 'rpm',*/
|
|
|
'swf', 'tar', 'zip', 'gif', 'png', 'flv', 'avi', 'ai', 'gz',
|
|
|
'jpg', 'mov', 'mp3', 'mp4', 'txt', 'webm', 'webp',
|
|
|
],
|
|
|
// mime类型
|
|
|
'mime' => [
|
|
|
/*'xlsx' => */'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
|
/*'xltx' => */'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
|
|
/*'potx' => */'application/vnd.openxmlformats-officedocument.presentationml.template',
|
|
|
/*'ppsx' => */'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
|
|
/*'pptx' => */'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
|
/*'sldx' => */'application/vnd.openxmlformats-officedocument.presentationml.slide',
|
|
|
/*'docx' => */'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
|
/*'dotx' => */'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
|
|
/*'xlam' => */'application/vnd.ms-excel.addin.macroEnabled.12',
|
|
|
/*'xlsb' => */'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
|
|
|
/*'apk' => */'application/vnd.android.package-archive',
|
|
|
/*'doc' => */'application/msword',
|
|
|
/*'pdf' => */'application/pdf',
|
|
|
/*'xls' => */'application/vnd.ms-excel',
|
|
|
/*'ppt' => */'application/vnd.ms-powerpoint',
|
|
|
// /*'jar' => */'application/java-archive',
|
|
|
// /*'js' => */'application/javascript',
|
|
|
// /*'json' => */'application/json',
|
|
|
// /*'rpm' => */'application/x-rpm',
|
|
|
/*'swf' => */'application/x-shockwave-flash',
|
|
|
/*'tar' => */'application/x-tar',
|
|
|
/*'zip' => */'application/zip',
|
|
|
/*'gif' => */'image/gif',
|
|
|
/*'png' => */'image/png',
|
|
|
/*'flv' => */'video/x-flv',
|
|
|
/*'avi' => */'video/x-msvideo',
|
|
|
/*'ai' => */'application/postscript',
|
|
|
/*'gz' => */'application/x-gzip',
|
|
|
/*'jpg' => */'image/jpeg',
|
|
|
/*'mov' => */'video/quicktime',
|
|
|
/*'mp3' => */'audio/mpeg',
|
|
|
/*'mp4' => */'video/mp4',
|
|
|
/*'txt' => */'text/plain',
|
|
|
/*'webm' => */'video/webm',
|
|
|
/*'webp' => */'image/webp',
|
|
|
|
|
|
],
|
|
|
// 磁盘
|
|
|
'disk' => 'local2',
|
|
|
// 目录
|
|
|
'path' => '/email/'.$this->username.'/',
|
|
|
];
|
|
|
// 设置配置
|
|
|
Config::set('upload.email_upload',$upconfig);
|
|
|
|
|
|
foreach ($data['file'] as $k=>$file){
|
|
|
if($file instanceof UploadedFile){
|
|
|
$data['file'][$k] = [
|
|
|
'path' => Upload::put($file,'email_upload'),
|
|
|
'origin_name' => $file->getClientOriginalName(),
|
|
|
];
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 保存的目录
|
|
|
$path = config('filesystems.disks.local2.root');
|
|
|
foreach ($data['file'] as &$f){
|
|
|
$f['path'] = realpath($path.'/'.$f['path']);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
if($this->send($data)){
|
|
|
return $this->echoJson(1,'邮件发送成功');
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $this->echoJson($result);
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 自动路由时
|
|
|
* @return array
|
|
|
* @time 2022/8/1 16:28
|
|
|
*/
|
|
|
public function getRoute(){
|
|
|
return [
|
|
|
[
|
|
|
'name' => '同步文件夹',
|
|
|
'route' => '?_task=sync&_action=folder'
|
|
|
],
|
|
|
[
|
|
|
'name' => '同步邮件列表',
|
|
|
'route' => '?_task=sync&_action=list'
|
|
|
],
|
|
|
[
|
|
|
'name' => '邮件详情',
|
|
|
'route' => '?_task=info&id=1972'
|
|
|
],
|
|
|
[
|
|
|
'name' => '邮件列表',
|
|
|
'route' => '?_task=list'
|
|
|
],
|
|
|
[
|
|
|
'name' => '邮箱',
|
|
|
'route' => '?_task=mail'
|
|
|
],
|
|
|
[
|
|
|
'name' => '添加邮箱',
|
|
|
'route' => '?_task=add'
|
|
|
],
|
|
|
[
|
|
|
'name' => '邮箱联系人',
|
|
|
'route' => '?_task=contact'
|
|
|
],
|
|
|
];
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 同步文件夹
|
|
|
* 缺点,远程修改文件名称后,同步回来,就会新建文件夹,会导致邮件混乱,
|
|
|
* 处理方法,删除本地的,重新同步该文件夹下面的所有邮件,虽然不友好,也只能这样做。
|
|
|
* @time 2022/8/1 16:09
|
|
|
*/
|
|
|
public function syncFolder($folder=''){
|
|
|
|
|
|
// 同步单个文件夹
|
|
|
if($folder){
|
|
|
$status = $this->client()->selectFolder($this->folder);
|
|
|
// 更新数量
|
|
|
EmailFolder::_updateNum($this->folderId,$status['EXISTS']??null, $status['UNSEEN']??null);
|
|
|
|
|
|
return EmailFolder::_firstAndEmailId($this->folderId,$this->id);
|
|
|
}
|
|
|
|
|
|
|
|
|
// 读取所有文件夹,未解密
|
|
|
$folders = $this->client()->getFolder();
|
|
|
|
|
|
DB::beginTransaction();
|
|
|
foreach ($folders as $folder){
|
|
|
|
|
|
// 处理子父文件夹
|
|
|
$folder['id'] = explode('/',$folder['folder']);
|
|
|
$folder['name'] = explode('/',$folder['parseFolder']);
|
|
|
$pid = 0;
|
|
|
foreach ($folder['id'] as $k=>$item){
|
|
|
// 插入到数据库
|
|
|
$pid = EmailFolder::_insert(
|
|
|
$this->user_id,
|
|
|
$this->id,
|
|
|
$folder['name'][$k],
|
|
|
$item,
|
|
|
$pid
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
DB::commit();
|
|
|
|
|
|
return EmailFolder::_all($this->id);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 同步当前用户的邮件数量
|
|
|
* @return array
|
|
|
* [surplus_num] 剩余待拉取数量
|
|
|
* [ids] 本次拉取后保存后的id
|
|
|
* @time 2022/8/2 14:06
|
|
|
*/
|
|
|
public function syncEmailList($msgno=[],$use_email_id=0,$use_email='',$use_folder_id=0,$use_folder=''){
|
|
|
|
|
|
$use_email_id = $use_email_id ? : $this->id;
|
|
|
$use_email = $use_email ? : $this->username;
|
|
|
$use_folder_id = $use_folder_id ? : $this->folderId;
|
|
|
$use_folder = $use_folder ? : $this->folder;
|
|
|
|
|
|
// 零时增加时长
|
|
|
// set_time_limit(180);
|
|
|
// 选择文件夹
|
|
|
$status = $this->client($use_email)->selectFolder($use_folder);
|
|
|
if (!isset($status['EXISTS']) || !$status['EXISTS']){
|
|
|
return [
|
|
|
'ids' => [],
|
|
|
'folder' => $status,
|
|
|
'end' => true
|
|
|
];
|
|
|
}
|
|
|
$end = false;
|
|
|
|
|
|
if($msgno){
|
|
|
goto SYNCEMAILLIST;
|
|
|
}
|
|
|
|
|
|
// 最后拉取的时间,如果是第一次
|
|
|
$lastMsgno = EmailList::_lastMsgno($use_email_id, $use_folder_id);
|
|
|
|
|
|
$nu = 20;
|
|
|
if(!$msgno){
|
|
|
if(!$lastMsgno){
|
|
|
$msgno = range(1,$nu);
|
|
|
}else{
|
|
|
$msgno = range($lastMsgno,$lastMsgno+$nu);
|
|
|
|
|
|
if($lastMsgno > $status['EXISTS']){
|
|
|
$msgno = range($status['EXISTS'] > $nu ? $status['EXISTS'] - $nu : 1,$status['EXISTS']);
|
|
|
}
|
|
|
// 一样就不拉新的
|
|
|
if($lastMsgno == $status['EXISTS']){
|
|
|
return [
|
|
|
'ids' => [],
|
|
|
'folder' => $status,
|
|
|
'end' => true,
|
|
|
];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 更新数量
|
|
|
EmailFolder::_updateNum($use_folder_id,$status['EXISTS'], $status['UNSEEN']??null);
|
|
|
|
|
|
// 说明是第一次
|
|
|
if(!$lastMsgno){
|
|
|
$pgnu = 1000;
|
|
|
DB::beginTransaction();
|
|
|
for ($n=0;$n<$status['EXISTS']/$pgnu;$n++){
|
|
|
|
|
|
$i = ($pgnu*$n)+1;
|
|
|
$max = $i+$pgnu;
|
|
|
$max = $max > $status['EXISTS'] ? $status['EXISTS']+1 : $max;
|
|
|
$sql = [];
|
|
|
while ($i<$max){
|
|
|
$sql[] = "(".$use_email_id.",{$i},".$use_folder_id.",1)";
|
|
|
$i++;
|
|
|
}
|
|
|
// bug 使用事务,无法插入数据
|
|
|
$ret = DB::insert("insert INTO email_lists (`email_id`,`msgno`,`folder_id`,`seen`) VALUES ".implode(',',$sql));
|
|
|
if(!$ret){
|
|
|
DB::rollBack();
|
|
|
abort(500,'同步失败');
|
|
|
}
|
|
|
}
|
|
|
DB::commit();
|
|
|
// 最新的数量
|
|
|
$msgno = range($status['EXISTS'] > $nu ? $status['EXISTS'] - $nu : 1,$status['EXISTS']);
|
|
|
// 不同步
|
|
|
return [
|
|
|
'ids' => [1],
|
|
|
'folder' => $status,
|
|
|
'end' => $end,
|
|
|
];
|
|
|
}
|
|
|
SYNCEMAILLIST:
|
|
|
// 是否有id
|
|
|
$dataids = EmailList::_getIdsByMsgno($use_email_id,$use_folder_id,$msgno);
|
|
|
|
|
|
$this->client($use_email)->debug(false,storage_path('logs'));
|
|
|
// 循环
|
|
|
$results = $this->client($use_email)->fetchHeader($msgno);
|
|
|
|
|
|
if($results){
|
|
|
DB::beginTransaction();
|
|
|
// 批量插入
|
|
|
foreach ($results as $key=>$result){
|
|
|
if($key == $status['EXISTS']){
|
|
|
$end = true;
|
|
|
}
|
|
|
// $header = $this->client($use_email)->getHeader($result['RFC822.HEADER']??'');
|
|
|
$header = &$result['HEADER.FIELDS'];
|
|
|
|
|
|
foreach ($result['FLAGS'] as $k=>$FLAG){
|
|
|
$result['FLAGS'][$k] = strtolower(str_replace('\\','',$FLAG));
|
|
|
}
|
|
|
try {
|
|
|
|
|
|
$file_header = &$result['BODYSTRUCTURE'];
|
|
|
|
|
|
// 没有收件人
|
|
|
if(!empty($header['To'])){
|
|
|
$header['To'] = MailFun::toOrFrom($header['To']);
|
|
|
}else{
|
|
|
$header['To'] = [];
|
|
|
}
|
|
|
|
|
|
|
|
|
$header['From'] = MailFun::toOrFrom($header['From']);
|
|
|
|
|
|
$data = [
|
|
|
'id' => $dataids[$key]??0,
|
|
|
'msgno' => $key,
|
|
|
'uid' => $result['UID'],
|
|
|
'subject' => $header['Subject'],
|
|
|
// 'cc' => $header['Cc']??'',
|
|
|
'from' => $header['From'][0]['email']??'',
|
|
|
'from_name' => $header['From'][0]['name']??'',
|
|
|
'to' => $header['To']?implode(',',array_column($header['To'],'email')):'',
|
|
|
'to_name' => json_encode($header['To']),
|
|
|
'date' => isset($header['Date'])&&$header['Date'] ? strtotime(is_array($header['Date']) ? $header['Date'][0] : $header['Date']) : strtotime($result['INTERNALDATE']),
|
|
|
'message_id' => $header['Message-ID']??'',
|
|
|
'udate' => strtotime($result['INTERNALDATE']),
|
|
|
// 'size' => $result['RFC822.SIZE'],
|
|
|
'recent' => in_array('recent',$result['FLAGS']),
|
|
|
'seen' => in_array('seen',$result['FLAGS']),
|
|
|
'draft' => in_array('draft',$result['FLAGS']),
|
|
|
'flagged' => in_array('flagged',$result['FLAGS']),
|
|
|
'answered' => in_array('answered',$result['FLAGS']),
|
|
|
'folder_id' => $use_folder_id,
|
|
|
'email_id' => $use_email_id,
|
|
|
'uuid' => $use_email_id.$use_folder_id.$result['UID'],
|
|
|
'is_file' => MailFun::isFile($file_header[$key]['BODYSTRUCTURE']??[]) //是否附件
|
|
|
];
|
|
|
}catch (\Throwable $e){
|
|
|
Log::error('邮件解析失败:'.$e->getMessage().print_r($result,true));
|
|
|
unset($results[$key]);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
$results[$key] = $data;
|
|
|
}
|
|
|
$ids = EmailList::_insertAll(array_values($results));
|
|
|
// 提交
|
|
|
DB::commit();
|
|
|
}else{
|
|
|
$end = true;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
'ids' => $ids??[],
|
|
|
'folder' => $status,
|
|
|
'end' => $end,
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 同步body
|
|
|
* @param int $uid
|
|
|
* @param int $id
|
|
|
* @return mixed
|
|
|
* @time 2022/8/2 15:11
|
|
|
*/
|
|
|
public function syncEMailBody(int $uid, $id = 0 ){
|
|
|
|
|
|
// $this->client()->debug(true,storage_path('logs'));
|
|
|
|
|
|
$this->client()->selectFolder($this->folder);
|
|
|
$body = $this->client()->fetch([$uid],'body',true);
|
|
|
|
|
|
/******* start ********/
|
|
|
// 记录原始数据,方便分析
|
|
|
$path = storage_path('logs/email/'.$this->username.'/');
|
|
|
if(!is_dir($path)){
|
|
|
mkdir($path,0775,true);
|
|
|
}
|
|
|
file_put_contents($path.$id.'.'.time().'.log',print_r($body,true));
|
|
|
/******** end *******/
|
|
|
|
|
|
if($body && $id){
|
|
|
$body = array_values($body);
|
|
|
|
|
|
$body = (new Body($body[0]['RFC822.TEXT'],$this->getFilePath()))->getItem();
|
|
|
|
|
|
|
|
|
EmailBody::_insert(
|
|
|
$id,
|
|
|
$body
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return EmailBody::_first($id);
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 设置标记
|
|
|
* @param array $uids
|
|
|
* @param string $flags
|
|
|
* @param string $mod +|-
|
|
|
* @return bool
|
|
|
* @throws \Exception
|
|
|
* @author:dc
|
|
|
* @time 2022/10/27 14:22
|
|
|
*/
|
|
|
public function setflagged(array $uids,string $flags,string $mod){
|
|
|
// 选择目录
|
|
|
$status = $this->client()->selectFolder($this->folder);
|
|
|
|
|
|
return $this->client()->flags($uids,[$flags],$mod,true);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 设置为已读
|
|
|
* @param int|array $uids
|
|
|
* @return bool
|
|
|
* @throws \Exception
|
|
|
* @author:dc
|
|
|
* @time 2022/10/26 17:08
|
|
|
*/
|
|
|
public function setSeen($uids):bool{
|
|
|
// 选择目录
|
|
|
$status = $this->client()->selectFolder($this->folder);
|
|
|
|
|
|
return $this->client()->flags($uids,[\App\Http\Mail\lib\client\Imap::FLAGS_SEEN],'+',true);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 设置为未读
|
|
|
* @param $uids
|
|
|
* @return bool
|
|
|
* @throws \Exception
|
|
|
* @author:dc
|
|
|
* @time 2022/10/26 17:11
|
|
|
*/
|
|
|
public function delSeen($uids):bool{
|
|
|
// 选择目录
|
|
|
$status = $this->client()->selectFolder($this->folder);
|
|
|
|
|
|
return $this->client()->flags($uids,[\App\Http\Mail\lib\client\Imap::FLAGS_SEEN],'-',true);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 邮件列表
|
|
|
* @param array $mail_id
|
|
|
* @param array $folder
|
|
|
* @param int $total
|
|
|
* @return mixed|object
|
|
|
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
|
|
* @time 2022/8/16 17:34
|
|
|
*/
|
|
|
public function emailLists($mail_id = [], $folder = [], $total = 0, $search = []){
|
|
|
|
|
|
return EmailList::_paginate(function ($query) use ($mail_id, $folder,$search){
|
|
|
$query->whereIn('email_id',$mail_id)->whereIn('folder_id',$folder);
|
|
|
// 搜索
|
|
|
if(!empty($search['search'])){
|
|
|
$search = htmlspecialchars($search);
|
|
|
$query->where('subject','like',"%{$search}%");
|
|
|
}
|
|
|
// 已读
|
|
|
if(!empty($search['seen']) && $search['seen']){
|
|
|
$query->where('seen',0);
|
|
|
}
|
|
|
// 过滤
|
|
|
$query->where([
|
|
|
['subject','not like','%- snv'],
|
|
|
['subject','not like','%(Failure)'],
|
|
|
['subject','not like','%(Delay)']
|
|
|
]);
|
|
|
},20,$total);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 读取邮件详情
|
|
|
* @param $id
|
|
|
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
|
|
|
* @time 2022/8/2 15:13
|
|
|
*/
|
|
|
public function emailInfo($id){
|
|
|
$email = EmailList::_firstWithBody($id);
|
|
|
// 是否存在,并且是否属于自己
|
|
|
if($email && in_array($email->email_id,$this->email_ids)){
|
|
|
if(!$email->body && $email['uid']){
|
|
|
$email = $email->toArray();
|
|
|
try {
|
|
|
$email['body'] = $this->syncEMailBody($email['uid'],$email['id']);
|
|
|
}catch (\Throwable $e){
|
|
|
$email['body'] = $this->syncEMailBody($email['uid'],$email['id']);
|
|
|
}
|
|
|
}
|
|
|
return $email;
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 获取邮件服务器
|
|
|
* @param string $suffix
|
|
|
* @return array
|
|
|
* @time 2022/7/29 16:24
|
|
|
*/
|
|
|
public function host(string $suffix=''):array {
|
|
|
if($suffix){
|
|
|
return EmailHost::_get($suffix);
|
|
|
}
|
|
|
return EmailHost::_all();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取自己的服务器地址
|
|
|
* @param null $name
|
|
|
* @return array|mixed|string
|
|
|
* @time 2022/8/1 15:46
|
|
|
*/
|
|
|
public function hostMy($name=null) {
|
|
|
|
|
|
// if(!$this->host){
|
|
|
// $data = Email::_first($this->id);
|
|
|
// $this->host = [
|
|
|
// 'imap' => $data['imap'],
|
|
|
// 'smtp' => $data['smtp'],
|
|
|
// ];
|
|
|
// }
|
|
|
|
|
|
if($name){
|
|
|
return $this->host[$name]??'';
|
|
|
}
|
|
|
|
|
|
return $this->host;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 添加邮件服务器
|
|
|
* @param string $suffix
|
|
|
* @param string $imap
|
|
|
* @param string $smtp
|
|
|
* @return int email host 表的id
|
|
|
* @time 2022/7/29 16:28
|
|
|
*/
|
|
|
public function hostAdd(string $suffix, string $imap, string $smtp):int{
|
|
|
return EmailHost::_insert($suffix, $imap, $smtp);
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 添加一个邮箱地址
|
|
|
* @time 2022/7/29 16:51
|
|
|
*/
|
|
|
public function emailAdd($data=[]){
|
|
|
$data = $data ? $data : $this->request->post();
|
|
|
if(!$data){
|
|
|
throw new \Exception(Lang::__('empty_form'));
|
|
|
}
|
|
|
|
|
|
$validator = Validator::make($data,[
|
|
|
'email' => ['required','email'],
|
|
|
'password' => ['required'],
|
|
|
],[
|
|
|
'email.required' => 'email_required',
|
|
|
'email.email' => 'email_validator',
|
|
|
'password.required' => 'password_required',
|
|
|
]);
|
|
|
|
|
|
if($validator->fails()){
|
|
|
throw new \Exception(Lang::__($validator->errors()->first()));
|
|
|
}
|
|
|
$data['email'] = trim($data['email']);
|
|
|
$data['password'] = trim($data['password']);
|
|
|
$data['email_name'] = trim($data['email_name']??$data['email']);
|
|
|
|
|
|
// 检查imap服务器
|
|
|
if($data['host']??''){
|
|
|
$host = [
|
|
|
'imap' => trim($data['host']),
|
|
|
'smtp' => trim($data['smtp']??'')
|
|
|
];
|
|
|
}else{
|
|
|
// 获取数据库中有的
|
|
|
$host = $this->host(explode('@',$data['email'])[1]);
|
|
|
}
|
|
|
|
|
|
if(!$host){
|
|
|
// 是否存在imap服务器
|
|
|
throw new \Exception(Lang::__('host_required'));
|
|
|
}
|
|
|
|
|
|
// 登录名
|
|
|
$this->username = $data['email'];
|
|
|
|
|
|
// 进行远程登录
|
|
|
// imap imap.qq.com
|
|
|
$imap = new \App\Http\Mail\lib\client\Imap();
|
|
|
$imap->login($this->ssl.$host['imap'].':'.$this->port,$data['email'],$data['password']);
|
|
|
|
|
|
// 添加邮箱,并绑定user_id
|
|
|
$model = Email::_add($this->user_id, $data['email'], $data['password'], $data['email_name'],$host);
|
|
|
|
|
|
try {
|
|
|
// 添加一个联系人的默认分组
|
|
|
$this->contactGroupSave('默认分组');
|
|
|
}catch (\Throwable $e){
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
// 退出imap登录
|
|
|
$imap->loginOut();
|
|
|
|
|
|
return $model;
|
|
|
|
|
|
// throw new \Exception(Lang::__('email_insert_error'));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 读取所有邮箱
|
|
|
* @return array
|
|
|
* @time 2022/8/1 9:34
|
|
|
*/
|
|
|
public function emails():array {
|
|
|
return Email::_get($this->user_id);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 文件夹
|
|
|
* @return array
|
|
|
* @time 2022/8/4 17:34
|
|
|
*/
|
|
|
public function folders($id){
|
|
|
|
|
|
$result = EmailFolder::_all($id);
|
|
|
if(!$result){
|
|
|
$result = $this->syncFolder();
|
|
|
}
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 联系人列表
|
|
|
* @return mixed
|
|
|
* @time 2022/8/4 9:05
|
|
|
*/
|
|
|
public function contact($is_group=false){
|
|
|
|
|
|
return EmailContact::_all($this->id,$is_group);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 联系人管理视图
|
|
|
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
|
|
* @author:dc
|
|
|
* @time 2022/11/2 16:37
|
|
|
*/
|
|
|
public function contact_view(){
|
|
|
|
|
|
|
|
|
return view('admin/email/contacts',[
|
|
|
// 'emails' => $this->emails(),
|
|
|
'email' => $this->username,
|
|
|
// 'groups' => $this->contactGroup(),
|
|
|
// 'contacts' => $this->contact()
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 添加联系人
|
|
|
* @return EmailContact
|
|
|
* @throws \Exception
|
|
|
* @time 2022/8/4 9:46
|
|
|
*/
|
|
|
public function contactAdd(){
|
|
|
|
|
|
$data = $this->request->post();
|
|
|
|
|
|
$validator = Validator::make($data,[
|
|
|
'group_id' => ['required'],
|
|
|
'email' => ['required','email'],
|
|
|
'email_name' => ['required','max:100'],
|
|
|
'remark' => ['max:200'],
|
|
|
],[
|
|
|
'group_id.required' => 'contact_group_exists',
|
|
|
'email.required' => 'contact_email_required',
|
|
|
'email.email' => 'contact_email_error',
|
|
|
'email_name.required' => 'contact_email_name_required',
|
|
|
'email_name.max' => 'contact_email_name_max',
|
|
|
'remark.max' => 'contact_remark_max',
|
|
|
]);
|
|
|
// 验证数据
|
|
|
if($validator->fails()){
|
|
|
|
|
|
$p = '';
|
|
|
|
|
|
switch ($validator->errors()->first()){
|
|
|
case 'contact_email_name_max': $p = '100';break;
|
|
|
case 'contact_remark_max': $p = '200';break;
|
|
|
}
|
|
|
|
|
|
throw new \Exception(
|
|
|
Lang::__(
|
|
|
$validator->errors()->first(),
|
|
|
$p
|
|
|
)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
$group = EmailContactGroup::_first($data['group_id']);
|
|
|
if(!$group || $group['email_id']!=$this->id){
|
|
|
abort(600,Lang::__('contact_group_exists'));
|
|
|
}
|
|
|
|
|
|
|
|
|
return EmailContact::_save($this->id,$data);
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 联系人详情
|
|
|
* @param $id
|
|
|
* @return array
|
|
|
* @author:dc
|
|
|
* @time 2022/11/4 17:02
|
|
|
*/
|
|
|
public function contactInfo($id){
|
|
|
return EmailContact::_first($this->id,$id);
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 删除联系人
|
|
|
* @time 2022/8/4 9:52
|
|
|
*/
|
|
|
public function contactDel(){
|
|
|
$contact_id = (int) $this->request->get('contact_id');
|
|
|
if(!$contact_id || EmailContact::_del($this->id,$contact_id)){
|
|
|
abort(600,Lang::__('del_error'));
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 联系人分组
|
|
|
* @return mixed
|
|
|
* @time 2022/8/4 10:13
|
|
|
*/
|
|
|
public function contactGroup($is_contact=false){
|
|
|
return EmailContactGroup::_all($this->id,$is_contact);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 添加联系人分组
|
|
|
* @return EmailContactGroup
|
|
|
* @throws \Exception
|
|
|
* @time 2022/8/4 10:47
|
|
|
*/
|
|
|
public function contactGroupSave($name,$id=0){
|
|
|
if(!$name || mb_strlen($name)>100){
|
|
|
throw new \Exception(Lang::__('contact_group_name_error',1,100));
|
|
|
}
|
|
|
|
|
|
$name = htmlspecialchars($name);
|
|
|
|
|
|
$data = EmailContactGroup::_firstByName($this->id,$name);
|
|
|
if(($data && !$id) || ($data && $id && $data['id'] != $id)){
|
|
|
throw new \Exception(Lang::__('contact_group_name_unique'));
|
|
|
}
|
|
|
|
|
|
return EmailContactGroup::_save($this->id,$name,$id);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 删除
|
|
|
* @return false|mixed
|
|
|
* @time 2022/8/4 10:49
|
|
|
*/
|
|
|
public function contactGroupDel($group_id){
|
|
|
|
|
|
if(!$group_id){
|
|
|
GROUP_DEL_ABORT:
|
|
|
abort(600,Lang::__('contact_group_del_id'));
|
|
|
}
|
|
|
|
|
|
// 是否有联系人在其中
|
|
|
if(EmailContact::_count($group_id, $this->id)){
|
|
|
abort(600,Lang::__('contact_group_del_contact'));
|
|
|
}
|
|
|
|
|
|
// 删除
|
|
|
if(!EmailContactGroup::_del($group_id, $this->id)){
|
|
|
goto GROUP_DEL_ABORT;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 发送邮件
|
|
|
* @time 2022/8/3 16:08
|
|
|
*/
|
|
|
public function send($data=[]){
|
|
|
|
|
|
$data = $data ? $data : $this->request->post();
|
|
|
|
|
|
$data['is_text'] = intval($data['is_text']??0);//纯文本
|
|
|
// $data['save_send'] = intval($data['save_send']??0);//保存到已发送
|
|
|
$data['priority'] = intval($data['priority']??0);//紧急
|
|
|
$data['receipt'] = intval($data['receipt']??0);//需要回执
|
|
|
$data['encrypt'] = intval($data['encrypt']??0);//加密邮件
|
|
|
|
|
|
|
|
|
|
|
|
// 是纯文本还是html
|
|
|
$data['body'] = $data['is_text'] ? ($data['text']??'') : ($data['html']??'');
|
|
|
|
|
|
// 是否是回复邮件
|
|
|
if(!empty($data['reply']['id']) && !empty($data['reply']['email_id'])){
|
|
|
|
|
|
// 标题,主题
|
|
|
if(empty($data['subject'])){
|
|
|
throw new \Exception(Lang::__('send_email_subject_error',500));
|
|
|
}
|
|
|
|
|
|
// 查询
|
|
|
$originData = EmailList::_first($data['reply']['id']);
|
|
|
// 是否存在邮件
|
|
|
if($originData && $originData['email_id'] == $data['reply']['email_id'] && in_array($originData['email_id'],$this->email_ids)){
|
|
|
$emaildata = Email::_firstById($originData['email_id']);
|
|
|
if(!$emaildata){
|
|
|
throw new \Exception('发件人异常',600);
|
|
|
}
|
|
|
try {
|
|
|
// 立刻发送邮件
|
|
|
MailFun::sendEmail(
|
|
|
$emaildata['smtp'],$emaildata['email'],$emaildata['password'],
|
|
|
$emaildata['email_name'],['email'=>$originData['from'],'name'=>$originData['from_name']]
|
|
|
,$data['subject'],$data['body'],$data['file']??[],$data['receipt'],$data['priority']?1:3
|
|
|
);
|
|
|
// 记录下
|
|
|
EmailLog::error(print_r([
|
|
|
'manage_id'=>the_manage('id'),
|
|
|
'data' => $data,
|
|
|
'回复邮件'
|
|
|
],true));
|
|
|
return true;
|
|
|
}catch (\Throwable $e){
|
|
|
throw new \Exception($e->getMessage());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
// 收件人
|
|
|
if(empty($data['to'])){
|
|
|
SEND_EMAIL_TO_ERROR:
|
|
|
throw new \Exception(Lang::__('send_email_to_error'));
|
|
|
}
|
|
|
// 是否是数组
|
|
|
$data['to'] = explode("\n",trim($data['to']));
|
|
|
if(!$data['to']){
|
|
|
goto SEND_EMAIL_TO_ERROR;
|
|
|
}
|
|
|
// $to = [
|
|
|
// 'email' => 'xxx@qq.com',
|
|
|
// 'name' => 'xxx'
|
|
|
// ];
|
|
|
foreach ($data['to'] as $k=>$to){
|
|
|
$to = trim($to);
|
|
|
$data['to'][$to] = [];
|
|
|
$data['to'][$to]['email'] = $to;
|
|
|
$data['to'][$to]['name'] = explode('@',$to)[0];
|
|
|
|
|
|
// 是否是邮箱
|
|
|
if(!preg_match('/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/',$data['to'][$to]['email'])){
|
|
|
// throw new \Exception(Lang::__('send_email_to_error',$data['to'][$to]['email']));
|
|
|
unset($data['to'][$to]);
|
|
|
}
|
|
|
|
|
|
unset($data['to'][$k]);
|
|
|
}
|
|
|
|
|
|
$data['to'] = array_values($data['to']);
|
|
|
if(!$data['to']){
|
|
|
goto SEND_EMAIL_TO_ERROR;
|
|
|
}
|
|
|
|
|
|
// 时间
|
|
|
if($data['send_time'] != 'now'){
|
|
|
$data['send_time'] = explode(',',$data['send_time']);
|
|
|
if($data['send_time'][0] < 24 && $data['send_time'][1] < 24){
|
|
|
$data['send_time'] = implode(',',$data['send_time']);
|
|
|
}else{
|
|
|
$data['send_time'] = '21,11';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$tags = get('tags');
|
|
|
if (!$tags || !is_array($tags)){
|
|
|
throw new \Exception('请选择tag标签');
|
|
|
}
|
|
|
|
|
|
|
|
|
// 加入任务
|
|
|
$job_id = EmailSendJob::_insert([
|
|
|
'title' => $data['subject']??'',
|
|
|
'to' => $data['to'],
|
|
|
'the_manage_id' => the_manage('id'),
|
|
|
'email_id' => $this->id,
|
|
|
'user_id' => $this->user_id,
|
|
|
'data' => $data,
|
|
|
'send_time' => $data['send_time'],
|
|
|
'tags' => $tags
|
|
|
]);
|
|
|
if($job_id){
|
|
|
// 发送一个任务
|
|
|
SendJob::dispatch($job_id);
|
|
|
return $job_id;
|
|
|
}
|
|
|
|
|
|
throw new \Exception(Lang::__('email_send_error'));
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 下载附件
|
|
|
* @param $name
|
|
|
* @param string $originname
|
|
|
* @return string|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
|
|
* @author:dc
|
|
|
* @time 2022/11/2 10:59
|
|
|
*/
|
|
|
public function download($name,$originname=''){
|
|
|
$file = $this->getFilePath().$name;
|
|
|
if(is_file($file)){
|
|
|
return response()->download($file,$originname ? : $name);
|
|
|
}
|
|
|
return '';
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
* @param mixed $data
|
|
|
* @param string $message
|
|
|
* @param int $status
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
* @author:dc
|
|
|
* @time 2022/8/1 11:05
|
|
|
*/
|
|
|
private function echoJson($data='', $message='', $status=200):\Illuminate\Http\JsonResponse{
|
|
|
$data = [
|
|
|
'data' => $data,
|
|
|
'message' => $message,
|
|
|
'status' => $status
|
|
|
];
|
|
|
return response()->json($data,200,[]);
|
|
|
}
|
|
|
|
|
|
} |
...
|
...
|
|