diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e1e360c..4769dc8 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,53 +1,57 @@ name: run-tests -on: [push, pull_request] +on: + - push + - pull_request jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest] - php: [8.0, 8.1, 8.2] - laravel: [9.*, 8.*, 10.*] - stability: [prefer-stable] - exclude: - - php: 8.0 - laravel: 10.* - - php: 8.2 - laravel: 8.* - include: - - laravel: 10.* - testbench: 8.* - - laravel: 9.* - testbench: 7.* - - laravel: 8.* - testbench: 6.23 - - name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick - coverage: none - -# - name: Setup problem matchers -# run: | -# echo "::add-matcher::${{ runner.tool_cache }}/php.json" -# echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" -# - - name: Install dependencies - run: | - composer install - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update - composer update --${{ matrix.stability }} --prefer-dist --no-interaction - - - name: Execute tests - run: vendor/bin/phpunit + test: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + php: [8.0, 8.1, 8.2] + laravel: ['8.*', '9.*', '10.*', '11.*'] + stability: [prefer-stable] + exclude: + - php: 8.0 + laravel: 10.* + - php: 8.2 + laravel: 8.* + - laravel: 11.* + php: 8.0 + - laravel: 11.* + php: 8.1 + include: + - laravel: 10.* + testbench: 8.* + - laravel: 9.* + testbench: 7.* + - laravel: 8.* + testbench: 6.23 + - laravel: 11.* + testbench: 9.* + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick + coverage: none + + - name: Install dependencies + run: | + composer install + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update + composer update --${{ matrix.stability }} --prefer-dist --no-interaction + + - name: Execute tests + run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index 6cc69e4..cd85aa3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ vendor coverage .phpunit.result.cache .idea +.phpunit.cache \ No newline at end of file diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index df16b68..0000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,19 +0,0 @@ -filter: - excluded_paths: [tests/*] - -checks: - php: - remove_extra_empty_lines: true - remove_php_closing_tag: true - remove_trailing_whitespace: true - fix_use_statements: - remove_unused: true - preserve_multiple: false - preserve_blanklines: true - order_alphabetically: true - fix_php_opening_tag: true - fix_linefeed: true - fix_line_ending: true - fix_identation_4spaces: true - fix_doc_comments: true - diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2780ef2..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: php - -php: - - 7.3 - - 7.4 - - 8.0 - -env: - matrix: - - COMPOSER_FLAGS="--prefer-lowest" - - COMPOSER_FLAGS="" - -before_script: - - travis_retry composer self-update - - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source - -script: - - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover - -after_script: - - php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover diff --git a/README.md b/README.md index ffe1a31..cf9098f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Laravel Mailbox 📬 [![Latest Version on Packagist](https://img.shields.io/packagist/v/beyondcode/laravel-mailbox.svg?style=flat-square)](https://packagist.org/packages/beyondcode/laravel-mailbox) -[![Build Status](https://img.shields.io/travis/beyondcode/laravel-mailbox/master.svg?style=flat-square)](https://travis-ci.org/beyondcode/laravel-mailbox) -[![Quality Score](https://img.shields.io/scrutinizer/g/beyondcode/laravel-mailbox.svg?style=flat-square)](https://scrutinizer-ci.com/g/beyondcode/laravel-mailbox) [![Total Downloads](https://img.shields.io/packagist/dt/beyondcode/laravel-mailbox.svg?style=flat-square)](https://packagist.org/packages/beyondcode/laravel-mailbox) Handle incoming emails in your Laravel application. @@ -11,15 +9,11 @@ Handle incoming emails in your Laravel application. Mailbox::from('{username}@gmail.com', function (InboundEmail $email, $username) { // Access email attributes and content $subject = $email->subject(); - + $email->reply(new ReplyMailable); }); ``` -[![https://phppackagedevelopment.com](https://beyondco.de/courses/phppd.jpg)](https://phppackagedevelopment.com) - -If you want to learn how to create reusable PHP packages yourself, take a look at my upcoming [PHP Package Development](https://phppackagedevelopment.com) video course. - ## Installation diff --git a/composer.json b/composer.json index 46d849f..54c8a4d 100644 --- a/composer.json +++ b/composer.json @@ -17,19 +17,19 @@ ], "require": { "php": "^8.0", - "illuminate/container": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/database": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/log": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/routing": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/container": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/database": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/log": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/routing": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "willdurand/email-reply-parser": "^2.8", - "zbateson/mail-mime-parser": "^1.1" + "zbateson/mail-mime-parser": "^1.1|^2.4" }, "require-dev": { "laminas/laminas-mail": "^2.13", "mockery/mockery": "^1.2", - "orchestra/testbench": "^4.0|^5.0|^7.0|^8.0", - "phpunit/phpunit": "^7.0|^8.0|^9.3" + "orchestra/testbench": "^4.0|^5.0|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^7.0|^8.0|^9.3|^10.5" }, "autoload": { "psr-4": { diff --git a/docs/drivers/drivers.md b/docs/drivers/drivers.md index d3896e0..1136e5f 100644 --- a/docs/drivers/drivers.md +++ b/docs/drivers/drivers.md @@ -53,12 +53,16 @@ Be sure the check the box labeled "Post the raw, full MIME message." when settin ## MailCare +::: warning +To use MailCare with Laravel Mailbox, you will need to generate a random password and store it as the `MAILBOX_HTTP_PASSWORD` environment variable. The default username is "laravel-mailbox", but you can change it using the `MAILBOX_HTTP_USERNAME` environment variable. +::: + You can then set your `MAILBOX_DRIVER` to "mailcare". -Next you will need to configure MailCare, to send incoming emails to your application at `/laravel-mailbox/mailcare`: -- Activate authentication and automation features. -- Create a new automation with the URL `https://your-application.com/laravel-mailbox/mailcare` -- Be sure the check the box labeled "Post the raw, full MIME message." +Next you will need to configure MailCare, to send incoming emails to your application at `/laravel-mailbox/mailcare`. +- Ask support to activate authentication and automation features. +- Create a new automation, if your application is at `https://awesome-laravel.com`, it would be with the URL `https://MAILBOX_HTTP_USERNAME:MAILBOX_HTTP_PASSWORD@awesome-laravel.com/laravel-mailbox/mailcare` +- Be sure the check the box labeled "Post the raw, full MIME message " See ["MailCare"](https://mailcare.io) for more information. @@ -66,5 +70,5 @@ See ["MailCare"](https://mailcare.io) for more information. When working locally, you might not want to use real incoming emails while testing your application. Out of the box, this package supports Laravel's "log" mail driver for incoming emails. -To test incoming emails, set both your `MAIL_DRIVER` and your `MAILBOX_DRIVER` in your `.env` file to "log". +To test incoming emails, set both your `MAIL_MAILER` and your `MAILBOX_DRIVER` in your `.env` file to "log". Now every time you send an email in your application, this email will appear in your `laravel.log` file and will try to call one of your configured Mailboxes. diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 89d7e61..8a7588d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,33 +1,12 @@ - - - - tests - - - - - src/ - - - - - - - - - - - - - + + + + tests + + + + + + diff --git a/phpunit.xml.dist.bak b/phpunit.xml.dist.bak new file mode 100644 index 0000000..6967f7f --- /dev/null +++ b/phpunit.xml.dist.bak @@ -0,0 +1,21 @@ + + + + + tests + + + + + + + diff --git a/src/Console/CleanEmails.php b/src/Console/CleanEmails.php index 2631192..8cd8e3c 100644 --- a/src/Console/CleanEmails.php +++ b/src/Console/CleanEmails.php @@ -12,6 +12,8 @@ class CleanEmails extends Command protected $description = 'Clean up old incoming email logs.'; + protected $amountDeleted = 0; + public function handle() { $this->comment('Cleaning old incoming email logs...'); @@ -29,13 +31,21 @@ public function handle() /** @var InboundEmail $modelClass */ $modelClass = config('mailbox.model'); - $models = $modelClass::where('created_at', '<', $cutOffDate)->get(); + // chunk the deletion to avoid memory issues - $models->each->delete(); + $this->amountDeleted = 0; - $amountDeleted = $models->count(); + $modelClass::where('created_at', '<', $cutOffDate) + ->select('id') + ->chunk(100, function ($models) use ($modelClass) { + foreach ($models as $model) { + $modelInstance = $modelClass::find($model->id); + $modelInstance->delete(); + $this->amountDeleted++; + } + }); - $this->info("Deleted {$amountDeleted} record(s) from the Mailbox logs."); + $this->info("Deleted {$this->amountDeleted} record(s) from the Mailbox logs."); $this->comment('All done!'); } diff --git a/src/Http/Middleware/MailboxBasicAuthentication.php b/src/Http/Middleware/MailboxBasicAuthentication.php index 09020fc..6e747d9 100644 --- a/src/Http/Middleware/MailboxBasicAuthentication.php +++ b/src/Http/Middleware/MailboxBasicAuthentication.php @@ -12,7 +12,7 @@ public function handle($request, Closure $next) $user = $request->getUser(); $password = $request->getPassword(); - if (($user === config('mailbox.basic_auth.username') && $password === config('mailbox.basic_auth.password'))) { + if ($user === config('mailbox.basic_auth.username') && $password === config('mailbox.basic_auth.password')) { return $next($request); } diff --git a/src/Http/Requests/MailCareRequest.php b/src/Http/Requests/MailCareRequest.php index fd4405b..0e2215d 100644 --- a/src/Http/Requests/MailCareRequest.php +++ b/src/Http/Requests/MailCareRequest.php @@ -4,19 +4,28 @@ use BeyondCode\Mailbox\InboundEmail; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Support\Facades\Validator; class MailCareRequest extends FormRequest { - public function validator() + public function rules() { - return Validator::make($this->all(), [ - 'email' => 'required', + return [ + 'content_type' => 'required|in:message/rfc2822', + ]; + } + + public function prepareForValidation() + { + $this->merge([ + 'content_type' => $this->headers->get('Content-type'), ]); } public function email() { - return InboundEmail::fromMessage($this->get('email')); + /** @var InboundEmail $modelClass */ + $modelClass = config('mailbox.model'); + + return $modelClass::fromMessage($this->getContent()); } } diff --git a/src/InboundEmail.php b/src/InboundEmail.php index 089bb02..884445f 100644 --- a/src/InboundEmail.php +++ b/src/InboundEmail.php @@ -141,7 +141,7 @@ public function attachments() public function message(): MimeMessage { - $this->mimeMessage = $this->mimeMessage ?: MimeMessage::from($this->message); + $this->mimeMessage = $this->mimeMessage ?: MimeMessage::from($this->message, true); return $this->mimeMessage; } @@ -193,4 +193,43 @@ public function isValid(): bool { return $this->from() !== '' && ($this->isText() || $this->isHtml()); } + + public function isAutoReply($checkCommonSubjects = true): bool + { + if ($this->headerValue('x-autorespond')) { + return true; + } + + if (in_array($this->headerValue('precedence'), ['auto_reply', 'bulk', 'junk'])) { + return true; + } + + if (in_array($this->headerValue('x-precedence'), ['auto_reply', 'bulk', 'junk'])) { + return true; + } + if (in_array($this->headerValue('auto-submitted'), ['auto-replied', 'auto-generated'])) { + return true; + } + + if ($checkCommonSubjects) { + return Str::startsWith($this->subject(), [ + 'Auto:', + 'Automatic reply', + 'Autosvar', + 'Automatisk svar', + 'Automatisch antwoord', + 'Abwesenheitsnotiz', + 'Risposta Non al computer', + 'Automatisch antwoord', + 'Auto Response', + 'Respuesta automática', + 'Fuori sede', + 'Out of Office', + 'Frånvaro', + 'Réponse automatique', + ]); + } + + return false; + } } diff --git a/src/Routing/Route.php b/src/Routing/Route.php index 33e4eca..e4f9ff2 100644 --- a/src/Routing/Route.php +++ b/src/Routing/Route.php @@ -83,24 +83,24 @@ protected function gatherMatchSubjectsFromMessage(InboundEmail $message) switch ($this->subject) { case self::FROM: return [$message->from()]; - break; + break; case self::TO: return $this->convertMessageAddresses($message->to()); - break; + break; case self::CC: return $this->convertMessageAddresses($message->cc()); - break; + break; case self::BCC: return $this->convertMessageAddresses($message->bcc()); break; case self::SUBJECT: return [$message->subject()]; - break; + break; } } /** - * @param $addresses AddressPart[] + * @param $addresses AddressPart[] * @return array */ protected function convertMessageAddresses($addresses): array diff --git a/tests/MailboxRouteTest.php b/tests/MailboxRouteTest.php index 7e1de29..5adfcd0 100644 --- a/tests/MailboxRouteTest.php +++ b/tests/MailboxRouteTest.php @@ -8,7 +8,7 @@ class MailboxRouteTest extends TestCase { - public function emailDataProvider() + public static function emailDataProvider() { return [ ['hello@beyondco.de', 'hello@beyondco.de', 'wrong@beyondco.de'], @@ -18,6 +18,7 @@ public function emailDataProvider() /** * @test + * * @dataProvider emailDataProvider */ public function it_matches_from_mails($fromMail, $successfulPattern, $failingPattern) @@ -36,6 +37,7 @@ public function it_matches_from_mails($fromMail, $successfulPattern, $failingPat /** * @test + * * @dataProvider emailDataProvider */ public function it_matches_to_mails($toMail, $successfulPattern, $failingPattern) @@ -54,6 +56,7 @@ public function it_matches_to_mails($toMail, $successfulPattern, $failingPattern /** * @test + * * @dataProvider emailDataProvider */ public function it_matches_cc_mails($ccMail, $successfulPattern, $failingPattern) @@ -72,6 +75,7 @@ public function it_matches_cc_mails($ccMail, $successfulPattern, $failingPattern /** * @test + * * @dataProvider emailDataProvider */ public function it_matches_bcc_mails($bccMail, $successfulPattern, $failingPattern) @@ -90,6 +94,7 @@ public function it_matches_bcc_mails($bccMail, $successfulPattern, $failingPatte /** * @test + * * @dataProvider subjectDataProvider */ public function it_matches_subjects($subject, $successfulPattern, $failingPattern) @@ -125,7 +130,7 @@ public function it_matches_requirements() $this->assertTrue($route->matches($message)); } - public function subjectDataProvider() + public static function subjectDataProvider() { return [ ['New Laravel Packages', 'New Laravel Packages', 'Old Laravel Packages'],