Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to have multiple links per public calendar #20214

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 60 additions & 27 deletions apps/dav/lib/CalDAV/CalDavBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -2339,57 +2339,90 @@ public function getShares($resourceId, $calendarType=self::CALENDAR_TYPE_CALENDA
}

/**
* @param boolean $value
* @param \OCA\DAV\CalDAV\Calendar $calendar
* @return string|null
* @param Calendar $calendar
* @return string
*/
public function setPublishStatus($value, $calendar) {
public function addPublicLink(Calendar $calendar): string {
$calendarId = $calendar->getResourceId();
$publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);

$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
'\OCA\DAV\CalDAV\CalDavBackend::addPublicLink',
[
'calendarId' => $calendarId,
'calendarData' => $this->getCalendarById($calendarId),
'publicuri' => $publicUri,
]));

$query = $this->db->getQueryBuilder();
$query->insert('dav_shares')
->values([
'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
'type' => $query->createNamedParameter('calendar'),
'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
'publicuri' => $query->createNamedParameter($publicUri)
]);
$query->execute();
return $publicUri;
}

/**
* @param Calendar $calendar
* @param string $publicUri
*/
public function removePublicLink(Calendar $calendar, string $publicUri) {
$calendarId = $calendar->getResourceId();

$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
'\OCA\DAV\CalDAV\CalDavBackend::removePublicLinks',
[
'calendarId' => $calendarId,
'calendarData' => $this->getCalendarById($calendarId),
'public' => $value,
'publicuri' => $publicUri
]));

$query = $this->db->getQueryBuilder();
if ($value) {
$publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
$query->insert('dav_shares')
->values([
'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
'type' => $query->createNamedParameter('calendar'),
'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
'publicuri' => $query->createNamedParameter($publicUri)
]);
$query->execute();
return $publicUri;
}
$query->delete('dav_shares')
->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
->where($query->expr()->eq('publicuri', $query->createNamedParameter($publicUri)))
->andWhere($query->expr()->eq('resourceid', $query->createNamedParameter($calendarId)))
->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
$query->execute();
return null;
}

/**
* @param \OCA\DAV\CalDAV\Calendar $calendar
* @return mixed
* @param Calendar $calendar
*/
public function removeAllPublicLinks(Calendar $calendar) {
$calendarId = $calendar->getResourceId();

$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
'\OCA\DAV\CalDAV\CalDavBackend::removeAllPublicLinks',
[
'calendarId' => $calendarId,
'calendarData' => $this->getCalendarById($calendarId),
]));

$query = $this->db->getQueryBuilder();
$query->delete('dav_shares')
->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendarId)))
->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
$query->execute();
}

