作者 邓超

xxx

要显示太多修改。

为保证性能只显示 30 of 30+ 个文件。

  1 +root = true
  2 +
  3 +[*]
  4 +charset = utf-8
  5 +end_of_line = lf
  6 +indent_size = 4
  7 +indent_style = space
  8 +insert_final_newline = true
  9 +trim_trailing_whitespace = true
  10 +
  11 +[*.md]
  12 +trim_trailing_whitespace = false
  13 +
  14 +[*.{yml,yaml}]
  15 +indent_size = 2
  16 +
  17 +[docker-compose.yml]
  18 +indent_size = 4
  1 +APP_NAME=Laravel
  2 +APP_ENV=local
  3 +APP_KEY=
  4 +APP_DEBUG=true
  5 +APP_URL=http://localhost
  6 +
  7 +LOG_CHANNEL=stack
  8 +LOG_DEPRECATIONS_CHANNEL=null
  9 +LOG_LEVEL=debug
  10 +
  11 +DB_CONNECTION=mysql
  12 +DB_HOST=127.0.0.1
  13 +DB_PORT=3306
  14 +DB_DATABASE=laravel
  15 +DB_USERNAME=root
  16 +DB_PASSWORD=
  17 +
  18 +BROADCAST_DRIVER=log
  19 +CACHE_DRIVER=file
  20 +FILESYSTEM_DISK=local
  21 +QUEUE_CONNECTION=sync
  22 +SESSION_DRIVER=file
  23 +SESSION_LIFETIME=120
  24 +
  25 +MEMCACHED_HOST=127.0.0.1
  26 +
  27 +REDIS_HOST=127.0.0.1
  28 +REDIS_PASSWORD=null
  29 +REDIS_PORT=6379
  30 +
  31 +MAIL_MAILER=smtp
  32 +MAIL_HOST=mailpit
  33 +MAIL_PORT=1025
  34 +MAIL_USERNAME=null
  35 +MAIL_PASSWORD=null
  36 +MAIL_ENCRYPTION=null
  37 +MAIL_FROM_ADDRESS="hello@example.com"
  38 +MAIL_FROM_NAME="${APP_NAME}"
  39 +
  40 +AWS_ACCESS_KEY_ID=
  41 +AWS_SECRET_ACCESS_KEY=
  42 +AWS_DEFAULT_REGION=us-east-1
  43 +AWS_BUCKET=
  44 +AWS_USE_PATH_STYLE_ENDPOINT=false
  45 +
  46 +PUSHER_APP_ID=
  47 +PUSHER_APP_KEY=
  48 +PUSHER_APP_SECRET=
  49 +PUSHER_HOST=
  50 +PUSHER_PORT=443
  51 +PUSHER_SCHEME=https
  52 +PUSHER_APP_CLUSTER=mt1
  53 +
  54 +VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
  55 +VITE_PUSHER_HOST="${PUSHER_HOST}"
  56 +VITE_PUSHER_PORT="${PUSHER_PORT}"
  57 +VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
  58 +VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
  1 +* text=auto
  2 +
  3 +*.blade.php diff=html
  4 +*.css diff=css
  5 +*.html diff=html
  6 +*.md diff=markdown
  7 +*.php diff=php
  8 +
  9 +/.github export-ignore
  10 +CHANGELOG.md export-ignore
  11 +.styleci.yml export-ignore
  1 +/node_modules
  2 +/public/build
  3 +/public/hot
  4 +/public/storage
  5 +/storage/*.key
  6 +/vendor
  7 +.env
  8 +.env.backup
  9 +.env.production
  10 +.phpunit.result.cache
  11 +Homestead.json
  12 +Homestead.yaml
  13 +auth.json
  14 +npm-debug.log
  15 +yarn-error.log
  16 +/.fleet
  17 +/.idea
  18 +/.vscode
  1 +<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
  2 +
  3 +<p align="center">
  4 +<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
  5 +<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
  6 +<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
  7 +<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
  8 +</p>
  9 +
  10 +## About Laravel
  11 +
  12 +Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
  13 +
  14 +- [Simple, fast routing engine](https://laravel.com/docs/routing).
  15 +- [Powerful dependency injection container](https://laravel.com/docs/container).
  16 +- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
  17 +- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
  18 +- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
  19 +- [Robust background job processing](https://laravel.com/docs/queues).
  20 +- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
  21 +
  22 +Laravel is accessible, powerful, and provides tools required for large, robust applications.
  23 +
  24 +## Learning Laravel
  25 +
  26 +Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
  27 +
  28 +You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
  29 +
  30 +If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 2000 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
  31 +
  32 +## Laravel Sponsors
  33 +
  34 +We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell).
  35 +
  36 +### Premium Partners
  37 +
  38 +- **[Vehikl](https://vehikl.com/)**
  39 +- **[Tighten Co.](https://tighten.co)**
  40 +- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
  41 +- **[64 Robots](https://64robots.com)**
  42 +- **[Cubet Techno Labs](https://cubettech.com)**
  43 +- **[Cyber-Duck](https://cyber-duck.co.uk)**
  44 +- **[Many](https://www.many.co.uk)**
  45 +- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)**
  46 +- **[DevSquad](https://devsquad.com)**
  47 +- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
  48 +- **[OP.GG](https://op.gg)**
  49 +- **[WebReinvent](https://webreinvent.com/?utm_source=laravel&utm_medium=github&utm_campaign=patreon-sponsors)**
  50 +- **[Lendio](https://lendio.com)**
  51 +
  52 +## Contributing
  53 +
  54 +Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
  55 +
  56 +## Code of Conduct
  57 +
  58 +In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
  59 +
  60 +## Security Vulnerabilities
  61 +
  62 +If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
  63 +
  64 +## License
  65 +
  66 +The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
  1 +<?php
  2 +
  3 +namespace App\Console\Commands;
  4 +
  5 +use Illuminate\Console\Command;
  6 +use function Co\run;
  7 +
  8 +class Demo extends Command
  9 +{
  10 + /**
  11 + * The name and signature of the console command.
  12 + *
  13 + * @var string
  14 + */
  15 + protected $signature = 'demo';
  16 +
  17 + /**
  18 + * The console command description.
  19 + *
  20 + * @var string
  21 + */
  22 + protected $description = '测试命令';
  23 +
  24 + /**
  25 + * Execute the console command.
  26 + *
  27 + * @return int
  28 + */
  29 + public function handle()
  30 + {
  31 +
  32 +// run(function (){
  33 +//
  34 +// });
  35 +
  36 + return Command::SUCCESS;
  37 + }
  38 +}
  1 +<?php
  2 +
  3 +namespace App\Console;
  4 +
  5 +use Illuminate\Console\Scheduling\Schedule;
  6 +use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
  7 +
  8 +class Kernel extends ConsoleKernel
  9 +{
  10 + /**
  11 + * Define the application's command schedule.
  12 + *
  13 + * @param \Illuminate\Console\Scheduling\Schedule $schedule
  14 + * @return void
  15 + */
  16 + protected function schedule(Schedule $schedule)
  17 + {
  18 + // $schedule->command('inspire')->hourly();
  19 + }
  20 +
  21 + /**
  22 + * Register the commands for the application.
  23 + *
  24 + * @return void
  25 + */
  26 + protected function commands()
  27 + {
  28 + $this->load(__DIR__.'/Commands');
  29 +
  30 + require base_path('routes/console.php');
  31 + }
  32 +}
  1 +<?php
  2 +
  3 +namespace App\Exceptions;
  4 +
  5 +use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
  6 +use Throwable;
  7 +
  8 +class Handler extends ExceptionHandler
  9 +{
  10 + /**
  11 + * A list of exception types with their corresponding custom log levels.
  12 + *
  13 + * @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
  14 + */
  15 + protected $levels = [
  16 + //
  17 + ];
  18 +
  19 + /**
  20 + * A list of the exception types that are not reported.
  21 + *
  22 + * @var array<int, class-string<\Throwable>>
  23 + */
  24 + protected $dontReport = [
  25 + //
  26 + ];
  27 +
  28 + /**
  29 + * A list of the inputs that are never flashed to the session on validation exceptions.
  30 + *
  31 + * @var array<int, string>
  32 + */
  33 + protected $dontFlash = [
  34 + 'current_password',
  35 + 'password',
  36 + 'password_confirmation',
  37 + ];
  38 +
  39 + /**
  40 + * Register the exception handling callbacks for the application.
  41 + *
  42 + * @return void
  43 + */
  44 + public function register()
  45 + {
  46 + $this->reportable(function (Throwable $e) {
  47 + //
  48 + });
  49 + }
  50 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Controllers;
  4 +
  5 +use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
  6 +use Illuminate\Foundation\Bus\DispatchesJobs;
  7 +use Illuminate\Foundation\Validation\ValidatesRequests;
  8 +use Illuminate\Routing\Controller as BaseController;
  9 +
  10 +class Controller extends BaseController
  11 +{
  12 + use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
  13 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Controllers;
  4 +
  5 +use App\Models\Email;
  6 +use App\Models\Host;
  7 +use Helper\Fun;
  8 +use Helper\Mail\Imap;
  9 +
  10 +/**
  11 + * 提供邮件各项数据
  12 + * @author:dc
  13 + * @time 2023/2/4 11:18
  14 + * Class MailApi
  15 + * @package App\Http\Controllers
  16 + */
  17 +class MailApi
  18 +{
  19 +
  20 + /**
  21 + * 添加新的邮箱
  22 + * @author:dc
  23 + * @time 2023/2/4 15:37
  24 + */
  25 + public function login(){
  26 +// $mail,$password,$imap,$smtp
  27 + $formData = request()->only(['email','password','imap','smtp']);
  28 + $validator = validator($formData,[
  29 + 'email' => ['required','email'],
  30 + 'password' => ['required','min:8','max:32'],
  31 + 'imap' => ['required'],
  32 + 'smtp' => ['required'],
  33 + ],[
  34 +
  35 + ]);
  36 +
  37 + if($validator->fails()){
  38 + Fun::response()
  39 + ->message($validator->errors()->first())
  40 + ->status(400)
  41 + ->throw();
  42 + }
  43 +
  44 + // host
  45 + $model = Email::_first($formData['email']);
  46 + if(!$model){
  47 + $model = new Email();
  48 + $model->email = $formData['email'];
  49 + }
  50 +
  51 + $model->imap = $formData['imap'];
  52 + $model->smtp = $formData['smtp'];
  53 + $model->password = encrypt($formData['password']);
  54 +
  55 + $imap = new Imap();
  56 + // 是否初始成功
  57 + try {
  58 + $imap->login("ssl://{$formData['imap']}:993",$model->email,$model->password);
  59 + }catch (\Throwable $e){
  60 + Fun::response()
  61 + ->message($e->getMessage())
  62 + ->status(400)
  63 + ->throw();
  64 + }
  65 +
  66 + // 登录成功了,密码验证字段通过
  67 + $model->pass_error = 0;
  68 + // 保存好邮箱
  69 + $model->save();
  70 +
  71 + // 开始同步文件夹
  72 + $folder = $imap->getFolder();
  73 +
  74 + Fun::response()
  75 + ->data($folder)
  76 + ->throw();
  77 + }
  78 +
  79 +
  80 + /**
  81 + * 读取服务器上已记录的各个邮箱的服务器地址
  82 + * @author:dc
  83 + * @time 2023/2/4 15:12
  84 + */
  85 + public function host(){
  86 +
  87 + $host = Host::_all();
  88 +
  89 + Fun::response()->data($host)->throw();
  90 +
  91 + }
  92 +
  93 +
  94 +
  95 + public function lists(){
  96 +
  97 + }
  98 +
  99 +}
  1 +<?php
  2 +
  3 +namespace App\Http;
  4 +
  5 +use Illuminate\Foundation\Http\Kernel as HttpKernel;
  6 +
  7 +class Kernel extends HttpKernel
  8 +{
  9 + /**
  10 + * The application's global HTTP middleware stack.
  11 + *
  12 + * These middleware are run during every request to your application.
  13 + *
  14 + * @var array<int, class-string|string>
  15 + */
  16 + protected $middleware = [
  17 + // \App\Http\Middleware\TrustHosts::class,
  18 + \App\Http\Middleware\TrustProxies::class,
  19 + \Illuminate\Http\Middleware\HandleCors::class,
  20 + \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
  21 + \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
  22 + \App\Http\Middleware\TrimStrings::class,
  23 + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
  24 + ];
  25 +
  26 + /**
  27 + * The application's route middleware groups.
  28 + *
  29 + * @var array<string, array<int, class-string|string>>
  30 + */
  31 + protected $middlewareGroups = [
  32 + 'web' => [
  33 + \App\Http\Middleware\EncryptCookies::class,
  34 + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
  35 + \Illuminate\Session\Middleware\StartSession::class,
  36 + \Illuminate\View\Middleware\ShareErrorsFromSession::class,
  37 + \App\Http\Middleware\VerifyCsrfToken::class,
  38 + \Illuminate\Routing\Middleware\SubstituteBindings::class,
  39 + ],
  40 +
  41 + 'api' => [
  42 + // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
  43 + 'throttle:api',
  44 + \Illuminate\Routing\Middleware\SubstituteBindings::class,
  45 + ],
  46 + ];
  47 +
  48 + /**
  49 + * The application's route middleware.
  50 + *
  51 + * These middleware may be assigned to groups or used individually.
  52 + *
  53 + * @var array<string, class-string|string>
  54 + */
  55 + protected $routeMiddleware = [
  56 + 'auth' => \App\Http\Middleware\Authenticate::class,
  57 + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
  58 + 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
  59 + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
  60 + 'can' => \Illuminate\Auth\Middleware\Authorize::class,
  61 + 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
  62 + 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
  63 + 'signed' => \App\Http\Middleware\ValidateSignature::class,
  64 + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
  65 + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
  66 + ];
  67 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Middleware;
  4 +
  5 +use Illuminate\Auth\Middleware\Authenticate as Middleware;
  6 +
  7 +class Authenticate extends Middleware
  8 +{
  9 + /**
  10 + * Get the path the user should be redirected to when they are not authenticated.
  11 + *
  12 + * @param \Illuminate\Http\Request $request
  13 + * @return string|null
  14 + */
  15 + protected function redirectTo($request)
  16 + {
  17 + if (! $request->expectsJson()) {
  18 + return route('login');
  19 + }
  20 + }
  21 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Middleware;
  4 +
  5 +use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
  6 +
  7 +class EncryptCookies extends Middleware
  8 +{
  9 + /**
  10 + * The names of the cookies that should not be encrypted.
  11 + *
  12 + * @var array<int, string>
  13 + */
  14 + protected $except = [
  15 + //
  16 + ];
  17 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Middleware;
  4 +
  5 +use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
  6 +
  7 +class PreventRequestsDuringMaintenance extends Middleware
  8 +{
  9 + /**
  10 + * The URIs that should be reachable while maintenance mode is enabled.
  11 + *
  12 + * @var array<int, string>
  13 + */
  14 + protected $except = [
  15 + //
  16 + ];
  17 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Middleware;
  4 +
  5 +use App\Providers\RouteServiceProvider;
  6 +use Closure;
  7 +use Illuminate\Http\Request;
  8 +use Illuminate\Support\Facades\Auth;
  9 +
  10 +class RedirectIfAuthenticated
  11 +{
  12 + /**
  13 + * Handle an incoming request.
  14 + *
  15 + * @param \Illuminate\Http\Request $request
  16 + * @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
  17 + * @param string|null ...$guards
  18 + * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
  19 + */
  20 + public function handle(Request $request, Closure $next, ...$guards)
  21 + {
  22 + $guards = empty($guards) ? [null] : $guards;
  23 +
  24 + foreach ($guards as $guard) {
  25 + if (Auth::guard($guard)->check()) {
  26 + return redirect(RouteServiceProvider::HOME);
  27 + }
  28 + }
  29 +
  30 + return $next($request);
  31 + }
  32 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Middleware;
  4 +
  5 +use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
  6 +
  7 +class TrimStrings extends Middleware
  8 +{
  9 + /**
  10 + * The names of the attributes that should not be trimmed.
  11 + *
  12 + * @var array<int, string>
  13 + */
  14 + protected $except = [
  15 + 'current_password',
  16 + 'password',
  17 + 'password_confirmation',
  18 + ];
  19 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Middleware;
  4 +
  5 +use Illuminate\Http\Middleware\TrustHosts as Middleware;
  6 +
  7 +class TrustHosts extends Middleware
  8 +{
  9 + /**
  10 + * Get the host patterns that should be trusted.
  11 + *
  12 + * @return array<int, string|null>
  13 + */
  14 + public function hosts()
  15 + {
  16 + return [
  17 + $this->allSubdomainsOfApplicationUrl(),
  18 + ];
  19 + }
  20 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Middleware;
  4 +
  5 +use Illuminate\Http\Middleware\TrustProxies as Middleware;
  6 +use Illuminate\Http\Request;
  7 +
  8 +class TrustProxies extends Middleware
  9 +{
  10 + /**
  11 + * The trusted proxies for this application.
  12 + *
  13 + * @var array<int, string>|string|null
  14 + */
  15 + protected $proxies;
  16 +
  17 + /**
  18 + * The headers that should be used to detect proxies.
  19 + *
  20 + * @var int
  21 + */
  22 + protected $headers =
  23 + Request::HEADER_X_FORWARDED_FOR |
  24 + Request::HEADER_X_FORWARDED_HOST |
  25 + Request::HEADER_X_FORWARDED_PORT |
  26 + Request::HEADER_X_FORWARDED_PROTO |
  27 + Request::HEADER_X_FORWARDED_AWS_ELB;
  28 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Middleware;
  4 +
  5 +use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
  6 +
  7 +class ValidateSignature extends Middleware
  8 +{
  9 + /**
  10 + * The names of the query string parameters that should be ignored.
  11 + *
  12 + * @var array<int, string>
  13 + */
  14 + protected $except = [
  15 + // 'fbclid',
  16 + // 'utm_campaign',
  17 + // 'utm_content',
  18 + // 'utm_medium',
  19 + // 'utm_source',
  20 + // 'utm_term',
  21 + ];
  22 +}
  1 +<?php
  2 +
  3 +namespace App\Http\Middleware;
  4 +
  5 +use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
  6 +
  7 +class VerifyCsrfToken extends Middleware
  8 +{
  9 + /**
  10 + * The URIs that should be excluded from CSRF verification.
  11 + *
  12 + * @var array<int, string>
  13 + */
  14 + protected $except = [
  15 + //
  16 + ];
  17 +}
  1 +<?php
  2 +
  3 +namespace App\Mail\Jobs;
  4 +
  5 +/**
  6 + *
  7 + * @time 2022/7/29 15:11
  8 + * Class ImapApi
  9 + * @package App\Mail\Jobs
  10 + */
  11 +class ImapApi {
  12 +
  13 + /**
  14 + * @var ImapApi
  15 + */
  16 + private static $Instance;
  17 +
  18 + /**
  19 + * 如 imap.qq.com
  20 + * @var string
  21 + */
  22 + private $host;
  23 +
  24 + /**
  25 + * 邮箱地址
  26 + * @var string
  27 + */
  28 + private $username;
  29 +
  30 + /**
  31 + * 密码
  32 + * @var string
  33 + */
  34 + private $password;
  35 +
  36 + /**
  37 + * 端口
  38 + * @var int
  39 + */
  40 + private $port = 993;
  41 +
  42 +
  43 + private function __construct(){}
  44 +
  45 +
  46 + /**
  47 + * @return ImapApi
  48 + */
  49 + public static function getInstance(): ImapApi
  50 + {
  51 + if(!self::$Instance){
  52 + self::$Instance = new static();
  53 + }
  54 +
  55 + return self::$Instance;
  56 + }
  57 +
  58 +
  59 +
  60 +}
  1 +<?php
  2 +namespace App\Mail\Jobs;
  3 +
  4 +
  5 +use App\Http\Mail\lib\MailFun;
  6 +use App\Http\Mail\Models\Email;
  7 +use App\Http\Mail\Models\EmailSendJob;
  8 +use App\Http\Mail\Models\EmailSendJobStatu;
  9 +use App\Http\Models\EmailSendTemplate;
  10 +use App\Sk;
  11 +use Illuminate\Bus\Queueable;
  12 +use Illuminate\Contracts\Queue\ShouldQueue;
  13 +use Illuminate\Foundation\Bus\Dispatchable;
  14 +use Illuminate\Queue\InteractsWithQueue;
  15 +use Illuminate\Queue\SerializesModels;
  16 +use Illuminate\Support\Facades\Cache;
  17 +
  18 +/**
  19 + * @author:dc
  20 + * @time 2022/11/9 11:52
  21 + * Class SendJob
  22 + */
  23 +class SendJob implements ShouldQueue
  24 +{
  25 + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
  26 +
  27 + public $job_id;
  28 +
  29 + /**
  30 + * SendServiceMsg constructor.
  31 + * @param $id
  32 + */
  33 + public function __construct($id)
  34 + {
  35 + // 使用的链接
  36 + $this->connection = Sk::QUEUE_EMAIL_QUN;
  37 +
  38 + $this->job_id = $id;
  39 + }
  40 +
  41 + /**
  42 + * Execute the job.
  43 + *
  44 + * @return bool
  45 + */
  46 + public function handle()
  47 + {
  48 + // 查询邮件任务主体
  49 + $jobData = EmailSendJob::where('id',$this->job_id)
  50 + ->whereIn('status',[EmailSendJob::STATUS_WAIT,EmailSendJob::STATUS_RUNING])
  51 + ->first();
  52 +
  53 + if(!$jobData){
  54 + $this->log('任务不存在');
  55 + return false;
  56 + }
  57 +
  58 + // 发送时间
  59 + if(trim($jobData->send_time) != 'now'){
  60 + // 定时发送
  61 + list($start,$end) = explode(',',$jobData->send_time);
  62 + // 系统是中国时区,按照美国时区要慢13个小时
  63 + if(!(date('H') >= $start || date('H') < $end)){
  64 + // 也就是 美国早上 8点到晚上10点
  65 +// $this->log('休息中');
  66 + SendJob::dispatch($this->job_id)->delay(600);
  67 + return true;
  68 + }
  69 +
  70 + }
  71 +
  72 + $this->log('开始检查任务:'.$this->job_id);
  73 + if(!$jobData){
  74 + $this->log('没有找到任务');
  75 + return true;
  76 + }
  77 + // 查询需要发送的邮件
  78 + $jobStdata = EmailSendJobStatu::where(['job_id'=>$this->job_id,'status'=>EmailSendJobStatu::STATUS_WAIT])->first();
  79 + if(!$jobStdata){
  80 + $this->log('完成所有了');
  81 + // 是否完成
  82 + $jobData->status = EmailSendJob::STATUS_SUCCESS;
  83 + $jobData->save();
  84 + return true;
  85 + }
  86 + $this->log('找到任务:'.$jobStdata['id']);
  87 +
  88 + // 防止重复
  89 + $cachekey = 'email_send_job:'.$this->job_id.":".$jobStdata['id'];
  90 + // 存在
  91 + if(Cache::has($cachekey)){
  92 + $this->log("任务:{$jobStdata['id']}正在发送,跳过");
  93 + SendJob::dispatch($this->job_id)->delay(30);
  94 + return true;
  95 + }
  96 + // 占有2分钟
  97 + Cache::set($cachekey,$jobStdata['id'],120);
  98 +
  99 +
  100 + // 是否等待状态
  101 + if($jobData->status !== EmailSendJob::STATUS_RUNING){
  102 + $this->log('开始运行脚本了');
  103 + $jobData->status = EmailSendJob::STATUS_RUNING;;
  104 + $jobData->save();
  105 + }
  106 +
  107 + // 当前管理账号下所有绑定的邮件
  108 + $emailinfos = Email::_get($jobData->user_id,Email::STATUS_ACTIVE,['e.id','e.email','e.email_name','e.smtp','e.password','e.pwd_error']);
  109 +
  110 + $smtpErrorNum = 0;// 错误次数
  111 +
  112 + // 标签
  113 + $tags = $jobData->tags;
  114 + $tags = is_array($tags) ? $tags : explode(',',$tags);
  115 + // 模板列表
  116 + $tempLists = EmailSendTemplate::getAdminTagsList($tags);
  117 + if(!$tempLists){
  118 + // 是否完成
  119 + $jobData->status = EmailSendJob::STATUS_SUCCESS;
  120 + $jobData->save();
  121 + $this->log('没有可用的模板');
  122 + return false;
  123 + }
  124 + // 随机一个模板
  125 + $template = $tempLists[array_rand($tempLists->toArray())];
  126 +
  127 +
  128 + // 节点
  129 + EMAILRESETRANG:
  130 +
  131 + if(!$emailinfos){
  132 + $this->log('暂时没有可分配的账号');
  133 + // 再次发布任务,延时10分钟
  134 + SendJob::dispatch($this->job_id)->delay(120);
  135 + return true;
  136 + }
  137 +
  138 + // 随机一个邮件来当发送
  139 + $key = array_rand($emailinfos,1);
  140 + $emailinfo = $emailinfos ? $emailinfos[$key] : [];
  141 + if(!$emailinfo){
  142 + $this->log('没有分配到账号');
  143 + // 再次发布任务,延时10分钟
  144 + SendJob::dispatch($this->job_id)->delay(600);
  145 + return true;
  146 + }
  147 + unset($emailinfos[$key]);
  148 + $emailinfos = array_values($emailinfos);
  149 +
  150 + // 密码是否需要验证
  151 + if($emailinfo['pwd_error']){
  152 + $this->log('账号密码验证失败,需要修改密码 '.$emailinfo['email']);
  153 + // 重新随机一个,
  154 + goto EMAILRESETRANG;
  155 + }
  156 +
  157 + // 每个小时不能超过20
  158 + $cacheEmailSuccesshkey = 'email_job_email:'.$emailinfo['email'].":success_h";
  159 + if(Cache::get($cacheEmailSuccesshkey,0) >= 8){
  160 + $this->log('账号超过每小时8封了'.$emailinfo['email']);
  161 + // 重新随机一个,
  162 + goto EMAILRESETRANG;
  163 + }
  164 +
  165 + // 一天中是否超过100,
  166 +// $cacheEmailSuccessdkey = 'email_job_email:'.$emailinfo['email'].":success_d";
  167 +// if(Cache::get($cacheEmailSuccessdkey,0) >= 100){
  168 +// $this->log('账号超过每天100封了'.$emailinfo['email']);
  169 +// // 重新随机一个,
  170 +// goto EMAILRESETRANG;
  171 +// }
  172 +
  173 + $cachemailikey = 'email_job_email:'.$emailinfo['email'].":success_i";
  174 + if (Cache::has($cachemailikey)){
  175 + $this->log('账号没超过10分钟间隔 '.$emailinfo['email']);
  176 + // 重新随机一个,
  177 + goto EMAILRESETRANG;
  178 + }
  179 +
  180 + // 是否错误了
  181 + $cachemailierrordkey = 'email_job_email:'.$emailinfo['email'].":error_d";
  182 + if(Cache::has($cachemailierrordkey)){
  183 + $this->log('账号记录错误了,跳过 1小时'.$emailinfo['email']);
  184 + goto EMAILRESETRANG;
  185 + }
  186 +
  187 + Cache::set($cachemailikey,$jobStdata['id'],550);
  188 +
  189 + $this->log('找到账号:'.$emailinfo['email']);
  190 + // 数据发送的email
  191 + $data = json_decode($jobData->maildata,true);
  192 +
  193 + $data['body'] = $template['body'];
  194 + $data['subject'] = $template['subject'];
  195 +
  196 + try {
  197 +
  198 + // 替换邮件规则,
  199 + $data['body'] = str_replace('[read][/read]','<div style="opacity: 0;width: 1px;height: 1px;overflow: hidden;"><img src="https://king.shopk.com/_shopk_?mail='.base64_encode('logo|'.$jobStdata['id']).'" /></div>',$data['body']);
  200 + // 匹配链接规则
  201 + if (preg_match_all("/\[link:(.*)\](.*)\[\/link\]/U",$data['body'],$m)){
  202 + foreach ($m[0] as $mk=>$item){
  203 + $data['body'] = str_replace($item,'<a target="_blank" href="https://king.shopk.com/_shopk_?mail='.base64_encode('link|'.$jobStdata['id'].'|'.urlencode($m[1][$mk])).'">'.$m[2][$mk].'</a>',$data['body']);
  204 + }
  205 + }
  206 +
  207 + // 发送邮件
  208 + MailFun::sendEmail(
  209 + $emailinfo['smtp'],$emailinfo['email'],decrypt($emailinfo['password'])
  210 + ,$emailinfo['email_name'],$jobStdata['to_email'],$data['subject'],
  211 + $data['body'],$data['file']??[],false
  212 + ,($data['priority']??0) ? 1 : 3
  213 + );
  214 +
  215 + // 记录
  216 + $jobStdata->status = EmailSendJobStatu::STATUS_SUCCESS;
  217 + $jobStdata->send_email = $emailinfo['email'];
  218 + $jobStdata->time = date('Y-m-d H:i:s');
  219 + $this->log('成功了');
  220 + // 成功
  221 + $jobData->success = $jobData->success+1;
  222 +
  223 + // 24小时内不能超过100
  224 +// if(Cache::has($cacheEmailSuccessdkey)){
  225 +// // 加1
  226 +// Cache::increment($cacheEmailSuccessdkey);
  227 +// }else{
  228 +// Cache::set($cacheEmailSuccessdkey,1,86400);
  229 +// }
  230 +
  231 + // 每小时内不能超过20
  232 + if(Cache::has($cacheEmailSuccesshkey)){
  233 + // 加1
  234 + Cache::increment($cacheEmailSuccesshkey);
  235 + }else{
  236 + Cache::set($cacheEmailSuccesshkey,1,3600);
  237 + }
  238 +
  239 +
  240 +
  241 + } catch (\Exception $e) {
  242 + if($e->getMessage()=='SMTP Error: data not accepted.'){
  243 + // 下次跳过账号
  244 + if(!Cache::has($cachemailierrordkey)) {
  245 + Cache::set($cachemailierrordkey, $e->getMessage(), 3600);
  246 + }
  247 + }
  248 + // 无法验证,密码错误了,处理密码
  249 + if($e->getMessage()=='SMTP Error: Could not authenticate.'){
  250 + Email::_update(['id'=>$emailinfo['id']],['pwd_error'=>1]);
  251 + }
  252 +
  253 +
  254 + $smtpErrorNum++;
  255 + // 超过失败3次的
  256 + $error = $jobStdata->error;
  257 + $error[] = [
  258 + 'email' => $emailinfo['email'],
  259 + 'message' => $e->getMessage(),
  260 + 'file' => $e->getFile(),
  261 + 'line' => $e->getLine()
  262 + ];
  263 + $jobStdata->time = date('Y-m-d H:i:s');
  264 + $jobStdata->error = json_encode($error);
  265 +
  266 + // 如果失败了,重试3次
  267 + if($smtpErrorNum <= 20){
  268 + // 记录
  269 + $jobStdata->save();
  270 + $this->log('错误次数'.$smtpErrorNum);
  271 + goto EMAILRESETRANG;
  272 + }
  273 + $this->log('错误超过次数'.$smtpErrorNum);
  274 + // 记录
  275 + $jobStdata->status = EmailSendJobStatu::STATUS_ERROR;
  276 + $jobStdata->send_email = $emailinfo['email'];
  277 +
  278 + // 错误
  279 + $jobData->error = $jobData->error+1;
  280 + }
  281 +
  282 + // 保存
  283 + $jobStdata->save();
  284 + $jobData->save();
  285 + $this->log('完成');
  286 +
  287 + // 再次发布任务
  288 + SendJob::dispatch($this->job_id);
  289 +
  290 +
  291 + }
  292 +
  293 +
  294 + protected function log($content){
  295 + @file_put_contents(storage_path('logs/send_email_job_'.$this->job_id.'_.log'),date('Y-m-d H:i:s ').$content.PHP_EOL,FILE_APPEND);
  296 + }
  297 +
  298 +}
  1 +<?php
  2 +
  3 +namespace App\Mail;
  4 +
  5 +use App\Mail\Jobs\SendJob;
  6 +use App\Mail\lib\Lang;
  7 +use App\Mail\lib\MailFun;
  8 +use App\Mail\lib\MailParse\Body;
  9 +use App\Models\Email;
  10 +use App\Models\EmailBody;
  11 +use App\Models\EmailContact;
  12 +use App\Models\EmailContactGroup;
  13 +use App\Models\EmailFolder;
  14 +use App\Models\Host;
  15 +use App\Models\EmailList;
  16 +use App\Models\EmailLog;
  17 +use App\Models\EmailSendJob;
  18 +use Illuminate\Http\UploadedFile;
  19 +use Illuminate\Support\Facades\Config;
  20 +use Illuminate\Support\Facades\DB;
  21 +use Illuminate\Support\Facades\Log;
  22 +use Illuminate\Support\Facades\Validator;
  23 +
  24 +/**
  25 + * 邮件服务
  26 + * @time 2022/7/29 15:05
  27 + * Class Mail
  28 + * @package app\Mail
  29 + */
  30 +class Mail {
  31 +
  32 + /**
  33 + * @var Mail
  34 + */
  35 + private static $Instance;
  36 +
  37 + /**
  38 + * 如 imap.qq.com
  39 + * @var array
  40 + */
  41 + private $host = [
  42 + 'imap'=>'',
  43 + 'smtp'=>''
  44 + ];
  45 +
  46 + /**
  47 + * 用户email表的id
  48 + * @var int
  49 + */
  50 + private $id = 0;
  51 +
  52 + /**
  53 + * 邮箱地址
  54 + * @var string
  55 + */
  56 + private $username = '';
  57 + private $nickname = '';
  58 +
  59 + /**
  60 + * 密码
  61 + * @var string
  62 + */
  63 + private $password = '';
  64 +
  65 + /**
  66 + * 端口
  67 + * @var int 143 993
  68 + */
  69 + private $port = 993;
  70 + /**
  71 + * 协议
  72 + * @var string
  73 + */
  74 + private $ssl = 'ssl://';
  75 +
  76 + /**
  77 + * 用户id
  78 + * @var int
  79 + */
  80 + private $user_id = 0;
  81 +
  82 + /**
  83 + * 附件保存目录
  84 + * @var string
  85 + */
  86 + private $filePath;
  87 +
  88 +
  89 +
  90 + /**
  91 + * imap服务器连接
  92 + * @var \App\Http\Mail\lib\client\Imap[]
  93 + */
  94 + private $client;
  95 +
  96 +
  97 + /**
  98 + * 连接目录 INBOX
  99 + * @var string
  100 + */
  101 + private $folder = 'INBOX';
  102 +
  103 + /**
  104 + * 目录编号
  105 + * @var int
  106 + */
  107 + private $folderId = 0;
  108 +
  109 + /**
  110 + * @var array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Http\Request|string|null
  111 + */
  112 + private $request;
  113 +
  114 + /**
  115 + * 所有绑定邮箱的id
  116 + * @var array
  117 + */
  118 + private $email_ids = [];
  119 +
  120 +
  121 + private function __construct(){
  122 + $this->request = request();
  123 + }
  124 +
  125 +
  126 + /**
  127 + * @return Mail
  128 + */
  129 + public static function getInstance(int $user_id, string $email=''): Mail
  130 + {
  131 + $key = md5($user_id.$email);
  132 +
  133 + if(empty(self::$Instance[$key])){
  134 + $mail = self::$Instance[$key] = new static();
  135 +
  136 + $mail->user_id = $user_id;
  137 +
  138 + // 绑定的所有邮箱id
  139 + $mail->email_ids = array_column(Email::_getById($mail->user_id),'id');
  140 +
  141 + if($email){
  142 + $data = Email::_first($email);
  143 + // 是否存在并绑定了
  144 + if($data && in_array($data['id'],$mail->email_ids)){
  145 + $mail->id = $data['id'];
  146 + $mail->username = $email;
  147 + $mail->nickname = $data['email_name']??'';
  148 + $mail->password = $data['password'];
  149 + // 当前选择的邮箱
  150 + $mail->host = [
  151 + 'imap' => $data['imap'],
  152 + 'smtp' => $data['smtp'],
  153 + ];
  154 + // 设置目录
  155 + $mail->selectFolder($mail->folder);
  156 + }else{
  157 + throw new \Exception(Lang::__('email_not_bind',$email));
  158 + }
  159 + }
  160 + }
  161 +
  162 + // 设置邮件附件地址
  163 + self::$Instance[$key]->setFilePath(storage_path('/imap'));
  164 +
  165 + return self::$Instance[$key];
  166 + }
  167 +
  168 + /**
  169 + * 设置目录
  170 + * @param $path
  171 + * @time 2022/8/1 15:39
  172 + */
  173 + public function setFilePath($path){
  174 + $this->filePath = $path;
  175 +// if(!is_dir($this->filePath)){
  176 +// @mkdir($this->filePath,0775,true);
  177 +// }
  178 + }
  179 +
  180 + /**
  181 + * @return string
  182 + */
  183 + public function getFilePath(): string
  184 + {
  185 + $path = $this->filePath.'/'.$this->username.'/';
  186 +// if(is_dir($path)){
  187 +// mkdir($path,0775,true);
  188 +// }
  189 + return $path;
  190 + }
  191 +
  192 +
  193 + /**
  194 + * 选择使用的用户
  195 + * @param int $user_id
  196 + * @param string $email
  197 + * @return Mail
  198 + * @author:dc
  199 + * @time 2022/7/29 17:42
  200 + */
  201 + public static function use(int $user_id, string $email=''):Mail {
  202 + $mail = self::getInstance($user_id,$email);
  203 +
  204 +
  205 + return $mail;
  206 + }
  207 +
  208 + /**
  209 + * 选择文件夹
  210 + * @param string|int $folder
  211 + * @time 2022/8/4 14:54
  212 + */
  213 + public function selectFolder($folder){
  214 + if($folder) {
  215 + $lists = EmailFolder::_all($this->id, false);
  216 + // 找到目标文件的id,pid
  217 + foreach ($lists as $list) {
  218 + if ($list[is_numeric($folder)?'id':'folder'] == $folder) {
  219 + $id = $list['id'];// id
  220 + $pid = $list['pid']; // 上级id
  221 + $folder = $list['origin_folder'];
  222 + }
  223 + }
  224 +
  225 + // 不存在
  226 + if(!$lists){
  227 + $folder = 'INBOX';
  228 + $pid = 0;
  229 + // 这个名字是每个邮箱默认的,不可更改
  230 + $id = EmailFolder::_insert($this->user_id,$this->id,'INBOX','INBOX');
  231 + if($folder != 'INBOX'){
  232 + $id = 0;
  233 + }
  234 + }
  235 +
  236 + // 是否存
  237 + if ($id ?? 0) {
  238 + // 拿到上级,成为 dir/dir/dir
  239 + if ($pid ?? 0) {
  240 + EmailFolder::_firstTree($lists, $pid, $folder,'origin_folder');
  241 + }
  242 + $this->folderId = $id;
  243 + $this->folder = $folder;
  244 + $this->folderInfo = EmailFolder::_first($id);
  245 + } else {
  246 + throw new \Exception(Lang::__('email_folder_not', $folder));
  247 + }
  248 + }
  249 + }
  250 +
  251 + /**
  252 + * @return \App\Http\Mail\lib\client\Imap
  253 + * @throws \Exception
  254 + * @time 2022/8/5 17:22
  255 + */
  256 + public function client($email=''){
  257 +
  258 + $email = $email ? : $this->username;
  259 +
  260 + if(empty($this->client[$email])){
  261 + if($email == $this->username){
  262 + $this->login();
  263 + }else{
  264 + $data = Email::_first($email);
  265 + if($data){
  266 + $this->loginImap($data['imap'],$data['email'],$data['password']);
  267 + }
  268 + }
  269 + }
  270 + return $this->client[$email];
  271 + }
  272 +
  273 + /**
  274 + * 登录邮箱
  275 + * @param string $password
  276 + * @param string $imap
  277 + * @param string $smtp
  278 + * @return bool
  279 + * @throws \Exception
  280 + * @time 2022/8/5 17:21
  281 + */
  282 + public function login(string $password='',string $imap='',string $smtp=''):bool{
  283 + // host
  284 + if($imap && $this->username){
  285 + $this->hostAdd(explode('@',$this->username)[1],$imap,$smtp);
  286 + }
  287 +
  288 + if(!$password && !$this->password){
  289 + throw new \Exception(Lang::__('password_required'));
  290 + }
  291 +
  292 + if($password){
  293 + // 如果密码不一致,更新
  294 + if($password != $this->password){
  295 + Email::_changePwd($this->username, $password);
  296 + }
  297 +
  298 + $this->password = $password;
  299 + }
  300 +
  301 + if(!$this->hostMy()){
  302 + throw new \Exception(Lang::__('login_host'));
  303 + }
  304 +
  305 + // imap imap.qq.com
  306 + $this->loginImap($this->hostMy('imap'),$this->username,$this->password);
  307 + // 密码没验证成功
  308 +// if($this->id){
  309 +// Email::_update(['id'=>$this->id],['pwd_error'=>1]);
  310 +// }
  311 + return true;
  312 +
  313 + }
  314 +
  315 +
  316 + /**
  317 + * 公用链接
  318 + * @param $host
  319 + * @param $email
  320 + * @param $password
  321 + * @throws \Exception
  322 + * @author:dc
  323 + * @time 2022/12/7 9:35
  324 + */
  325 + public function loginImap($host,$email,$password):void {
  326 +
  327 + $this->client[$email] = new \App\Http\Mail\lib\client\Imap();
  328 + // 是否初始成功
  329 + $this->client[$email]->login($this->ssl.$host.':'.$this->port,$email,$password);
  330 +
  331 + }
  332 +
  333 + /**
  334 + * 自动路由
  335 + * @return \Illuminate\Http\JsonResponse|string|\Symfony\Component\HttpFoundation\BinaryFileResponse
  336 + * @throws \Illuminate\Contracts\Container\BindingResolutionException
  337 + * @throws \Psr\SimpleCache\InvalidArgumentException
  338 + * @throws \Throwable
  339 + * @author:dc
  340 + * @time 2022/8/1 11:07
  341 + */
  342 + public function autoRoute(){
  343 + $task = $this->request->get('_task');
  344 + $action = $this->request->get('_action');
  345 + $result = [];
  346 +
  347 + switch ($task){
  348 + case 'sync': {
  349 + // 同步操作
  350 + switch ($action){
  351 + case 'list': {
  352 + // 同步邮件列表
  353 + $msgno = $this->request->post('msgno',[]);
  354 + if(is_string($msgno)){
  355 + $msgno = explode(',',$msgno);
  356 + }
  357 + $result = $this->syncEmailList($msgno);
  358 + break;
  359 + }
  360 + // 同步文件夹
  361 + case 'folder': {
  362 + $folder = $this->request->post('folder');
  363 + $result = $this->syncFolder($folder);
  364 + break;
  365 + }
  366 + // 更新最新的
  367 + case 'new':{
  368 + $result = $this->syncEmailList();
  369 + break;
  370 + }
  371 + }
  372 + break;
  373 + }
  374 + // 设置标签,已读,未读,删除等
  375 + case 'flags':{
  376 + $ids = $this->request->post('ids');
  377 + $ids = is_string($ids) ? explode(',',$ids) : $ids;
  378 + $ids = is_array($ids) ? $ids : [$ids];
  379 + // 获取到邮件的uid
  380 + $uids = EmailList::_getUidsByIds($ids,$this->id);
  381 + $filed = '';
  382 + $value = 0;
  383 + $mod = '';
  384 + $flags = '';
  385 + // 标记操作
  386 + switch ($action){
  387 + // 标记已读
  388 + case 'setSeen':{
  389 + $filed = 'seen';
  390 + $value = 1;
  391 + $flags = 'seen';
  392 + $mod = '+';
  393 + break;
  394 + }
  395 + // 标记未读
  396 + case 'delSeen':{
  397 + $filed = 'seen';
  398 + $value = 0;
  399 + $flags = 'seen';
  400 + $mod = '-';
  401 + break;
  402 + }
  403 + case 'setFlagged':{
  404 + $filed = 'flagged';
  405 + $value = 1;
  406 + $flags = 'flagged';
  407 + $mod = '+';
  408 + break;
  409 + }
  410 + case 'delFlagged':{
  411 + $filed = 'flagged';
  412 + $value = 0;
  413 + $flags = 'flagged';
  414 + $mod = '-';
  415 + break;
  416 + }
  417 + }
  418 +
  419 + if($this->setflagged($uids,$flags,$mod)){
  420 + // 更新标签
  421 + if(EmailList::_setFlags(array_keys($uids),$this->id,$filed,$value)){
  422 + $result = array_keys($uids);
  423 + }
  424 + }
  425 + break;
  426 + }
  427 + case 'info': {
  428 + $id = (int) $this->request->get('id');
  429 + // 读取邮件
  430 + $result = $this->emailInfo($id);
  431 +
  432 + if(!$result){
  433 + $result = new static();
  434 + }else{
  435 + // 是否是数组
  436 + if(!is_array($result)) $result = $result->toArray();
  437 +
  438 + // 缓存一下
  439 + \Cache::set('app_email_info_'.$this->user_id.":".$result['id'],$result,3000);
  440 +
  441 + // 渲染视图
  442 + $result['body'] = view('admin/email/info',[
  443 + 'data' => $result
  444 + ])->render();
  445 + // 有邮件编码bug,必须要转
  446 + $result['body'] = base64_encode($result['body']);
  447 +
  448 + }
  449 + ///////////////////////// 这里是测试
  450 +// print_r($result['body']['text_html']);exit();
  451 +// foreach ($result['body']['text_html'] as $item){
  452 +// if(!empty($item['type']) && ($item['type'] == 'text/html' || $item['type'] == 'text/plain')){
  453 +// header("content-type:text/html;charset=".($item['charset']??'utf-8'));
  454 +// echo $item['body'];
  455 +// }
  456 +// }
  457 +// exit();
  458 + ///////////////////////
  459 +
  460 + break;
  461 + }
  462 + case 'list': {
  463 + if($this->id){
  464 + $eids = [$this->id];
  465 + }else{
  466 + $eids = array_column(Email::_get($this->user_id),'id');
  467 + }
  468 +
  469 + $total = 0;
  470 +
  471 + if($this->folder=='INBOX'){
  472 + $fids = EmailFolder::_user_folders($this->email_ids);
  473 +// $total = array_sum(array_column($fids,'exsts'));
  474 + $fids = array_column($fids,'id');
  475 +
  476 + $unseen = array_sum(array_column($fids,'unseen')); // 未读数量
  477 +// $unseen = EmailList::_getUnseenNum($eids); // 未读数量
  478 + }else{
  479 + $fids = [$this->folderId];
  480 +
  481 + $total = $this->folderInfo['exsts']??0;
  482 + }
  483 +
  484 + // 搜索
  485 + $search = [];
  486 + $search['search'] = $this->request->get('search');
  487 + $search['seen'] = $this->request->get('seen');
  488 +
  489 +
  490 + $result = $this->emailLists($eids,$fids,$total,$search)->toArray();
  491 +
  492 +
  493 +// if($this->folder == 'INBOX'){
  494 + $this->allemail = [];
  495 + foreach ($result['data'] as $k=>$datum){
  496 + if(!$datum['uid']){
  497 + // 邮件
  498 + if (empty($this->allemail[$datum['email_id']])){
  499 + $this->allemail[$datum['email_id']] = Email::_firstById($datum['email_id']);
  500 + }
  501 + if(empty($this->allemail[$datum['email_id']])){
  502 + continue;
  503 + }
  504 + // 同步
  505 + $id = $this->syncEmailList(
  506 + [$datum['msgno']],
  507 + $datum['email_id'],
  508 + $this->allemail[$datum['email_id']]['email'],
  509 + $datum['folder_id'],
  510 + $this->folder
  511 + );
  512 + if($id['ids']){
  513 + // 重新获取
  514 + $result['data'][$k] = EmailList::_first($id['ids'][0]);
  515 + }
  516 + }
  517 + // 干掉 -snv 和 (Failure)
  518 + if(preg_match("/(\-\ssnv)|(\(Failure\))|(\(Delay\))$/",$datum['subject'])){
  519 + unset($result['data'][$k]);
  520 + continue;
  521 + }
  522 + }
  523 + $result['data'] = array_values($result['data']);
  524 +// }
  525 +
  526 + // 未读邮件数量
  527 + $result['unseen'] = $unseen??0;
  528 + break;
  529 + }
  530 + case 'mail': {
  531 + $result = $this->emails();
  532 + break;
  533 + }
  534 + case 'add': {
  535 + $result = $this->emailAdd();
  536 + break;
  537 + }
  538 + case 'folder': {
  539 +
  540 + $email = $this->request->post('email');
  541 + if($email){
  542 + $ids = Email::_getIds($email);
  543 + }else{
  544 + $ids = $this->id;
  545 + }
  546 +
  547 + $result = $this->folders($ids);
  548 + break;
  549 + }
  550 + case 'contact': {
  551 + switch ($action){
  552 + case 'info': {
  553 + $id = $this->request->get('id');
  554 + $result = $this->contactInfo($id);
  555 + break;
  556 + }
  557 + case 'add': {
  558 + $result = $this->contactAdd();
  559 + break;
  560 + }
  561 + case 'del': {
  562 + $result = $this->contactDel();
  563 + break;
  564 + }
  565 + case 'group':{
  566 + $is_contact = $this->request->get('is_contact');
  567 + $result = $this->contactGroup($is_contact);
  568 + break;
  569 + }
  570 + case 'group_save': {
  571 + $name = $this->request->post('group_name');
  572 + $id = $this->request->post('id',0);
  573 + $result = $this->contactGroupSave($name,$id);
  574 + break;
  575 + }
  576 + case 'group_del': {
  577 + $group_id = (int) $this->request->get('group_id');
  578 + $result = $this->contactGroupDel($group_id);
  579 + break;
  580 + }
  581 + default: {
  582 + $is_group = $this->request->get('is_group',false);
  583 + $result = $this->contact($is_group);
  584 + break;
  585 + }
  586 + }
  587 + break;
  588 + }
  589 + // 下载附件
  590 + case 'download':{
  591 + $name = $this->request->get('name');
  592 + $originname = $this->request->get('originname');
  593 + return $this->download($name,$originname);
  594 + }
  595 + case 'contact_view':{
  596 + return $this->contact_view();
  597 + }
  598 + // 发送邮件
  599 + case 'send_email':{
  600 + $data = $this->request->post('to');
  601 +
  602 + // 文件收件人
  603 + $to = $this->request->file()['to']['to_email_file']??[];
  604 + if($to && $to instanceof UploadedFile){
  605 + $data['to'] = ($data['to']??'')."\n".$to->getContent();
  606 + }
  607 +
  608 +
  609 + // 附件
  610 + $data['file'] = $this->request->file()['to']['file']??[];
  611 + if($data['file']){
  612 +
  613 + $upconfig = [
  614 + // 上传文件的大小范围
  615 + 'size' => [
  616 + 'max' => 1024 * 1024 * 100, // 100M大了服务器不知道会发生什么
  617 + 'min' => 1, // 0k
  618 + ],
  619 + // 扩展名
  620 + 'ext' => [
  621 + 'xlsx', 'xltx', 'potx', 'ppsx', 'pptx', 'sldx', 'docx', 'dotx', 'xlam', 'xlsb',
  622 + 'apk', 'doc', 'pdf', 'xls', 'ppt', /*'jar', 'js', 'json', 'rpm',*/
  623 + 'swf', 'tar', 'zip', 'gif', 'png', 'flv', 'avi', 'ai', 'gz',
  624 + 'jpg', 'mov', 'mp3', 'mp4', 'txt', 'webm', 'webp',
  625 + ],
  626 + // mime类型
  627 + 'mime' => [
  628 + /*'xlsx' => */'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  629 + /*'xltx' => */'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
  630 + /*'potx' => */'application/vnd.openxmlformats-officedocument.presentationml.template',
  631 + /*'ppsx' => */'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
  632 + /*'pptx' => */'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  633 + /*'sldx' => */'application/vnd.openxmlformats-officedocument.presentationml.slide',
  634 + /*'docx' => */'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  635 + /*'dotx' => */'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
  636 + /*'xlam' => */'application/vnd.ms-excel.addin.macroEnabled.12',
  637 + /*'xlsb' => */'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
  638 + /*'apk' => */'application/vnd.android.package-archive',
  639 + /*'doc' => */'application/msword',
  640 + /*'pdf' => */'application/pdf',
  641 + /*'xls' => */'application/vnd.ms-excel',
  642 + /*'ppt' => */'application/vnd.ms-powerpoint',
  643 +// /*'jar' => */'application/java-archive',
  644 +// /*'js' => */'application/javascript',
  645 +// /*'json' => */'application/json',
  646 +// /*'rpm' => */'application/x-rpm',
  647 + /*'swf' => */'application/x-shockwave-flash',
  648 + /*'tar' => */'application/x-tar',
  649 + /*'zip' => */'application/zip',
  650 + /*'gif' => */'image/gif',
  651 + /*'png' => */'image/png',
  652 + /*'flv' => */'video/x-flv',
  653 + /*'avi' => */'video/x-msvideo',
  654 + /*'ai' => */'application/postscript',
  655 + /*'gz' => */'application/x-gzip',
  656 + /*'jpg' => */'image/jpeg',
  657 + /*'mov' => */'video/quicktime',
  658 + /*'mp3' => */'audio/mpeg',
  659 + /*'mp4' => */'video/mp4',
  660 + /*'txt' => */'text/plain',
  661 + /*'webm' => */'video/webm',
  662 + /*'webp' => */'image/webp',
  663 +
  664 + ],
  665 + // 磁盘
  666 + 'disk' => 'local2',
  667 + // 目录
  668 + 'path' => '/email/'.$this->username.'/',
  669 + ];
  670 + // 设置配置
  671 + Config::set('upload.email_upload',$upconfig);
  672 +
  673 + foreach ($data['file'] as $k=>$file){
  674 + if($file instanceof UploadedFile){
  675 + $data['file'][$k] = [
  676 + 'path' => Upload::put($file,'email_upload'),
  677 + 'origin_name' => $file->getClientOriginalName(),
  678 + ];
  679 + }
  680 +
  681 + }
  682 +
  683 + // 保存的目录
  684 + $path = config('filesystems.disks.local2.root');
  685 + foreach ($data['file'] as &$f){
  686 + $f['path'] = realpath($path.'/'.$f['path']);
  687 + }
  688 +
  689 + }
  690 +
  691 +
  692 + if($this->send($data)){
  693 + return $this->echoJson(1,'邮件发送成功');
  694 + }
  695 + }
  696 + }
  697 +
  698 + return $this->echoJson($result);
  699 +
  700 + }
  701 +
  702 + /**
  703 + * 自动路由时
  704 + * @return array
  705 + * @time 2022/8/1 16:28
  706 + */
  707 + public function getRoute(){
  708 + return [
  709 + [
  710 + 'name' => '同步文件夹',
  711 + 'route' => '?_task=sync&_action=folder'
  712 + ],
  713 + [
  714 + 'name' => '同步邮件列表',
  715 + 'route' => '?_task=sync&_action=list'
  716 + ],
  717 + [
  718 + 'name' => '邮件详情',
  719 + 'route' => '?_task=info&id=1972'
  720 + ],
  721 + [
  722 + 'name' => '邮件列表',
  723 + 'route' => '?_task=list'
  724 + ],
  725 + [
  726 + 'name' => '邮箱',
  727 + 'route' => '?_task=mail'
  728 + ],
  729 + [
  730 + 'name' => '添加邮箱',
  731 + 'route' => '?_task=add'
  732 + ],
  733 + [
  734 + 'name' => '邮箱联系人',
  735 + 'route' => '?_task=contact'
  736 + ],
  737 + ];
  738 + }
  739 +
  740 +
  741 + /**
  742 + * 同步文件夹
  743 + * 缺点,远程修改文件名称后,同步回来,就会新建文件夹,会导致邮件混乱,
  744 + * 处理方法,删除本地的,重新同步该文件夹下面的所有邮件,虽然不友好,也只能这样做。
  745 + * @time 2022/8/1 16:09
  746 + */
  747 + public function syncFolder($folder=''){
  748 +
  749 + // 同步单个文件夹
  750 + if($folder){
  751 + $status = $this->client()->selectFolder($this->folder);
  752 + // 更新数量
  753 + EmailFolder::_updateNum($this->folderId,$status['EXISTS']??null, $status['UNSEEN']??null);
  754 +
  755 + return EmailFolder::_firstAndEmailId($this->folderId,$this->id);
  756 + }
  757 +
  758 +
  759 + // 读取所有文件夹,未解密
  760 + $folders = $this->client()->getFolder();
  761 +
  762 + DB::beginTransaction();
  763 + foreach ($folders as $folder){
  764 +
  765 + // 处理子父文件夹
  766 + $folder['id'] = explode('/',$folder['folder']);
  767 + $folder['name'] = explode('/',$folder['parseFolder']);
  768 + $pid = 0;
  769 + foreach ($folder['id'] as $k=>$item){
  770 + // 插入到数据库
  771 + $pid = EmailFolder::_insert(
  772 + $this->user_id,
  773 + $this->id,
  774 + $folder['name'][$k],
  775 + $item,
  776 + $pid
  777 + );
  778 + }
  779 + }
  780 + DB::commit();
  781 +
  782 + return EmailFolder::_all($this->id);
  783 + }
  784 +
  785 + /**
  786 + * 同步当前用户的邮件数量
  787 + * @return array
  788 + * [surplus_num] 剩余待拉取数量
  789 + * [ids] 本次拉取后保存后的id
  790 + * @time 2022/8/2 14:06
  791 + */
  792 + public function syncEmailList($msgno=[],$use_email_id=0,$use_email='',$use_folder_id=0,$use_folder=''){
  793 +
  794 + $use_email_id = $use_email_id ? : $this->id;
  795 + $use_email = $use_email ? : $this->username;
  796 + $use_folder_id = $use_folder_id ? : $this->folderId;
  797 + $use_folder = $use_folder ? : $this->folder;
  798 +
  799 + // 零时增加时长
  800 +// set_time_limit(180);
  801 + // 选择文件夹
  802 + $status = $this->client($use_email)->selectFolder($use_folder);
  803 + if (!isset($status['EXISTS']) || !$status['EXISTS']){
  804 + return [
  805 + 'ids' => [],
  806 + 'folder' => $status,
  807 + 'end' => true
  808 + ];
  809 + }
  810 + $end = false;
  811 +
  812 + if($msgno){
  813 + goto SYNCEMAILLIST;
  814 + }
  815 +
  816 + // 最后拉取的时间,如果是第一次
  817 + $lastMsgno = EmailList::_lastMsgno($use_email_id, $use_folder_id);
  818 +
  819 + $nu = 20;
  820 + if(!$msgno){
  821 + if(!$lastMsgno){
  822 + $msgno = range(1,$nu);
  823 + }else{
  824 + $msgno = range($lastMsgno,$lastMsgno+$nu);
  825 +
  826 + if($lastMsgno > $status['EXISTS']){
  827 + $msgno = range($status['EXISTS'] > $nu ? $status['EXISTS'] - $nu : 1,$status['EXISTS']);
  828 + }
  829 + // 一样就不拉新的
  830 + if($lastMsgno == $status['EXISTS']){
  831 + return [
  832 + 'ids' => [],
  833 + 'folder' => $status,
  834 + 'end' => true,
  835 + ];
  836 + }
  837 + }
  838 + }
  839 +
  840 + // 更新数量
  841 + EmailFolder::_updateNum($use_folder_id,$status['EXISTS'], $status['UNSEEN']??null);
  842 +
  843 + // 说明是第一次
  844 + if(!$lastMsgno){
  845 + $pgnu = 1000;
  846 + DB::beginTransaction();
  847 + for ($n=0;$n<$status['EXISTS']/$pgnu;$n++){
  848 +
  849 + $i = ($pgnu*$n)+1;
  850 + $max = $i+$pgnu;
  851 + $max = $max > $status['EXISTS'] ? $status['EXISTS']+1 : $max;
  852 + $sql = [];
  853 + while ($i<$max){
  854 + $sql[] = "(".$use_email_id.",{$i},".$use_folder_id.",1)";
  855 + $i++;
  856 + }
  857 +// bug 使用事务,无法插入数据
  858 + $ret = DB::insert("insert INTO email_lists (`email_id`,`msgno`,`folder_id`,`seen`) VALUES ".implode(',',$sql));
  859 + if(!$ret){
  860 + DB::rollBack();
  861 + abort(500,'同步失败');
  862 + }
  863 + }
  864 + DB::commit();
  865 + // 最新的数量
  866 + $msgno = range($status['EXISTS'] > $nu ? $status['EXISTS'] - $nu : 1,$status['EXISTS']);
  867 + // 不同步
  868 + return [
  869 + 'ids' => [1],
  870 + 'folder' => $status,
  871 + 'end' => $end,
  872 + ];
  873 + }
  874 + SYNCEMAILLIST:
  875 + // 是否有id
  876 + $dataids = EmailList::_getIdsByMsgno($use_email_id,$use_folder_id,$msgno);
  877 +
  878 + $this->client($use_email)->debug(false,storage_path('logs'));
  879 + // 循环
  880 + $results = $this->client($use_email)->fetchHeader($msgno);
  881 +
  882 + if($results){
  883 + DB::beginTransaction();
  884 + // 批量插入
  885 + foreach ($results as $key=>$result){
  886 + if($key == $status['EXISTS']){
  887 + $end = true;
  888 + }
  889 +// $header = $this->client($use_email)->getHeader($result['RFC822.HEADER']??'');
  890 + $header = &$result['HEADER.FIELDS'];
  891 +
  892 + foreach ($result['FLAGS'] as $k=>$FLAG){
  893 + $result['FLAGS'][$k] = strtolower(str_replace('\\','',$FLAG));
  894 + }
  895 + try {
  896 +
  897 + $file_header = &$result['BODYSTRUCTURE'];
  898 +
  899 + // 没有收件人
  900 + if(!empty($header['To'])){
  901 + $header['To'] = MailFun::toOrFrom($header['To']);
  902 + }else{
  903 + $header['To'] = [];
  904 + }
  905 +
  906 +
  907 + $header['From'] = MailFun::toOrFrom($header['From']);
  908 +
  909 + $data = [
  910 + 'id' => $dataids[$key]??0,
  911 + 'msgno' => $key,
  912 + 'uid' => $result['UID'],
  913 + 'subject' => $header['Subject'],
  914 +// 'cc' => $header['Cc']??'',
  915 + 'from' => $header['From'][0]['email']??'',
  916 + 'from_name' => $header['From'][0]['name']??'',
  917 + 'to' => $header['To']?implode(',',array_column($header['To'],'email')):'',
  918 + 'to_name' => json_encode($header['To']),
  919 + 'date' => isset($header['Date'])&&$header['Date'] ? strtotime(is_array($header['Date']) ? $header['Date'][0] : $header['Date']) : strtotime($result['INTERNALDATE']),
  920 + 'message_id' => $header['Message-ID']??'',
  921 + 'udate' => strtotime($result['INTERNALDATE']),
  922 +// 'size' => $result['RFC822.SIZE'],
  923 + 'recent' => in_array('recent',$result['FLAGS']),
  924 + 'seen' => in_array('seen',$result['FLAGS']),
  925 + 'draft' => in_array('draft',$result['FLAGS']),
  926 + 'flagged' => in_array('flagged',$result['FLAGS']),
  927 + 'answered' => in_array('answered',$result['FLAGS']),
  928 + 'folder_id' => $use_folder_id,
  929 + 'email_id' => $use_email_id,
  930 + 'uuid' => $use_email_id.$use_folder_id.$result['UID'],
  931 + 'is_file' => MailFun::isFile($file_header[$key]['BODYSTRUCTURE']??[]) //是否附件
  932 + ];
  933 + }catch (\Throwable $e){
  934 + Log::error('邮件解析失败:'.$e->getMessage().print_r($result,true));
  935 + unset($results[$key]);
  936 + continue;
  937 + }
  938 +
  939 + $results[$key] = $data;
  940 + }
  941 + $ids = EmailList::_insertAll(array_values($results));
  942 + // 提交
  943 + DB::commit();
  944 + }else{
  945 + $end = true;
  946 + }
  947 +
  948 +
  949 +
  950 +
  951 + return [
  952 + 'ids' => $ids??[],
  953 + 'folder' => $status,
  954 + 'end' => $end,
  955 + ];
  956 +
  957 + }
  958 +
  959 +
  960 + /**
  961 + * 同步body
  962 + * @param int $uid
  963 + * @param int $id
  964 + * @return mixed
  965 + * @time 2022/8/2 15:11
  966 + */
  967 + public function syncEMailBody(int $uid, $id = 0 ){
  968 +
  969 +// $this->client()->debug(true,storage_path('logs'));
  970 +
  971 + $this->client()->selectFolder($this->folder);
  972 + $body = $this->client()->fetch([$uid],'body',true);
  973 +
  974 + /******* start ********/
  975 + // 记录原始数据,方便分析
  976 + $path = storage_path('logs/email/'.$this->username.'/');
  977 + if(!is_dir($path)){
  978 + mkdir($path,0775,true);
  979 + }
  980 + file_put_contents($path.$id.'.'.time().'.log',print_r($body,true));
  981 + /******** end *******/
  982 +
  983 + if($body && $id){
  984 + $body = array_values($body);
  985 +
  986 + $body = (new Body($body[0]['RFC822.TEXT'],$this->getFilePath()))->getItem();
  987 +
  988 +
  989 + EmailBody::_insert(
  990 + $id,
  991 + $body
  992 + );
  993 + }
  994 +
  995 + return EmailBody::_first($id);
  996 + }
  997 +
  998 +
  999 + /**
  1000 + * 设置标记
  1001 + * @param array $uids
  1002 + * @param string $flags
  1003 + * @param string $mod +|-
  1004 + * @return bool
  1005 + * @throws \Exception
  1006 + * @author:dc
  1007 + * @time 2022/10/27 14:22
  1008 + */
  1009 + public function setflagged(array $uids,string $flags,string $mod){
  1010 + // 选择目录
  1011 + $status = $this->client()->selectFolder($this->folder);
  1012 +
  1013 + return $this->client()->flags($uids,[$flags],$mod,true);
  1014 + }
  1015 +
  1016 + /**
  1017 + * 设置为已读
  1018 + * @param int|array $uids
  1019 + * @return bool
  1020 + * @throws \Exception
  1021 + * @author:dc
  1022 + * @time 2022/10/26 17:08
  1023 + */
  1024 + public function setSeen($uids):bool{
  1025 + // 选择目录
  1026 + $status = $this->client()->selectFolder($this->folder);
  1027 +
  1028 + return $this->client()->flags($uids,[\App\Http\Mail\lib\client\Imap::FLAGS_SEEN],'+',true);
  1029 + }
  1030 +
  1031 + /**
  1032 + * 设置为未读
  1033 + * @param $uids
  1034 + * @return bool
  1035 + * @throws \Exception
  1036 + * @author:dc
  1037 + * @time 2022/10/26 17:11
  1038 + */
  1039 + public function delSeen($uids):bool{
  1040 + // 选择目录
  1041 + $status = $this->client()->selectFolder($this->folder);
  1042 +
  1043 + return $this->client()->flags($uids,[\App\Http\Mail\lib\client\Imap::FLAGS_SEEN],'-',true);
  1044 + }
  1045 +
  1046 +
  1047 +
  1048 + /**
  1049 + * 邮件列表
  1050 + * @param array $mail_id
  1051 + * @param array $folder
  1052 + * @param int $total
  1053 + * @return mixed|object
  1054 + * @throws \Illuminate\Contracts\Container\BindingResolutionException
  1055 + * @time 2022/8/16 17:34
  1056 + */
  1057 + public function emailLists($mail_id = [], $folder = [], $total = 0, $search = []){
  1058 +
  1059 + return EmailList::_paginate(function ($query) use ($mail_id, $folder,$search){
  1060 + $query->whereIn('email_id',$mail_id)->whereIn('folder_id',$folder);
  1061 + // 搜索
  1062 + if(!empty($search['search'])){
  1063 + $search = htmlspecialchars($search);
  1064 + $query->where('subject','like',"%{$search}%");
  1065 + }
  1066 + // 已读
  1067 + if(!empty($search['seen']) && $search['seen']){
  1068 + $query->where('seen',0);
  1069 + }
  1070 + // 过滤
  1071 + $query->where([
  1072 + ['subject','not like','%- snv'],
  1073 + ['subject','not like','%(Failure)'],
  1074 + ['subject','not like','%(Delay)']
  1075 + ]);
  1076 + },20,$total);
  1077 + }
  1078 +
  1079 + /**
  1080 + * 读取邮件详情
  1081 + * @param $id
  1082 + * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
  1083 + * @time 2022/8/2 15:13
  1084 + */
  1085 + public function emailInfo($id){
  1086 + $email = EmailList::_firstWithBody($id);
  1087 + // 是否存在,并且是否属于自己
  1088 + if($email && in_array($email->email_id,$this->email_ids)){
  1089 + if(!$email->body && $email['uid']){
  1090 + $email = $email->toArray();
  1091 + try {
  1092 + $email['body'] = $this->syncEMailBody($email['uid'],$email['id']);
  1093 + }catch (\Throwable $e){
  1094 + $email['body'] = $this->syncEMailBody($email['uid'],$email['id']);
  1095 + }
  1096 + }
  1097 + return $email;
  1098 + }
  1099 + return null;
  1100 + }
  1101 +
  1102 +
  1103 +
  1104 +
  1105 + /**
  1106 + * 获取邮件服务器
  1107 + * @param string $suffix
  1108 + * @return array
  1109 + * @time 2022/7/29 16:24
  1110 + */
  1111 + public function host(string $suffix=''):array {
  1112 + if($suffix){
  1113 + return EmailHost::_get($suffix);
  1114 + }
  1115 + return EmailHost::_all();
  1116 + }
  1117 +
  1118 + /**
  1119 + * 获取自己的服务器地址
  1120 + * @param null $name
  1121 + * @return array|mixed|string
  1122 + * @time 2022/8/1 15:46
  1123 + */
  1124 + public function hostMy($name=null) {
  1125 +
  1126 +// if(!$this->host){
  1127 +// $data = Email::_first($this->id);
  1128 +// $this->host = [
  1129 +// 'imap' => $data['imap'],
  1130 +// 'smtp' => $data['smtp'],
  1131 +// ];
  1132 +// }
  1133 +
  1134 + if($name){
  1135 + return $this->host[$name]??'';
  1136 + }
  1137 +
  1138 + return $this->host;
  1139 + }
  1140 +
  1141 + /**
  1142 + * 添加邮件服务器
  1143 + * @param string $suffix
  1144 + * @param string $imap
  1145 + * @param string $smtp
  1146 + * @return int email host 表的id
  1147 + * @time 2022/7/29 16:28
  1148 + */
  1149 + public function hostAdd(string $suffix, string $imap, string $smtp):int{
  1150 + return EmailHost::_insert($suffix, $imap, $smtp);
  1151 + }
  1152 +
  1153 +
  1154 + /**
  1155 + * 添加一个邮箱地址
  1156 + * @time 2022/7/29 16:51
  1157 + */
  1158 + public function emailAdd($data=[]){
  1159 + $data = $data ? $data : $this->request->post();
  1160 + if(!$data){
  1161 + throw new \Exception(Lang::__('empty_form'));
  1162 + }
  1163 +
  1164 + $validator = Validator::make($data,[
  1165 + 'email' => ['required','email'],
  1166 + 'password' => ['required'],
  1167 + ],[
  1168 + 'email.required' => 'email_required',
  1169 + 'email.email' => 'email_validator',
  1170 + 'password.required' => 'password_required',
  1171 + ]);
  1172 +
  1173 + if($validator->fails()){
  1174 + throw new \Exception(Lang::__($validator->errors()->first()));
  1175 + }
  1176 + $data['email'] = trim($data['email']);
  1177 + $data['password'] = trim($data['password']);
  1178 + $data['email_name'] = trim($data['email_name']??$data['email']);
  1179 +
  1180 + // 检查imap服务器
  1181 + if($data['host']??''){
  1182 + $host = [
  1183 + 'imap' => trim($data['host']),
  1184 + 'smtp' => trim($data['smtp']??'')
  1185 + ];
  1186 + }else{
  1187 + // 获取数据库中有的
  1188 + $host = $this->host(explode('@',$data['email'])[1]);
  1189 + }
  1190 +
  1191 + if(!$host){
  1192 + // 是否存在imap服务器
  1193 + throw new \Exception(Lang::__('host_required'));
  1194 + }
  1195 +
  1196 + // 登录名
  1197 + $this->username = $data['email'];
  1198 +
  1199 + // 进行远程登录
  1200 + // imap imap.qq.com
  1201 + $imap = new \App\Http\Mail\lib\client\Imap();
  1202 + $imap->login($this->ssl.$host['imap'].':'.$this->port,$data['email'],$data['password']);
  1203 +
  1204 + // 添加邮箱,并绑定user_id
  1205 + $model = Email::_add($this->user_id, $data['email'], $data['password'], $data['email_name'],$host);
  1206 +
  1207 + try {
  1208 + // 添加一个联系人的默认分组
  1209 + $this->contactGroupSave('默认分组');
  1210 + }catch (\Throwable $e){
  1211 +
  1212 + }
  1213 +
  1214 +
  1215 + // 退出imap登录
  1216 + $imap->loginOut();
  1217 +
  1218 + return $model;
  1219 +
  1220 +// throw new \Exception(Lang::__('email_insert_error'));
  1221 + }
  1222 +
  1223 + /**
  1224 + * 读取所有邮箱
  1225 + * @return array
  1226 + * @time 2022/8/1 9:34
  1227 + */
  1228 + public function emails():array {
  1229 + return Email::_get($this->user_id);
  1230 + }
  1231 +
  1232 + /**
  1233 + * 文件夹
  1234 + * @return array
  1235 + * @time 2022/8/4 17:34
  1236 + */
  1237 + public function folders($id){
  1238 +
  1239 + $result = EmailFolder::_all($id);
  1240 + if(!$result){
  1241 + $result = $this->syncFolder();
  1242 + }
  1243 + return $result;
  1244 + }
  1245 +
  1246 +
  1247 + /**
  1248 + * 联系人列表
  1249 + * @return mixed
  1250 + * @time 2022/8/4 9:05
  1251 + */
  1252 + public function contact($is_group=false){
  1253 +
  1254 + return EmailContact::_all($this->id,$is_group);
  1255 + }
  1256 +
  1257 + /**
  1258 + * 联系人管理视图
  1259 + * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
  1260 + * @author:dc
  1261 + * @time 2022/11/2 16:37
  1262 + */
  1263 + public function contact_view(){
  1264 +
  1265 +
  1266 + return view('admin/email/contacts',[
  1267 +// 'emails' => $this->emails(),
  1268 + 'email' => $this->username,
  1269 +// 'groups' => $this->contactGroup(),
  1270 +// 'contacts' => $this->contact()
  1271 + ]);
  1272 + }
  1273 +
  1274 +
  1275 + /**
  1276 + * 添加联系人
  1277 + * @return EmailContact
  1278 + * @throws \Exception
  1279 + * @time 2022/8/4 9:46
  1280 + */
  1281 + public function contactAdd(){
  1282 +
  1283 + $data = $this->request->post();
  1284 +
  1285 + $validator = Validator::make($data,[
  1286 + 'group_id' => ['required'],
  1287 + 'email' => ['required','email'],
  1288 + 'email_name' => ['required','max:100'],
  1289 + 'remark' => ['max:200'],
  1290 + ],[
  1291 + 'group_id.required' => 'contact_group_exists',
  1292 + 'email.required' => 'contact_email_required',
  1293 + 'email.email' => 'contact_email_error',
  1294 + 'email_name.required' => 'contact_email_name_required',
  1295 + 'email_name.max' => 'contact_email_name_max',
  1296 + 'remark.max' => 'contact_remark_max',
  1297 + ]);
  1298 + // 验证数据
  1299 + if($validator->fails()){
  1300 +
  1301 + $p = '';
  1302 +
  1303 + switch ($validator->errors()->first()){
  1304 + case 'contact_email_name_max': $p = '100';break;
  1305 + case 'contact_remark_max': $p = '200';break;
  1306 + }
  1307 +
  1308 + throw new \Exception(
  1309 + Lang::__(
  1310 + $validator->errors()->first(),
  1311 + $p
  1312 + )
  1313 + );
  1314 + }
  1315 +
  1316 + $group = EmailContactGroup::_first($data['group_id']);
  1317 + if(!$group || $group['email_id']!=$this->id){
  1318 + abort(600,Lang::__('contact_group_exists'));
  1319 + }
  1320 +
  1321 +
  1322 + return EmailContact::_save($this->id,$data);
  1323 + }
  1324 +
  1325 +
  1326 + /**
  1327 + * 联系人详情
  1328 + * @param $id
  1329 + * @return array
  1330 + * @author:dc
  1331 + * @time 2022/11/4 17:02
  1332 + */
  1333 + public function contactInfo($id){
  1334 + return EmailContact::_first($this->id,$id);
  1335 + }
  1336 +
  1337 +
  1338 + /**
  1339 + * 删除联系人
  1340 + * @time 2022/8/4 9:52
  1341 + */
  1342 + public function contactDel(){
  1343 + $contact_id = (int) $this->request->get('contact_id');
  1344 + if(!$contact_id || EmailContact::_del($this->id,$contact_id)){
  1345 + abort(600,Lang::__('del_error'));
  1346 + }
  1347 + return true;
  1348 + }
  1349 +
  1350 + /**
  1351 + * 联系人分组
  1352 + * @return mixed
  1353 + * @time 2022/8/4 10:13
  1354 + */
  1355 + public function contactGroup($is_contact=false){
  1356 + return EmailContactGroup::_all($this->id,$is_contact);
  1357 + }
  1358 +
  1359 + /**
  1360 + * 添加联系人分组
  1361 + * @return EmailContactGroup
  1362 + * @throws \Exception
  1363 + * @time 2022/8/4 10:47
  1364 + */
  1365 + public function contactGroupSave($name,$id=0){
  1366 + if(!$name || mb_strlen($name)>100){
  1367 + throw new \Exception(Lang::__('contact_group_name_error',1,100));
  1368 + }
  1369 +
  1370 + $name = htmlspecialchars($name);
  1371 +
  1372 + $data = EmailContactGroup::_firstByName($this->id,$name);
  1373 + if(($data && !$id) || ($data && $id && $data['id'] != $id)){
  1374 + throw new \Exception(Lang::__('contact_group_name_unique'));
  1375 + }
  1376 +
  1377 + return EmailContactGroup::_save($this->id,$name,$id);
  1378 + }
  1379 +
  1380 + /**
  1381 + * 删除
  1382 + * @return false|mixed
  1383 + * @time 2022/8/4 10:49
  1384 + */
  1385 + public function contactGroupDel($group_id){
  1386 +
  1387 + if(!$group_id){
  1388 + GROUP_DEL_ABORT:
  1389 + abort(600,Lang::__('contact_group_del_id'));
  1390 + }
  1391 +
  1392 + // 是否有联系人在其中
  1393 + if(EmailContact::_count($group_id, $this->id)){
  1394 + abort(600,Lang::__('contact_group_del_contact'));
  1395 + }
  1396 +
  1397 + // 删除
  1398 + if(!EmailContactGroup::_del($group_id, $this->id)){
  1399 + goto GROUP_DEL_ABORT;
  1400 + }
  1401 +
  1402 + return true;
  1403 + }
  1404 +
  1405 +
  1406 +
  1407 +
  1408 +
  1409 + /**
  1410 + * 发送邮件
  1411 + * @time 2022/8/3 16:08
  1412 + */
  1413 + public function send($data=[]){
  1414 +
  1415 + $data = $data ? $data : $this->request->post();
  1416 +
  1417 + $data['is_text'] = intval($data['is_text']??0);//纯文本
  1418 +// $data['save_send'] = intval($data['save_send']??0);//保存到已发送
  1419 + $data['priority'] = intval($data['priority']??0);//紧急
  1420 + $data['receipt'] = intval($data['receipt']??0);//需要回执
  1421 + $data['encrypt'] = intval($data['encrypt']??0);//加密邮件
  1422 +
  1423 +
  1424 +
  1425 + // 是纯文本还是html
  1426 + $data['body'] = $data['is_text'] ? ($data['text']??'') : ($data['html']??'');
  1427 +
  1428 +// 是否是回复邮件
  1429 + if(!empty($data['reply']['id']) && !empty($data['reply']['email_id'])){
  1430 +
  1431 + // 标题,主题
  1432 + if(empty($data['subject'])){
  1433 + throw new \Exception(Lang::__('send_email_subject_error',500));
  1434 + }
  1435 +
  1436 + // 查询
  1437 + $originData = EmailList::_first($data['reply']['id']);
  1438 + // 是否存在邮件
  1439 + if($originData && $originData['email_id'] == $data['reply']['email_id'] && in_array($originData['email_id'],$this->email_ids)){
  1440 + $emaildata = Email::_firstById($originData['email_id']);
  1441 + if(!$emaildata){
  1442 + throw new \Exception('发件人异常',600);
  1443 + }
  1444 + try {
  1445 + // 立刻发送邮件
  1446 + MailFun::sendEmail(
  1447 + $emaildata['smtp'],$emaildata['email'],$emaildata['password'],
  1448 + $emaildata['email_name'],['email'=>$originData['from'],'name'=>$originData['from_name']]
  1449 + ,$data['subject'],$data['body'],$data['file']??[],$data['receipt'],$data['priority']?1:3
  1450 + );
  1451 + // 记录下
  1452 + EmailLog::error(print_r([
  1453 + 'manage_id'=>the_manage('id'),
  1454 + 'data' => $data,
  1455 + '回复邮件'
  1456 + ],true));
  1457 + return true;
  1458 + }catch (\Throwable $e){
  1459 + throw new \Exception($e->getMessage());
  1460 + }
  1461 + }
  1462 + }
  1463 +
  1464 +
  1465 + // 收件人
  1466 + if(empty($data['to'])){
  1467 + SEND_EMAIL_TO_ERROR:
  1468 + throw new \Exception(Lang::__('send_email_to_error'));
  1469 + }
  1470 + // 是否是数组
  1471 + $data['to'] = explode("\n",trim($data['to']));
  1472 + if(!$data['to']){
  1473 + goto SEND_EMAIL_TO_ERROR;
  1474 + }
  1475 +// $to = [
  1476 +// 'email' => 'xxx@qq.com',
  1477 +// 'name' => 'xxx'
  1478 +// ];
  1479 + foreach ($data['to'] as $k=>$to){
  1480 + $to = trim($to);
  1481 + $data['to'][$to] = [];
  1482 + $data['to'][$to]['email'] = $to;
  1483 + $data['to'][$to]['name'] = explode('@',$to)[0];
  1484 +
  1485 + // 是否是邮箱
  1486 + if(!preg_match('/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/',$data['to'][$to]['email'])){
  1487 +// throw new \Exception(Lang::__('send_email_to_error',$data['to'][$to]['email']));
  1488 + unset($data['to'][$to]);
  1489 + }
  1490 +
  1491 + unset($data['to'][$k]);
  1492 + }
  1493 +
  1494 + $data['to'] = array_values($data['to']);
  1495 + if(!$data['to']){
  1496 + goto SEND_EMAIL_TO_ERROR;
  1497 + }
  1498 +
  1499 + // 时间
  1500 + if($data['send_time'] != 'now'){
  1501 + $data['send_time'] = explode(',',$data['send_time']);
  1502 + if($data['send_time'][0] < 24 && $data['send_time'][1] < 24){
  1503 + $data['send_time'] = implode(',',$data['send_time']);
  1504 + }else{
  1505 + $data['send_time'] = '21,11';
  1506 + }
  1507 + }
  1508 +
  1509 + $tags = get('tags');
  1510 + if (!$tags || !is_array($tags)){
  1511 + throw new \Exception('请选择tag标签');
  1512 + }
  1513 +
  1514 +
  1515 + // 加入任务
  1516 + $job_id = EmailSendJob::_insert([
  1517 + 'title' => $data['subject']??'',
  1518 + 'to' => $data['to'],
  1519 + 'the_manage_id' => the_manage('id'),
  1520 + 'email_id' => $this->id,
  1521 + 'user_id' => $this->user_id,
  1522 + 'data' => $data,
  1523 + 'send_time' => $data['send_time'],
  1524 + 'tags' => $tags
  1525 + ]);
  1526 + if($job_id){
  1527 + // 发送一个任务
  1528 + SendJob::dispatch($job_id);
  1529 + return $job_id;
  1530 + }
  1531 +
  1532 + throw new \Exception(Lang::__('email_send_error'));
  1533 + }
  1534 +
  1535 +
  1536 + /**
  1537 + * 下载附件
  1538 + * @param $name
  1539 + * @param string $originname
  1540 + * @return string|\Symfony\Component\HttpFoundation\BinaryFileResponse
  1541 + * @author:dc
  1542 + * @time 2022/11/2 10:59
  1543 + */
  1544 + public function download($name,$originname=''){
  1545 + $file = $this->getFilePath().$name;
  1546 + if(is_file($file)){
  1547 + return response()->download($file,$originname ? : $name);
  1548 + }
  1549 + return '';
  1550 + }
  1551 +
  1552 +
  1553 +
  1554 +
  1555 +
  1556 + /**
  1557 + * @param mixed $data
  1558 + * @param string $message
  1559 + * @param int $status
  1560 + * @return \Illuminate\Http\JsonResponse
  1561 + * @author:dc
  1562 + * @time 2022/8/1 11:05
  1563 + */
  1564 + private function echoJson($data='', $message='', $status=200):\Illuminate\Http\JsonResponse{
  1565 + $data = [
  1566 + 'data' => $data,
  1567 + 'message' => $message,
  1568 + 'status' => $status
  1569 + ];
  1570 + return response()->json($data,200,[]);
  1571 + }
  1572 +
  1573 +}
  1 +<?php
  2 +
  3 +
  4 +
  5 +return $message = [
  6 + 'imap_server_error' => 'IMAP server error: %s',
  7 + 'login_host' => 'Enter the IMAP server address.',
  8 + 'empty_form' => 'The form data is empty.',
  9 + 'password_required' => 'The password field is required.',
  10 + 'password_error' => 'wrong password.',
  11 + 'host_required' => 'The IMAP server address must be.',
  12 + 'email_required' => 'Email address must be.',
  13 + 'email_validator' => 'Email format error.',
  14 + 'email_insert_error' => 'Failed to Add Mailbox.',
  15 + 'contact_email_required' => 'Contact email address must be specified.',
  16 + 'contact_group_exists' => 'The selected group does not exist.',
  17 + 'contact_email_error' => 'The email format of the contact is incorrect.',
  18 + 'contact_email_name_required' => 'Contact name must be entered.',
  19 + 'contact_email_name_max' => 'Contact name Maximum character %s.',
  20 + 'contact_group_name_error' => 'The contact group must be longer than %s and smaller than %s characters.',
  21 + 'contact_group_name_unique' => 'The contact group already exists.',
  22 + 'contact_group_del_id' => 'Grouping does not exist.',
  23 + 'contact_remark_max' => 'The contact remarks must contain %s characters.',
  24 + 'contact_group_del_contact' => 'There are contacts in the group and cannot be deleted.',
  25 + 'email_folder_not' => 'Folder (%s) does not exist.',
  26 + 'email_not_bind' => 'Mailbox (%s) is not bound.',
  27 + 'email_send_error' => 'Failed to send message (%s).',
  28 + 'del_error' => 'fail to delete.',
  29 + 'send_email_subject_error' => 'The message subject must be within the %s character.',
  30 + 'send_email_to_error' => 'The recipient must or is formatted incorrectly, %s.',
  31 +];
  1 +<?php
  2 +
  3 +
  4 +
  5 +return $message = [
  6 + 'imap_server_error' => 'IMAP服务器错误: %s',
  7 + 'login_host' => '请输入imap服务器地址',
  8 + 'empty_form' => '表单数据为空',
  9 + 'password_required' => '密码必须',
  10 + 'password_error' => '密码错误',
  11 + 'host_required' => 'imap服务器地址必须',
  12 + 'email_required' => '邮箱必须',
  13 + 'email_validator' => '邮箱格式错误',
  14 + 'email_insert_error' => '添加邮箱失败',
  15 + 'contact_email_required' => '联系人邮箱必须填写',
  16 + 'contact_group_exists' => '选择的分组不存在',
  17 + 'contact_email_error' => '联系人邮箱格式错误',
  18 + 'contact_email_name_required' => '联系人姓名必须填写',
  19 + 'contact_email_name_max' => '联系人姓名最大字符%s',
  20 + 'contact_group_name_error' => '联系人分组必须大于%s且小于%s字符',
  21 + 'contact_group_name_unique' => '联系人分组已存在',
  22 + 'contact_group_del_id' => '分组不存在',
  23 + 'contact_group_del_contact' => '分组下还有联系人,无法删除',
  24 + 'contact_remark_max' => '联系人备注在%s字符内',
  25 + 'email_folder_not' => '文件夹(%s)不存在',
  26 + 'email_not_bind' => '邮箱(%s)未绑定',
  27 + 'email_send_error' => '邮件发送失败(%s)',
  28 + 'del_error' => '删除失败',
  29 + 'send_email_subject_error' => '邮件主题必须且在%s字符内',
  30 + 'send_email_to_error' => '收件人必须或格式错误,%s',
  31 +];
  1 +<?php
  2 +
  3 +namespace App\Mail\lib;
  4 +
  5 +/**
  6 + * 中文情况
  7 + * @time 2022/8/1 15:32
  8 + * Class ImapUtf7
  9 + * @package App\Mail\lib
  10 + */
  11 +class ImapUtf7 {
  12 + static $imap_base64 =
  13 + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,';
  14 + static private function encode_b64imap($s) {
  15 + $a=0; $al=0; $res=''; $n=strlen($s);
  16 + for($i=0;$i<$n;$i++) {
  17 + $a=($a<<8)|ord($s[$i]); $al+=8;
  18 + for(;$al>=6;$al-=6) $res.=self::$imap_base64[($a>>($al-6))&0x3F];
  19 + }
  20 + if ($al>0) { $res.=self::$imap_base64[($a<<(6-$al))&0x3F]; }
  21 + return $res;
  22 + }
  23 + static private function encode_utf8_char($w) {
  24 + if ($w&0x80000000) return '';
  25 + if ($w&0xFC000000) $n=5; else
  26 + if ($w&0xFFE00000) $n=4; else
  27 + if ($w&0xFFFF0000) $n=3; else
  28 + if ($w&0xFFFFF800) $n=2; else
  29 + if ($w&0xFFFFFF80) $n=1; else return chr($w);
  30 + $res=chr(( (255<<(7-$n)) | ($w>>($n*6)) )&255);
  31 + while(--$n>=0) $res.=chr((($w>>($n*6))&0x3F)|0x80);
  32 + return $res;
  33 + }
  34 + static private function decode_b64imap($s) {
  35 + $a=0; $al=0; $res=''; $n=strlen($s);
  36 + for($i=0;$i<$n;$i++) {
  37 + $k=strpos(self::$imap_base64,$s[$i]); if ($k===FALSE) continue;
  38 + $a=($a<<6)|$k; $al+=6;
  39 + if ($al>=8) { $res.=chr(($a>>($al-8))&255);$al-=8; }
  40 + }
  41 + $r2=''; $n=strlen($res);
  42 + for($i=0;$i<$n;$i++) {
  43 + $c=ord($res[$i]); $i++;
  44 + if ($i<$n) $c=($c<<8) | ord($res[$i]);
  45 + $r2.=self::encode_utf8_char($c);
  46 + }
  47 + return $r2;
  48 + }
  49 + static function encode($s) {
  50 + $n=strlen($s);$err=0;$buf='';$res='';
  51 + for($i=0;$i<$n;) {
  52 + $x=ord($s[$i++]);
  53 + if (($x&0x80)==0x00) { $r=$x;$w=0; }
  54 + else if (($x&0xE0)==0xC0) { $w=1; $r=$x &0x1F; }
  55 + else if (($x&0xF0)==0xE0) { $w=2; $r=$x &0x0F; }
  56 + else if (($x&0xF8)==0xF0) { $w=3; $r=$x &0x07; }
  57 + else if (($x&0xFC)==0xF8) { $w=4; $r=$x &0x03; }
  58 + else if (($x&0xFE)==0xFC) { $w=5; $r=$x &0x01; }
  59 + else if (($x&0xC0)==0x80) { $w=0; $r=-1; $err++; }
  60 + else { $w=0;$r=-2;$err++; }
  61 + for($k=0;$k<$w && $i<$n; $k++) {
  62 + $x=ord($s[$i++]); if ($x&0xE0!=0x80) { $err++; }
  63 + $r=($r<<6)|($x&0x3F);
  64 + }
  65 + if ($r<0x20 || $r>0x7E ) {
  66 + $buf.=chr(($r>>8)&0xFF); $buf.=chr($r&0xFF);
  67 + } else {
  68 + if (strlen($buf)) {
  69 + $res.='&'.self::encode_b64imap($buf).'-';
  70 + $buf='';
  71 + }
  72 + if ($r==0x26) { $res.='&-'; } else $res.=chr($r);
  73 + }
  74 + }
  75 + if (strlen($buf)) $res.='&'.self::encode_b64imap($buf).'-';
  76 + return $res;
  77 + }
  78 + static function decode($s) {
  79 + $res=''; $n=strlen($s); $h=0;
  80 + while($h<$n) {
  81 + $t=strpos($s,'&',$h); if ($t===false) $t=$n;
  82 + $res.=substr($s,$h,$t-$h); $h=$t+1; if ($h>=$n) break;
  83 + $t=strpos($s,'-',$h); if ($t===false) $t=$n;
  84 + $k=$t-$h;
  85 + if ($k==0) $res.='&';
  86 + else $res.=self::decode_b64imap(substr($s,$h,$k));
  87 + $h=$t+1;
  88 + }
  89 + return $res;
  90 + }
  91 +}
  1 +<?php
  2 +
  3 +namespace App\Mail\lib;
  4 +
  5 +/**
  6 + * @time 2022/8/3 9:42
  7 + * Class Lang
  8 + * @package App\Mail\lib
  9 + */
  10 +class Lang {
  11 +
  12 + public static $l;
  13 +
  14 + public static $data;
  15 +
  16 + /**
  17 + * @param $key
  18 + * @param mixed ...$var
  19 + * @return mixed
  20 + * @time 2022/8/3 9:41
  21 + */
  22 + public static function __($key,...$var){
  23 + // 是否加载语言
  24 + if (!isset(self::$data[self::$l])) self::load();
  25 + // 获取语言
  26 + $str = self::$data[self::$l][$key]??self::$data['en'][$key]??$key;
  27 +
  28 + // 数组填充到10个元素,避免出现元素不够而返回空字符
  29 + $var = array_pad($var,10,'');
  30 + // 替换
  31 + return @sprintf($str, ...$var);
  32 +
  33 + }
  34 +
  35 +
  36 + /**
  37 + * 加载
  38 + * @param string $l
  39 + * @time 2022/8/4 15:55
  40 + */
  41 + public static function load(string $l = ''){
  42 + self::$l = strtolower($l ? $l : app()->getLocale());
  43 +
  44 + self::$data[self::$l] = require_once __DIR__.'/../lang/'.self::$l.'.php';
  45 +
  46 + // default
  47 + if(!isset(self::$data['en'])){
  48 + self::$data['en'] = require_once __DIR__.'/../lang/en.php';
  49 + }
  50 +
  51 + }
  52 +
  53 +}
  1 +<?php
  2 +namespace App\Mail\lib;
  3 +
  4 +use PHPMailer\PHPMailer\PHPMailer;
  5 +use PHPMailer\PHPMailer\SMTP;
  6 +
  7 +/**
  8 + * 函数
  9 + * @time 2022/8/1 16:02
  10 + * Class MailFun
  11 + * @package App\Mail\lib
  12 + */
  13 +class MailFun {
  14 +
  15 + /**
  16 + * 列表 转 树数据
  17 + * @param $list
  18 + * @param string $pk
  19 + * @param string $pid
  20 + * @param string $child
  21 + * @param int $root
  22 + * @param bool $empty_child
  23 + * @return array
  24 + * @time 2022/8/1 16:05
  25 + */
  26 + public static function list2Tree($list, $pk='id',$pid = 'pid',$child = '_child',$root=0, $empty_child=true) {
  27 + // 创建Tree
  28 + $tree = array();
  29 + if(is_array($list)) {
  30 + // 创建基于主键的数组引用
  31 + $refer = array();
  32 + foreach ($list as $key => $data) {
  33 + if($empty_child){
  34 + $list[$key][$child] = [];
  35 + }
  36 + $refer[$data[$pk]] = &$list[$key];
  37 + }
  38 + foreach ($list as $key => $data) {
  39 + // 判断是否存在parent
  40 + $parentId = $data[$pid];
  41 + if ($root == $parentId) {
  42 + $tree[] = &$list[$key];
  43 + }else{
  44 + if (isset($refer[$parentId])) {
  45 + $refer[$parentId][$child][] = &$list[$key];
  46 + }
  47 + }
  48 + }
  49 + }
  50 + return $tree;
  51 + }
  52 +
  53 + /**
  54 + * json encode
  55 + * @param $data
  56 + * @param int $option
  57 + * @return false|string
  58 + * @time 2022/8/2 15:57
  59 + */
  60 + public static function json_en($data,$option=\JSON_UNESCAPED_UNICODE){
  61 + return \json_encode($data,$option);
  62 + }
  63 +
  64 +
  65 + /**
  66 + * 解码
  67 + * @param $string
  68 + * @param string $charset
  69 + * @return string
  70 + * @time 2022/8/15 9:31
  71 + */
  72 + public static function decodeMimeStr($string, $charset = 'utf-8') {
  73 + $newString = '';
  74 + $elements = imap_mime_header_decode($string);
  75 +// print_r($elements);
  76 + for($i = 0; $i < count($elements); $i++) {
  77 + if($elements[$i]->charset == 'default') {
  78 + $elements[$i]->charset = 'iso-8859-1';
  79 + }
  80 + $newString .= self::convertStringEncoding($elements[$i]->text, $elements[$i]->charset, $charset);
  81 + }
  82 + return $newString;
  83 + }
  84 +
  85 + public static function convertStringEncoding($string, $fromEncoding, $toEncoding) {
  86 + $convertedString = null;
  87 + if($string && $fromEncoding != $toEncoding) {
  88 + $convertedString = @iconv($fromEncoding, $toEncoding . '//IGNORE', $string);
  89 + if(!$convertedString && extension_loaded('mbstring')) {
  90 + $convertedString = @mb_convert_encoding($string, $toEncoding, $fromEncoding);
  91 + }
  92 + }
  93 + return $convertedString ?: $string;
  94 + }
  95 +
  96 +
  97 + /**
  98 + * 验证是否有附件 BODYSTRUCTURE值
  99 + * @param array $BODYSTRUCTURE
  100 + * @return int
  101 + * @author:dc
  102 + * @time 2022/11/1 10:57
  103 + */
  104 + public static function isFile(array $BODYSTRUCTURE):int {
  105 +// foreach ($BODYSTRUCTURE as $item){
  106 +// if($item[0] === 'APPLICATION'){
  107 +// return 1;
  108 +// }
  109 +// }
  110 +// return 0;
  111 + $json = json_encode($BODYSTRUCTURE);
  112 + return strpos($json,'"attachment"')!==false;
  113 + }
  114 +
  115 +
  116 + /**
  117 + * 邮件收件人/发件人
  118 + * @param $str
  119 + * @return array
  120 + * @author:dc
  121 + * @time 2022/11/8 9:36
  122 + */
  123 + public static function toOrFrom($str){
  124 +
  125 + $strs = explode(',',$str);
  126 + foreach ($strs as $k=>$s){
  127 + preg_match('/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/',$s,$email);
  128 + if(empty($email[0])){
  129 + $s = [
  130 + 'email' => '',
  131 + 'name' => $s
  132 + ];
  133 + }else{
  134 + $s = str_replace([$email[0],'"','<','>','&gt;','&lt;'],'',$s);
  135 + $s = trim($s);
  136 + $s = [
  137 + 'email' => $email[0],
  138 + 'name' => $s
  139 + ];
  140 + }
  141 + if(empty($s['name'])){
  142 + $s['name'] = explode('@',$s['email'])[0]??'';
  143 + }
  144 +
  145 + if(!empty($s['email'])){
  146 + $strs[$k] = $s;
  147 + }else{
  148 + unset($strs[$k]);
  149 + }
  150 +
  151 + }
  152 +
  153 + return $strs;
  154 +
  155 +
  156 + }
  157 +
  158 +
  159 + /**
  160 + * @param string $smtp smtp服务器地址
  161 + * @param string $username 发件人
  162 + * @param string $password 发件人密码
  163 + * @param string $nickname 昵称
  164 + * @param string|array $to_email 收件人,邮件或['email'=>'','name'=>'']
  165 + * @param string $subject 标题,主题
  166 + * @param string $body 文本内容
  167 + * @param array $files 文件 ['origin_name'=>'','path'=>'']
  168 + * @param false $receipt 是否回执
  169 + * @param int $priority 是否紧急 1紧急 3正常 5慢
  170 + * @return bool
  171 + * @throws \PHPMailer\PHPMailer\Exception
  172 + * @author:dc
  173 + * @time 2022/11/11 14:26
  174 + */
  175 + public static function sendEmail(string $smtp,string $username,string $password,string $nickname,$to_email,string $subject,string $body,$files=[],$receipt=false,$priority=3){
  176 +
  177 + // 邮件对象
  178 + $mail = new PHPMailer(true);
  179 + //Server settings
  180 + $mail->SMTPDebug = SMTP::DEBUG_CLIENT;//调试输出 SMTP::DEBUG_SERVER; //Enable verbose debug output
  181 + $mail->isSMTP(); //Send using SMTP
  182 + $mail->Host = $smtp; //Set the SMTP server to send through
  183 + $mail->SMTPAuth = true; //Enable SMTP authentication
  184 + $mail->Username = $username; //SMTP username
  185 + $mail->Password = $password; //SMTP password
  186 + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; //Enable implicit TLS encryption
  187 + $mail->Port = 465; //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS`
  188 + $mail->CharSet = 'utf-8';
  189 + $mail->Encoding = PHPMailer::ENCODING_QUOTED_PRINTABLE;
  190 +
  191 + //Recipients,设置发件人
  192 + $mail->setFrom($username, $nickname);// 显示邮件来自谁
  193 + // //Add a recipient,设置收件人 这里必须是一对一发送
  194 + if(is_array($to_email)){
  195 + $mail->addAddress($to_email['email'], $to_email['name']);
  196 + }else{
  197 + $mail->addAddress($to_email, '');
  198 + }
  199 +
  200 +// //回复到那个邮件
  201 +// $mail->addAddress($reply_to['email'], $reply_to['name']); //Add a recipient
  202 +// // 抄送
  203 +// $mail->addCC($cc['email'],$cc['name']);//
  204 +// // 密送
  205 +// $mail->addBCC($bcc['email'],$bcc['name']);
  206 +
  207 +
  208 + //Attachments 附件
  209 + if($files){
  210 + foreach ($files as $file){
  211 + // 添加到邮箱中
  212 + $mail->addAttachment($file['path'], $file['origin_name']); //Add attachments
  213 + }
  214 + }
  215 +
  216 + // 回执,阅读后收回执的邮箱
  217 + if($receipt){
  218 + $mail->ConfirmReadingTo = $receipt;
  219 + }
  220 + // 是否紧急邮件
  221 +// Options: null (default), 1 = High, 3 = Normal, 5 = low.
  222 + $mail->Priority = $priority;
  223 +
  224 + //Content 主题,标题
  225 + $mail->Subject = $subject;
  226 +
  227 + $mail->isHTML(true); //Set email format to HTML
  228 + $mail->Body = $body;// html格式的内容
  229 +
  230 + // 发送
  231 + if($mail->send()){
  232 + return true;
  233 + }
  234 +
  235 + throw new \Exception($mail->ErrorInfo,500);
  236 +
  237 + }
  238 +
  239 +
  240 +}
  1 +<?php
  2 +
  3 +namespace App\Mail\lib\MailParse;
  4 +
  5 +use App\Mail\lib\MailFun;
  6 +
  7 +/**
  8 + * 解析邮件body内容
  9 + * 通过 fetch msgno RFC822.text 获取到的内容
  10 + * 此内容包含html 文本 附件
  11 + * @author:dc
  12 + * @time 2022/8/12 9:15
  13 + * Class Body
  14 + * @package App\Mail\lib
  15 + */
  16 +class Body {
  17 +
  18 + /**
  19 + * @var string
  20 + */
  21 + private $body;
  22 +
  23 + /**
  24 + *
  25 + * @var array
  26 + */
  27 + private $item = [];
  28 +
  29 + /**
  30 + * 保存的目录
  31 + * @var string
  32 + */
  33 + private $fileSavePath;
  34 +
  35 +
  36 + /**
  37 + * Body constructor.
  38 + * @param string $body
  39 + * @param string $fileSavePath
  40 + */
  41 + public function __construct(string $body, string $fileSavePath='/')
  42 + {
  43 + $this->body = $body = trim($body);
  44 +
  45 + $this->fileSavePath = $fileSavePath;
  46 +
  47 + // 这个是描述特殊文本
  48 + if(strpos($body,'This is a multi-part message in MIME format.')===0){
  49 + $body = trim($body,'This is a multi-part message in MIME format.');
  50 + $body = trim($body);
  51 + }
  52 +
  53 + // 163 有
  54 + if(strpos($body,'------=_Part')!==false){
  55 + $this->parse($body,'------=_Part');
  56 + }
  57 + elseif (mb_strpos($body,'------=_NextPart')!==false){
  58 + $this->parse($body,'------=_NextPart');
  59 + }
  60 + elseif (mb_strpos($body,'----_NmP')!==false){
  61 + $this->parse($body,'----_NmP');
  62 + }
  63 + elseif (mb_strpos($body,'--_=_swift')!==false){
  64 + $this->parse($body,'--_=_swift');
  65 + }
  66 + elseif (mb_strpos($body,'----==_mimepart')!==false){
  67 + $this->parse($body,'----==_mimepart');
  68 + }
  69 + elseif (mb_strpos($body,'--------------Boundary')!==false){
  70 + $this->parse($body,'--------------Boundary');
  71 + }
  72 + elseif (mb_strpos($body,'--=-')!==false){
  73 + $this->parse($body,'--=-');
  74 + }
  75 + // 很多--开始的,且不规则
  76 + elseif(strpos($body,'--')===0){
  77 + // 获取第一行
  78 + $tag = $this->body_get_tag($body,'--');
  79 + // 以第一行为标准
  80 + $this->parse($body,trim($tag));
  81 + }
  82 + // 直接html
  83 + elseif (mb_strpos($body,'<')===0){
  84 + $body = quoted_printable_decode($body);
  85 +// preg_match("/<meta(?!\s*(?:name|value)\s*=)(?:[^>]*?content\s*=[\s\"']*)?([^>]*?)[\s\"';]*charset\s*=[\s\"']*([^\s\"'\/>]*)/",$body,$icon);
  86 +// if(!empty($icon[2])){
  87 +// // 解码
  88 +// $body = mb_convert_encoding($body,'utf-8',$icon[2]);
  89 +// }
  90 + $this->setItem(['type'=>'text/html','body'=>$body]);
  91 + }
  92 + else{
  93 + // qq的是base64
  94 + if(rtrim($body,'=') == rtrim(base64_encode(base64_decode($body)),'=')){
  95 + $this->setItem(['type'=>'text/plain','body'=>base64_decode($body)]);
  96 + }else{
  97 + $this->setItem(['type'=>'text/plain','body'=>$body]);
  98 + }
  99 +
  100 + }
  101 +
  102 +
  103 + }
  104 +
  105 + /**
  106 + * 获取标签
  107 + * @param $body
  108 + * @param $tag
  109 + * @return mixed|string
  110 + * @author:dc
  111 + * @time 2022/8/12 10:49
  112 + */
  113 + private function body_get_tag($body,$tag){
  114 + preg_match("/{$tag}[\w\W].*/i",$body,$result);
  115 + if(!empty($result[0])) {
  116 + return $result[0];
  117 + }
  118 + return '';
  119 + }
  120 +
  121 + /**
  122 + * @param $item
  123 + */
  124 + private function setItem($item): void
  125 + {
  126 + $this->item[] = $item;
  127 + }
  128 +
  129 +
  130 + /**
  131 + * @return []
  132 + */
  133 + public function getItem(): array
  134 + {
  135 + return $this->item;
  136 + }
  137 +
  138 +
  139 + /**
  140 + * 开始解析
  141 + * @param string $body
  142 + * @param string $tag
  143 + * @return array
  144 + * @author:dc
  145 + * @time 2022/8/12 9:50
  146 + */
  147 + private function parse(string $body, string $tag){
  148 +
  149 + // 删除第一个标签前面的数据,一般情况无用
  150 + $body = mb_substr($this->body,strpos($this->body,$tag),99999999999);
  151 +
  152 + // 有附件的情况
  153 + preg_match('/boundary="([-_A-Za-z0-9=\.]{1,})"/i',$body,$boundary);
  154 + if($boundary[0]??''){
  155 + $body = str_replace($boundary[0],'',$body);
  156 +// $body = mb_substr($body,mb_strpos($body,$boundary[0])+strlen($boundary[0]),99999999999);
  157 + }
  158 + // 附件情况
  159 + if(!empty($boundary[1])){
  160 + preg_match_all('/.*'.$boundary[1].'.*/i',$body,$boundary_tag);
  161 + $body = str_replace($boundary_tag[0],'{--tag--}',$body);
  162 + }
  163 + // 查找tag块
  164 + preg_match_all("/(".$tag.".*+\n)/i",$body."\r\n\r\n",$he);
  165 + // 把每个tag块分开成数组
  166 + if(!empty($he[0])){
  167 + foreach ($he[0] as $hk=>$h){
  168 + $he[0][$hk] = trim($h);
  169 + }
  170 + arsort($he[0]);
  171 + $body = str_replace($he[0],'{--tag--}',$body);
  172 + }
  173 + $body = explode('{--tag--}',$body);
  174 + // 处理
  175 + foreach ($body as $key=>$item){
  176 + $data = [];
  177 +
  178 + $item = trim($item);
  179 + // 附件的头
  180 + if(!$item) { continue; }
  181 +
  182 + // 邮件体包含邮件体
  183 + if(preg_match("/boundary=\"([-_a-z0-9]{5,})\"/Ui",$item,$bm)){
  184 + if (strpos($item,$bm[1].'--')!==false){
  185 + $data = (new self('--'.$bm[1]."\r\n".$item,$this->fileSavePath))->getItem();
  186 +// $this->setItem($data);
  187 + // 合并邮件体
  188 + $this->item = array_merge($this->item,$data);
  189 + }
  190 + continue;
  191 + }
  192 +
  193 + // 先解码解码
  194 + $encode = $this->body_match_tag('Content-Transfer-Encoding:',$item);
  195 + if($encode){
  196 + $data['encode'] = strtolower($encode['text']);
  197 + $item = str_replace($encode['origin'],'',$item);
  198 + }
  199 +
  200 + // 内容类型
  201 + $type = $this->preg_match_type($item);
  202 + if($type){
  203 + $data['type'] = strtolower($type['type']);
  204 + // 编码
  205 + if(isset($type['charset'])){
  206 + $data['charset'] = strtolower($type['charset']);
  207 + }
  208 + // nama。附件
  209 + if(isset($type['name'])){
  210 + $data['name'] = $type['name'];
  211 + }
  212 + // 删除
  213 + $item = str_replace($type['origin'],'',$item);
  214 + }
  215 +
  216 + //
  217 + if(empty($data['charset'])){
  218 + // 编码
  219 + $code = $this->preg_match_charset($item);
  220 + if($code){
  221 + $data['charset'] = strtolower($code['charset']);
  222 +
  223 + $item = str_replace($code['origin'],'',$item);
  224 + }
  225 + }
  226 +
  227 + // 先匹配留存文件名称
  228 + preg_match('/filename="(\w?.*)"/',$item,$filename);
  229 + if(!empty($filename[1])){
  230 + $filename = MailFun::decodeMimeStr($filename[1]);
  231 + }
  232 +
  233 +
  234 + // 删除不需要的tag属性,如果需要进进行解析
  235 + $item = $this->body_remove_tag($item,'Content-Description:');
  236 + $item = $this->body_remove_tag($item,'Content-Disposition:');
  237 + $item = $this->body_remove_tag($item,'Mime-Version:');
  238 +
  239 + $data['body'] = trim($item);
  240 +
  241 + if(!empty($data['type'])){
  242 + // 邮件头
  243 + if($data['type'] == 'multipart/alternative'){
  244 +
  245 + }
  246 + // 是文本还是附件
  247 + else if(strpos($data['type'],'text/') === 0 ){
  248 + // body解密
  249 + switch($data['encode']??''){
  250 + case 'base64': {
  251 + $data['body'] = base64_decode($data['body']);
  252 + break;
  253 + }
  254 + case 'quoted-printable': {
  255 + $data['body'] = quoted_printable_decode($data['body']);
  256 + break;
  257 + }
  258 + case '8bit': {
  259 + try {
  260 + $data['body'] = DeCoding::de8bit($data['body']);
  261 + $data['body'] = quoted_printable_decode($data['body']);
  262 + }catch (\Throwable $e){
  263 +
  264 + }
  265 +
  266 + break;
  267 + }
  268 + }
  269 +
  270 + // 转码
  271 +// if(isset($data['charset']) && $data['charset']){
  272 +// $debody = @mb_convert_encoding($data['body'],'utf-8',$data['charset']);
  273 +// if($debody){
  274 +// $data['body'] = $debody;
  275 +// $debody = null;
  276 +// }
  277 +// }
  278 +
  279 +
  280 + }
  281 + // 系统退信//里面包含了发送邮件所有内容,这里不记录
  282 + elseif (strpos($data['type'],'message') === 0){
  283 + $data['body'] = '';// 一般不需要这些内容,如有需要就要重新解析
  284 + }
  285 + elseif (!empty($data['type']) && $data['body']){
  286 + // 解析附件
  287 + $data = $this->parseFile($data,$filename);
  288 + }
  289 + }
  290 +
  291 + $this->setItem($data);
  292 +
  293 + }
  294 +
  295 + }
  296 +
  297 +
  298 + /**
  299 + * 解析文件
  300 + * @param $item
  301 + * @return array|mixed
  302 + * @author:dc
  303 + * @time 2022/8/12 10:40
  304 + */
  305 + private function parseFile($item,$filename=''){
  306 + $data = [];
  307 + // 查找文件名
  308 +
  309 + $data['filename'] = $this->file_save_name($item['body'],'filename');
  310 + $data['name'] = $this->file_save_name($item['body'],'name');
  311 + $data['name'] = $data['name'] ? : ($item['name']??$filename);
  312 + $data['filename'] = $data['filename'] ? : $data['name'];
  313 +
  314 + // 是否有文件名
  315 + if(empty($data['filename']) || strpos($data['filename'],'.')===false){
  316 + return $item;
  317 + }
  318 +
  319 + $ext = explode('.',$data['filename']);
  320 + $ext = end($ext);
  321 +
  322 +// if(!empty($item['type'])){
  323 +// // 文件类型来判断后缀
  324 +// // // download it from http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
  325 +// if(is_readable(__DIR__.'/mime.types')){
  326 +// $f = fopen(__DIR__.'/mime.types','r');
  327 +// while(!feof($f)){
  328 +// $fext = fgets($f);
  329 +// if($fext){
  330 +// $fext = strtolower($fext);
  331 +// $item['type'] = strtolower($item['type']);
  332 +// // 找到了类型后缀
  333 +// if(strpos($fext,$item['type']) === 0){
  334 +// $ext = trim(str_replace($item['type'],'',$fext));
  335 +// break;//找到了要跳出循环
  336 +// }
  337 +// }
  338 +// }
  339 +// // 关闭文件
  340 +// fclose($f);
  341 +// }
  342 +// }
  343 +
  344 + // 找不到后缀,说明不是文件
  345 +// if(empty($ext)){
  346 + // 文件后缀
  347 +// $ext = explode('.',$data['filename']);
  348 +// $ext = count($ext) > 1 ? ($ext[count($ext)-1]??'') : '';
  349 + // 直接返回
  350 +// return $item['body'];
  351 +// }
  352 +
  353 +
  354 + // content id
  355 + preg_match("/Content-ID:[\s].*<[\w\W]{1,}>/i",$item['body'],$result);
  356 + if (!empty($result[0])){
  357 + $data['content-id'] = explode('<',$result[0]);
  358 + $data['content-id'] = $data['content-id'][1];
  359 + $data['content-id'] = trim($data['content-id']);
  360 + $data['content-id'] = trim($data['content-id'],'>');
  361 +
  362 + $item['body'] = str_replace($result[0],'',$item['body']);
  363 + }
  364 +
  365 + $item['body'] = str_replace($result,'',$item['body']);
  366 +
  367 +
  368 + $content = base64_decode(trim($item['body']));
  369 +
  370 + if($content){
  371 + // 目录
  372 + $data['path'] = $this->fileSavePath;
  373 + if(!is_dir($data['path'])){
  374 + mkdir($data['path'],0775,true);
  375 + }
  376 + $data['signName'] = md5($content).($ext ? '.'.$ext : '');
  377 + $data['path'] = $data['path'].'/'.$data['signName'];
  378 + // 保存文件
  379 + @file_put_contents($data['path'],$content);
  380 + }
  381 +
  382 +
  383 + return $data;
  384 + }
  385 +
  386 + // 获取文件名称
  387 + private function file_save_name(&$body,$tag){
  388 + preg_match('/'.$tag.'="[(\S\W.*\s.*)]{1,}"/i',$body,$result);
  389 + if($result[0]??''){
  390 + $body = str_replace($result[0],'',$body);
  391 + }
  392 + $val = trim(str_replace([$tag.'=','"',"'"],'',$result[0]??''));
  393 + if ($val && strpos($val,'=?')===0){
  394 +
  395 + $val = iconv_mime_decode($val,ICONV_MIME_DECODE_CONTINUE_ON_ERROR,'utf-8');
  396 + }
  397 +
  398 + return $val;
  399 + }
  400 +
  401 + /**
  402 + * 删除tag
  403 + * @param $body
  404 + * @param $tag
  405 + * @return mixed|string|string[]
  406 + * @author:dc
  407 + * @time 2022/8/12 10:34
  408 + */
  409 + private function body_remove_tag($body,$tag){
  410 + preg_match("/{$tag}[\w\W].*/i",$body,$result);
  411 + if(!empty($result[0])) {
  412 + $body = str_replace($result, '', $body);
  413 + }
  414 + return $body;
  415 + }
  416 +
  417 + /**
  418 + * 读取编码
  419 + * @param $item
  420 + * @return array
  421 + * @author:dc
  422 + * @time 2022/8/12 10:28
  423 + */
  424 + private static function preg_match_charset($item){
  425 + // 匹配内容 type
  426 + preg_match('/charset[ \t]{0,}=[ \t]{0,}"?[ \t0-9a-zA-Z-]{1,}"?/i',$item,$result);
  427 +
  428 + if(!empty($result[0])){
  429 + $ret['origin'] = trim($result[0]);
  430 + // charset
  431 + $ret['charset'] = trim(str_replace(['charset','=','"',"'"],'',$ret['origin']));
  432 +
  433 + return $ret;
  434 + }
  435 + return [];
  436 + }
  437 +
  438 + /**
  439 + * 解析type
  440 + * @param $item
  441 + * @return array
  442 + * @author:dc
  443 + * @time 2022/8/12 10:26
  444 + */
  445 + private function preg_match_type($item){
  446 + // 匹配内容 type
  447 + preg_match("/Content-Type:[\w\W].*/i",$item,$result);
  448 +
  449 + if(!empty($result[0])){
  450 + $ret['origin'] = trim($result[0]);
  451 + // type
  452 + $type = str_replace(['Content-Type:','"',"'"],'',$ret['origin']);
  453 + $type = explode(';',$type);
  454 + // 类型
  455 + $ret['type'] = trim($type[0]);
  456 + if(isset($type[1]) && $type[1]){
  457 + // 编码
  458 + $r = explode('=',$type[1]);
  459 + $ret[strtolower(trim($r[0]))] = trim($r[1]??'');
  460 + }
  461 + return $ret;
  462 + }
  463 + return [];
  464 + }
  465 +
  466 + /**
  467 + * 匹配tag
  468 + * @param $tag
  469 + * @param $item
  470 + * @return array
  471 + * @author:dc
  472 + * @time 2022/8/12 10:05
  473 + */
  474 + private function body_match_tag($tag,$item){
  475 + // tag Content-Transfer-Encoding:
  476 + preg_match("/".$tag."[\w\W].*/i",$item,$result);
  477 +
  478 + if(!empty($result[0])){
  479 + $ret['origin'] = trim($result[0]);
  480 + // charset
  481 + $ret['text'] = trim(str_replace([$tag,'"',"'"],'',$ret['origin']));
  482 +
  483 + return $ret;
  484 + }
  485 + return [];
  486 + }
  487 +
  488 +
  489 +}
  1 +<?php
  2 +
  3 +namespace App\Mail\lib\MailParse;
  4 +
  5 +/**
  6 + * 解码邮件内容
  7 + * @author:dc
  8 + * @time 2022/8/12 9:33
  9 + * Class DeCoding
  10 + * @package App\Mail\lib\MailParse
  11 + */
  12 +class DeCoding {
  13 +
  14 +
  15 + /**
  16 + * @param $sText
  17 + * @param bool $bEmulate_imap_8bit
  18 + * @return string
  19 + * @author:dc
  20 + * @time 2022/8/12 9:34
  21 + */
  22 + public static function de8bit($sText,$bEmulate_imap_8bit=true) {
  23 + // split text into lines
  24 + $aLines=explode(chr(13).chr(10),$sText);
  25 +
  26 + for ($i=0;$i<count($aLines);$i++) {
  27 + $sLine =& $aLines[$i];
  28 + if (strlen($sLine)===0) continue; // do nothing, if empty
  29 +
  30 + $sRegExp = '/[^\x09\x20\x21-\x3C\x3E-\x7E]/e';
  31 +
  32 + // imap_8bit encodes x09 everywhere, not only at lineends,
  33 + // for EBCDIC safeness encode !"#$@[\]^`{|}~,
  34 + // for complete safeness encode every character :)
  35 + if ($bEmulate_imap_8bit)
  36 + $sRegExp = '/[^\x20\x21-\x3C\x3E-\x7E]/e';
  37 +
  38 + $sReplmt = 'sprintf( "=%02X", ord ( "$0" ) ) ;';
  39 + $sLine = preg_replace( $sRegExp, $sReplmt, $sLine );
  40 +
  41 + // encode x09,x20 at lineends
  42 + {
  43 + $iLength = strlen($sLine);
  44 + $iLastChar = ord($sLine{$iLength-1});
  45 +
  46 + // !!!!!!!!
  47 + // imap_8_bit does not encode x20 at the very end of a text,
  48 + // here is, where I don't agree with imap_8_bit,
  49 + // please correct me, if I'm wrong,
  50 + // or comment next line for RFC2045 conformance, if you like
  51 + if (!($bEmulate_imap_8bit && ($i==count($aLines)-1)))
  52 +
  53 + if (($iLastChar==0x09)||($iLastChar==0x20)) {
  54 + $sLine{$iLength-1}='=';
  55 + $sLine .= ($iLastChar==0x09)?'09':'20';
  56 + }
  57 + } // imap_8bit encodes x20 before chr(13), too
  58 + // although IMHO not requested by RFC2045, why not do it safer :)
  59 + // and why not encode any x20 around chr(10) or chr(13)
  60 + if ($bEmulate_imap_8bit) {
  61 + $sLine=str_replace(' =0D','=20=0D',$sLine);
  62 + //$sLine=str_replace(' =0A','=20=0A',$sLine);
  63 + //$sLine=str_replace('=0D ','=0D=20',$sLine);
  64 + //$sLine=str_replace('=0A ','=0A=20',$sLine);
  65 + }
  66 +
  67 + // finally split into softlines no longer than 76 chars,
  68 + // for even more safeness one could encode x09,x20
  69 + // at the very first character of the line
  70 + // and after soft linebreaks, as well,
  71 + // but this wouldn't be caught by such an easy RegExp
  72 + preg_match_all( '/.{1,73}([^=]{0,2})?/', $sLine, $aMatch );
  73 + $sLine = implode( '=' . chr(13).chr(10), $aMatch[0] ); // add soft crlf's
  74 + }
  75 +
  76 + // join lines into text
  77 + return implode(chr(13).chr(10),$aLines);
  78 + }
  79 +}