From 071cdc32e8bb2270a85573552676890a046d2710 Mon Sep 17 00:00:00 2001 From: sbsoerf Date: Tue, 12 Sep 2023 10:46:55 +0200 Subject: [PATCH 01/20] Add reminder for ICS --- README.md | 6 ++++++ src/Generators/Ics.php | 18 ++++++++++++++++ tests/Generators/IcsGeneratorTest.php | 21 +++++++++++++++++++ ...can_generate_with_a_custom_reminder__1.txt | 18 ++++++++++++++++ ...an_generate_with_a_default_reminder__1.txt | 18 ++++++++++++++++ 5 files changed, 81 insertions(+) create mode 100644 tests/Generators/__snapshots__/IcsGeneratorTest__it_can_generate_with_a_custom_reminder__1.txt create mode 100644 tests/Generators/__snapshots__/IcsGeneratorTest__it_can_generate_with_a_default_reminder__1.txt diff --git a/README.md b/README.md index 37067c7..9cb47e7 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,12 @@ echo $link->webOffice(); // Generate a data uri for an ics file (for iCal & Outlook) echo $link->ics(); +// Generate a data uri for an ics file with default reminder (for iCal & Outlook) +echo $link->ics(['REMINDER' => []]); + +// Generate a data uri for an ics file with a custom reminder (for iCal & Outlook) +echo $link->ics(['REMINDER' => ['DESCRIPTION' => 'Remind me', 'TIME' => DateTime::createFromFormat('Y-m-d H:i', '2018-02-01 08:15', new DateTimeZone('UTC'))]]); + // 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 b738974..63c2a97 100644 --- a/src/Generators/Ics.php +++ b/src/Generators/Ics.php @@ -61,6 +61,24 @@ public function generate(Link $link): string $url[] = 'URL;VALUE=URI:'.$this->options['URL']; } + if (isset($this->options['REMINDER'])) { + $description = 'Reminder: '.$this->escapeString($link->title); + if (isset($this->options['REMINDER']['DESCRIPTION'])) { + $description = $this->escapeString($this->options['REMINDER']['DESCRIPTION']); + } + + $trigger = '-PT15M'; + if (isset($this->options[ 'REMINDER'][ 'TIME'])) { + $trigger = 'VALUE=DATE-TIME:'.gmdate($dateTimeFormat, $this->options[ 'REMINDER'][ 'TIME']->getTimestamp()); + } + + $url[] = 'BEGIN:VALARM'; + $url[] = 'ACTION:DISPLAY'; + $url[] = 'DESCRIPTION:'.$description; + $url[] = 'TRIGGER:'.$trigger; + $url[] = 'END:VALARM'; + } + $url[] = 'END:VEVENT'; $url[] = 'END:VCALENDAR'; diff --git a/tests/Generators/IcsGeneratorTest.php b/tests/Generators/IcsGeneratorTest.php index 9c3b481..ad613b6 100644 --- a/tests/Generators/IcsGeneratorTest.php +++ b/tests/Generators/IcsGeneratorTest.php @@ -2,6 +2,8 @@ namespace Spatie\CalendarLinks\Tests\Generators; +use DateTime; +use DateTimeZone; use Spatie\CalendarLinks\Generator; use Spatie\CalendarLinks\Generators\Ics; use Spatie\CalendarLinks\Tests\TestCase; @@ -53,4 +55,23 @@ public function it_has_a_product_dtstamp(): void $this->generator(['DTSTAMP' => '20180201T090000Z'])->generate($this->createShortEventLink()) ); } + + /** @test */ + public function it_can_generate_with_a_default_reminder(): void + { + $this->assertMatchesSnapshot( + $this->generator(['REMINDER' => []])->generate($this->createShortEventLink()) + ); + } + + /** @test */ + public function it_can_generate_with_a_custom_reminder(): void + { + $this->assertMatchesSnapshot( + $this->generator(['REMINDER' => [ + 'DESCRIPTION' => 'Party with balloons and cake!', + 'TIME' => DateTime::createFromFormat('Y-m-d H:i', '2018-02-01 08:15', new DateTimeZone('UTC')) + ]])->generate($this->createShortEventLink()) + ); + } } diff --git a/tests/Generators/__snapshots__/IcsGeneratorTest__it_can_generate_with_a_custom_reminder__1.txt b/tests/Generators/__snapshots__/IcsGeneratorTest__it_can_generate_with_a_custom_reminder__1.txt new file mode 100644 index 0000000..b799d9e --- /dev/null +++ b/tests/Generators/__snapshots__/IcsGeneratorTest__it_can_generate_with_a_custom_reminder__1.txt @@ -0,0 +1,18 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Spatie calendar-links +BEGIN:VEVENT +UID:94ab75add84a67c019eae57539658036 +SUMMARY:Birthday +DTSTAMP:20180201T090000Z +DTSTART:20180201T090000Z +DTEND:20180201T180000Z +DESCRIPTION:With balloons\, clowns and stuff\nBring a dog\, bring a frog +LOCATION:Party Lane 1A\, 1337 Funtown +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:Party with balloons and cake! +TRIGGER:VALUE=DATE-TIME:20180201T081500Z +END:VALARM +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/tests/Generators/__snapshots__/IcsGeneratorTest__it_can_generate_with_a_default_reminder__1.txt b/tests/Generators/__snapshots__/IcsGeneratorTest__it_can_generate_with_a_default_reminder__1.txt new file mode 100644 index 0000000..933cd81 --- /dev/null +++ b/tests/Generators/__snapshots__/IcsGeneratorTest__it_can_generate_with_a_default_reminder__1.txt @@ -0,0 +1,18 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Spatie calendar-links +BEGIN:VEVENT +UID:94ab75add84a67c019eae57539658036 +SUMMARY:Birthday +DTSTAMP:20180201T090000Z +DTSTART:20180201T090000Z +DTEND:20180201T180000Z +DESCRIPTION:With balloons\, clowns and stuff\nBring a dog\, bring a frog +LOCATION:Party Lane 1A\, 1337 Funtown +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:Reminder: Birthday +TRIGGER:-PT15M +END:VALARM +END:VEVENT +END:VCALENDAR \ No newline at end of file From dc00345e5f79afd6f38d2d2a51d99204a740eff7 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Tue, 12 Sep 2023 11:02:36 +0200 Subject: [PATCH 02/20] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 37067c7..785debb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ - -[](https://supportukrainenow.org) - # Generate add to calendar links for Google, iCal and other calendar systems [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/calendar-links.svg?style=flat-square)](https://packagist.org/packages/spatie/calendar-links) From c3fbdcdc23e04eca5808d3c2e80fb21263451dce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 12:54:10 +0000 Subject: [PATCH 03/20] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/backward-compatibility-check.yml | 2 +- .github/workflows/php-cs-fixer.yml | 2 +- .github/workflows/psalm.yml | 2 +- .github/workflows/run-tests.yml | 2 +- .github/workflows/update-changelog.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/backward-compatibility-check.yml b/.github/workflows/backward-compatibility-check.yml index 56d9700..a6fcfa1 100644 --- a/.github/workflows/backward-compatibility-check.yml +++ b/.github/workflows/backward-compatibility-check.yml @@ -6,7 +6,7 @@ jobs: name: Backward compatibility check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: fetch tags run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml index d64c8e2..32d4ece 100644 --- a/.github/workflows/php-cs-fixer.yml +++ b/.github/workflows/php-cs-fixer.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Fix style uses: docker://oskarstark/php-cs-fixer-ga diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index 613fa3f..2f8be73 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -12,7 +12,7 @@ jobs: name: psalm runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index b558a9e..0694d73 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index 215e50b..7ee7514 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: main From 46bb2b165c6fa7db0a8ebf05ee167bf413189767 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:52:43 +0000 Subject: [PATCH 04/20] Bump stefanzweifel/git-auto-commit-action from 4 to 5 Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 4 to 5. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v4...v5) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/php-cs-fixer.yml | 2 +- .github/workflows/update-changelog.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml index 32d4ece..c356c5d 100644 --- a/.github/workflows/php-cs-fixer.yml +++ b/.github/workflows/php-cs-fixer.yml @@ -22,6 +22,6 @@ jobs: args: --config=.php-cs-fixer.php --allow-risky=yes - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4 + uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: Fix styling diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index 7ee7514..f3bee89 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -21,7 +21,7 @@ jobs: release-notes: ${{ github.event.release.body }} - name: Commit updated CHANGELOG - uses: stefanzweifel/git-auto-commit-action@v4 + uses: stefanzweifel/git-auto-commit-action@v5 with: branch: main commit_message: Update CHANGELOG From e84231e4c1d99c63fb307814914bf85a9a3ee727 Mon Sep 17 00:00:00 2001 From: atymic Date: Fri, 26 Jan 2024 09:26:05 +0100 Subject: [PATCH 05/20] fix: all day event issues --- .gitignore | 1 + src/Generators/Ics.php | 4 +-- src/Link.php | 13 +++++++++ tests/Generators/GoogleGeneratorTest.php | 16 +++++++++++ tests/Generators/IcsGeneratorTest.php | 16 +++++++++++ ...y_generates_all_day_events_by_dates__1.txt | 1 + ...ly_generates_all_day_events_by_days__1.txt | 1 + ...y_generates_all_day_events_by_dates__1.txt | 12 +++++++++ ...ly_generates_all_day_events_by_days__1.txt | 12 +++++++++ tests/TestCase.php | 27 +++++++++++++++++++ 10 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 tests/Generators/__snapshots__/GoogleGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt create mode 100644 tests/Generators/__snapshots__/GoogleGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt create mode 100644 tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt create mode 100644 tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt diff --git a/.gitignore b/.gitignore index b240708..2a712b7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ vendor coverage .phpunit.result.cache .php-cs-fixer.cache +.phpunit.cache diff --git a/src/Generators/Ics.php b/src/Generators/Ics.php index b738974..3db48e3 100644 --- a/src/Generators/Ics.php +++ b/src/Generators/Ics.php @@ -41,8 +41,8 @@ public function generate(Link $link): string $dateTimeFormat = $link->allDay ? $this->dateFormat : $this->dateTimeFormat; if ($link->allDay) { - $url[] = 'DTSTAMP:'.gmdate($dateTimeFormat, $link->from->getTimestamp()); - $url[] = 'DTSTART:'.gmdate($dateTimeFormat, $link->from->getTimestamp()); + $url[] = 'DTSTAMP:'.$link->from->format($dateTimeFormat); + $url[] = 'DTSTART:'.$link->from->format($dateTimeFormat); $url[] = 'DURATION:P'.(max(1, $link->from->diff($link->to)->days)).'D'; } else { $url[] = 'DTSTAMP:'.gmdate($dateTimeFormat, $link->from->getTimestamp()); diff --git a/src/Link.php b/src/Link.php index 56ab3c3..821b0e3 100644 --- a/src/Link.php +++ b/src/Link.php @@ -61,6 +61,14 @@ public function __construct(string $title, \DateTimeInterface $from, \DateTimeIn */ public static function create(string $title, \DateTimeInterface $from, \DateTimeInterface $to, bool $allDay = false) { + // When creating all day events, we need to be in the UTC timezone as all day events are "floating" based on the user's timezone + if ($allDay) { + $startDate = new \DateTime($from->format('Y-m-d'), new \DateTimeZone('UTC')); + $numberOfDays = $from->diff($to)->days + 1; + + return self::createAllDay($title, $startDate, $numberOfDays); + } + return new static($title, $from, $to, $allDay); } @@ -74,6 +82,11 @@ public static function create(string $title, \DateTimeInterface $from, \DateTime */ public static function createAllDay(string $title, \DateTimeInterface $fromDate, int $numberOfDays = 1): self { + // In cases where the from date is not UTC, make sure it's UTC, size all day events are floating and non UTC dates cause bugs in the generators + if ($fromDate->getTimezone() !== new \DateTimeZone('UTC')) { + $fromDate = \DateTime::createFromFormat('Y-m-d', $fromDate->format('Y-m-d')); + } + $from = (clone $fromDate)->modify('midnight'); $to = (clone $from)->modify("+$numberOfDays days"); diff --git a/tests/Generators/GoogleGeneratorTest.php b/tests/Generators/GoogleGeneratorTest.php index 9d87a12..079e15b 100644 --- a/tests/Generators/GoogleGeneratorTest.php +++ b/tests/Generators/GoogleGeneratorTest.php @@ -19,4 +19,20 @@ protected function linkMethodName(): string { return 'google'; } + + /** @test */ + public function it_correctly_generates_all_day_events_by_days(): void + { + $this->assertMatchesSnapshot( + $this->generator()->generate($this->createAllDayEventMultipleDaysWithTimezoneLink()) + ); + } + + /** @test */ + public function it_correctly_generates_all_day_events_by_dates(): void + { + $this->assertMatchesSnapshot( + $this->generator()->generate($this->createEventMultipleDaysViaStartEndWithTimezoneLink()) + ); + } } diff --git a/tests/Generators/IcsGeneratorTest.php b/tests/Generators/IcsGeneratorTest.php index 9c3b481..11e2431 100644 --- a/tests/Generators/IcsGeneratorTest.php +++ b/tests/Generators/IcsGeneratorTest.php @@ -53,4 +53,20 @@ public function it_has_a_product_dtstamp(): void $this->generator(['DTSTAMP' => '20180201T090000Z'])->generate($this->createShortEventLink()) ); } + + /** @test */ + public function it_correctly_generates_all_day_events_by_days(): void + { + $this->assertMatchesSnapshot( + $this->generator()->generate($this->createAllDayEventMultipleDaysWithTimezoneLink()) + ); + } + + /** @test */ + public function it_correctly_generates_all_day_events_by_dates(): void + { + $this->assertMatchesSnapshot( + $this->generator()->generate($this->createEventMultipleDaysViaStartEndWithTimezoneLink()) + ); + } } diff --git a/tests/Generators/__snapshots__/GoogleGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt b/tests/Generators/__snapshots__/GoogleGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt new file mode 100644 index 0000000..2feec82 --- /dev/null +++ b/tests/Generators/__snapshots__/GoogleGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt @@ -0,0 +1 @@ +https://calendar.google.com/calendar/render?action=TEMPLATE&dates=20240125/20240131&ctz=UTC&text=All+day+bugs&details=Testing+all+day \ No newline at end of file diff --git a/tests/Generators/__snapshots__/GoogleGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt b/tests/Generators/__snapshots__/GoogleGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt new file mode 100644 index 0000000..3f414ee --- /dev/null +++ b/tests/Generators/__snapshots__/GoogleGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt @@ -0,0 +1 @@ +https://calendar.google.com/calendar/render?action=TEMPLATE&dates=20240125/20240130&ctz=UTC&text=All+day+bugs&details=Testing+all+day \ No newline at end of file diff --git a/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt b/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt new file mode 100644 index 0000000..28af5d4 --- /dev/null +++ b/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt @@ -0,0 +1,12 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Spatie calendar-links +BEGIN:VEVENT +UID:8fe3eececcd6b020db3b47ef55e7cf89 +SUMMARY:All day bugs +DTSTAMP:20240125 +DTSTART:20240125 +DURATION:P6D +DESCRIPTION:Testing all day +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt b/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt new file mode 100644 index 0000000..d230a35 --- /dev/null +++ b/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt @@ -0,0 +1,12 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Spatie calendar-links +BEGIN:VEVENT +UID:b4be522f87b9894dadd2b9cd5479136b +SUMMARY:All day bugs +DTSTAMP:20240125 +DTSTART:20240125 +DURATION:P5D +DESCRIPTION:Testing all day +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index 085d97b..138a70f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -68,6 +68,33 @@ protected function createMultipleDaysAllDayEventLink(bool $immutable = false): L )->description($description)->address('Party Lane 1A, 1337 Funtown'); } + protected function createAllDayEventMultipleDaysWithTimezoneLink(bool $immutable = false): Link + { + $description = 'Testing all day'; + + $dateTimeClass = $immutable ? DateTimeImmutable::class : DateTime::class; + + return Link::createAllDay( + 'All day bugs', + $dateTimeClass::createFromFormat('Y-m-d', '2024-01-25', new DateTimeZone('Pacific/Wake'))->setTime(0,0), + 5 + )->description($description); + } + + protected function createEventMultipleDaysViaStartEndWithTimezoneLink(bool $immutable = false): Link + { + $description = 'Testing all day'; + + $dateTimeClass = $immutable ? DateTimeImmutable::class : DateTime::class; + + return Link::create( + 'All day bugs', + $dateTimeClass::createFromFormat('Y-m-d', '2024-01-25', new DateTimeZone('Pacific/Wake'))->setTime(0,0), + $dateTimeClass::createFromFormat('Y-m-d', '2024-01-30', new DateTimeZone('Pacific/Wake'))->setTime(0,0), + true, + )->description($description); + } + protected function createDescriptionIsHTMLcodeEventLink(bool $immutable = false): Link { $description = 'With balloons, clowns and stuff From f0e946b5f82ffb9aa475e7b36b7cc714b5f643f9 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 14:19:23 +0530 Subject: [PATCH 06/20] 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 07/20] 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 08/20] 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 e6319b1ec029e0a6a4fe832062cc24d3e51a8b97 Mon Sep 17 00:00:00 2001 From: atymic Date: Fri, 26 Jan 2024 10:08:08 +0100 Subject: [PATCH 09/20] fix: web outlook url --- .gitignore | 1 + src/Generators/WebOutlook.php | 2 +- ..._description_is_html_code_event_link_with_allday_flag__1.txt | 2 +- ...ratorTest__it_can_generate_a_multiple_days_event_link__1.txt | 2 +- ..._generate_a_multiple_days_event_link_with_allday_flag__1.txt | 2 +- ...lookGeneratorTest__it_can_generate_a_short_event_link__1.txt | 2 +- ...rTest__it_can_generate_a_single_day_allday_event_link__1.txt | 2 +- 7 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index b240708..2a712b7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ vendor coverage .phpunit.result.cache .php-cs-fixer.cache +.phpunit.cache diff --git a/src/Generators/WebOutlook.php b/src/Generators/WebOutlook.php index d8fa080..f0465d3 100644 --- a/src/Generators/WebOutlook.php +++ b/src/Generators/WebOutlook.php @@ -4,7 +4,7 @@ class WebOutlook extends BaseOutlook { - protected const BASE_URL = 'https://outlook.live.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent'; + protected const BASE_URL = 'https://outlook.live.com/calendar/action/compose?path=/calendar/action/compose&rru=addevent'; /** @inheritDoc */ public function baseUrl(): string diff --git a/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_description_is_html_code_event_link_with_allday_flag__1.txt b/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_description_is_html_code_event_link_with_allday_flag__1.txt index d1b2612..71ac395 100644 --- a/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_description_is_html_code_event_link_with_allday_flag__1.txt +++ b/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_description_is_html_code_event_link_with_allday_flag__1.txt @@ -1 +1 @@ -https://outlook.live.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent&startdt=2018-02-01T09:00:00Z&enddt=2018-02-01T18:00:00Z&subject=Birthday%20Party%20%2B1&body=With%20balloons%2C%20clowns%20and%20stuff%0ABring%20a%20dog%2C%20bring%20a%20frog.%0AThere%20will%20be%20line%20breaks%20on%20it.%0AProject%20link%20%3Ca%20href%3D%22https%3A%2F%2Fgithub.com%2Fspatie%2Fcalendar-links%22%3Ecalendar-links%3C%2Fa%3E%0A%3Cimg%20src%3D%22https%3A%2F%2Fgithub-ads.s3.eu-central-1.amazonaws.com%2Fcalendar-links.jpg%3Ft%3D1%22%20width%3D%22419px%22%20%2F%3E%0A%3Cbr%3E%0AThank%20you.%0A&location=Party%20Lane%201A%2C%201337%20Funtown \ No newline at end of file +https://outlook.live.com/calendar/action/compose?path=/calendar/action/compose&rru=addevent&startdt=2018-02-01T09:00:00Z&enddt=2018-02-01T18:00:00Z&subject=Birthday%20Party%20%2B1&body=With%20balloons%2C%20clowns%20and%20stuff%0ABring%20a%20dog%2C%20bring%20a%20frog.%0AThere%20will%20be%20line%20breaks%20on%20it.%0AProject%20link%20%3Ca%20href%3D%22https%3A%2F%2Fgithub.com%2Fspatie%2Fcalendar-links%22%3Ecalendar-links%3C%2Fa%3E%0A%3Cimg%20src%3D%22https%3A%2F%2Fgithub-ads.s3.eu-central-1.amazonaws.com%2Fcalendar-links.jpg%3Ft%3D1%22%20width%3D%22419px%22%20%2F%3E%0A%3Cbr%3E%0AThank%20you.%0A&location=Party%20Lane%201A%2C%201337%20Funtown \ No newline at end of file diff --git a/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_multiple_days_event_link__1.txt b/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_multiple_days_event_link__1.txt index ba93d74..e44b469 100644 --- a/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_multiple_days_event_link__1.txt +++ b/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_multiple_days_event_link__1.txt @@ -1 +1 @@ -https://outlook.live.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent&startdt=2019-12-31T23:00:00Z&enddt=2020-01-01T01:00:00Z&subject=New%20Year&body=With%20balloons%2C%20clowns%20and%20stuff%0ABring%20a%20dog%2C%20bring%20a%20frog&location=Party%20Lane%201A%2C%201337%20Funtown \ No newline at end of file +https://outlook.live.com/calendar/action/compose?path=/calendar/action/compose&rru=addevent&startdt=2019-12-31T23:00:00Z&enddt=2020-01-01T01:00:00Z&subject=New%20Year&body=With%20balloons%2C%20clowns%20and%20stuff%0ABring%20a%20dog%2C%20bring%20a%20frog&location=Party%20Lane%201A%2C%201337%20Funtown \ No newline at end of file diff --git a/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_multiple_days_event_link_with_allday_flag__1.txt b/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_multiple_days_event_link_with_allday_flag__1.txt index af87068..e0de1ab 100644 --- a/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_multiple_days_event_link_with_allday_flag__1.txt +++ b/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_multiple_days_event_link_with_allday_flag__1.txt @@ -1 +1 @@ -https://outlook.live.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent&startdt=2018-02-01&enddt=2018-02-06&allday=true&subject=Birthday&body=With%20balloons%2C%20clowns%20and%20stuff%0ABring%20a%20dog%2C%20bring%20a%20frog&location=Party%20Lane%201A%2C%201337%20Funtown \ No newline at end of file +https://outlook.live.com/calendar/action/compose?path=/calendar/action/compose&rru=addevent&startdt=2018-02-01&enddt=2018-02-06&allday=true&subject=Birthday&body=With%20balloons%2C%20clowns%20and%20stuff%0ABring%20a%20dog%2C%20bring%20a%20frog&location=Party%20Lane%201A%2C%201337%20Funtown \ No newline at end of file diff --git a/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_short_event_link__1.txt b/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_short_event_link__1.txt index cca7e10..079416d 100644 --- a/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_short_event_link__1.txt +++ b/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_short_event_link__1.txt @@ -1 +1 @@ -https://outlook.live.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent&startdt=2018-02-01T09:00:00Z&enddt=2018-02-01T18:00:00Z&subject=Birthday&body=With%20balloons%2C%20clowns%20and%20stuff%0ABring%20a%20dog%2C%20bring%20a%20frog&location=Party%20Lane%201A%2C%201337%20Funtown \ No newline at end of file +https://outlook.live.com/calendar/action/compose?path=/calendar/action/compose&rru=addevent&startdt=2018-02-01T09:00:00Z&enddt=2018-02-01T18:00:00Z&subject=Birthday&body=With%20balloons%2C%20clowns%20and%20stuff%0ABring%20a%20dog%2C%20bring%20a%20frog&location=Party%20Lane%201A%2C%201337%20Funtown \ No newline at end of file diff --git a/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_single_day_allday_event_link__1.txt b/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_single_day_allday_event_link__1.txt index 1a33c07..6e75b2d 100644 --- a/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_single_day_allday_event_link__1.txt +++ b/tests/Generators/__snapshots__/WebOutlookGeneratorTest__it_can_generate_a_single_day_allday_event_link__1.txt @@ -1 +1 @@ -https://outlook.live.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent&startdt=2018-02-01&enddt=2018-02-02&allday=true&subject=Birthday&body=With%20balloons%2C%20clowns%20and%20stuff%0ABring%20a%20dog%2C%20bring%20a%20frog&location=Party%20Lane%201A%2C%201337%20Funtown \ No newline at end of file +https://outlook.live.com/calendar/action/compose?path=/calendar/action/compose&rru=addevent&startdt=2018-02-01&enddt=2018-02-02&allday=true&subject=Birthday&body=With%20balloons%2C%20clowns%20and%20stuff%0ABring%20a%20dog%2C%20bring%20a%20frog&location=Party%20Lane%201A%2C%201337%20Funtown \ No newline at end of file From 1c587970c9088bbd3f4b1e022d0fb050bbf8b112 Mon Sep 17 00:00:00 2001 From: alies-dev Date: Fri, 26 Jan 2024 09:09:21 +0000 Subject: [PATCH 10/20] Fix styling --- tests/TestCase.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 138a70f..903f6ae 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -76,7 +76,7 @@ protected function createAllDayEventMultipleDaysWithTimezoneLink(bool $immutable return Link::createAllDay( 'All day bugs', - $dateTimeClass::createFromFormat('Y-m-d', '2024-01-25', new DateTimeZone('Pacific/Wake'))->setTime(0,0), + $dateTimeClass::createFromFormat('Y-m-d', '2024-01-25', new DateTimeZone('Pacific/Wake'))->setTime(0, 0), 5 )->description($description); } @@ -89,8 +89,8 @@ protected function createEventMultipleDaysViaStartEndWithTimezoneLink(bool $immu return Link::create( 'All day bugs', - $dateTimeClass::createFromFormat('Y-m-d', '2024-01-25', new DateTimeZone('Pacific/Wake'))->setTime(0,0), - $dateTimeClass::createFromFormat('Y-m-d', '2024-01-30', new DateTimeZone('Pacific/Wake'))->setTime(0,0), + $dateTimeClass::createFromFormat('Y-m-d', '2024-01-25', new DateTimeZone('Pacific/Wake'))->setTime(0, 0), + $dateTimeClass::createFromFormat('Y-m-d', '2024-01-30', new DateTimeZone('Pacific/Wake'))->setTime(0, 0), true, )->description($description); } From 28d5971d23f96b1308ac27f8daffecfe290f0f14 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 15:01:10 +0530 Subject: [PATCH 11/20] 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()) ); } } From cee81c9b1735d28bae8fad948491ef44d549eb67 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 15:08:13 +0530 Subject: [PATCH 12/20] Update dependencies --- README.md | 14 ++++----- composer.json | 8 ++--- phpunit.xml.dist | 25 +++++++-------- psalm-baseline.xml | 56 ++++++++++++++++++++++++++++------ psalm.xml | 2 ++ src/Generators/BaseOutlook.php | 1 + 6 files changed, 72 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 531613e..b27aec1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Psalm level](https://shepherd.dev/github/spatie/calendar-links/level.svg)](https://shepherd.dev/github/spatie/calendar-links) -Using this package you can generate links to add events to calendar systems. Here's a quick example: +Using this package, you can generate links to add events to calendar systems. Here's a quick example: ```php use Spatie\CalendarLinks\Link; @@ -22,7 +22,7 @@ Link::create( This will output: `https://calendar.google.com/calendar/render?action=TEMPLATE&text=Birthday&dates=20180201T090000/20180201T180000&sprop=&sprop=name:` -If you follow that link (and are authenticated with Google) you'll see a screen to add the event to your calendar. +If you follow that link (and are authenticated with Google), you’ll see a screen to add the event to your calendar. The package can also generate ics files that you can open in several email and calendar programs, including Microsoft Outlook, Google Calendar, and Apple Calendar. @@ -76,17 +76,15 @@ echo $link->ics([], ['format' => 'file']); // e.g. to attach ics as a file to an echo $link->formatWith(new \Your\Generator()); ``` -> ⚠️ ICS download links don't work in IE and EdgeHTML-based Edge browsers, see [details](https://github.com/spatie/calendar-links/issues/71). - ## Package principles -1. it should produce a small output (to keep pagesize small) +1. it should produce a small output (to keep page-size small) 2. it should be fast (no any external heavy dependencies) 3. all features should be supported by at least 2 generators (different services have different features) ## Changelog -Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. +Please see [CHANGELOG](CHANGELOG.md) for more information. ## Testing @@ -100,11 +98,11 @@ Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTI ## Security -If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. +If you've found a bug regarding security, please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. ## Postcardware -You're free to use this package (it's [MIT-licensed](LICENSE.md)), but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. +You're free to use this package (it's [MIT-licensed](LICENSE.md)), but if it makes it to your production environment, we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. Our address is: Spatie, Samberstraat 69D, 2060 Antwerp, Belgium. 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/phpunit.xml.dist b/phpunit.xml.dist index 93d19e0..83d1d55 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,19 +1,20 @@ - - - src - - - - - tests - - + + + tests + + + + + src + + diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 793cfb5..6eaa7cc 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,36 +1,72 @@ - + - + + + + + + $dateFormat + $dateTimeFormat + + setTimezone setTimezone - + - + + + + + $dateFormat + + setTimezone setTimezone - + + + + + + + + $dateFormat + - + + + + + $dateFormat + + setTimezone setTimezone - + + + self::createAllDay($title, $startDate, $numberOfDays) + $property - - modify - clone $from clone $to + + modify + new static($title, $from, $to, $allDay) + + + $presentationOptions + + diff --git a/psalm.xml b/psalm.xml index cb3e980..b71cbbe 100644 --- a/psalm.xml +++ b/psalm.xml @@ -4,6 +4,8 @@ xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" errorLevel="2" + findUnusedBaselineEntry="true" + findUnusedCode="false" findUnusedVariablesAndParams="true" resolveFromConfigFile="true" useDocblockPropertyTypes="true" diff --git a/src/Generators/BaseOutlook.php b/src/Generators/BaseOutlook.php index 15d0434..8f0fc82 100644 --- a/src/Generators/BaseOutlook.php +++ b/src/Generators/BaseOutlook.php @@ -13,6 +13,7 @@ abstract class BaseOutlook implements Generator { /** @var string {@see https://www.php.net/manual/en/function.date.php} */ protected $dateFormat = 'Y-m-d'; + /** @var string {@see https://www.php.net/manual/en/function.date.php} */ protected $dateTimeFormat = 'Y-m-d\TH:i:s\Z'; From ef70059437827b27d056b4eb563d84c0890fde2f Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 15:10:10 +0530 Subject: [PATCH 13/20] Bump the PHP version to 8.1 normalize composer.json --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index c6eb937..0b5a5bf 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,11 @@ { "name": "spatie/calendar-links", "description": "Generate add to calendar links for Google, iCal and other calendar systems", + "license": "MIT", "keywords": [ "spatie", "calendar-links" ], - "homepage": "https://github.com/spatie/calendar-links", - "license": "MIT", "authors": [ { "name": "Sebastian De Deyne", @@ -15,8 +14,9 @@ "role": "Developer" } ], + "homepage": "https://github.com/spatie/calendar-links", "require": { - "php": "^8.0" + "php": "^8.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.48", @@ -34,14 +34,14 @@ "Spatie\\CalendarLinks\\Tests\\": "tests" } }, + "config": { + "sort-packages": true + }, "scripts": { "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes", "psalm": "vendor/bin/psalm --find-unused-psalm-suppress --output-format=phpstorm", "psalm:ci": "vendor/bin/psalm --find-unused-psalm-suppress --output-format=github --shepherd", "test": "vendor/bin/phpunit", "test:update-snapshots": "vendor/bin/phpunit -d --update-snapshots" - }, - "config": { - "sort-packages": true } } From f178150e4972208b28519d7f5465ff4ea8d9b0e1 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 15:12:59 +0530 Subject: [PATCH 14/20] Update PHP versions on CI --- .github/workflows/psalm.yml | 3 +-- .github/workflows/run-tests.yml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index 2f8be73..2fa8ac1 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -17,8 +17,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.2' - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick + php-version: '8.3' coverage: none - name: Cache composer dependencies diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 0694d73..1df62a1 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -24,7 +24,7 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest] - php: [8.0, 8.1, 8.2] + php: [8.1, 8.2, 8.3] dependency-version: [prefer-lowest, prefer-stable] name: P${{ matrix.php }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} From cb438e90db11b5b6e20f4ecaed9c39a4e04592bf Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 15:18:32 +0530 Subject: [PATCH 15/20] Fix tests --- tests/Generators/IcsGeneratorTest.php | 2 +- ...orTest__it_correctly_generates_all_day_events_by_days__1.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Generators/IcsGeneratorTest.php b/tests/Generators/IcsGeneratorTest.php index edaff03..8f4f998 100644 --- a/tests/Generators/IcsGeneratorTest.php +++ b/tests/Generators/IcsGeneratorTest.php @@ -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' => Ics::FORMAT_FILE])->generate($this->createShortEventLink()) + $this->generator([], ['format' => Ics::FORMAT_HTML])->generate($this->createShortEventLink()) ); } diff --git a/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt b/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt index d230a35..f159c15 100644 --- a/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt +++ b/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt @@ -2,7 +2,7 @@ BEGIN:VCALENDAR VERSION:2.0 PRODID:Spatie calendar-links BEGIN:VEVENT -UID:b4be522f87b9894dadd2b9cd5479136b +UID:a05fc4dac68ae6064aaae69dcdfd60a6 SUMMARY:All day bugs DTSTAMP:20240125 DTSTART:20240125 From 33cbd2fbff53cae20e14711b0430d84ac6e95abb Mon Sep 17 00:00:00 2001 From: alies-dev Date: Fri, 26 Jan 2024 09:57:26 +0000 Subject: [PATCH 16/20] Fix styling --- tests/Generators/IcsGeneratorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Generators/IcsGeneratorTest.php b/tests/Generators/IcsGeneratorTest.php index 1564879..d93f73f 100644 --- a/tests/Generators/IcsGeneratorTest.php +++ b/tests/Generators/IcsGeneratorTest.php @@ -90,7 +90,7 @@ public function it_can_generate_with_a_custom_reminder(): void $this->assertMatchesSnapshot( $this->generator(['REMINDER' => [ 'DESCRIPTION' => 'Party with balloons and cake!', - 'TIME' => DateTime::createFromFormat('Y-m-d H:i', '2018-02-01 08:15', new DateTimeZone('UTC')) + 'TIME' => DateTime::createFromFormat('Y-m-d H:i', '2018-02-01 08:15', new DateTimeZone('UTC')), ]])->generate($this->createShortEventLink()) ); } From 1ffc15a0514ea697bfb9ed4db4e3e1f2154c8972 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 16:19:00 +0530 Subject: [PATCH 17/20] Use method extraction, fix issues reported by Psalm --- README.md | 8 +++---- src/Generators/Ics.php | 50 +++++++++++++++++++++++++++--------------- src/Link.php | 2 ++ 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 212dc66..5ee5d19 100644 --- a/README.md +++ b/README.md @@ -70,13 +70,11 @@ echo $link->webOffice(); // 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 for an ics file with default reminder (for iCal & Outlook) +// Generate a data uri for an ics file with the default reminder (for iCal & Outlook) echo $link->ics(['REMINDER' => []]); - // Generate a data uri for an ics file with a custom reminder (for iCal & Outlook) -echo $link->ics(['REMINDER' => ['DESCRIPTION' => 'Remind me', 'TIME' => DateTime::createFromFormat('Y-m-d H:i', '2018-02-01 08:15', new DateTimeZone('UTC'))]]); +echo $link->ics(['REMINDER' => ['DESCRIPTION' => 'Remind me', 'TIME' => 1706264894]]); +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 0103d32..248b674 100644 --- a/src/Generators/Ics.php +++ b/src/Generators/Ics.php @@ -7,6 +7,7 @@ /** * @see https://icalendar.org/RFC-Specifications/iCalendar-RFC-5545/ + * @psalm-type IcsOptions = array{UID?: string, URL?: string, REMINDER?: array{DESCRIPTION?: string, TIME?: \DateTimeInterface}} */ class Ics implements Generator { @@ -15,17 +16,18 @@ class Ics implements Generator /** @var string {@see https://www.php.net/manual/en/function.date.php} */ protected $dateFormat = 'Ymd'; + /** @var string */ protected $dateTimeFormat = 'Ymd\THis\Z'; - /** @var array */ + /** @var IcsOptions */ protected $options = []; /** @var array{format?: self::FORMAT_*} */ protected $presentationOptions = []; /** - * @param array $options Optional ICS properties and components + * @param IcsOptions $options Optional ICS properties and components * @param array{format?: self::FORMAT_*} $presentationOptions */ public function __construct(array $options = [], array $presentationOptions = []) @@ -69,22 +71,8 @@ public function generate(Link $link): string $url[] = 'URL;VALUE=URI:'.$this->options['URL']; } - if (isset($this->options['REMINDER'])) { - $description = 'Reminder: '.$this->escapeString($link->title); - if (isset($this->options['REMINDER']['DESCRIPTION'])) { - $description = $this->escapeString($this->options['REMINDER']['DESCRIPTION']); - } - - $trigger = '-PT15M'; - if (isset($this->options[ 'REMINDER'][ 'TIME'])) { - $trigger = 'VALUE=DATE-TIME:'.gmdate($dateTimeFormat, $this->options[ 'REMINDER'][ 'TIME']->getTimestamp()); - } - - $url[] = 'BEGIN:VALARM'; - $url[] = 'ACTION:DISPLAY'; - $url[] = 'DESCRIPTION:'.$description; - $url[] = 'TRIGGER:'.$trigger; - $url[] = 'END:VALARM'; + if (is_array($this->options['REMINDER'] ?? null)) { + $url = [...$url, ...$this->generateAlertComponent($link)]; } $url[] = 'END:VEVENT'; @@ -125,4 +113,30 @@ protected function generateEventUid(Link $link): string $link->address )); } + + /** + * @param \Spatie\CalendarLinks\Link $link + * @return list + */ + private function generateAlertComponent(Link $link): array + { + $description = $this->options['REMINDER']['DESCRIPTION'] ?? null; + if (! is_string($description)) { + $description = 'Reminder: '.$this->escapeString($link->title); + } + + $trigger = '-PT15M'; + if (($reminderTime = $this->options['REMINDER']['TIME'] ?? null) instanceof \DateTimeInterface) { + $trigger = 'VALUE=DATE-TIME:'.gmdate($this->dateTimeFormat, $reminderTime->getTimestamp()); + } + + $alarmComponent = []; + $alarmComponent[] = 'BEGIN:VALARM'; + $alarmComponent[] = 'ACTION:DISPLAY'; + $alarmComponent[] = 'DESCRIPTION:'.$description; + $alarmComponent[] = 'TRIGGER:'.$trigger; + $alarmComponent[] = 'END:VALARM'; + + return $alarmComponent; + } } diff --git a/src/Link.php b/src/Link.php index e584e0c..9230480 100644 --- a/src/Link.php +++ b/src/Link.php @@ -16,6 +16,7 @@ * @property-read string $description * @property-read string $address * @property-read bool $allDay + * @psalm-import-type IcsOptions from \Spatie\CalendarLinks\Generators\Ics */ class Link { @@ -128,6 +129,7 @@ public function google(): string } /** + * @psalm-param IcsOptions $options ICS specific properties and components * @param array $options ICS specific properties and components * @param array{format?: \Spatie\CalendarLinks\Generators\Ics::FORMAT_*} $presentationOptions * @return string From cd7f8a8cce8b148d85bf3a835e7e470672dffb19 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 16:28:04 +0530 Subject: [PATCH 18/20] Fix example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ee5d19..41e7752 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ echo $link->ics(['URL' => 'https://my-page.com', 'UID' => 'custom-id']); // // Generate a data uri for an ics file with the default reminder (for iCal & Outlook) echo $link->ics(['REMINDER' => []]); // Generate a data uri for an ics file with a custom reminder (for iCal & Outlook) -echo $link->ics(['REMINDER' => ['DESCRIPTION' => 'Remind me', 'TIME' => 1706264894]]); +echo $link->ics(['REMINDER' => ['DESCRIPTION' => 'Remind me', 'TIME' => new \DateTime('tomorrow 12:30 UTC')]]); echo $link->ics([], ['format' => 'file']); // e.g. to attach ics as a file to an email. // Generate a data URI using arbitrary generator: From 150d014c813b1d34e7acc1e8d78ccaf4e0d7522d Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Fri, 26 Jan 2024 16:30:52 +0530 Subject: [PATCH 19/20] Align examples --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 41e7752..7715b75 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,11 @@ echo $link->webOffice(); // 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']); // -// Generate a data uri for an ics file with the default reminder (for iCal & Outlook) -echo $link->ics(['REMINDER' => []]); -// Generate a data uri for an ics file with a custom reminder (for iCal & Outlook) -echo $link->ics(['REMINDER' => ['DESCRIPTION' => 'Remind me', 'TIME' => new \DateTime('tomorrow 12:30 UTC')]]); -echo $link->ics([], ['format' => 'file']); // e.g. to attach ics as a file to an email. +echo $link->ics(['UID' => 'custom-id']); // Custom UID (to update existing events) +echo $link->ics(['URL' => 'https://my-page.com']); // Custom URL +echo $link->ics(['REMINDER' => []]); // Add the default reminder (for iCal & Outlook) +echo $link->ics(['REMINDER' => ['DESCRIPTION' => 'Remind me', 'TIME' => new \DateTime('tomorrow 12:30 UTC')]]); // Add a custom reminder +echo $link->ics([], ['format' => 'file']); // use file output; e.g. to attach ics as a file to an email. // Generate a data URI using arbitrary generator: echo $link->formatWith(new \Your\Generator()); From 7a59395c5761948242f952fcaca6fbdae384d047 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Sun, 28 Jan 2024 11:45:25 +0530 Subject: [PATCH 20/20] Refine princinple --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7715b75..483e74d 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ echo $link->formatWith(new \Your\Generator()); 1. it should produce a small output (to keep page-size small) 2. it should be fast (no any external heavy dependencies) -3. all features should be supported by at least 2 generators (different services have different features) +3. all `Link` class features should be supported by at least 2 generators (different services have different features) ## Changelog