/**
* @param Calendar $calendar
* @return array
*/
public function getPublishStatus($calendar) {
public function getPublicURIs($calendar): array {
$query = $this->db->getQueryBuilder();
$result = $query->select('publicuri')
->from('dav_shares')
->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
->execute();

$row = $result->fetch();
$result->closeCursor();
return $row ? reset($row) : false;
return $result->fetchAll(\PDO::FETCH_ASSOC);
}

/**
Expand Down
29 changes: 20 additions & 9 deletions apps/dav/lib/CalDAV/Calendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -350,21 +350,32 @@ public function calendarQuery(array $filters) {
return $uris;
}

public function addPublicLink() {
$publicUri = $this->caldavBackend->addPublicLink($this);
$this->calendarInfo['publicuris'][] = $publicUri;
return $publicUri;
}

public function removePublicLink(string $publicUri) {
$this->caldavBackend->removePublicLink($this, $publicUri);
}

public function removeAllPublicLinks() {
$this->caldavBackend->removeAllPublicLinks($this);
}

/**
* @param boolean $value
* @return string|null
* @return bool $value
*/
public function setPublishStatus($value) {
$publicUri = $this->caldavBackend->setPublishStatus($value, $this);
$this->calendarInfo['publicuri'] = $publicUri;
return $publicUri;
public function getPublishStatus(): bool {
return count($this->getPublicURIs()) > 0;
}

/**
* @return mixed $value
* @return mixed
*/
public function getPublishStatus() {
return $this->caldavBackend->getPublishStatus($this);
public function getPublicURIs() : array {
return array_map(function ($publicURI) { return $publicURI['publicuri']; }, $this->caldavBackend->getPublicURIs($this));
}

public function canWrite() {
Expand Down
118 changes: 78 additions & 40 deletions apps/dav/lib/CalDAV/Publishing/PublishPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

class PublishPlugin extends ServerPlugin {
const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
const NS_NEXTCLOUD = 'http://nextcloud.com/ns/';

/**
* Reference to SabreDAV server object.
Expand Down Expand Up @@ -121,10 +122,22 @@ public function propFind(PropFind $propFind, INode $node) {
$propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) {
if ($node->getPublishStatus()) {
// We return the publish-url only if the calendar is published.
$token = $node->getPublishStatus();
$token = reset($node->getPublicURIs());
$publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token;

return new Publisher($publishUrl, true);
return new Publisher([$publishUrl => true]);
}
});

$propFind->handle('{'.self::NS_NEXTCLOUD.'}publish-urls', function () use ($node) {
if ($node->getPublishStatus()) {
// We return the publish-url only if the calendar is published.
$tokens = $node->getPublicURIs();
$publishUrls = array_map(function ($token) {
return [$this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token => true];
}, $tokens);

return new Publisher($publishUrls);
}
});

Expand Down Expand Up @@ -172,65 +185,90 @@ public function httpPost(RequestInterface $request, ResponseInterface $response)
// re-populated the request body with the existing data.
$request->setBody($requestBody);

$this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
$message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);

switch ($documentType) {

case '{'.self::NS_CALENDARSERVER.'}publish-calendar' :

// We can only deal with IShareableCalendar objects
if (!$node instanceof Calendar) {
return;
}
$this->server->transactionType = 'post-publish-calendar';
// We can only deal with IShareableCalendar objects
if (!$node instanceof Calendar) {
return;
}
$this->server->transactionType = 'post-publish-calendar';

// Getting ACL info
$acl = $this->server->getPlugin('acl');
// Getting ACL info
$acl = $this->server->getPlugin('acl');

// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
}
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
}

$node->setPublishStatus(true);
$node->addPublicLink();

// iCloud sends back the 202, so we will too.
$response->setStatus(202);
// iCloud sends back the 202, so we will too.
$response->setStatus(202);

// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');

// Breaking the event chain
return false;
// Breaking the event chain
return false;

case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar' :

// We can only deal with IShareableCalendar objects
if (!$node instanceof Calendar) {
return;
}
$this->server->transactionType = 'post-unpublish-calendar';
// We can only deal with IShareableCalendar objects
if (!$node instanceof Calendar) {
return;
}
$this->server->transactionType = 'post-unpublish-all-calendars';

// Getting ACL info
$acl = $this->server->getPlugin('acl');

// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
}

$node->removeAllPublicLinks();

$response->setStatus(200);

// Getting ACL info
$acl = $this->server->getPlugin('acl');
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');

// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
}
// Breaking the event chain
return false;

$node->setPublishStatus(false);
case '{'.self::NS_NEXTCLOUD.'}unpublish-calendar' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof Calendar) {
return;
}
$this->server->transactionType = 'post-unpublish-calendar';

// Getting ACL info
$acl = $this->server->getPlugin('acl');

// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
}

$response->setStatus(200);
$node->removePublicLink($message);

// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');
$response->setStatus(200);

// Breaking the event chain
return false;
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');

// Breaking the event chain
return false;
}
}
}
32 changes: 13 additions & 19 deletions apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,26 @@
class Publisher implements XmlSerializable {

/**
* @var string $publishUrl
* @var string $publishUrls
*/
protected $publishUrl;
protected $publishUrls;

/**
* @var boolean $isPublished
* @param array $publishUrls
*/
protected $isPublished;

/**
* @param string $publishUrl
* @param boolean $isPublished
*/
function __construct($publishUrl, $isPublished) {
$this->publishUrl = $publishUrl;
$this->isPublished = $isPublished;
function __construct(array $publishUrls) {
$this->publishUrls = $publishUrls;
}

/**
* @return string
* @return array
*/
function getValue() {
return $this->publishUrl;
function getValue(): array {
return array_keys($this->publishUrls);
}

/**
* The xmlSerialize metod is called during xml writing.
* The xmlSerialize method is called during xml writing.
*
* Use the $writer argument to write its own xml serialization.
*
Expand All @@ -75,12 +68,13 @@ function getValue() {
* @return void
*/
function xmlSerialize(Writer $writer) {
if (!$this->isPublished) {
foreach ($this->publishUrls as $publishUrl => $isPublished)
if (!$isPublished) {
// for pre-publish-url
$writer->write($this->publishUrl);
$writer->write($publishUrl);
} else {
// for publish-url
$writer->writeElement('{DAV:}href', $this->publishUrl);
$writer->writeElement('{DAV:}href', $publishUrl);
}
}
}
Loading