diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..811a793 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,74 @@ +name: Run tests + +on: + push: + schedule: + - cron: '0 0 * * *' + +jobs: + php-tests: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + php: [7.4, 7.3, 7.2] + laravel: [6.*, 5.8.*, 5.7.*, 5.6.*] + os: [ubuntu-latest] + include: + - laravel: 6.* + testbench: 4.* + - laravel: 5.8.* + testbench: 3.8.* + - laravel: 5.7.* + testbench: 3.7.* + - laravel: 5.6.* + testbench: 3.6.* + exclude: + - laravel: 5.7.* + php: 7.4 + - laravel: 5.6.* + php: 7.4 + - laravel: 5.5.* + php: 7.4 + + name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} + + services: + mysql: + image: mysql:5.7.27 + env: + MYSQL_USER: root + MYSQL_ROOT_PASSWORD: root + MYSQL_PASSWORD: + MYSQL_ALLOW_EMPTY_PASSWORD: true + MYSQL_DATABASE: test + ports: + - 3307:3306 + volumes: + - $HOME/mysql:/var/lib/mysql + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - name: Checkout code + uses: actions/checkout@v1 + + - name: Setup PHP + uses: shivammathur/setup-php@v1 + with: + php-version: ${{ matrix.php }} + extensions: mbstring, dom, fileinfo, mysql + coverage: none + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update + composer update --prefer-stable --prefer-dist --no-interaction --no-suggest + - name: Execute tests + env: + CI_DB_DRIVER: mysql + CI_DB_HOST: 127.0.0.1 + CI_DB_PORT: 3307 + CI_DB_DATABASE: test + CI_DB_USERNAME: root + CI_DB_PASSWORD: root + run: vendor/bin/phpunit \ No newline at end of file diff --git a/.gitignore b/.gitignore index b3aec00..4218a0b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ vendor/ .DS_Store composer.lock +.phpunit.result.cache \ No newline at end of file diff --git a/.styleci.yml b/.styleci.yml deleted file mode 100644 index 6894532..0000000 --- a/.styleci.yml +++ /dev/null @@ -1,7 +0,0 @@ -preset: laravel - -enabled: - - concat_with_spaces - -disabled: - - concat_without_spaces diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 545e6f9..0000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -language: php - -sudo: false - -services: - - mysql - -matrix: - fast_finish: true - include: - # SQLite - - php: 7.0 - env: CI_DB_DRIVER="sqlite" CI_DB_DATABASE=":memory:" - - php: 7.1 - env: CI_DB_DRIVER="sqlite" CI_DB_DATABASE=":memory:" - # MySQL 5.7 - - php: 7.0 - env: CI_DB_DRIVER="mysql" CI_DB_HOST="127.0.0.1" CI_DB_DATABASE="travis" CI_DB_USERNAME="root" - - php: 7.1 - env: CI_DB_DRIVER="mysql" CI_DB_HOST="127.0.0.1" CI_DB_DATABASE="travis" CI_DB_USERNAME="root" - # MariaDB - - php: 7.0 - env: CI_DB_DRIVER="mysql" CI_DB_HOST="127.0.0.1" CI_DB_DATABASE="travis" CI_DB_USERNAME="root" - addons: - mariadb: 10.0 - - php: 7.1 - env: CI_DB_DRIVER="mysql" CI_DB_HOST="127.0.0.1" CI_DB_DATABASE="travis" CI_DB_USERNAME="root" - addons: - mariadb: 10.0 - -install: travis_retry composer install --no-interaction --prefer-source - -script: - - mysql --version - - vendor/bin/phpunit --verbose diff --git a/CHANGELOG.md b/CHANGELOG.md index 953843e..1037001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 4.1.1 - 2020-01-11 + +**Fixed** + +- Fixed inline attachments could not be stored +- Fixed PHP 7.4 issue when reading empty Mailable from address + ## 4.1.0 - 2019-07-13 **Added** diff --git a/README.md b/README.md index 98a5d9c..8d1ae45 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

-Build Status +Build Status Latest Stable Version License

