diff --git a/README.md b/README.md index 785debb..531613e 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,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 3db48e3..35797e7 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,17 @@ class Ics implements Generator /** @var array */ protected $options = []; + /** @var array{format?: self::FORMAT_*} */ + protected $presentationOptions = []; + /** - * @param array $options + * @param array $options Optional ICS properties and components + * @param array{format?: self::FORMAT_*} $presentationOptions */ - public function __construct(array $options = []) + public function __construct(array $options = [], array $presentationOptions = []) { $this->options = $options; + $this->presentationOptions = $presentationOptions; } /** {@inheritDoc} */ @@ -64,7 +72,12 @@ public function generate(Link $link): string $url[] = 'END:VEVENT'; $url[] = 'END:VCALENDAR'; - return $this->buildLink($url); + $format = $this->presentationOptions['format'] ?? self::FORMAT_HTML; + + return match ($format) { + 'file' => $this->buildFile($url), + default => $this->buildLink($url), + }; } protected function buildLink(array $propertiesAndComponents): string @@ -72,6 +85,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 821b0e3..e584e0c 100644 --- a/src/Link.php +++ b/src/Link.php @@ -128,12 +128,13 @@ public function google(): string } /** - * @param array $options + * @param array $options ICS specific properties and components + * @param array{format?: \Spatie\CalendarLinks\Generators\Ics::FORMAT_*} $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 11e2431..edaff03 100644 --- a/tests/Generators/IcsGeneratorTest.php +++ b/tests/Generators/IcsGeneratorTest.php @@ -14,15 +14,11 @@ 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'] ??= Ics::FORMAT_FILE; + + return new Ics($options, $presentationOptions); } protected function linkMethodName(): string @@ -34,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'])->generate($this->createShortEventLink()) + $this->generator(['UID' => 'random-uid', ['format' => Ics::FORMAT_FILE]])->generate($this->createShortEventLink()) ); } @@ -42,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'])->generate($this->createShortEventLink()) + $this->generator(['PRODID' => 'Spatie calendar-links'], ['format' => Ics::FORMAT_FILE])->generate($this->createShortEventLink()) ); } @@ -50,7 +46,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' => Ics::FORMAT_FILE])->generate($this->createShortEventLink()) + ); + } + + /** @test */ + public function it_generates_base64_encoded_link_for_html(): void + { + $this->assertMatchesSnapshot( + $this->generator([], ['format' => Ics::FORMAT_FILE])->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