Page MenuHomePhorge

D4659.1775453803.diff
No OneTemporary

Authored By
Unknown
Size
8 KB
Referenced Files
None
Subscribers
None

D4659.1775453803.diff

diff --git a/src/app/Backends/DAV.php b/src/app/Backends/DAV.php
--- a/src/app/Backends/DAV.php
+++ b/src/app/Backends/DAV.php
@@ -249,6 +249,34 @@
return $response !== false;
}
+ /**
+ * Create a DAV folder (collection)
+ *
+ * @param DAV\Folder $folder Folder object
+ *
+ * @return bool True on success, False on error
+ */
+ public function folderCreate(DAV\Folder $folder)
+ {
+ $response = $this->request($folder->href, 'MKCOL', $folder->toXML('mkcol'));
+
+ return $response !== false;
+ }
+
+ /**
+ * Delete a DAV folder (collection)
+ *
+ * @param string $location Folder location
+ *
+ * @return bool True on success, False on error
+ */
+ public function folderDelete($location)
+ {
+ $response = $this->request($location, 'DELETE');
+
+ return $response !== false;
+ }
+
/**
* Get all properties of a folder.
*
@@ -258,15 +286,10 @@
*/
public function folderInfo(string $location)
{
- $body = '<?xml version="1.0" encoding="utf-8"?>'
- . '<d:propfind xmlns:d="DAV:">'
- . '<d:allprop/>'
- . '</d:propfind>';
+ $body = DAV\Folder::propfindXML();
// Note: Cyrus CardDAV service requires Depth:1 (CalDAV works without it)
- $headers = ['Depth' => 1, 'Prefer' => 'return-minimal'];
-
- $response = $this->request($location, 'PROPFIND', $body, $headers);
+ $response = $this->request($location, 'PROPFIND', $body, ['Depth' => 0, 'Prefer' => 'return-minimal']);
if (!empty($response) && ($element = $response->getElementsByTagName('response')->item(0))) {
return DAV\Folder::fromDomElement($element);
@@ -275,6 +298,61 @@
return false;
}
+ /**
+ * Update a DAV folder (collection)
+ *
+ * @param DAV\Folder $folder Folder object
+ *
+ * @return bool True on success, False on error
+ */
+ public function folderUpdate(DAV\Folder $folder)
+ {
+ // Note: Changing resourcetype property is forbidden (at least by Cyrus)
+
+ $response = $this->request($folder->href, 'PROPPATCH', $folder->toXML('propertyupdate'));
+
+ return $response !== false;
+ }
+
+ /**
+ * Initialize DAV folders (collections)
+ *
+ * @param \App\User $user User object
+ * @param array<array> $folders Folders list (path, displayname, type, components)
+ *
+ * @return bool True on success, False on error
+ */
+ public static function initDefaultFolders(\App\User $user, array $folders)
+ {
+ // FIXME: It looks like we'll need a way to authenticate the user, or cyrus admin
+ $dav = new self($email, $password);
+
+ foreach ($folders as $props) {
+ $folder = new DAV\Folder();
+ $folder->href = "addressbooks/user/{$user->email}/{$props['path']}";
+ $folder->name = $props['displayname'] ?? '';
+ $folder->types = ['collection', $props['type']];
+ $folder->components = $props['components'] ?? [];
+
+ // folder already exists? check the properties and update if needed
+ if ($existing = $dav->folderInfo($folder->href)) {
+ if ($existing->name != $folder->name || $existing->components != $folder->components) {
+ if (!$dav->folderUpdate($folder)) {
+ \Log::error("Failed to update DAV folder {$folder->href}");
+ return false;
+ }
+ }
+ }
+
+ if (!$dav->folderCreate($folder)) {
+ \Log::error("Failed to create DAV folder {$folder->href}");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
/**
* Search DAV objects in a folder.
*
diff --git a/src/app/Backends/DAV/Folder.php b/src/app/Backends/DAV/Folder.php
--- a/src/app/Backends/DAV/Folder.php
+++ b/src/app/Backends/DAV/Folder.php
@@ -74,4 +74,82 @@
return $folder;
}
+
+ /**
+ * Parse folder properties input into XML string to use in a request
+ *
+ * @return string
+ */
+ public function toXML($tag)
+ {
+ $ns = 'xmlns:d="DAV:"';
+ $props = '';
+ $type = null;
+
+ if (in_array('addressbook', $this->types)) {
+ $ns .= ' xmlns:c="urn:ietf:params:xml:ns:carddav"';
+ $type = 'addressbook';
+ } elseif (in_array('calendar', $this->types)) {
+ $ns .= ' xmlns:c="urn:ietf:params:xml:ns:caldav"';
+ $type = 'calendar';
+ }
+
+ $props .= '<d:resourcetype><d:collection/>' . ($type ? "<c:{$type}/>" : '') . '</d:resourcetype>';
+
+ if (!empty($this->components)) {
+ $props .= '<c:supported-calendar-component-set>';
+ foreach ($this->components as $component) {
+ $props .= '<c:comp name="' . $component . '"/>';
+ }
+ $props .= '</c:supported-calendar-component-set>';
+ }
+
+ if ($this->name !== null) {
+ $props .= '<d:displayname>' . htmlspecialchars($this->name, ENT_XML1, 'UTF-8') . '</d:displayname>';
+ }
+
+ if ($this->color !== null) {
+ $color = $this->color;
+ if (strlen($color) && $color[0] != '#') {
+ $color = '#' . $color;
+ }
+
+ $ns .= ' xmlns:a="http://apple.com/ns/ical/"';
+ $props .= '<a:calendar-color>' . htmlspecialchars($color, ENT_XML1, 'UTF-8') . '</a:calendar-color>';
+ }
+
+ return '<?xml version="1.0" encoding="utf-8"?>'
+ . "<d:{$tag} {$ns}><d:set><d:prop>{$props}</d:prop></d:set></d:{$tag}>";
+ }
+
+ /**
+ * Get XML string for PROPFIND query on a folder
+ *
+ * @return string
+ */
+ public static function propfindXML()
+ {
+ $ns = implode(' ', [
+ 'xmlns:d="DAV:"',
+ // 'xmlns:cs="http://calendarserver.org/ns/"',
+ 'xmlns:c="urn:ietf:params:xml:ns:caldav"',
+ // 'xmlns:a="http://apple.com/ns/ical/"',
+ // 'xmlns:k="Kolab:"'
+ ]);
+
+ // Note: <allprop> does not include some of the properties we're interested in
+ return '<?xml version="1.0" encoding="utf-8"?>'
+ . '<d:propfind ' . $ns . '>'
+ . '<d:prop>'
+ // . '<a:calendar-color/>'
+ . '<c:supported-calendar-component-set/>'
+ // . '<cs:getctag/>'
+ // . '<d:acl/>'
+ // . '<d:current-user-privilege-set/>'
+ . '<d:resourcetype/>'
+ . '<d:displayname/>'
+ // . '<k:alarms/>'
+ . '</d:prop>'
+ . '</d:propfind>';
+ }
}
diff --git a/src/app/Jobs/User/CreateJob.php b/src/app/Jobs/User/CreateJob.php
--- a/src/app/Jobs/User/CreateJob.php
+++ b/src/app/Jobs/User/CreateJob.php
@@ -104,6 +104,13 @@
$user->status |= \App\User::STATUS_IMAP_READY;
}
+ $folders = \config('services.dav.default_folders');
+ if (count($folders)) {
+ if (!\App\Backends\DAV::initDefaultFolders($user, $folders)) {
+ throw new \Exception("Failed to initialize DAV folders for user {$this->userId}.");
+ }
+ }
+
// Make user active in non-mandate mode only
if (
!($wallet = $user->wallet())
diff --git a/src/config/services.php b/src/config/services.php
--- a/src/config/services.php
+++ b/src/config/services.php
@@ -1,5 +1,32 @@
<?php
+$dav_folders = [];
+if (env('DAV_WITH_DEFAULT_FOLDERS', false)) {
+ $dav_folders = [
+ [
+ // FIXME: Should we use something else than 'Default'? This is
+ // what Cyrus creates, and I didn't find a setting to change it,
+ // we can only disable creation of the folder.
+ 'path' => 'Default',
+ 'displayname' => 'Calendar',
+ 'components' => ['VEVENT'],
+ 'type' => 'calendar',
+ ],
+ [
+ 'path' => 'Tasks',
+ 'displayname' => 'Tasks',
+ 'components' => ['VTODO'],
+ 'type' => 'calendar',
+ ],
+ [
+ // FIXME: Same here, should we use 'Contacts'?
+ 'path' => 'Default',
+ 'displayname' => 'Contacts',
+ 'type' => 'addressbook',
+ ],
+ ];
+}
+
return [
/*
@@ -58,6 +85,7 @@
'dav' => [
'uri' => env('DAV_URI', 'https://proxy/'),
+ 'default_folders' => $dav_folders,
],
'activesync' => [

File Metadata

Mime Type
text/plain
Expires
Mon, Apr 6, 5:36 AM (13 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18824412
Default Alt Text
D4659.1775453803.diff (8 KB)

Event Timeline