From f0e946b5f82ffb9aa475e7b36b7cc714b5f643f9 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 14:19:23 +0530 Subject: [PATCH 1/4] Support file output by ICS --- .gitattributes | 1 + composer.json | 8 +++--- src/Generators/Ics.php | 21 +++++++++++++--- src/Link.php | 7 +++--- tests/Generators/IcsGeneratorTest.php | 25 +++++++++++-------- ...erates_base64_encoded_link_for_html__1.txt | 1 + 6 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 tests/Generators/__snapshots__/IcsGeneratorTest__it_generates_base64_encoded_link_for_html__1.txt diff --git a/.gitattributes b/.gitattributes index adfaba5..313045a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,3 +19,4 @@ tests/**/__snapshots__/IcsGeneratorTest* text eol=crlf # PHPUnit .phpunit.cache +.phpunit.cache/ diff --git a/composer.json b/composer.json index c7390c5..c6eb937 100644 --- a/composer.json +++ b/composer.json @@ -19,10 +19,10 @@ "php": "^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.14", - "phpunit/phpunit": "^9.6 || ^10.0", - "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.0", - "vimeo/psalm": "^5.6" + "friendsofphp/php-cs-fixer": "^3.48", + "phpunit/phpunit": "^10.1", + "spatie/phpunit-snapshot-assertions": "^5.1", + "vimeo/psalm": "^5.20" }, "autoload": { "psr-4": { diff --git a/src/Generators/Ics.php b/src/Generators/Ics.php index b738974..a6a8b2e 100644 --- a/src/Generators/Ics.php +++ b/src/Generators/Ics.php @@ -18,12 +18,17 @@ class Ics implements Generator /** @var array */ protected $options = []; + /** @var array{format?: string} */ + protected $presentationOptions = []; + /** - * @param array $options + * @param array $options Optional ICS properties and components + * @param array{format?: string} $presentationOptions */ - public function __construct(array $options = []) + public function __construct(array $options = [], array $presentationOptions = []) { $this->options = $options; + $this->presentationOptions = $presentationOptions; } /** {@inheritDoc} */ @@ -64,7 +69,12 @@ public function generate(Link $link): string $url[] = 'END:VEVENT'; $url[] = 'END:VCALENDAR'; - return $this->buildLink($url); + $format = $this->presentationOptions['format'] ?? 'html'; + + return match ($format) { + 'file' => $this->buildFile($url), + default => $this->buildLink($url), + }; } protected function buildLink(array $propertiesAndComponents): string @@ -72,6 +82,11 @@ protected function buildLink(array $propertiesAndComponents): string return 'data:text/calendar;charset=utf8;base64,'.base64_encode(implode("\r\n", $propertiesAndComponents)); } + protected function buildFile(array $propertiesAndComponents): string + { + return implode("\r\n", $propertiesAndComponents); + } + /** @see https://tools.ietf.org/html/rfc5545.html#section-3.3.11 */ protected function escapeString(string $field): string { diff --git a/src/Link.php b/src/Link.php index 56ab3c3..60e9dee 100644 --- a/src/Link.php +++ b/src/Link.php @@ -115,12 +115,13 @@ public function google(): string } /** - * @param array $options + * @param array $options ICS specific properties and components + * @param array{format?: string} $presentationOptions * @return string */ - public function ics(array $options = []): string + public function ics(array $options = [], array $presentationOptions = []): string { - return $this->formatWith(new Ics($options)); + return $this->formatWith(new Ics($options, $presentationOptions)); } public function yahoo(): string diff --git a/tests/Generators/IcsGeneratorTest.php b/tests/Generators/IcsGeneratorTest.php index 9c3b481..8e1a82f 100644 --- a/tests/Generators/IcsGeneratorTest.php +++ b/tests/Generators/IcsGeneratorTest.php @@ -14,15 +14,10 @@ class IcsGeneratorTest extends TestCase * @param array $options @see \Spatie\CalendarLinks\Generators\Ics::__construct * @return \Spatie\CalendarLinks\Generator */ - protected function generator(array $options = []): Generator + protected function generator(array $options = [], array $presentationOptions = []): Generator { - // extend base class just to make output more readable and simplify reviewing of the snapshot diff - return new class($options) extends Ics { - protected function buildLink(array $propertiesAndComponents): string - { - return implode("\r\n", $propertiesAndComponents); - } - }; + $presentationOptions['format'] ??= 'file'; + return new Ics($options, $presentationOptions); } protected function linkMethodName(): string @@ -34,7 +29,7 @@ protected function linkMethodName(): string public function it_can_generate_an_ics_link_with_custom_uid(): void { $this->assertMatchesSnapshot( - $this->generator(['UID' => 'random-uid'])->generate($this->createShortEventLink()) + $this->generator(['UID' => 'random-uid', ['format' => 'file']])->generate($this->createShortEventLink()) ); } @@ -42,7 +37,7 @@ public function it_can_generate_an_ics_link_with_custom_uid(): void public function it_has_a_product_id(): void { $this->assertMatchesSnapshot( - $this->generator(['PRODID' => 'Spatie calendar-links'])->generate($this->createShortEventLink()) + $this->generator(['PRODID' => 'Spatie calendar-links'], ['format' => 'file'])->generate($this->createShortEventLink()) ); } @@ -50,7 +45,15 @@ public function it_has_a_product_id(): void public function it_has_a_product_dtstamp(): void { $this->assertMatchesSnapshot( - $this->generator(['DTSTAMP' => '20180201T090000Z'])->generate($this->createShortEventLink()) + $this->generator(['DTSTAMP' => '20180201T090000Z'], ['format' => 'file'])->generate($this->createShortEventLink()) + ); + } + + /** @test */ + public function it_generates_base64_encoded_link_for_html(): void + { + $this->assertMatchesSnapshot( + $this->generator([], ['format' => 'html'])->generate($this->createShortEventLink()) ); } } diff --git a/tests/Generators/__snapshots__/IcsGeneratorTest__it_generates_base64_encoded_link_for_html__1.txt b/tests/Generators/__snapshots__/IcsGeneratorTest__it_generates_base64_encoded_link_for_html__1.txt new file mode 100644 index 0000000..4639dd1 --- /dev/null +++ b/tests/Generators/__snapshots__/IcsGeneratorTest__it_generates_base64_encoded_link_for_html__1.txt @@ -0,0 +1 @@ +data:text/calendar;charset=utf8;base64,QkVHSU46VkNBTEVOREFSDQpWRVJTSU9OOjIuMA0KUFJPRElEOlNwYXRpZSBjYWxlbmRhci1saW5rcw0KQkVHSU46VkVWRU5UDQpVSUQ6OTRhYjc1YWRkODRhNjdjMDE5ZWFlNTc1Mzk2NTgwMzYNClNVTU1BUlk6QmlydGhkYXkNCkRUU1RBTVA6MjAxODAyMDFUMDkwMDAwWg0KRFRTVEFSVDoyMDE4MDIwMVQwOTAwMDBaDQpEVEVORDoyMDE4MDIwMVQxODAwMDBaDQpERVNDUklQVElPTjpXaXRoIGJhbGxvb25zXCwgY2xvd25zIGFuZCBzdHVmZlxuQnJpbmcgYSBkb2dcLCBicmluZyBhIGZyb2cNCkxPQ0FUSU9OOlBhcnR5IExhbmUgMUFcLCAxMzM3IEZ1bnRvd24NCkVORDpWRVZFTlQNCkVORDpWQ0FMRU5EQVI= \ No newline at end of file From 68a99fd9d326be13d5240e64b2d6393e6baaa7bd Mon Sep 17 00:00:00 2001 From: alies-dev Date: Fri, 26 Jan 2024 08:49:46 +0000 Subject: [PATCH 2/4] Fix styling --- tests/Generators/IcsGeneratorTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Generators/IcsGeneratorTest.php b/tests/Generators/IcsGeneratorTest.php index 8e1a82f..3e025db 100644 --- a/tests/Generators/IcsGeneratorTest.php +++ b/tests/Generators/IcsGeneratorTest.php @@ -17,6 +17,7 @@ class IcsGeneratorTest extends TestCase protected function generator(array $options = [], array $presentationOptions = []): Generator { $presentationOptions['format'] ??= 'file'; + return new Ics($options, $presentationOptions); } From aafb9ee194d36654ce04de2615f4fa0bdbb91faa Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 14:26:01 +0530 Subject: [PATCH 3/4] Remove unrelated changes from the PR --- .gitattributes | 1 - composer.json | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.gitattributes b/.gitattributes index 313045a..adfaba5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,4 +19,3 @@ tests/**/__snapshots__/IcsGeneratorTest* text eol=crlf # PHPUnit .phpunit.cache -.phpunit.cache/ diff --git a/composer.json b/composer.json index c6eb937..c7390c5 100644 --- a/composer.json +++ b/composer.json @@ -19,10 +19,10 @@ "php": "^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.48", - "phpunit/phpunit": "^10.1", - "spatie/phpunit-snapshot-assertions": "^5.1", - "vimeo/psalm": "^5.20" + "friendsofphp/php-cs-fixer": "^3.14", + "phpunit/phpunit": "^9.6 || ^10.0", + "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.0", + "vimeo/psalm": "^5.6" }, "autoload": { "psr-4": { From 28d5971d23f96b1308ac27f8daffecfe290f0f14 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 15:01:10 +0530 Subject: [PATCH 4/4] Add consts, improve examples --- README.md | 4 +++- src/Generators/Ics.php | 9 ++++++--- src/Link.php | 2 +- tests/Generators/IcsGeneratorTest.php | 10 +++++----- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 37067c7..5df1984 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,10 @@ echo $link->webOutlook(); // Generate a link to create an event on outlook.office.com calendar echo $link->webOffice(); -// Generate a data uri for an ics file (for iCal & Outlook) +// Generate a data URI for an ics file (for iCal & Outlook) echo $link->ics(); +echo $link->ics(['URL' => 'https://my-page.com', 'UID' => 'custom-id']); // +echo $link->ics([], ['format' => 'file']); // e.g. to attach ics as a file to an email. // Generate a data URI using arbitrary generator: echo $link->formatWith(new \Your\Generator()); diff --git a/src/Generators/Ics.php b/src/Generators/Ics.php index a6a8b2e..f4b33de 100644 --- a/src/Generators/Ics.php +++ b/src/Generators/Ics.php @@ -10,6 +10,9 @@ */ class Ics implements Generator { + public const FORMAT_HTML = 'html'; + public const FORMAT_FILE = 'file'; + /** @var string {@see https://www.php.net/manual/en/function.date.php} */ protected $dateFormat = 'Ymd'; /** @var string */ @@ -18,12 +21,12 @@ class Ics implements Generator /** @var array */ protected $options = []; - /** @var array{format?: string} */ + /** @var array{format?: self::FORMAT_*} */ protected $presentationOptions = []; /** * @param array $options Optional ICS properties and components - * @param array{format?: string} $presentationOptions + * @param array{format?: self::FORMAT_*} $presentationOptions */ public function __construct(array $options = [], array $presentationOptions = []) { @@ -69,7 +72,7 @@ public function generate(Link $link): string $url[] = 'END:VEVENT'; $url[] = 'END:VCALENDAR'; - $format = $this->presentationOptions['format'] ?? 'html'; + $format = $this->presentationOptions['format'] ?? self::FORMAT_HTML; return match ($format) { 'file' => $this->buildFile($url), diff --git a/src/Link.php b/src/Link.php index 60e9dee..103b81e 100644 --- a/src/Link.php +++ b/src/Link.php @@ -116,7 +116,7 @@ public function google(): string /** * @param array $options ICS specific properties and components - * @param array{format?: string} $presentationOptions + * @param array{format?: \Spatie\CalendarLinks\Generators\Ics::FORMAT_*} $presentationOptions * @return string */ public function ics(array $options = [], array $presentationOptions = []): string diff --git a/tests/Generators/IcsGeneratorTest.php b/tests/Generators/IcsGeneratorTest.php index 3e025db..0c43cad 100644 --- a/tests/Generators/IcsGeneratorTest.php +++ b/tests/Generators/IcsGeneratorTest.php @@ -16,7 +16,7 @@ class IcsGeneratorTest extends TestCase */ protected function generator(array $options = [], array $presentationOptions = []): Generator { - $presentationOptions['format'] ??= 'file'; + $presentationOptions['format'] ??= Ics::FORMAT_FILE; return new Ics($options, $presentationOptions); } @@ -30,7 +30,7 @@ protected function linkMethodName(): string public function it_can_generate_an_ics_link_with_custom_uid(): void { $this->assertMatchesSnapshot( - $this->generator(['UID' => 'random-uid', ['format' => 'file']])->generate($this->createShortEventLink()) + $this->generator(['UID' => 'random-uid', ['format' => Ics::FORMAT_FILE]])->generate($this->createShortEventLink()) ); } @@ -38,7 +38,7 @@ public function it_can_generate_an_ics_link_with_custom_uid(): void public function it_has_a_product_id(): void { $this->assertMatchesSnapshot( - $this->generator(['PRODID' => 'Spatie calendar-links'], ['format' => 'file'])->generate($this->createShortEventLink()) + $this->generator(['PRODID' => 'Spatie calendar-links'], ['format' => Ics::FORMAT_FILE])->generate($this->createShortEventLink()) ); } @@ -46,7 +46,7 @@ public function it_has_a_product_id(): void public function it_has_a_product_dtstamp(): void { $this->assertMatchesSnapshot( - $this->generator(['DTSTAMP' => '20180201T090000Z'], ['format' => 'file'])->generate($this->createShortEventLink()) + $this->generator(['DTSTAMP' => '20180201T090000Z'], ['format' => Ics::FORMAT_FILE])->generate($this->createShortEventLink()) ); } @@ -54,7 +54,7 @@ public function it_has_a_product_dtstamp(): void public function it_generates_base64_encoded_link_for_html(): void { $this->assertMatchesSnapshot( - $this->generator([], ['format' => 'html'])->generate($this->createShortEventLink()) + $this->generator([], ['format' => Ics::FORMAT_FILE])->generate($this->createShortEventLink()) ); } }