From b2eed9a9c58c013fccb0f0daea5ab6a63b48564a Mon Sep 17 00:00:00 2001 From: shibafu Date: Tue, 4 Feb 2020 01:43:58 +0900 Subject: [PATCH 01/19] =?UTF-8?q?league/csv=E3=81=A8openpear/stream=5Ffilt?= =?UTF-8?q?er=5Fmbstring=E3=82=92=E4=BE=9D=E5=AD=98=E9=96=A2=E4=BF=82?= =?UTF-8?q?=E3=81=AB=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Providers/AppServiceProvider.php | 2 + composer.json | 8 ++ composer.lock | 116 ++++++++++++++++++++++++++- 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 675d30a..f952321 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -19,6 +19,8 @@ class AppServiceProvider extends ServiceProvider Blade::directive('parsedown', function ($expression) { return "text($expression); ?>"; }); + + stream_filter_register('convert.mbstring.*', 'Stream_Filter_Mbstring'); } /** diff --git a/composer.json b/composer.json index 3b89d39..9c5900a 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,12 @@ "keywords": ["framework", "laravel"], "license": "MIT", "type": "project", + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/xcezx/Stream_Filter_Mbstring" + } + ], "require": { "php": ">=7.1.0", "anhskohbo/no-captcha": "^3.0", @@ -13,7 +19,9 @@ "jakeasmith/http_build_url": "^1.0", "laravel/framework": "5.5.*", "laravel/tinker": "~1.0", + "league/csv": "^9.5", "misd/linkify": "^1.1", + "openpear/stream_filter_mbstring": "dev-master", "staudenmeir/eloquent-eager-limit": "^1.0", "symfony/css-selector": "^4.3", "symfony/dom-crawler": "^4.3" diff --git a/composer.lock b/composer.lock index d21588d..e448a5d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b6dfb80c350a7276bb2513a1aeb3d602", + "content-hash": "a391908b8086044d943007dce1ff4f12", "packages": [ { "name": "anhskohbo/no-captcha", @@ -1190,6 +1190,75 @@ ], "time": "2019-08-07T15:10:45+00:00" }, + { + "name": "league/csv", + "version": "9.5.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/csv.git", + "reference": "b348d09d0d258a4f068efb50a2510dc63101c213" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/b348d09d0d258a4f068efb50a2510dc63101c213", + "reference": "b348d09d0d258a4f068efb50a2510dc63101c213", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=7.0.10" + }, + "require-dev": { + "ext-curl": "*", + "friendsofphp/php-cs-fixer": "^2.12", + "phpstan/phpstan": "^0.9.2", + "phpstan/phpstan-phpunit": "^0.9.4", + "phpstan/phpstan-strict-rules": "^0.9.0", + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Csv\\": "src" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://github.com/nyamsprod/", + "role": "Developer" + } + ], + "description": "Csv data manipulation made easy in PHP", + "homepage": "http://csv.thephpleague.com", + "keywords": [ + "csv", + "export", + "filter", + "import", + "read", + "write" + ], + "time": "2019-12-15T19:51:41+00:00" + }, { "name": "league/flysystem", "version": "1.0.63", @@ -1555,6 +1624,47 @@ ], "time": "2019-11-08T13:50:10+00:00" }, + { + "name": "openpear/stream_filter_mbstring", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/xcezx/Stream_Filter_Mbstring.git", + "reference": "1c5ab27fd874e74d3d2bfdb9b74d3ebe017e6e14" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/xcezx/Stream_Filter_Mbstring/zipball/1c5ab27fd874e74d3d2bfdb9b74d3ebe017e6e14", + "reference": "1c5ab27fd874e74d3d2bfdb9b74d3ebe017e6e14", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Stream_Filter_Mbstring": "src/" + } + }, + "license": [ + "PHP-3.01" + ], + "authors": [ + { + "name": "hnw", + "role": "developer" + }, + { + "name": "MAEKAWA Tsuyoshi", + "email": "main.xcezx@gmail.com", + "role": "maintainer" + } + ], + "description": "mbstring を使って文字列変換を行う stream filter", + "support": { + "source": "https://github.com/xcezx/Stream_Filter_Mbstring/tree/master", + "issues": "https://github.com/xcezx/Stream_Filter_Mbstring/issues" + }, + "time": "2012-11-21T12:10:21+00:00" + }, { "name": "paragonie/random_compat", "version": "v9.99.99", @@ -6410,7 +6520,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "openpear/stream_filter_mbstring": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { From 38ae540009cc4129c49efd0516dd4b222757ded3 Mon Sep 17 00:00:00 2001 From: shibafu Date: Tue, 4 Feb 2020 01:44:16 +0900 Subject: [PATCH 02/19] =?UTF-8?q?=E6=96=87=E5=AD=97=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E5=88=A4=E5=AE=9A=E3=81=A8=E5=BF=85=E9=A0=88=E5=88=97?= =?UTF-8?q?=E3=81=AE=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E3=81=BE=E3=81=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Exceptions/CsvImportException.php | 29 +++++++ app/Io/CheckinCsvImporter.php | 76 +++++++++++++++++++ tests/Unit/Io/CheckinCsvImporterTest.php | 37 +++++++++ .../Csv/incompatible-charset.eucjp.csv | 2 + tests/fixture/Csv/missing-time.sjis.csv | 1 + tests/fixture/Csv/missing-time.utf8.csv | 1 + 6 files changed, 146 insertions(+) create mode 100644 app/Exceptions/CsvImportException.php create mode 100644 app/Io/CheckinCsvImporter.php create mode 100644 tests/Unit/Io/CheckinCsvImporterTest.php create mode 100644 tests/fixture/Csv/incompatible-charset.eucjp.csv create mode 100644 tests/fixture/Csv/missing-time.sjis.csv create mode 100644 tests/fixture/Csv/missing-time.utf8.csv diff --git a/app/Exceptions/CsvImportException.php b/app/Exceptions/CsvImportException.php new file mode 100644 index 0000000..eee24cf --- /dev/null +++ b/app/Exceptions/CsvImportException.php @@ -0,0 +1,29 @@ +errors = $errors; + } + + /** + * @return string[] + */ + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php new file mode 100644 index 0000000..b02d309 --- /dev/null +++ b/app/Io/CheckinCsvImporter.php @@ -0,0 +1,76 @@ +filename = $filename; + } + + public function execute() + { + // Guess charset + $head = file_get_contents($this->filename, false, null, 0, 1024); + $charset = mb_detect_encoding($head, ['ASCII', 'UTF-8', 'SJIS-win'], true); + if (array_search($charset, ['UTF-8', 'SJIS-win'], true) === false) { + throw new CsvImportException(['文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。']); + } + + // Open CSV + $csv = Reader::createFromPath($this->filename, 'r'); + $csv->setHeaderOffset(0); + if ($charset === 'SJIS-win') { + $csv->addStreamFilter('convert.mbstring.encoding.SJIS-win:UTF-8'); + } + + // Import + DB::transaction(function () use ($csv) { + $errors = []; + + if (!in_array('日時', $csv->getHeader(), true)) { + $errors[] = '日時列は必須です。'; + throw new CsvImportException($errors); + } + + foreach ($csv->getRecords() as $offset => $record) { + $ejaculation = new Ejaculation(); + + $checkinAt = $record['日時'] ?? null; + if (empty($checkinAt)) { + $errors[] = "{$offset} 行 : 日時列は必須です。"; + continue; + } + if (preg_match('/\A20\d{2}[-/](1[0-2]|0?\d)[-/](0?\d|[1-2]\d|3[01]) (0?\d|1\d|2[0-4]):(0?\d|[1-5]\d)(?P:(0?\d|[1-5]\d))?\z/', $checkinAt, $checkinAtMatches) !== 1) { + $errors[] = "{$offset} 行 : 日時列の書式が正しくありません。"; + continue; + } + if (empty($checkinAtMatches['second'])) { + $checkinAt .= ':00'; + } + $checkinAt = str_replace('/', '-', $checkinAt); + try { + $ejaculation->ejaculated_date = Carbon::createFromFormat('Y-m-d H:i:s', $checkinAt); + } catch (\InvalidArgumentException $e) { + $errors[] = "{$offset} 行 : 日時列に不正な値が入力されています。"; + } + + $ejaculation->save(); + } + + if (!empty($errors)) { + throw new CsvImportException($errors); + } + }); + } +} diff --git a/tests/Unit/Io/CheckinCsvImporterTest.php b/tests/Unit/Io/CheckinCsvImporterTest.php new file mode 100644 index 0000000..a85935d --- /dev/null +++ b/tests/Unit/Io/CheckinCsvImporterTest.php @@ -0,0 +1,37 @@ +expectException(CsvImportException::class); + $this->expectExceptionMessage('文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。'); + + $importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/incompatible-charset.eucjp.csv'); + $importer->execute(); + } + + public function testMissingTimeUTF8() + { + $this->expectException(CsvImportException::class); + $this->expectExceptionMessage('日時列は必須です。'); + + $importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/missing-time.utf8.csv'); + $importer->execute(); + } + + public function testMissingTimeSJIS() + { + $this->expectException(CsvImportException::class); + $this->expectExceptionMessage('日時列は必須です。'); + + $importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/missing-time.sjis.csv'); + $importer->execute(); + } +} diff --git a/tests/fixture/Csv/incompatible-charset.eucjp.csv b/tests/fixture/Csv/incompatible-charset.eucjp.csv new file mode 100644 index 0000000..866ed4d --- /dev/null +++ b/tests/fixture/Csv/incompatible-charset.eucjp.csv @@ -0,0 +1,2 @@ +,Ρ, +2019-01-01 00:01:02,ƥȥƥȤ,https://example.com/ diff --git a/tests/fixture/Csv/missing-time.sjis.csv b/tests/fixture/Csv/missing-time.sjis.csv new file mode 100644 index 0000000..99b9d73 --- /dev/null +++ b/tests/fixture/Csv/missing-time.sjis.csv @@ -0,0 +1 @@ +m[g,IJYN,^O1 diff --git a/tests/fixture/Csv/missing-time.utf8.csv b/tests/fixture/Csv/missing-time.utf8.csv new file mode 100644 index 0000000..b0bc641 --- /dev/null +++ b/tests/fixture/Csv/missing-time.utf8.csv @@ -0,0 +1 @@ +ノート,オカズリンク,タグ1 From 64065ce9e66092bdfe8f11f64f5a2264de3c3587 Mon Sep 17 00:00:00 2001 From: shibafu Date: Wed, 5 Feb 2020 00:52:13 +0900 Subject: [PATCH 03/19] =?UTF-8?q?=E3=81=96=E3=81=A3=E3=81=8F=E3=82=8A?= =?UTF-8?q?=E3=81=A8=E3=81=97=E3=81=9F=E5=87=A6=E7=90=86=E3=82=92=E6=9B=B8?= =?UTF-8?q?=E3=81=84=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Io/CheckinCsvImporter.php | 55 +++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index b02d309..1fd2f0d 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -4,6 +4,7 @@ namespace App\Io; use App\Ejaculation; use App\Exceptions\CsvImportException; +use App\Tag; use Carbon\Carbon; use Illuminate\Support\Facades\DB; use League\Csv\Reader; @@ -51,20 +52,64 @@ class CheckinCsvImporter $errors[] = "{$offset} 行 : 日時列は必須です。"; continue; } - if (preg_match('/\A20\d{2}[-/](1[0-2]|0?\d)[-/](0?\d|[1-2]\d|3[01]) (0?\d|1\d|2[0-4]):(0?\d|[1-5]\d)(?P:(0?\d|[1-5]\d))?\z/', $checkinAt, $checkinAtMatches) !== 1) { + if (preg_match('/\A20\d{2}[-/](1[0-2]|0?\d)[-/](0?\d|[1-2]\d|3[01]) (0?\d|1\d|2[0-4]):(0?\d|[1-5]\d)(:(0?\d|[1-5]\d))?\z/', $checkinAt) !== 1) { $errors[] = "{$offset} 行 : 日時列の書式が正しくありません。"; continue; } - if (empty($checkinAtMatches['second'])) { - $checkinAt .= ':00'; - } $checkinAt = str_replace('/', '-', $checkinAt); try { - $ejaculation->ejaculated_date = Carbon::createFromFormat('Y-m-d H:i:s', $checkinAt); + $ejaculation->ejaculated_date = Carbon::createFromFormat('!Y-m-d H:i+', $checkinAt); } catch (\InvalidArgumentException $e) { $errors[] = "{$offset} 行 : 日時列に不正な値が入力されています。"; + continue; } + if (!empty($record['ノート'])) { + $note = $record['ノート']; + + if (mb_strlen($note) > 500) { + $errors[] = "{$offset} 行 : ノート列は500文字以内にしてください。"; + continue; + } + + $ejaculation->note = $note; + } + + if (!empty($record['オカズリンク'])) { + $link = $record['オカズリンク']; + + if (mb_strlen($link) > 2000) { + $errors[] = "{$offset} 行 : オカズリンク列は500文字以内にしてください。"; + continue; + } + + // TODO: URL Validation + + $ejaculation->link = $link; + } + + $tagIds = []; + for ($i = 1; $i <= 32; $i++) { + $column = 'タグ' . $i; + if (empty($record[$column])) { + break; + } else { + $tag = trim($record[$column]); + + if (empty($tag)) { + break; + } + if (mb_strlen($tag) > 255) { + $errors[] = "{$offset} 行 : {$column}列は255文字以内にしてください。"; + continue 2; + } + + $tag = Tag::firstOrCreate(['name' => $tag]); + $tagIds[] = $tag->id; + } + } + $ejaculation->tags()->sync($tagIds); + $ejaculation->save(); } From 67b697a60032bc9a24574e2bf383b111fa697d8b Mon Sep 17 00:00:00 2001 From: shibafu Date: Thu, 13 Feb 2020 01:03:54 +0900 Subject: [PATCH 04/19] =?UTF-8?q?Validator=E3=82=92=E4=BD=BF=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=8F=E3=81=AA=E3=81=A3=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 日時のバリデーションが思うように動いていなかったので、カスタムルールを追加 --- app/Io/CheckinCsvImporter.php | 58 ++++++----------- app/Rules/CsvDateTime.php | 65 +++++++++++++++++++ tests/Unit/Io/CheckinCsvImporterTest.php | 62 +++++++++++++++++- tests/fixture/Csv/date-nosecond.utf8.csv | 2 + .../fixture/Csv/date-nozero-nosecond.utf8.csv | 2 + tests/fixture/Csv/date-nozero.utf8.csv | 2 + tests/fixture/Csv/date.utf8.csv | 2 + .../Csv/incompatible-charset.eucjp.csv | 2 +- 8 files changed, 153 insertions(+), 42 deletions(-) create mode 100644 app/Rules/CsvDateTime.php create mode 100644 tests/fixture/Csv/date-nosecond.utf8.csv create mode 100644 tests/fixture/Csv/date-nozero-nosecond.utf8.csv create mode 100644 tests/fixture/Csv/date-nozero.utf8.csv create mode 100644 tests/fixture/Csv/date.utf8.csv diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index 1fd2f0d..2a7bb91 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -4,18 +4,24 @@ namespace App\Io; use App\Ejaculation; use App\Exceptions\CsvImportException; +use App\Rules\CsvDateTime; use App\Tag; +use App\User; use Carbon\Carbon; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Validator; use League\Csv\Reader; class CheckinCsvImporter { + /** @var User Target user */ + private $user; /** @var string CSV filename */ private $filename; - public function __construct(string $filename) + public function __construct(User $user, string $filename) { + $this->user = $user; $this->filename = $filename; } @@ -45,48 +51,24 @@ class CheckinCsvImporter } foreach ($csv->getRecords() as $offset => $record) { - $ejaculation = new Ejaculation(); + $ejaculation = new Ejaculation(['user_id' => $this->user->id]); - $checkinAt = $record['日時'] ?? null; - if (empty($checkinAt)) { - $errors[] = "{$offset} 行 : 日時列は必須です。"; - continue; - } - if (preg_match('/\A20\d{2}[-/](1[0-2]|0?\d)[-/](0?\d|[1-2]\d|3[01]) (0?\d|1\d|2[0-4]):(0?\d|[1-5]\d)(:(0?\d|[1-5]\d))?\z/', $checkinAt) !== 1) { - $errors[] = "{$offset} 行 : 日時列の書式が正しくありません。"; - continue; - } - $checkinAt = str_replace('/', '-', $checkinAt); - try { - $ejaculation->ejaculated_date = Carbon::createFromFormat('!Y-m-d H:i+', $checkinAt); - } catch (\InvalidArgumentException $e) { - $errors[] = "{$offset} 行 : 日時列に不正な値が入力されています。"; - continue; - } + $validator = Validator::make($record, [ + '日時' => ['required', new CsvDateTime()], + 'ノート' => 'nullable|string|max:500', + 'オカズリンク' => 'nullable|url|max:2000', + ]); - if (!empty($record['ノート'])) { - $note = $record['ノート']; - - if (mb_strlen($note) > 500) { - $errors[] = "{$offset} 行 : ノート列は500文字以内にしてください。"; - continue; + if ($validator->fails()) { + foreach ($validator->errors()->all() as $message) { + $errors[] = "{$offset} 行 : {$message}"; } - - $ejaculation->note = $note; + continue; } - if (!empty($record['オカズリンク'])) { - $link = $record['オカズリンク']; - - if (mb_strlen($link) > 2000) { - $errors[] = "{$offset} 行 : オカズリンク列は500文字以内にしてください。"; - continue; - } - - // TODO: URL Validation - - $ejaculation->link = $link; - } + $ejaculation->ejaculated_date = Carbon::createFromFormat('!Y/m/d H:i+', $record['日時']); + $ejaculation->note = $record['ノート'] ?? ''; + $ejaculation->link = $record['オカズリンク'] ?? ''; $tagIds = []; for ($i = 1; $i <= 32; $i++) { diff --git a/app/Rules/CsvDateTime.php b/app/Rules/CsvDateTime.php new file mode 100644 index 0000000..118c5ba --- /dev/null +++ b/app/Rules/CsvDateTime.php @@ -0,0 +1,65 @@ +format($format) === $value) { + return true; + } + } + + return false; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message() + { + return ':attribute の形式は "年/月/日 時:分" にしてください。'; + } +} diff --git a/tests/Unit/Io/CheckinCsvImporterTest.php b/tests/Unit/Io/CheckinCsvImporterTest.php index a85935d..3a81ded 100644 --- a/tests/Unit/Io/CheckinCsvImporterTest.php +++ b/tests/Unit/Io/CheckinCsvImporterTest.php @@ -4,34 +4,90 @@ namespace Tests\Unit\Io; use App\Exceptions\CsvImportException; use App\Io\CheckinCsvImporter; +use App\User; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Carbon; use Tests\TestCase; class CheckinCsvImporterTest extends TestCase { + use RefreshDatabase; + public function testIncompatibleCharsetEUCJP() { + $user = factory(User::class)->create(); $this->expectException(CsvImportException::class); $this->expectExceptionMessage('文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。'); - $importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/incompatible-charset.eucjp.csv'); + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/incompatible-charset.eucjp.csv'); $importer->execute(); } public function testMissingTimeUTF8() { + $user = factory(User::class)->create(); $this->expectException(CsvImportException::class); $this->expectExceptionMessage('日時列は必須です。'); - $importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/missing-time.utf8.csv'); + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/missing-time.utf8.csv'); $importer->execute(); } public function testMissingTimeSJIS() { + $user = factory(User::class)->create(); $this->expectException(CsvImportException::class); $this->expectExceptionMessage('日時列は必須です。'); - $importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/missing-time.sjis.csv'); + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/missing-time.sjis.csv'); $importer->execute(); } + + public function testDateNoSecondUTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nosecond.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); + } + + public function testDateNoZeroNoSecondUTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nozero-nosecond.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); + } + + public function testDateUTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); + } + + public function testDateNoZeroUTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nozero.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); + } } diff --git a/tests/fixture/Csv/date-nosecond.utf8.csv b/tests/fixture/Csv/date-nosecond.utf8.csv new file mode 100644 index 0000000..a100b02 --- /dev/null +++ b/tests/fixture/Csv/date-nosecond.utf8.csv @@ -0,0 +1,2 @@ +日時 +2020/01/23 06:01 diff --git a/tests/fixture/Csv/date-nozero-nosecond.utf8.csv b/tests/fixture/Csv/date-nozero-nosecond.utf8.csv new file mode 100644 index 0000000..60cb295 --- /dev/null +++ b/tests/fixture/Csv/date-nozero-nosecond.utf8.csv @@ -0,0 +1,2 @@ +日時 +2020/1/23 6:01 diff --git a/tests/fixture/Csv/date-nozero.utf8.csv b/tests/fixture/Csv/date-nozero.utf8.csv new file mode 100644 index 0000000..e0b3ede --- /dev/null +++ b/tests/fixture/Csv/date-nozero.utf8.csv @@ -0,0 +1,2 @@ +日時 +2020/1/23 6:01:02 diff --git a/tests/fixture/Csv/date.utf8.csv b/tests/fixture/Csv/date.utf8.csv new file mode 100644 index 0000000..24ee353 --- /dev/null +++ b/tests/fixture/Csv/date.utf8.csv @@ -0,0 +1,2 @@ +日時 +2020/01/23 06:01:02 diff --git a/tests/fixture/Csv/incompatible-charset.eucjp.csv b/tests/fixture/Csv/incompatible-charset.eucjp.csv index 866ed4d..62dcc8d 100644 --- a/tests/fixture/Csv/incompatible-charset.eucjp.csv +++ b/tests/fixture/Csv/incompatible-charset.eucjp.csv @@ -1,2 +1,2 @@ ,Ρ, -2019-01-01 00:01:02,ƥȥƥȤ,https://example.com/ +2019/1/01 0:01,ƥȥƥȤ,https://example.com/ From b29a82435cb695cb0b2e20fa53094f62d3866cf5 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sat, 15 Feb 2020 22:53:10 +0900 Subject: [PATCH 05/19] =?UTF-8?q?DataProvider=E3=82=92=E4=BD=BF=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E6=9B=B8=E3=81=84?= =?UTF-8?q?=E3=81=A6=E3=81=BF=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Unit/Io/CheckinCsvImporterTest.php | 68 +++++++++--------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/tests/Unit/Io/CheckinCsvImporterTest.php b/tests/Unit/Io/CheckinCsvImporterTest.php index 3a81ded..5776838 100644 --- a/tests/Unit/Io/CheckinCsvImporterTest.php +++ b/tests/Unit/Io/CheckinCsvImporterTest.php @@ -23,71 +23,51 @@ class CheckinCsvImporterTest extends TestCase $importer->execute(); } - public function testMissingTimeUTF8() + /** + * @dataProvider provideMissingTime + */ + public function testMissingTime($filename) { $user = factory(User::class)->create(); $this->expectException(CsvImportException::class); $this->expectExceptionMessage('日時列は必須です。'); - $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/missing-time.utf8.csv'); + $importer = new CheckinCsvImporter($user, $filename); $importer->execute(); } - public function testMissingTimeSJIS() + public function provideMissingTime() { - $user = factory(User::class)->create(); - $this->expectException(CsvImportException::class); - $this->expectExceptionMessage('日時列は必須です。'); - - $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/missing-time.sjis.csv'); - $importer->execute(); + return [ + 'UTF8' => [__DIR__ . '/../../fixture/Csv/missing-time.utf8.csv'], + 'SJIS' => [__DIR__ . '/../../fixture/Csv/missing-time.sjis.csv'], + ]; } - public function testDateNoSecondUTF8() + /** + * @dataProvider provideDate + */ + public function testDate($expectedDate, $filename) { $user = factory(User::class)->create(); - $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nosecond.utf8.csv'); + $importer = new CheckinCsvImporter($user, $filename); $importer->execute(); $ejaculation = $user->ejaculations()->first(); $this->assertSame(1, $user->ejaculations()->count()); - $this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); + $this->assertEquals($expectedDate, $ejaculation->ejaculated_date); } - public function testDateNoZeroNoSecondUTF8() + public function provideDate() { - $user = factory(User::class)->create(); + $date = Carbon::create(2020, 1, 23, 6, 1, 0, 'Asia/Tokyo'); - $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nozero-nosecond.utf8.csv'); - $importer->execute(); - $ejaculation = $user->ejaculations()->first(); - - $this->assertSame(1, $user->ejaculations()->count()); - $this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); - } - - public function testDateUTF8() - { - $user = factory(User::class)->create(); - - $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date.utf8.csv'); - $importer->execute(); - $ejaculation = $user->ejaculations()->first(); - - $this->assertSame(1, $user->ejaculations()->count()); - $this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); - } - - public function testDateNoZeroUTF8() - { - $user = factory(User::class)->create(); - - $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nozero.utf8.csv'); - $importer->execute(); - $ejaculation = $user->ejaculations()->first(); - - $this->assertSame(1, $user->ejaculations()->count()); - $this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); + return [ + 'Zero, Second, UTF8' => [$date, __DIR__ . '/../../fixture/Csv/date.utf8.csv'], + 'NoZero, Second, UTF8' => [$date, __DIR__ . '/../../fixture/Csv/date-nozero.utf8.csv'], + 'Zero, NoSecond, UTF8' => [$date, __DIR__ . '/../../fixture/Csv/date-nosecond.utf8.csv'], + 'NoZero, NoSecond, UTF8' => [$date, __DIR__ . '/../../fixture/Csv/date-nozero-nosecond.utf8.csv'], + ]; } } From ea59bcf150bd5f59986906ea858555c94396f6ae Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 16 Feb 2020 14:28:30 +0900 Subject: [PATCH 06/19] =?UTF-8?q?=E6=97=A5=E4=BB=98=E5=BD=A2=E5=BC=8F?= =?UTF-8?q?=E3=81=A8=E7=AF=84=E5=9B=B2=E3=81=AE=E3=83=81=E3=82=A7=E3=83=83?= =?UTF-8?q?=E3=82=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Io/CheckinCsvImporter.php | 5 ++-- app/Rules/CsvDateTime.php | 21 ++++++++++++++-- tests/Unit/Io/CheckinCsvImporterTest.php | 31 ++++++++++++++++++++++++ tests/fixture/Csv/invalid-date.utf8.csv | 17 +++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 tests/fixture/Csv/invalid-date.utf8.csv diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index 2a7bb91..92a9ac6 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -51,6 +51,7 @@ class CheckinCsvImporter } foreach ($csv->getRecords() as $offset => $record) { + $line = $offset + 1; $ejaculation = new Ejaculation(['user_id' => $this->user->id]); $validator = Validator::make($record, [ @@ -61,7 +62,7 @@ class CheckinCsvImporter if ($validator->fails()) { foreach ($validator->errors()->all() as $message) { - $errors[] = "{$offset} 行 : {$message}"; + $errors[] = "{$line} 行 : {$message}"; } continue; } @@ -82,7 +83,7 @@ class CheckinCsvImporter break; } if (mb_strlen($tag) > 255) { - $errors[] = "{$offset} 行 : {$column}列は255文字以内にしてください。"; + $errors[] = "{$line} 行 : {$column}列は255文字以内にしてください。"; continue 2; } diff --git a/app/Rules/CsvDateTime.php b/app/Rules/CsvDateTime.php index 118c5ba..76159ef 100644 --- a/app/Rules/CsvDateTime.php +++ b/app/Rules/CsvDateTime.php @@ -17,6 +17,12 @@ class CsvDateTime implements Rule 'Y/n/j G:i', ]; + const MINIMUM_TIMESTAMP = 946652400; // 2000-01-01 00:00:00 JST + const MAXIMUM_TIMESTAMP = 4102412399; // 2099-12-31 23:59:59 JST + + /** @var string Validation error message */ + private $message = ':attribute の形式は "年/月/日 時:分" にしてください。'; + /** * Create a new rule instance. * @@ -44,8 +50,19 @@ class CsvDateTime implements Rule foreach (self::VALID_FORMATS as $format) { $date = \DateTime::createFromFormat('!' . $format, $value); + if (!$date) { + continue; + } - if ($date && $date->format($format) === $value) { + $timestamp = (int) $date->format('U'); + if ($timestamp < self::MINIMUM_TIMESTAMP || self::MAXIMUM_TIMESTAMP < $timestamp) { + $this->message = ':attribute は 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。'; + + return false; + } + + $formatted = $date->format($format); + if ($formatted === $value) { return true; } } @@ -60,6 +77,6 @@ class CsvDateTime implements Rule */ public function message() { - return ':attribute の形式は "年/月/日 時:分" にしてください。'; + return $this->message; } } diff --git a/tests/Unit/Io/CheckinCsvImporterTest.php b/tests/Unit/Io/CheckinCsvImporterTest.php index 5776838..71256d0 100644 --- a/tests/Unit/Io/CheckinCsvImporterTest.php +++ b/tests/Unit/Io/CheckinCsvImporterTest.php @@ -70,4 +70,35 @@ class CheckinCsvImporterTest extends TestCase 'NoZero, NoSecond, UTF8' => [$date, __DIR__ . '/../../fixture/Csv/date-nozero-nosecond.utf8.csv'], ]; } + + public function testInvalidDate() + { + $user = factory(User::class)->create(); + + try { + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/invalid-date.utf8.csv'); + $importer->execute(); + } catch (CsvImportException $e) { + $this->assertSame('2 行 : 日時 は 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。', $e->getErrors()[0]); + $this->assertSame('3 行 : 日時 は 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。', $e->getErrors()[1]); + $this->assertSame('4 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[2]); + $this->assertSame('5 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[3]); + $this->assertSame('6 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[4]); + $this->assertSame('7 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[5]); + $this->assertSame('8 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[6]); + $this->assertSame('9 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[7]); + $this->assertSame('10 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[8]); + $this->assertSame('11 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[9]); + $this->assertSame('12 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[10]); + $this->assertSame('13 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[11]); + $this->assertSame('14 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[12]); + $this->assertSame('15 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[13]); + $this->assertSame('16 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[14]); + $this->assertSame('17 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[15]); + + return; + } + + $this->fail('期待する例外が発生していません'); + } } diff --git a/tests/fixture/Csv/invalid-date.utf8.csv b/tests/fixture/Csv/invalid-date.utf8.csv new file mode 100644 index 0000000..3c60935 --- /dev/null +++ b/tests/fixture/Csv/invalid-date.utf8.csv @@ -0,0 +1,17 @@ +日時,ノート +1999/12/31 23:59:59,最小境界 +2100/01/01 00:00:00,最大境界 +-1/01/01 00:00:00,存在しない日付 +2019/-1/01 00:00:00,存在しない日付 +2019/01/-1 00:00:00,存在しない日付 +2019/02/29 00:00:00,存在しない日付 +2019/00/01 00:00:00,存在しない日付 +2019/01/00 00:00:00,存在しない日付 +2019/01/32 00:00:00,存在しない日付 +2019/13/01 00:00:00,存在しない日付 +2019/01/01 00:60:00,存在しない時刻 +2019/01/01 24:00:00,存在しない時刻 +2019/01/01 00:00:60,存在しない時刻 +2019/01/01 -1:00:00,存在しない時刻 +2019/01/01 00:-1:00,存在しない時刻 +2019/01/01 00:00:-1,存在しない時刻 From cef69a1545e7eafa4fca1021d1f0f295f97ad314 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 16 Feb 2020 14:49:55 +0900 Subject: [PATCH 07/19] =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=88=E3=81=AE?= =?UTF-8?q?=E6=96=87=E5=AD=97=E6=95=B0,=E6=94=B9=E8=A1=8C=E3=81=AA?= =?UTF-8?q?=E3=81=A9=E3=81=AE=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Unit/Io/CheckinCsvImporterTest.php | 35 +++++++++++++++++++ .../Csv/note-over-length.ascii.utf8.csv | 2 ++ .../fixture/Csv/note-over-length.jp.utf8.csv | 2 ++ tests/fixture/Csv/note.utf8.csv | 5 +++ 4 files changed, 44 insertions(+) create mode 100644 tests/fixture/Csv/note-over-length.ascii.utf8.csv create mode 100644 tests/fixture/Csv/note-over-length.jp.utf8.csv create mode 100644 tests/fixture/Csv/note.utf8.csv diff --git a/tests/Unit/Io/CheckinCsvImporterTest.php b/tests/Unit/Io/CheckinCsvImporterTest.php index 71256d0..b4cecfb 100644 --- a/tests/Unit/Io/CheckinCsvImporterTest.php +++ b/tests/Unit/Io/CheckinCsvImporterTest.php @@ -101,4 +101,39 @@ class CheckinCsvImporterTest extends TestCase $this->fail('期待する例外が発生していません'); } + + public function testNoteUTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/note.utf8.csv'); + $importer->execute(); + $ejaculations = $user->ejaculations()->orderBy('ejaculated_date')->get(); + + $this->assertCount(3, $ejaculations); + $this->assertEquals('The quick brown fox jumps over the lazy dog. 素早い茶色の狐はのろまな犬を飛び越える', $ejaculations[0]->note); + $this->assertEquals("The quick brown fox jumps over the lazy dog.\n素早い茶色の狐はのろまな犬を飛び越える", $ejaculations[1]->note); + $this->assertEquals('The quick brown fox jumps over the "lazy" dog.', $ejaculations[2]->note); + } + + /** + * @dataProvider provideNoteOverLength + */ + public function testNoteOverLength($filename) + { + $user = factory(User::class)->create(); + $this->expectException(CsvImportException::class); + $this->expectExceptionMessage('2 行 : ノートには500文字以下の文字列を指定してください。'); + + $importer = new CheckinCsvImporter($user, $filename); + $importer->execute(); + } + + public function provideNoteOverLength() + { + return [ + 'ASCII Only, UTF8' => [__DIR__ . '/../../fixture/Csv/note-over-length.ascii.utf8.csv'], + 'JP, UTF8' => [__DIR__ . '/../../fixture/Csv/note-over-length.jp.utf8.csv'], + ]; + } } diff --git a/tests/fixture/Csv/note-over-length.ascii.utf8.csv b/tests/fixture/Csv/note-over-length.ascii.utf8.csv new file mode 100644 index 0000000..33d1c37 --- /dev/null +++ b/tests/fixture/Csv/note-over-length.ascii.utf8.csv @@ -0,0 +1,2 @@ +日時,ノート +2020/01/23 06:01,oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooox diff --git a/tests/fixture/Csv/note-over-length.jp.utf8.csv b/tests/fixture/Csv/note-over-length.jp.utf8.csv new file mode 100644 index 0000000..47d4c4e --- /dev/null +++ b/tests/fixture/Csv/note-over-length.jp.utf8.csv @@ -0,0 +1,2 @@ +日時,ノート +2020/01/23 06:01,ああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああい diff --git a/tests/fixture/Csv/note.utf8.csv b/tests/fixture/Csv/note.utf8.csv new file mode 100644 index 0000000..a585535 --- /dev/null +++ b/tests/fixture/Csv/note.utf8.csv @@ -0,0 +1,5 @@ +日時,ノート +2020/01/23 06:01,The quick brown fox jumps over the lazy dog. 素早い茶色の狐はのろまな犬を飛び越える +2020/01/23 06:02,"The quick brown fox jumps over the lazy dog. +素早い茶色の狐はのろまな犬を飛び越える" +2020/01/23 06:03,"The quick brown fox jumps over the ""lazy"" dog." From 3def26ddb96199cb1c6c35c3c0d05c06aa13b332 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 16 Feb 2020 15:28:11 +0900 Subject: [PATCH 08/19] =?UTF-8?q?Fixture=E3=81=AE=E6=94=B9=E8=A1=8C?= =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92CRLF=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/fixture/Csv/date-nosecond.utf8.csv | 4 +-- .../fixture/Csv/date-nozero-nosecond.utf8.csv | 4 +-- tests/fixture/Csv/date-nozero.utf8.csv | 4 +-- tests/fixture/Csv/date.utf8.csv | 4 +-- .../Csv/incompatible-charset.eucjp.csv | 4 +-- tests/fixture/Csv/invalid-date.utf8.csv | 34 +++++++++---------- tests/fixture/Csv/missing-time.sjis.csv | 2 +- tests/fixture/Csv/missing-time.utf8.csv | 2 +- .../Csv/note-over-length.ascii.utf8.csv | 4 +-- .../fixture/Csv/note-over-length.jp.utf8.csv | 4 +-- tests/fixture/Csv/note.utf8.csv | 10 +++--- 11 files changed, 38 insertions(+), 38 deletions(-) diff --git a/tests/fixture/Csv/date-nosecond.utf8.csv b/tests/fixture/Csv/date-nosecond.utf8.csv index a100b02..5285a20 100644 --- a/tests/fixture/Csv/date-nosecond.utf8.csv +++ b/tests/fixture/Csv/date-nosecond.utf8.csv @@ -1,2 +1,2 @@ -日時 -2020/01/23 06:01 +日時 +2020/01/23 06:01 diff --git a/tests/fixture/Csv/date-nozero-nosecond.utf8.csv b/tests/fixture/Csv/date-nozero-nosecond.utf8.csv index 60cb295..436840f 100644 --- a/tests/fixture/Csv/date-nozero-nosecond.utf8.csv +++ b/tests/fixture/Csv/date-nozero-nosecond.utf8.csv @@ -1,2 +1,2 @@ -日時 -2020/1/23 6:01 +日時 +2020/1/23 6:01 diff --git a/tests/fixture/Csv/date-nozero.utf8.csv b/tests/fixture/Csv/date-nozero.utf8.csv index e0b3ede..769cda9 100644 --- a/tests/fixture/Csv/date-nozero.utf8.csv +++ b/tests/fixture/Csv/date-nozero.utf8.csv @@ -1,2 +1,2 @@ -日時 -2020/1/23 6:01:02 +日時 +2020/1/23 6:01:02 diff --git a/tests/fixture/Csv/date.utf8.csv b/tests/fixture/Csv/date.utf8.csv index 24ee353..6e72631 100644 --- a/tests/fixture/Csv/date.utf8.csv +++ b/tests/fixture/Csv/date.utf8.csv @@ -1,2 +1,2 @@ -日時 -2020/01/23 06:01:02 +日時 +2020/01/23 06:01:02 diff --git a/tests/fixture/Csv/incompatible-charset.eucjp.csv b/tests/fixture/Csv/incompatible-charset.eucjp.csv index 62dcc8d..050a68d 100644 --- a/tests/fixture/Csv/incompatible-charset.eucjp.csv +++ b/tests/fixture/Csv/incompatible-charset.eucjp.csv @@ -1,2 +1,2 @@ -,Ρ, -2019/1/01 0:01,ƥȥƥȤ,https://example.com/ +,Ρ, +2019/1/01 0:01,ƥȥƥȤ,https://example.com/ diff --git a/tests/fixture/Csv/invalid-date.utf8.csv b/tests/fixture/Csv/invalid-date.utf8.csv index 3c60935..2b542ba 100644 --- a/tests/fixture/Csv/invalid-date.utf8.csv +++ b/tests/fixture/Csv/invalid-date.utf8.csv @@ -1,17 +1,17 @@ -日時,ノート -1999/12/31 23:59:59,最小境界 -2100/01/01 00:00:00,最大境界 --1/01/01 00:00:00,存在しない日付 -2019/-1/01 00:00:00,存在しない日付 -2019/01/-1 00:00:00,存在しない日付 -2019/02/29 00:00:00,存在しない日付 -2019/00/01 00:00:00,存在しない日付 -2019/01/00 00:00:00,存在しない日付 -2019/01/32 00:00:00,存在しない日付 -2019/13/01 00:00:00,存在しない日付 -2019/01/01 00:60:00,存在しない時刻 -2019/01/01 24:00:00,存在しない時刻 -2019/01/01 00:00:60,存在しない時刻 -2019/01/01 -1:00:00,存在しない時刻 -2019/01/01 00:-1:00,存在しない時刻 -2019/01/01 00:00:-1,存在しない時刻 +日時,ノート +1999/12/31 23:59:59,最小境界 +2100/01/01 00:00:00,最大境界 +-1/01/01 00:00:00,存在しない日付 +2019/-1/01 00:00:00,存在しない日付 +2019/01/-1 00:00:00,存在しない日付 +2019/02/29 00:00:00,存在しない日付 +2019/00/01 00:00:00,存在しない日付 +2019/01/00 00:00:00,存在しない日付 +2019/01/32 00:00:00,存在しない日付 +2019/13/01 00:00:00,存在しない日付 +2019/01/01 00:60:00,存在しない時刻 +2019/01/01 24:00:00,存在しない時刻 +2019/01/01 00:00:60,存在しない時刻 +2019/01/01 -1:00:00,存在しない時刻 +2019/01/01 00:-1:00,存在しない時刻 +2019/01/01 00:00:-1,存在しない時刻 diff --git a/tests/fixture/Csv/missing-time.sjis.csv b/tests/fixture/Csv/missing-time.sjis.csv index 99b9d73..8c88d46 100644 --- a/tests/fixture/Csv/missing-time.sjis.csv +++ b/tests/fixture/Csv/missing-time.sjis.csv @@ -1 +1 @@ -m[g,IJYN,^O1 +m[g,IJYN,^O1 diff --git a/tests/fixture/Csv/missing-time.utf8.csv b/tests/fixture/Csv/missing-time.utf8.csv index b0bc641..b9176aa 100644 --- a/tests/fixture/Csv/missing-time.utf8.csv +++ b/tests/fixture/Csv/missing-time.utf8.csv @@ -1 +1 @@ -ノート,オカズリンク,タグ1 +ノート,オカズリンク,タグ1 diff --git a/tests/fixture/Csv/note-over-length.ascii.utf8.csv b/tests/fixture/Csv/note-over-length.ascii.utf8.csv index 33d1c37..02e0edf 100644 --- a/tests/fixture/Csv/note-over-length.ascii.utf8.csv +++ b/tests/fixture/Csv/note-over-length.ascii.utf8.csv @@ -1,2 +1,2 @@ -日時,ノート -2020/01/23 06:01,oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooox +日時,ノート +2020/01/23 06:01,oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooox diff --git a/tests/fixture/Csv/note-over-length.jp.utf8.csv b/tests/fixture/Csv/note-over-length.jp.utf8.csv index 47d4c4e..8daed63 100644 --- a/tests/fixture/Csv/note-over-length.jp.utf8.csv +++ b/tests/fixture/Csv/note-over-length.jp.utf8.csv @@ -1,2 +1,2 @@ -日時,ノート -2020/01/23 06:01,ああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああい +日時,ノート +2020/01/23 06:01,ああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああい diff --git a/tests/fixture/Csv/note.utf8.csv b/tests/fixture/Csv/note.utf8.csv index a585535..872008f 100644 --- a/tests/fixture/Csv/note.utf8.csv +++ b/tests/fixture/Csv/note.utf8.csv @@ -1,5 +1,5 @@ -日時,ノート -2020/01/23 06:01,The quick brown fox jumps over the lazy dog. 素早い茶色の狐はのろまな犬を飛び越える -2020/01/23 06:02,"The quick brown fox jumps over the lazy dog. -素早い茶色の狐はのろまな犬を飛び越える" -2020/01/23 06:03,"The quick brown fox jumps over the ""lazy"" dog." +日時,ノート +2020/01/23 06:01,The quick brown fox jumps over the lazy dog. 素早い茶色の狐はのろまな犬を飛び越える +2020/01/23 06:02,"The quick brown fox jumps over the lazy dog. +素早い茶色の狐はのろまな犬を飛び越える" +2020/01/23 06:03,"The quick brown fox jumps over the ""lazy"" dog." From 7259ee3647ad15838cf0f84bdde7e82edb9f9062 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 16 Feb 2020 15:29:06 +0900 Subject: [PATCH 09/19] =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=88=E3=81=AE?= =?UTF-8?q?=E6=94=B9=E8=A1=8C=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E6=AD=A3?= =?UTF-8?q?=E8=A6=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Io/CheckinCsvImporter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index 92a9ac6..6445f79 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -68,7 +68,7 @@ class CheckinCsvImporter } $ejaculation->ejaculated_date = Carbon::createFromFormat('!Y/m/d H:i+', $record['日時']); - $ejaculation->note = $record['ノート'] ?? ''; + $ejaculation->note = str_replace(["\r\n", "\r"], "\n", $record['ノート'] ?? ''); $ejaculation->link = $record['オカズリンク'] ?? ''; $tagIds = []; From 45eba305285af363c49f73b81af8f5801780dad3 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 16 Feb 2020 17:01:16 +0900 Subject: [PATCH 10/19] =?UTF-8?q?=E6=96=87=E5=AD=97=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E5=88=A4=E5=AE=9A=E3=81=8C=E4=B8=8A=E6=89=8B=E3=81=8F?= =?UTF-8?q?=E8=A1=8C=E3=81=8B=E3=81=AA=E3=81=84=E3=82=B1=E3=83=BC=E3=82=B9?= =?UTF-8?q?=E3=81=8C=E3=81=82=E3=81=A3=E3=81=9F=E3=81=AE=E3=81=A7=E3=80=81?= =?UTF-8?q?=E3=82=82=E3=81=86=E5=B0=91=E3=81=97=E7=B2=98=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AA=E3=82=A2=E3=83=AB=E3=82=B4=E3=83=AA=E3=82=BA?= =?UTF-8?q?=E3=83=A0=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Io/CheckinCsvImporter.php | 53 +++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index 6445f79..f91a063 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -28,11 +28,7 @@ class CheckinCsvImporter public function execute() { // Guess charset - $head = file_get_contents($this->filename, false, null, 0, 1024); - $charset = mb_detect_encoding($head, ['ASCII', 'UTF-8', 'SJIS-win'], true); - if (array_search($charset, ['UTF-8', 'SJIS-win'], true) === false) { - throw new CsvImportException(['文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。']); - } + $charset = $this->guessCharset($this->filename); // Open CSV $csv = Reader::createFromPath($this->filename, 'r'); @@ -101,4 +97,51 @@ class CheckinCsvImporter } }); } + + /** + * 指定されたファイルを読み込み、文字コードの判定を行います。 + * @param string $filename CSVファイル名 + * @param int $samplingLength ファイルの先頭から何バイトを判定に使用するかを指定 + * @return string 検出した文字コード (UTF-8, SJIS-win, ...) + * @throws CsvImportException ファイルの読み込みに失敗した、文字コードを判定できなかった、または非対応文字コードを検出した場合にスロー + */ + private function guessCharset(string $filename, int $samplingLength = 1024): string + { + $fp = fopen($filename, 'rb'); + if (!$fp) { + throw new CsvImportException(['CSVファイルの読み込み中にエラーが発生しました。']); + } + + try { + $head = fread($fp, $samplingLength); + if ($head === false) { + throw new CsvImportException(['CSVファイルの読み込み中にエラーが発生しました。']); + } + + for ($addition = 0; $addition < 4; $addition++) { + $charset = mb_detect_encoding($head, ['ASCII', 'UTF-8', 'SJIS-win'], true); + if ($charset) { + if (array_search($charset, ['UTF-8', 'SJIS-win'], true) === false) { + throw new CsvImportException(['文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。']); + } else { + return $charset; + } + } + + // 1バイト追加で読み込んだら、文字境界に到達して上手く判定できるかもしれない + if (feof($fp)) { + break; + } + $next = fread($fp, 1); + if ($next === false) { + throw new CsvImportException(['CSVファイルの読み込み中にエラーが発生しました。']); + } + $head .= $next; + } + + throw new CsvImportException(['文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。']); + } finally { + fclose($fp); + } + } } From 22845fe2791e38eed1642f96bc180d9e99508bcb Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 16 Feb 2020 22:05:03 +0900 Subject: [PATCH 11/19] =?UTF-8?q?=E3=82=AA=E3=82=AB=E3=82=BA=E3=83=AA?= =?UTF-8?q?=E3=83=B3=E3=82=AF=E3=81=AE=E5=88=A4=E5=AE=9A=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Unit/Io/CheckinCsvImporterTest.php | 32 +++++++++++++++++++++ tests/fixture/Csv/link-not-url.utf8.csv | 3 ++ tests/fixture/Csv/link-over-length.utf8.csv | 3 ++ tests/fixture/Csv/link.utf8.csv | 2 ++ 4 files changed, 40 insertions(+) create mode 100644 tests/fixture/Csv/link-not-url.utf8.csv create mode 100644 tests/fixture/Csv/link-over-length.utf8.csv create mode 100644 tests/fixture/Csv/link.utf8.csv diff --git a/tests/Unit/Io/CheckinCsvImporterTest.php b/tests/Unit/Io/CheckinCsvImporterTest.php index b4cecfb..8a628f1 100644 --- a/tests/Unit/Io/CheckinCsvImporterTest.php +++ b/tests/Unit/Io/CheckinCsvImporterTest.php @@ -136,4 +136,36 @@ class CheckinCsvImporterTest extends TestCase 'JP, UTF8' => [__DIR__ . '/../../fixture/Csv/note-over-length.jp.utf8.csv'], ]; } + + public function testLinkUTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/link.utf8.csv'); + $importer->execute(); + $ejaculations = $user->ejaculations()->orderBy('ejaculated_date')->get(); + + $this->assertCount(1, $ejaculations); + $this->assertEquals('http://example.com', $ejaculations[0]->link); + } + + public function testLinkOverLengthUTF8() + { + $user = factory(User::class)->create(); + $this->expectException(CsvImportException::class); + $this->expectExceptionMessage('3 行 : オカズリンクには2000文字以下の文字列を指定してください。'); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/link-over-length.utf8.csv'); + $importer->execute(); + } + + public function testLinkIsNotUrlUTF8() + { + $user = factory(User::class)->create(); + $this->expectException(CsvImportException::class); + $this->expectExceptionMessage('2 行 : オカズリンクには正しい形式のURLを指定してください。'); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/link-not-url.utf8.csv'); + $importer->execute(); + } } diff --git a/tests/fixture/Csv/link-not-url.utf8.csv b/tests/fixture/Csv/link-not-url.utf8.csv new file mode 100644 index 0000000..9adb8c8 --- /dev/null +++ b/tests/fixture/Csv/link-not-url.utf8.csv @@ -0,0 +1,3 @@ +日時,オカズリンク +2020/01/23 06:01,example + diff --git a/tests/fixture/Csv/link-over-length.utf8.csv b/tests/fixture/Csv/link-over-length.utf8.csv new file mode 100644 index 0000000..45c33d0 --- /dev/null +++ b/tests/fixture/Csv/link-over-length.utf8.csv @@ -0,0 +1,3 @@ +日時,オカズリンク +2020/01/23 06:01,https://example.com/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/exam +2020/01/23 06:01,https://example.com/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/examp diff --git a/tests/fixture/Csv/link.utf8.csv b/tests/fixture/Csv/link.utf8.csv new file mode 100644 index 0000000..e7167fb --- /dev/null +++ b/tests/fixture/Csv/link.utf8.csv @@ -0,0 +1,2 @@ +日時,オカズリンク +2020/01/23 06:01,http://example.com From 272e7ecc61f9aae95697c4a5a21df7c5834c017e Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 16 Feb 2020 22:06:12 +0900 Subject: [PATCH 12/19] =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AE=E7=B5=B1=E4=B8=80?= =?UTF-8?q?=E6=84=9F=E3=82=92=E9=AB=98=E3=82=81=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Rules/CsvDateTime.php | 4 +-- tests/Unit/Io/CheckinCsvImporterTest.php | 32 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/Rules/CsvDateTime.php b/app/Rules/CsvDateTime.php index 76159ef..02a5261 100644 --- a/app/Rules/CsvDateTime.php +++ b/app/Rules/CsvDateTime.php @@ -21,7 +21,7 @@ class CsvDateTime implements Rule const MAXIMUM_TIMESTAMP = 4102412399; // 2099-12-31 23:59:59 JST /** @var string Validation error message */ - private $message = ':attribute の形式は "年/月/日 時:分" にしてください。'; + private $message = ':attributeの形式は "年/月/日 時:分" にしてください。'; /** * Create a new rule instance. @@ -56,7 +56,7 @@ class CsvDateTime implements Rule $timestamp = (int) $date->format('U'); if ($timestamp < self::MINIMUM_TIMESTAMP || self::MAXIMUM_TIMESTAMP < $timestamp) { - $this->message = ':attribute は 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。'; + $this->message = ':attributeは 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。'; return false; } diff --git a/tests/Unit/Io/CheckinCsvImporterTest.php b/tests/Unit/Io/CheckinCsvImporterTest.php index 8a628f1..cf91770 100644 --- a/tests/Unit/Io/CheckinCsvImporterTest.php +++ b/tests/Unit/Io/CheckinCsvImporterTest.php @@ -79,22 +79,22 @@ class CheckinCsvImporterTest extends TestCase $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/invalid-date.utf8.csv'); $importer->execute(); } catch (CsvImportException $e) { - $this->assertSame('2 行 : 日時 は 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。', $e->getErrors()[0]); - $this->assertSame('3 行 : 日時 は 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。', $e->getErrors()[1]); - $this->assertSame('4 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[2]); - $this->assertSame('5 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[3]); - $this->assertSame('6 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[4]); - $this->assertSame('7 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[5]); - $this->assertSame('8 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[6]); - $this->assertSame('9 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[7]); - $this->assertSame('10 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[8]); - $this->assertSame('11 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[9]); - $this->assertSame('12 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[10]); - $this->assertSame('13 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[11]); - $this->assertSame('14 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[12]); - $this->assertSame('15 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[13]); - $this->assertSame('16 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[14]); - $this->assertSame('17 行 : 日時 の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[15]); + $this->assertSame('2 行 : 日時は 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。', $e->getErrors()[0]); + $this->assertSame('3 行 : 日時は 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。', $e->getErrors()[1]); + $this->assertSame('4 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[2]); + $this->assertSame('5 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[3]); + $this->assertSame('6 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[4]); + $this->assertSame('7 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[5]); + $this->assertSame('8 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[6]); + $this->assertSame('9 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[7]); + $this->assertSame('10 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[8]); + $this->assertSame('11 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[9]); + $this->assertSame('12 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[10]); + $this->assertSame('13 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[11]); + $this->assertSame('14 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[12]); + $this->assertSame('15 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[13]); + $this->assertSame('16 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[14]); + $this->assertSame('17 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[15]); return; } From 24a5017334249ff5511c9f56b446a7d14165d487 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 16 Feb 2020 22:43:07 +0900 Subject: [PATCH 13/19] =?UTF-8?q?=E3=82=BF=E3=82=B0=E5=88=97=E3=81=AE?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Io/CheckinCsvImporter.php | 9 ++- tests/Unit/Io/CheckinCsvImporterTest.php | 63 ++++++++++++++++++++ tests/fixture/Csv/.editorconfig | 2 + tests/fixture/Csv/tag-jumped-column.utf8.csv | 2 + tests/fixture/Csv/tag-multiline.utf8.csv | 3 + tests/fixture/Csv/tag-over-length.utf8.csv | 3 + tests/fixture/Csv/tag1.utf8.csv | 2 + tests/fixture/Csv/tag2.utf8.csv | 2 + 8 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 tests/fixture/Csv/.editorconfig create mode 100644 tests/fixture/Csv/tag-jumped-column.utf8.csv create mode 100644 tests/fixture/Csv/tag-multiline.utf8.csv create mode 100644 tests/fixture/Csv/tag-over-length.utf8.csv create mode 100644 tests/fixture/Csv/tag1.utf8.csv create mode 100644 tests/fixture/Csv/tag2.utf8.csv diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index f91a063..364a007 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -79,7 +79,11 @@ class CheckinCsvImporter break; } if (mb_strlen($tag) > 255) { - $errors[] = "{$line} 行 : {$column}列は255文字以内にしてください。"; + $errors[] = "{$line} 行 : {$column}は255文字以内にしてください。"; + continue 2; + } + if (strpos($tag, "\n") !== false) { + $errors[] = "{$line} 行 : {$column}に改行を含めることはできません。"; continue 2; } @@ -87,9 +91,8 @@ class CheckinCsvImporter $tagIds[] = $tag->id; } } - $ejaculation->tags()->sync($tagIds); - $ejaculation->save(); + $ejaculation->tags()->sync($tagIds); } if (!empty($errors)) { diff --git a/tests/Unit/Io/CheckinCsvImporterTest.php b/tests/Unit/Io/CheckinCsvImporterTest.php index cf91770..f3ecfb3 100644 --- a/tests/Unit/Io/CheckinCsvImporterTest.php +++ b/tests/Unit/Io/CheckinCsvImporterTest.php @@ -168,4 +168,67 @@ class CheckinCsvImporterTest extends TestCase $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/link-not-url.utf8.csv'); $importer->execute(); } + + public function testTag1UTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag1.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + $tags = $ejaculation->tags()->get(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertCount(1, $tags); + $this->assertEquals('貧乳', $tags[0]->name); + } + + public function testTag2UTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag2.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + $tags = $ejaculation->tags()->get(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertCount(2, $tags); + $this->assertEquals('貧乳', $tags[0]->name); + $this->assertEquals('巨乳', $tags[1]->name); + } + + public function testTagOverLengthUTF8() + { + $user = factory(User::class)->create(); + $this->expectException(CsvImportException::class); + $this->expectExceptionMessage('3 行 : タグ1は255文字以内にしてください。'); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag-over-length.utf8.csv'); + $importer->execute(); + } + + public function testTagCantAcceptJumpedColumnUTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag-jumped-column.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + $tags = $ejaculation->tags()->get(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertCount(1, $tags); + $this->assertEquals('貧乳', $tags[0]->name); + } + + public function testTagCantAcceptMultilineUTF8() + { + $user = factory(User::class)->create(); + $this->expectException(CsvImportException::class); + $this->expectExceptionMessage('2 行 : タグ1に改行を含めることはできません。'); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag-multiline.utf8.csv'); + $importer->execute(); + } } diff --git a/tests/fixture/Csv/.editorconfig b/tests/fixture/Csv/.editorconfig new file mode 100644 index 0000000..c9e4d60 --- /dev/null +++ b/tests/fixture/Csv/.editorconfig @@ -0,0 +1,2 @@ +[*.csv] +end_of_line = crlf diff --git a/tests/fixture/Csv/tag-jumped-column.utf8.csv b/tests/fixture/Csv/tag-jumped-column.utf8.csv new file mode 100644 index 0000000..773db36 --- /dev/null +++ b/tests/fixture/Csv/tag-jumped-column.utf8.csv @@ -0,0 +1,2 @@ +日時,タグ1,タグ3 +2020/01/23 06:01,貧乳,巨乳 diff --git a/tests/fixture/Csv/tag-multiline.utf8.csv b/tests/fixture/Csv/tag-multiline.utf8.csv new file mode 100644 index 0000000..ee1bd38 --- /dev/null +++ b/tests/fixture/Csv/tag-multiline.utf8.csv @@ -0,0 +1,3 @@ +日時,タグ1 +2020/01/23 06:01,"複数行の +タグ" diff --git a/tests/fixture/Csv/tag-over-length.utf8.csv b/tests/fixture/Csv/tag-over-length.utf8.csv new file mode 100644 index 0000000..891308e --- /dev/null +++ b/tests/fixture/Csv/tag-over-length.utf8.csv @@ -0,0 +1,3 @@ +日時,タグ1 +2020/01/23 06:01,ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +2020/01/23 06:02,ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooox diff --git a/tests/fixture/Csv/tag1.utf8.csv b/tests/fixture/Csv/tag1.utf8.csv new file mode 100644 index 0000000..1d7561c --- /dev/null +++ b/tests/fixture/Csv/tag1.utf8.csv @@ -0,0 +1,2 @@ +日時,タグ1 +2020/01/23 06:01,貧乳 diff --git a/tests/fixture/Csv/tag2.utf8.csv b/tests/fixture/Csv/tag2.utf8.csv new file mode 100644 index 0000000..10e82fd --- /dev/null +++ b/tests/fixture/Csv/tag2.utf8.csv @@ -0,0 +1,2 @@ +日時,タグ1,タグ2 +2020/01/23 06:01,貧乳,巨乳 From 84b955b19522b7a73566013bdba43e9837ba8ba9 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 16 Feb 2020 23:09:30 +0900 Subject: [PATCH 14/19] =?UTF-8?q?continue=202=E3=81=AF=E3=82=AD=E3=83=A2?= =?UTF-8?q?=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Io/CheckinCsvImporter.php | 67 ++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index 364a007..e9b86ec 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -67,32 +67,17 @@ class CheckinCsvImporter $ejaculation->note = str_replace(["\r\n", "\r"], "\n", $record['ノート'] ?? ''); $ejaculation->link = $record['オカズリンク'] ?? ''; - $tagIds = []; - for ($i = 1; $i <= 32; $i++) { - $column = 'タグ' . $i; - if (empty($record[$column])) { - break; - } else { - $tag = trim($record[$column]); - - if (empty($tag)) { - break; - } - if (mb_strlen($tag) > 255) { - $errors[] = "{$line} 行 : {$column}は255文字以内にしてください。"; - continue 2; - } - if (strpos($tag, "\n") !== false) { - $errors[] = "{$line} 行 : {$column}に改行を含めることはできません。"; - continue 2; - } - - $tag = Tag::firstOrCreate(['name' => $tag]); - $tagIds[] = $tag->id; - } + try { + $tags = $this->parseTags($line, $record); + } catch (CsvImportException $e) { + $errors = array_merge($errors, $e->getErrors()); + continue; } + $ejaculation->save(); - $ejaculation->tags()->sync($tagIds); + if (!empty($tags)) { + $ejaculation->tags()->sync(collect($tags)->pluck('id')); + } } if (!empty($errors)) { @@ -147,4 +132,38 @@ class CheckinCsvImporter fclose($fp); } } + + /** + * タグ列をパースします。 + * @param int $line 現在の行番号 (1 origin) + * @param array $record 対象行のデータ + * @return Tag[] + * @throws CsvImportException バリデーションエラーが発生した場合にスロー + */ + private function parseTags(int $line, array $record): array + { + $tags = []; + for ($i = 1; $i <= 32; $i++) { + $column = 'タグ' . $i; + if (empty($record[$column])) { + break; + } else { + $tag = trim($record[$column]); + + if (empty($tag)) { + break; + } + if (mb_strlen($tag) > 255) { + throw new CsvImportException(["{$line} 行 : {$column}は255文字以内にしてください。"]); + } + if (strpos($tag, "\n") !== false) { + throw new CsvImportException(["{$line} 行 : {$column}に改行を含めることはできません。"]); + } + + $tags[] = Tag::firstOrCreate(['name' => $tag]); + } + } + + return $tags; + } } From 794cdf2be602cbfc8d5d94d896440348e37f70ad Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 16 Feb 2020 23:17:07 +0900 Subject: [PATCH 15/19] =?UTF-8?q?argument=20unpacking=E3=81=AE=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=E3=82=92=E6=80=9D=E3=81=84=E5=87=BA=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Exceptions/CsvImportException.php | 2 +- app/Io/CheckinCsvImporter.php | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/Exceptions/CsvImportException.php b/app/Exceptions/CsvImportException.php index eee24cf..5571dc6 100644 --- a/app/Exceptions/CsvImportException.php +++ b/app/Exceptions/CsvImportException.php @@ -13,7 +13,7 @@ class CsvImportException extends \RuntimeException * CsvImportException constructor. * @param string[] $errors */ - public function __construct(array $errors) + public function __construct(...$errors) { parent::__construct(array_first($errors)); $this->errors = $errors; diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index e9b86ec..5b41b6c 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -43,7 +43,10 @@ class CheckinCsvImporter if (!in_array('日時', $csv->getHeader(), true)) { $errors[] = '日時列は必須です。'; - throw new CsvImportException($errors); + } + + if (!empty($errors)) { + throw new CsvImportException(...$errors); } foreach ($csv->getRecords() as $offset => $record) { @@ -81,7 +84,7 @@ class CheckinCsvImporter } if (!empty($errors)) { - throw new CsvImportException($errors); + throw new CsvImportException(...$errors); } }); } @@ -97,20 +100,20 @@ class CheckinCsvImporter { $fp = fopen($filename, 'rb'); if (!$fp) { - throw new CsvImportException(['CSVファイルの読み込み中にエラーが発生しました。']); + throw new CsvImportException('CSVファイルの読み込み中にエラーが発生しました。'); } try { $head = fread($fp, $samplingLength); if ($head === false) { - throw new CsvImportException(['CSVファイルの読み込み中にエラーが発生しました。']); + throw new CsvImportException('CSVファイルの読み込み中にエラーが発生しました。'); } for ($addition = 0; $addition < 4; $addition++) { $charset = mb_detect_encoding($head, ['ASCII', 'UTF-8', 'SJIS-win'], true); if ($charset) { if (array_search($charset, ['UTF-8', 'SJIS-win'], true) === false) { - throw new CsvImportException(['文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。']); + throw new CsvImportException('文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。'); } else { return $charset; } @@ -122,12 +125,12 @@ class CheckinCsvImporter } $next = fread($fp, 1); if ($next === false) { - throw new CsvImportException(['CSVファイルの読み込み中にエラーが発生しました。']); + throw new CsvImportException('CSVファイルの読み込み中にエラーが発生しました。'); } $head .= $next; } - throw new CsvImportException(['文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。']); + throw new CsvImportException('文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。'); } finally { fclose($fp); } @@ -154,10 +157,10 @@ class CheckinCsvImporter break; } if (mb_strlen($tag) > 255) { - throw new CsvImportException(["{$line} 行 : {$column}は255文字以内にしてください。"]); + throw new CsvImportException("{$line} 行 : {$column}は255文字以内にしてください。"); } if (strpos($tag, "\n") !== false) { - throw new CsvImportException(["{$line} 行 : {$column}に改行を含めることはできません。"]); + throw new CsvImportException("{$line} 行 : {$column}に改行を含めることはできません。"); } $tags[] = Tag::firstOrCreate(['name' => $tag]); From 0a53199399a7dab2cac543e0800573bfcb96b159 Mon Sep 17 00:00:00 2001 From: shibafu Date: Sun, 16 Feb 2020 23:21:17 +0900 Subject: [PATCH 16/19] =?UTF-8?q?=E3=83=8D=E3=82=B9=E3=83=88=E3=82=92?= =?UTF-8?q?=E3=81=A1=E3=82=87=E3=81=A3=E3=81=A8=E6=B8=9B=E3=82=89=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Io/CheckinCsvImporter.php | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index 5b41b6c..30d13d4 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -150,21 +150,20 @@ class CheckinCsvImporter $column = 'タグ' . $i; if (empty($record[$column])) { break; - } else { - $tag = trim($record[$column]); - - if (empty($tag)) { - break; - } - if (mb_strlen($tag) > 255) { - throw new CsvImportException("{$line} 行 : {$column}は255文字以内にしてください。"); - } - if (strpos($tag, "\n") !== false) { - throw new CsvImportException("{$line} 行 : {$column}に改行を含めることはできません。"); - } - - $tags[] = Tag::firstOrCreate(['name' => $tag]); } + + $tag = trim($record[$column]); + if (empty($tag)) { + break; + } + if (mb_strlen($tag) > 255) { + throw new CsvImportException("{$line} 行 : {$column}は255文字以内にしてください。"); + } + if (strpos($tag, "\n") !== false) { + throw new CsvImportException("{$line} 行 : {$column}に改行を含めることはできません。"); + } + + $tags[] = Tag::firstOrCreate(['name' => $tag]); } return $tags; From 6387d4e8536dc3acaf66b5e8042861aa77953ad9 Mon Sep 17 00:00:00 2001 From: shibafu Date: Tue, 18 Feb 2020 02:03:49 +0900 Subject: [PATCH 17/19] =?UTF-8?q?=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=83=87=E3=83=BC=E3=82=BF=E3=81=8CCSV?= =?UTF-8?q?=E3=81=A7=E6=8A=95=E5=85=A5=E3=81=95=E3=82=8C=E3=81=9F=E3=81=93?= =?UTF-8?q?=E3=81=A8=E3=82=92=E8=A8=98=E9=8C=B2=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Ejaculation.php | 3 ++ app/Io/CheckinCsvImporter.php | 1 + ...2_17_085327_add_source_to_ejaculations.php | 38 +++++++++++++++++++ database/seeds/DatabaseSeeder.php | 2 +- database/seeds/EjaculationSourcesSeeder.php | 19 ++++++++++ tests/Unit/Io/CheckinCsvImporterTest.php | 19 ++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2020_02_17_085327_add_source_to_ejaculations.php create mode 100644 database/seeds/EjaculationSourcesSeeder.php diff --git a/app/Ejaculation.php b/app/Ejaculation.php index 0f3522c..a270f40 100644 --- a/app/Ejaculation.php +++ b/app/Ejaculation.php @@ -12,6 +12,9 @@ class Ejaculation extends Model { use HasEagerLimit; + const SOURCE_WEB = 'web'; + const SOURCE_CSV = 'csv'; + protected $fillable = [ 'user_id', 'ejaculated_date', 'note', 'geo_latitude', 'geo_longitude', 'link', diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index 30d13d4..1239667 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -69,6 +69,7 @@ class CheckinCsvImporter $ejaculation->ejaculated_date = Carbon::createFromFormat('!Y/m/d H:i+', $record['日時']); $ejaculation->note = str_replace(["\r\n", "\r"], "\n", $record['ノート'] ?? ''); $ejaculation->link = $record['オカズリンク'] ?? ''; + $ejaculation->source = Ejaculation::SOURCE_CSV; try { $tags = $this->parseTags($line, $record); diff --git a/database/migrations/2020_02_17_085327_add_source_to_ejaculations.php b/database/migrations/2020_02_17_085327_add_source_to_ejaculations.php new file mode 100644 index 0000000..965e398 --- /dev/null +++ b/database/migrations/2020_02_17_085327_add_source_to_ejaculations.php @@ -0,0 +1,38 @@ +string('name'); + $table->primary('name'); + }); + Schema::table('ejaculations', function (Blueprint $table) { + $table->string('source')->nullable(); + $table->foreign('source')->references('name')->on('ejaculation_sources'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('ejaculations', function (Blueprint $table) { + $table->dropColumn('source'); + }); + Schema::drop('ejaculation_sources'); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index e119db6..3f0fc32 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -11,6 +11,6 @@ class DatabaseSeeder extends Seeder */ public function run() { - // $this->call(UsersTableSeeder::class); + $this->call(EjaculationSourcesSeeder::class); } } diff --git a/database/seeds/EjaculationSourcesSeeder.php b/database/seeds/EjaculationSourcesSeeder.php new file mode 100644 index 0000000..a762a08 --- /dev/null +++ b/database/seeds/EjaculationSourcesSeeder.php @@ -0,0 +1,19 @@ +insert(['name' => $source]); + } + } +} diff --git a/tests/Unit/Io/CheckinCsvImporterTest.php b/tests/Unit/Io/CheckinCsvImporterTest.php index f3ecfb3..a486760 100644 --- a/tests/Unit/Io/CheckinCsvImporterTest.php +++ b/tests/Unit/Io/CheckinCsvImporterTest.php @@ -2,6 +2,7 @@ namespace Tests\Unit\Io; +use App\Ejaculation; use App\Exceptions\CsvImportException; use App\Io\CheckinCsvImporter; use App\User; @@ -13,6 +14,12 @@ class CheckinCsvImporterTest extends TestCase { use RefreshDatabase; + protected function setUp() + { + parent::setUp(); + $this->seed(); + } + public function testIncompatibleCharsetEUCJP() { $user = factory(User::class)->create(); @@ -231,4 +238,16 @@ class CheckinCsvImporterTest extends TestCase $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag-multiline.utf8.csv'); $importer->execute(); } + + public function testSourceIsCsv() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertEquals(Ejaculation::SOURCE_CSV, $ejaculation->source); + } } From 41ce5229c74ae055948ef78b0c7aac9363ab6e62 Mon Sep 17 00:00:00 2001 From: shibafu Date: Tue, 18 Feb 2020 02:09:01 +0900 Subject: [PATCH 18/19] =?UTF-8?q?=E3=81=93=E3=82=8C=E3=81=AF=E3=81=84?= =?UTF-8?q?=E3=82=8F=E3=82=86=E3=82=8B=E3=82=B5=E3=83=BC=E3=83=93=E3=82=B9?= =?UTF-8?q?=E3=81=AA=E3=81=AE=E3=81=A7=E3=81=AF=E3=81=A8=E6=80=9D=E3=81=A3?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/{Io => Services}/CheckinCsvImporter.php | 2 +- tests/Unit/{Io => Services}/CheckinCsvImporterTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename app/{Io => Services}/CheckinCsvImporter.php (99%) rename tests/Unit/{Io => Services}/CheckinCsvImporterTest.php (99%) diff --git a/app/Io/CheckinCsvImporter.php b/app/Services/CheckinCsvImporter.php similarity index 99% rename from app/Io/CheckinCsvImporter.php rename to app/Services/CheckinCsvImporter.php index 1239667..b4906f5 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Services/CheckinCsvImporter.php @@ -1,6 +1,6 @@ Date: Thu, 14 May 2020 00:27:09 +0900 Subject: [PATCH 19/19] =?UTF-8?q?=E3=82=BF=E3=82=B0=E5=88=97=E3=81=AE?= =?UTF-8?q?=E7=95=AA=E5=8F=B7=E9=A3=9B=E3=81=B3=E3=82=92=E8=A8=B1=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Services/CheckinCsvImporter.php | 12 +++++++----- .../Unit/Services/CheckinCsvImporterTest.php | 19 +++++++++++++++++-- tests/fixture/Csv/tag-33-column.utf8.csv | 2 ++ 3 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 tests/fixture/Csv/tag-33-column.utf8.csv diff --git a/app/Services/CheckinCsvImporter.php b/app/Services/CheckinCsvImporter.php index b4906f5..d99f51a 100644 --- a/app/Services/CheckinCsvImporter.php +++ b/app/Services/CheckinCsvImporter.php @@ -147,15 +147,14 @@ class CheckinCsvImporter private function parseTags(int $line, array $record): array { $tags = []; - for ($i = 1; $i <= 32; $i++) { - $column = 'タグ' . $i; - if (empty($record[$column])) { - break; + foreach (array_keys($record) as $column) { + if (preg_match('/\Aタグ\d{1,2}\z/u', $column) !== 1) { + continue; } $tag = trim($record[$column]); if (empty($tag)) { - break; + continue; } if (mb_strlen($tag) > 255) { throw new CsvImportException("{$line} 行 : {$column}は255文字以内にしてください。"); @@ -165,6 +164,9 @@ class CheckinCsvImporter } $tags[] = Tag::firstOrCreate(['name' => $tag]); + if (count($tags) >= 32) { + break; + } } return $tags; diff --git a/tests/Unit/Services/CheckinCsvImporterTest.php b/tests/Unit/Services/CheckinCsvImporterTest.php index 0128267..51307da 100644 --- a/tests/Unit/Services/CheckinCsvImporterTest.php +++ b/tests/Unit/Services/CheckinCsvImporterTest.php @@ -215,7 +215,7 @@ class CheckinCsvImporterTest extends TestCase $importer->execute(); } - public function testTagCantAcceptJumpedColumnUTF8() + public function testTagCanAcceptJumpedColumnUTF8() { $user = factory(User::class)->create(); @@ -225,8 +225,9 @@ class CheckinCsvImporterTest extends TestCase $tags = $ejaculation->tags()->get(); $this->assertSame(1, $user->ejaculations()->count()); - $this->assertCount(1, $tags); + $this->assertCount(2, $tags); $this->assertEquals('貧乳', $tags[0]->name); + $this->assertEquals('巨乳', $tags[1]->name); } public function testTagCantAcceptMultilineUTF8() @@ -239,6 +240,20 @@ class CheckinCsvImporterTest extends TestCase $importer->execute(); } + public function testTagCanAccept32ColumnsUTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag-33-column.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + $tags = $ejaculation->tags()->get(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertCount(32, $tags); + $this->assertEquals('み', $tags[31]->name); + } + public function testSourceIsCsv() { $user = factory(User::class)->create(); diff --git a/tests/fixture/Csv/tag-33-column.utf8.csv b/tests/fixture/Csv/tag-33-column.utf8.csv new file mode 100644 index 0000000..85670a2 --- /dev/null +++ b/tests/fixture/Csv/tag-33-column.utf8.csv @@ -0,0 +1,2 @@ +日時,タグ1,タグ2,タグ3,タグ4,タグ5,タグ6,タグ7,タグ8,タグ9,タグ10,タグ11,タグ12,タグ13,タグ14,タグ15,タグ16,タグ17,タグ18,タグ19,タグ20,タグ21,タグ22,タグ23,タグ24,タグ25,タグ26,タグ27,タグ28,タグ29,タグ30,タグ31,タグ32,タグ33 +2020/01/23 06:01,あ,い,う,え,お,か,き,く,け,こ,さ,し,す,せ,そ,た,ち,つ,て,と,な,に,ぬ,ね,の,は,ひ,ふ,へ,ほ,ま,み,む