Skip to content

Commit e969fa4

Browse files
author
tchapi
committed
calendar order default
1 parent eab47cf commit e969fa4

3 files changed

Lines changed: 133 additions & 2 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Give calendarsubscriptions.calendarorder a default value.
12+
*/
13+
final class Version20260908210000 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return 'Default calendarsubscriptions.calendarorder to 0, as sabre/dav omits the column when a client subscribes without a calendar-order';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
$engine = $this->connection->getDatabasePlatform()->getName();
23+
24+
// \Sabre\CalDAV\Backend\PDO::createSubscription() only lists `calendarorder` in its
25+
// INSERT when the client sent {http://apple.com/ns/ical/}calendar-order. Without a
26+
// default, subscribing to a feed then fails with a NOT NULL violation (HTTP 500).
27+
if ('mysql' === $engine) {
28+
$this->addSql('ALTER TABLE calendarsubscriptions CHANGE calendarorder calendarorder INT DEFAULT 0 NOT NULL');
29+
} elseif ('postgresql' === $engine) {
30+
$this->addSql('ALTER TABLE calendarsubscriptions ALTER COLUMN calendarorder SET DEFAULT 0');
31+
} elseif ('sqlite' === $engine) {
32+
// SQLite cannot alter a column in place: add the replacement, copy, swap, drop.
33+
$this->addSql('ALTER TABLE calendarsubscriptions ADD COLUMN new_calendarorder INTEGER DEFAULT 0 NOT NULL');
34+
$this->addSql('UPDATE calendarsubscriptions SET new_calendarorder = calendarorder');
35+
$this->addSql('ALTER TABLE calendarsubscriptions RENAME COLUMN calendarorder TO old_calendarorder');
36+
$this->addSql('ALTER TABLE calendarsubscriptions RENAME COLUMN new_calendarorder TO calendarorder');
37+
$this->addSql('ALTER TABLE calendarsubscriptions DROP COLUMN old_calendarorder');
38+
}
39+
}
40+
41+
public function down(Schema $schema): void
42+
{
43+
$engine = $this->connection->getDatabasePlatform()->getName();
44+
45+
if ('mysql' === $engine) {
46+
$this->addSql('ALTER TABLE calendarsubscriptions CHANGE calendarorder calendarorder INT NOT NULL');
47+
} elseif ('postgresql' === $engine) {
48+
$this->addSql('ALTER TABLE calendarsubscriptions ALTER COLUMN calendarorder DROP DEFAULT');
49+
} elseif ('sqlite' === $engine) {
50+
// SQLite refuses to ADD a NOT NULL column without a default, so the only way back
51+
// is to rebuild the table with its original definition.
52+
$this->addSql('CREATE TABLE calendarsubscriptions_old (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, uri VARCHAR(255) NOT NULL, principaluri VARCHAR(255) NOT NULL, source CLOB DEFAULT NULL, displayname VARCHAR(255) DEFAULT NULL, refreshrate VARCHAR(10) DEFAULT NULL, calendarorder INTEGER NOT NULL, calendarcolor VARCHAR(10) DEFAULT NULL, striptodos SMALLINT DEFAULT NULL, stripalarms SMALLINT DEFAULT NULL, stripattachments SMALLINT DEFAULT NULL, lastmodified INTEGER DEFAULT NULL)');
53+
$this->addSql('INSERT INTO calendarsubscriptions_old (id, uri, principaluri, source, displayname, refreshrate, calendarorder, calendarcolor, striptodos, stripalarms, stripattachments, lastmodified) SELECT id, uri, principaluri, source, displayname, refreshrate, calendarorder, calendarcolor, striptodos, stripalarms, stripattachments, lastmodified FROM calendarsubscriptions');
54+
$this->addSql('DROP TABLE calendarsubscriptions');
55+
$this->addSql('ALTER TABLE calendarsubscriptions_old RENAME TO calendarsubscriptions');
56+
}
57+
}
58+
}

src/Entity/CalendarSubscription.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ class CalendarSubscription
2828
#[ORM\Column(name: 'refreshrate', type: 'string', length: 10, nullable: true)]
2929
private $refreshRate;
3030

31-
#[ORM\Column(name: 'calendarorder', type: 'integer')]
32-
private $calendarOrder;
31+
#[ORM\Column(name: 'calendarorder', type: 'integer', options: ['default' => 0])]
32+
private $calendarOrder = 0;
3333

3434
#[ORM\Column(name: 'calendarcolor', type: 'string', length: 10, nullable: true)]
3535
private $calendarColor;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Tests\Functional;
6+
7+
use Doctrine\ORM\EntityManagerInterface;
8+
use Sabre\CalDAV\Backend\PDO as CalendarBackend;
9+
use Sabre\DAV\Xml\Property\Href;
10+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
11+
12+
class CalendarSubscriptionTest extends KernelTestCase
13+
{
14+
private const PRINCIPAL = 'principals/test_user';
15+
private const SOURCE = 'https://example.org/holidays.ics';
16+
17+
private EntityManagerInterface $em;
18+
private CalendarBackend $backend;
19+
20+
protected function setUp(): void
21+
{
22+
self::bootKernel();
23+
24+
$this->em = static::getContainer()->get(EntityManagerInterface::class);
25+
$this->backend = new CalendarBackend($this->em->getConnection()->getNativeConnection());
26+
27+
$this->em->getConnection()->beginTransaction();
28+
}
29+
30+
protected function tearDown(): void
31+
{
32+
$this->em->getConnection()->rollBack();
33+
parent::tearDown();
34+
}
35+
36+
private function subscriptionFor(string $uri): array
37+
{
38+
foreach ($this->backend->getSubscriptionsForUser(self::PRINCIPAL) as $subscription) {
39+
if ($uri === $subscription['uri']) {
40+
return $subscription;
41+
}
42+
}
43+
44+
$this->fail(sprintf('No subscription found for uri "%s"', $uri));
45+
}
46+
47+
/**
48+
* Regression test: `calendarorder` had no default, and sabre only lists it in its INSERT
49+
* when the client sent {http://apple.com/ns/ical/}calendar-order. Subscribing without one
50+
* therefore failed with a NOT NULL violation.
51+
*/
52+
public function testSubscriptionCanBeCreatedWithoutACalendarOrder(): void
53+
{
54+
$this->backend->createSubscription(self::PRINCIPAL, 'holidays', [
55+
'{http://calendarserver.org/ns/}source' => new Href(self::SOURCE),
56+
]);
57+
58+
$subscription = $this->subscriptionFor('holidays');
59+
60+
$this->assertSame(self::SOURCE, $subscription['source']);
61+
$this->assertSame(0, (int) $subscription['{http://apple.com/ns/ical/}calendar-order']);
62+
}
63+
64+
public function testAnExplicitCalendarOrderIsStillHonoured(): void
65+
{
66+
$this->backend->createSubscription(self::PRINCIPAL, 'ordered', [
67+
'{http://calendarserver.org/ns/}source' => new Href(self::SOURCE),
68+
'{http://apple.com/ns/ical/}calendar-order' => 3,
69+
]);
70+
71+
$this->assertSame(3, (int) $this->subscriptionFor('ordered')['{http://apple.com/ns/ical/}calendar-order']);
72+
}
73+
}

0 commit comments

Comments
 (0)