diff --git a/composer.json b/composer.json index 1e4e728..c505a69 100644 --- a/composer.json +++ b/composer.json @@ -25,14 +25,13 @@ ] } }, + "require": { + "ext-json": "*" + }, "require-dev": { - "illuminate/database": "5.5.*", - "illuminate/console": "5.5.*", - "illuminate/validation": "5.5.*", - "orchestra/testbench": "^3.5", - "orchestra/database": "^3.5", - "phpunit/phpunit": "^6.0", - "mockery/mockery": "^1.0", - "dompdf/dompdf": "^0.8.2" + "mockery/mockery": "^1.2", + "orchestra/testbench": "^3.5 || ^3.6 || ^3.7 || ^3.8 || ^4.0", + "symfony/console": "^4.4", + "tecnickcom/tcpdf": "^6.3" } } diff --git a/src/HasEncryptedAttributes.php b/src/HasEncryptedAttributes.php index ce782ac..a73984d 100644 --- a/src/HasEncryptedAttributes.php +++ b/src/HasEncryptedAttributes.php @@ -32,7 +32,6 @@ trait HasEncryptedAttributes 'cc', 'bcc', 'variables', - 'attachments', ]; /** @@ -61,6 +60,25 @@ public function getAttribute($key) } } + // BC fix for attachments in 4.1.0 and lower. + // Attachments were stored json encoded. + // Because this doesn't work for raw attachments, value is now serialized. + // Check if value is json encoded or serialized, and decode or unserialize accordingly. + if ($key == 'attachments') { + if (substr($value, 0, 2) === 'a:') { + $unserialized = @unserialize($value); + if ($value !== false) { + $value = $unserialized; + } + } else { + $decoded = json_decode($value, true); + + if (! is_null($decoded)) { + $value = $decoded; + } + } + } + return $value; } } diff --git a/src/MailableReader.php b/src/MailableReader.php index a8f35da..38c4872 100644 --- a/src/MailableReader.php +++ b/src/MailableReader.php @@ -68,6 +68,10 @@ private function readFrom(EmailComposer $composer) { $from = reset($composer->getData('mailable')->from); + if (!$from) { + return; + } + $composer->from( $from['address'], $from['name'] diff --git a/src/Preparer.php b/src/Preparer.php index b4e98fd..e2fba6b 100644 --- a/src/Preparer.php +++ b/src/Preparer.php @@ -198,7 +198,7 @@ private function prepareAttachments(EmailComposer $composer) } $composer->getEmail()->fill([ - 'attachments' => json_encode($attachments), + 'attachments' => serialize($attachments), ]); } diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php index 2f65045..034fc4c 100644 --- a/tests/DatabaseInteractionTest.php +++ b/tests/DatabaseInteractionTest.php @@ -2,8 +2,9 @@ namespace Tests; -use Dompdf\Dompdf; +use Carbon\Carbon; use Illuminate\Support\Facades\DB; +use TCPDF; class DatabaseInteractionTest extends TestCase { @@ -85,9 +86,10 @@ public function scheduled_date_should_be_saved_correctly() $this->assertNull(DB::table('emails')->find(1)->scheduled_at); $this->assertNull($email->getScheduledDate()); + Carbon::setTestNow(Carbon::create(2019, 1, 1, 1, 2, 3)); $email = $this->scheduleEmail('+2 weeks'); $this->assertNotNull(DB::table('emails')->find(2)->scheduled_at); - $this->assertEquals(date('Y-m-d H:i:s', strtotime('+2 weeks')), $email->getScheduledDate()); + $this->assertEquals('2019-01-15 01:02:03', $email->getScheduledDate()); } /** @test */ @@ -156,7 +158,9 @@ public function attempts_should_be_zero() /** @test */ public function the_scheduled_date_should_be_saved_correctly() { - $scheduledFor = date('Y-m-d H:i:s', strtotime('+2 weeks')); + Carbon::setTestNow(Carbon::now()); + + $scheduledFor = date('Y-m-d H:i:s', Carbon::now()->addWeek(2)->timestamp); $email = $this->scheduleEmail('+2 weeks'); @@ -205,12 +209,13 @@ public function attachments_should_be_saved_correctly() /** @test */ public function in_memory_attachments_should_be_saved_correctly() { - $pdf = new Dompdf; - $pdf->loadHtml('Hello CI!'); - $pdf->setPaper('A4', 'landscape'); + $pdf = new TCPDF; + $pdf->Write(0, 'Hello CI!'); + + $rawData = $pdf->Output('generated.pdf', 'S'); $email = $this->composeEmail() - ->attachData($pdf->outputHtml(), 'generated.pdf', [ + ->attachData($rawData, 'generated.pdf', [ 'mime' => 'application/pdf', ]) ->send(); @@ -218,6 +223,6 @@ public function in_memory_attachments_should_be_saved_correctly() $this->assertCount(1, $email->getAttachments()); $this->assertEquals('rawAttachment', $email->getAttachments()[0]['type']); - $this->assertEquals($pdf->outputHtml(), $email->getAttachments()[0]['attachment']['data']); + $this->assertEquals(md5($rawData), md5($email->getAttachments()[0]['attachment']['data'])); } } diff --git a/tests/EncryptionTest.php b/tests/EncryptionTest.php index c4087e2..d023c88 100644 --- a/tests/EncryptionTest.php +++ b/tests/EncryptionTest.php @@ -4,7 +4,7 @@ class EncryptionTest extends TestCase { - public function setUp() + public function setUp(): void { parent::setUp(); diff --git a/tests/SendEmailsCommandTest.php b/tests/SendEmailsCommandTest.php index fbbf3fd..21770bd 100644 --- a/tests/SendEmailsCommandTest.php +++ b/tests/SendEmailsCommandTest.php @@ -37,7 +37,9 @@ public function an_email_should_not_be_sent_once_it_is_marked_as_sent() $this->artisan('email:send'); - $this->assertEquals($firstSend = date('Y-m-d H:i:s'), $email->fresh()->getSendDate()); + $this->assertNotNull($firstSend = $email->fresh()->getSendDate()); + + sleep(1); $this->artisan('email:send'); @@ -55,7 +57,7 @@ public function if_an_email_fails_to_be_sent_it_should_be_logged_in_the_database $this->artisan('email:send'); $this->assertTrue($email->fresh()->hasFailed()); - $this->assertContains('Driver [does-not-exist] not supported.', $email->fresh()->getError()); + $this->assertStringContains('Driver [does-not-exist] not supported.', $email->fresh()->getError()); } /** @test */ diff --git a/tests/SenderTest.php b/tests/SenderTest.php index cd16a0e..9d9f54d 100644 --- a/tests/SenderTest.php +++ b/tests/SenderTest.php @@ -2,18 +2,17 @@ namespace Tests; -use Dompdf\Dompdf; use Swift_Events_SendEvent; use Illuminate\Support\Facades\Mail; use Stackkit\LaravelDatabaseEmails\Email; -use Stackkit\LaravelDatabaseEmails\Config; +use TCPDF; class SenderTest extends TestCase { /** @var Swift_Events_SendEvent[] */ public $sent = []; - public function setUp() + public function setUp(): void { parent::setUp(); @@ -194,12 +193,13 @@ public function attachments_are_not_added_if_the_data_is_not_valid() /** @test */ public function raw_attachments_are_added_to_the_email() { - $pdf = new Dompdf; - $pdf->loadHtml('Hello CI!'); - $pdf->setPaper('A4'); + $pdf = new TCPDF; + $pdf->Write(0, 'Hello CI!'); + + $rawData = $pdf->Output('generated.pdf', 'S'); $this->composeEmail() - ->attachData($pdf->outputHtml(), 'hello-ci.pdf', [ + ->attachData($rawData, 'hello-ci.pdf', [ 'mime' => 'application/pdf', ]) ->send(); @@ -211,7 +211,22 @@ public function raw_attachments_are_added_to_the_email() $this->assertCount(1, $attachments); $this->assertEquals('attachment; filename=hello-ci.pdf', $attachment->getHeaders()->get('content-disposition')->getFieldBody()); $this->assertEquals('application/pdf', $attachment->getContentType()); - $this->assertContains('Hello CI!', $attachment->getBody()); + $this->assertTrue(md5($attachment->getBody()) == md5($rawData)); + } + + /** @test */ + public function old_json_encoded_attachments_can_still_be_read() + { + $email = $this->sendEmail(); + $email->attachments = json_encode([1, 2, 3]); + $email->save(); + + $this->assertEquals([1, 2, 3], $email->fresh()->getAttachments()); + + $email->attachments = serialize([4, 5, 6]); + $email->save(); + + $this->assertEquals([4, 5, 6], $email->fresh()->getAttachments()); } /** @test */ diff --git a/tests/TestCase.php b/tests/TestCase.php index 1e7aa16..1565e14 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -9,7 +9,7 @@ class TestCase extends \Orchestra\Testbench\TestCase { protected $invalid; - public function setUp() + public function setUp(): void { parent::setUp(); @@ -25,10 +25,10 @@ function () { }, ]; - $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); - view()->addNamespace('tests', __DIR__ . '/views'); + $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); + Email::truncate(); } @@ -65,7 +65,6 @@ protected function schema() protected function getPackageProviders($app) { return [ - \Orchestra\Database\ConsoleServiceProvider::class, \Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider::class, ]; } @@ -86,6 +85,7 @@ protected function getEnvironmentSetUp($app) $app['config']->set('database.connections.testbench', [ 'driver' => getenv('CI_DB_DRIVER'), 'host' => getenv('CI_DB_HOST'), + 'port' => getenv('CI_DB_PORT'), 'database' => getenv('CI_DB_DATABASE'), 'username' => getenv('CI_DB_USERNAME'), 'password' => getenv('CI_DB_PASSWORD'), @@ -130,4 +130,13 @@ public function scheduleEmail($scheduledFor, $overwrite = []) { return $this->createEmail($overwrite)->schedule($scheduledFor); } + + public function assertStringContains($needle, $haystack) + { + if (method_exists($this, 'assertStringContainsString')) { + $this->assertStringContainsString($needle, $haystack); + } else { + $this->assertContains($needle, $haystack); + } + } } diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index beca390..0646c70 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -8,59 +8,54 @@ class ValidatorTest extends TestCase { - /** - * @test - * @expectedException InvalidArgumentException - */ + /** @test */ public function a_label_cannot_contain_more_than_255_characters() { + $this->expectException(InvalidArgumentException::class); + Email::compose() ->label(str_repeat('a', 256)) ->send(); } - /** - * @test - * @expectedException InvalidArgumentException - * @expectedExceptionMessage No recipient specified - */ + /** @test */ public function a_recipient_is_required() { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('No recipient specified'); + Email::compose() ->send(); } - /** - * @test - * @expectedException InvalidArgumentException - * @expectedExceptionMessage No recipient specified - */ + /** @test */ public function a_recipient_cannot_be_empty() { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('No recipient specified'); + Email::compose() ->recipient([]) ->send(); } - /** - * @test - * @expectedException InvalidArgumentException - * @expectedExceptionMessage E-mail address [not-a-valid-email-address] is invalid - */ + /** @test */ public function the_recipient_email_must_be_valid() { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); + Email::compose() ->recipient('not-a-valid-email-address') ->send(); } - /** - * @test - * @expectedException InvalidArgumentException - * @expectedExceptionMessage E-mail address [not-a-valid-email-address] is invalid - */ + /** @test */ public function cc_must_contain_valid_email_addresses() { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); + Email::compose() ->recipient('john@doe.com') ->cc([ @@ -70,13 +65,12 @@ public function cc_must_contain_valid_email_addresses() ->send(); } - /** - * @test - * @expectedException InvalidArgumentException - * @expectedExceptionMessage E-mail address [not-a-valid-email-address] is invalid - */ + /** @test */ public function bcc_must_contain_valid_email_addresses() { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('E-mail address [not-a-valid-email-address] is invalid'); + Email::compose() ->recipient('john@doe.com') ->bcc([ @@ -86,25 +80,23 @@ public function bcc_must_contain_valid_email_addresses() ->send(); } - /** - * @test - * @expectedException InvalidArgumentException - * @expectedExceptionMessage No subject specified - */ + /** @test */ public function a_subject_is_required() { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('No subject specified'); + Email::compose() ->recipient('john@doe.com') ->send(); } - /** - * @test - * @expectedException InvalidArgumentException - * @expectedExceptionMessage No view specified - */ + /** @test */ public function a_view_is_required() { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('No view specified'); + Email::compose() ->recipient('john@doe.com') ->subject('test')