Passed
Push — master ( b93e1e...342765 )
by Roeland
34:24 queued 23:05
created
apps/dav/lib/CalDAV/Activity/Filter/Todo.php 1 patch
Indentation   +53 added lines, -53 removed lines patch added patch discarded remove patch
@@ -28,65 +28,65 @@
 block discarded – undo
28 28
 
29 29
 class Todo implements IFilter {
30 30
 
31
-	/** @var IL10N */
32
-	protected $l;
31
+    /** @var IL10N */
32
+    protected $l;
33 33
 
34
-	/** @var IURLGenerator */
35
-	protected $url;
34
+    /** @var IURLGenerator */
35
+    protected $url;
36 36
 
37
-	public function __construct(IL10N $l, IURLGenerator $url) {
38
-		$this->l = $l;
39
-		$this->url = $url;
40
-	}
37
+    public function __construct(IL10N $l, IURLGenerator $url) {
38
+        $this->l = $l;
39
+        $this->url = $url;
40
+    }
41 41
 
42
-	/**
43
-	 * @return string Lowercase a-z and underscore only identifier
44
-	 * @since 11.0.0
45
-	 */
46
-	public function getIdentifier() {
47
-		return 'calendar_todo';
48
-	}
42
+    /**
43
+     * @return string Lowercase a-z and underscore only identifier
44
+     * @since 11.0.0
45
+     */
46
+    public function getIdentifier() {
47
+        return 'calendar_todo';
48
+    }
49 49
 
50
-	/**
51
-	 * @return string A translated string
52
-	 * @since 11.0.0
53
-	 */
54
-	public function getName() {
55
-		return $this->l->t('Todos');
56
-	}
50
+    /**
51
+     * @return string A translated string
52
+     * @since 11.0.0
53
+     */
54
+    public function getName() {
55
+        return $this->l->t('Todos');
56
+    }
57 57
 
58
-	/**
59
-	 * @return int whether the filter should be rather on the top or bottom of
60
-	 * the admin section. The filters are arranged in ascending order of the
61
-	 * priority values. It is required to return a value between 0 and 100.
62
-	 * @since 11.0.0
63
-	 */
64
-	public function getPriority() {
65
-		return 40;
66
-	}
58
+    /**
59
+     * @return int whether the filter should be rather on the top or bottom of
60
+     * the admin section. The filters are arranged in ascending order of the
61
+     * priority values. It is required to return a value between 0 and 100.
62
+     * @since 11.0.0
63
+     */
64
+    public function getPriority() {
65
+        return 40;
66
+    }
67 67
 
68
-	/**
69
-	 * @return string Full URL to an icon, empty string when none is given
70
-	 * @since 11.0.0
71
-	 */
72
-	public function getIcon() {
73
-		return $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/checkmark.svg'));
74
-	}
68
+    /**
69
+     * @return string Full URL to an icon, empty string when none is given
70
+     * @since 11.0.0
71
+     */
72
+    public function getIcon() {
73
+        return $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/checkmark.svg'));
74
+    }
75 75
 
76
-	/**
77
-	 * @param string[] $types
78
-	 * @return string[] An array of allowed apps from which activities should be displayed
79
-	 * @since 11.0.0
80
-	 */
81
-	public function filterTypes(array $types) {
82
-		return array_intersect(['calendar_todo'], $types);
83
-	}
76
+    /**
77
+     * @param string[] $types
78
+     * @return string[] An array of allowed apps from which activities should be displayed
79
+     * @since 11.0.0
80
+     */
81
+    public function filterTypes(array $types) {
82
+        return array_intersect(['calendar_todo'], $types);
83
+    }
84 84
 
85
-	/**
86
-	 * @return string[] An array of allowed apps from which activities should be displayed
87
-	 * @since 11.0.0
88
-	 */
89
-	public function allowedApps() {
90
-		return [];
91
-	}
85
+    /**
86
+     * @return string[] An array of allowed apps from which activities should be displayed
87
+     * @since 11.0.0
88
+     */
89
+    public function allowedApps() {
90
+        return [];
91
+    }
92 92
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Activity/Setting/Calendar.php 1 patch
Indentation   +59 added lines, -59 removed lines patch added patch discarded remove patch
@@ -27,72 +27,72 @@
 block discarded – undo
27 27
 
28 28
 class Calendar implements ISetting {
29 29
 
30
-	/** @var IL10N */
31
-	protected $l;
30
+    /** @var IL10N */
31
+    protected $l;
32 32
 
33
-	/**
34
-	 * @param IL10N $l
35
-	 */
36
-	public function __construct(IL10N $l) {
37
-		$this->l = $l;
38
-	}
33
+    /**
34
+     * @param IL10N $l
35
+     */
36
+    public function __construct(IL10N $l) {
37
+        $this->l = $l;
38
+    }
39 39
 
40
-	/**
41
-	 * @return string Lowercase a-z and underscore only identifier
42
-	 * @since 11.0.0
43
-	 */
44
-	public function getIdentifier() {
45
-		return 'calendar';
46
-	}
40
+    /**
41
+     * @return string Lowercase a-z and underscore only identifier
42
+     * @since 11.0.0
43
+     */
44
+    public function getIdentifier() {
45
+        return 'calendar';
46
+    }
47 47
 
48
-	/**
49
-	 * @return string A translated string
50
-	 * @since 11.0.0
51
-	 */
52
-	public function getName() {
53
-		return $this->l->t('A <strong>calendar</strong> was modified');
54
-	}
48
+    /**
49
+     * @return string A translated string
50
+     * @since 11.0.0
51
+     */
52
+    public function getName() {
53
+        return $this->l->t('A <strong>calendar</strong> was modified');
54
+    }
55 55
 
56
-	/**
57
-	 * @return int whether the filter should be rather on the top or bottom of
58
-	 * the admin section. The filters are arranged in ascending order of the
59
-	 * priority values. It is required to return a value between 0 and 100.
60
-	 * @since 11.0.0
61
-	 */
62
-	public function getPriority() {
63
-		return 50;
64
-	}
56
+    /**
57
+     * @return int whether the filter should be rather on the top or bottom of
58
+     * the admin section. The filters are arranged in ascending order of the
59
+     * priority values. It is required to return a value between 0 and 100.
60
+     * @since 11.0.0
61
+     */
62
+    public function getPriority() {
63
+        return 50;
64
+    }
65 65
 
66
-	/**
67
-	 * @return bool True when the option can be changed for the stream
68
-	 * @since 11.0.0
69
-	 */
70
-	public function canChangeStream() {
71
-		return true;
72
-	}
66
+    /**
67
+     * @return bool True when the option can be changed for the stream
68
+     * @since 11.0.0
69
+     */
70
+    public function canChangeStream() {
71
+        return true;
72
+    }
73 73
 
74
-	/**
75
-	 * @return bool True when the option can be changed for the stream
76
-	 * @since 11.0.0
77
-	 */
78
-	public function isDefaultEnabledStream() {
79
-		return true;
80
-	}
74
+    /**
75
+     * @return bool True when the option can be changed for the stream
76
+     * @since 11.0.0
77
+     */
78
+    public function isDefaultEnabledStream() {
79
+        return true;
80
+    }
81 81
 
82
-	/**
83
-	 * @return bool True when the option can be changed for the mail
84
-	 * @since 11.0.0
85
-	 */
86
-	public function canChangeMail() {
87
-		return true;
88
-	}
82
+    /**
83
+     * @return bool True when the option can be changed for the mail
84
+     * @since 11.0.0
85
+     */
86
+    public function canChangeMail() {
87
+        return true;
88
+    }
89 89
 
90
-	/**
91
-	 * @return bool True when the option can be changed for the stream
92
-	 * @since 11.0.0
93
-	 */
94
-	public function isDefaultEnabledMail() {
95
-		return false;
96
-	}
90
+    /**
91
+     * @return bool True when the option can be changed for the stream
92
+     * @since 11.0.0
93
+     */
94
+    public function isDefaultEnabledMail() {
95
+        return false;
96
+    }
97 97
 }
98 98
 
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Activity/Setting/Event.php 1 patch
Indentation   +59 added lines, -59 removed lines patch added patch discarded remove patch
@@ -27,72 +27,72 @@
 block discarded – undo
27 27
 
28 28
 class Event implements ISetting {
29 29
 
30
-	/** @var IL10N */
31
-	protected $l;
30
+    /** @var IL10N */
31
+    protected $l;
32 32
 
33
-	/**
34
-	 * @param IL10N $l
35
-	 */
36
-	public function __construct(IL10N $l) {
37
-		$this->l = $l;
38
-	}
33
+    /**
34
+     * @param IL10N $l
35
+     */
36
+    public function __construct(IL10N $l) {
37
+        $this->l = $l;
38
+    }
39 39
 
40
-	/**
41
-	 * @return string Lowercase a-z and underscore only identifier
42
-	 * @since 11.0.0
43
-	 */
44
-	public function getIdentifier() {
45
-		return 'calendar_event';
46
-	}
40
+    /**
41
+     * @return string Lowercase a-z and underscore only identifier
42
+     * @since 11.0.0
43
+     */
44
+    public function getIdentifier() {
45
+        return 'calendar_event';
46
+    }
47 47
 
48
-	/**
49
-	 * @return string A translated string
50
-	 * @since 11.0.0
51
-	 */
52
-	public function getName() {
53
-		return $this->l->t('A calendar <strong>event</strong> was modified');
54
-	}
48
+    /**
49
+     * @return string A translated string
50
+     * @since 11.0.0
51
+     */
52
+    public function getName() {
53
+        return $this->l->t('A calendar <strong>event</strong> was modified');
54
+    }
55 55
 
56
-	/**
57
-	 * @return int whether the filter should be rather on the top or bottom of
58
-	 * the admin section. The filters are arranged in ascending order of the
59
-	 * priority values. It is required to return a value between 0 and 100.
60
-	 * @since 11.0.0
61
-	 */
62
-	public function getPriority() {
63
-		return 50;
64
-	}
56
+    /**
57
+     * @return int whether the filter should be rather on the top or bottom of
58
+     * the admin section. The filters are arranged in ascending order of the
59
+     * priority values. It is required to return a value between 0 and 100.
60
+     * @since 11.0.0
61
+     */
62
+    public function getPriority() {
63
+        return 50;
64
+    }
65 65
 
66
-	/**
67
-	 * @return bool True when the option can be changed for the stream
68
-	 * @since 11.0.0
69
-	 */
70
-	public function canChangeStream() {
71
-		return true;
72
-	}
66
+    /**
67
+     * @return bool True when the option can be changed for the stream
68
+     * @since 11.0.0
69
+     */
70
+    public function canChangeStream() {
71
+        return true;
72
+    }
73 73
 
74
-	/**
75
-	 * @return bool True when the option can be changed for the stream
76
-	 * @since 11.0.0
77
-	 */
78
-	public function isDefaultEnabledStream() {
79
-		return true;
80
-	}
74
+    /**
75
+     * @return bool True when the option can be changed for the stream
76
+     * @since 11.0.0
77
+     */
78
+    public function isDefaultEnabledStream() {
79
+        return true;
80
+    }
81 81
 
82
-	/**
83
-	 * @return bool True when the option can be changed for the mail
84
-	 * @since 11.0.0
85
-	 */
86
-	public function canChangeMail() {
87
-		return true;
88
-	}
82
+    /**
83
+     * @return bool True when the option can be changed for the mail
84
+     * @since 11.0.0
85
+     */
86
+    public function canChangeMail() {
87
+        return true;
88
+    }
89 89
 
90
-	/**
91
-	 * @return bool True when the option can be changed for the stream
92
-	 * @since 11.0.0
93
-	 */
94
-	public function isDefaultEnabledMail() {
95
-		return false;
96
-	}
90
+    /**
91
+     * @return bool True when the option can be changed for the stream
92
+     * @since 11.0.0
93
+     */
94
+    public function isDefaultEnabledMail() {
95
+        return false;
96
+    }
97 97
 }
98 98
 
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Activity/Setting/Todo.php 1 patch
Indentation   +59 added lines, -59 removed lines patch added patch discarded remove patch
@@ -27,72 +27,72 @@
 block discarded – undo
27 27
 
28 28
 class Todo implements ISetting {
29 29
 
30
-	/** @var IL10N */
31
-	protected $l;
30
+    /** @var IL10N */
31
+    protected $l;
32 32
 
33
-	/**
34
-	 * @param IL10N $l
35
-	 */
36
-	public function __construct(IL10N $l) {
37
-		$this->l = $l;
38
-	}
33
+    /**
34
+     * @param IL10N $l
35
+     */
36
+    public function __construct(IL10N $l) {
37
+        $this->l = $l;
38
+    }
39 39
 
40
-	/**
41
-	 * @return string Lowercase a-z and underscore only identifier
42
-	 * @since 11.0.0
43
-	 */
44
-	public function getIdentifier() {
45
-		return 'calendar_todo';
46
-	}
40
+    /**
41
+     * @return string Lowercase a-z and underscore only identifier
42
+     * @since 11.0.0
43
+     */
44
+    public function getIdentifier() {
45
+        return 'calendar_todo';
46
+    }
47 47
 
48
-	/**
49
-	 * @return string A translated string
50
-	 * @since 11.0.0
51
-	 */
52
-	public function getName() {
53
-		return $this->l->t('A calendar <strong>todo</strong> was modified');
54
-	}
48
+    /**
49
+     * @return string A translated string
50
+     * @since 11.0.0
51
+     */
52
+    public function getName() {
53
+        return $this->l->t('A calendar <strong>todo</strong> was modified');
54
+    }
55 55
 
56
-	/**
57
-	 * @return int whether the filter should be rather on the top or bottom of
58
-	 * the admin section. The filters are arranged in ascending order of the
59
-	 * priority values. It is required to return a value between 0 and 100.
60
-	 * @since 11.0.0
61
-	 */
62
-	public function getPriority() {
63
-		return 50;
64
-	}
56
+    /**
57
+     * @return int whether the filter should be rather on the top or bottom of
58
+     * the admin section. The filters are arranged in ascending order of the
59
+     * priority values. It is required to return a value between 0 and 100.
60
+     * @since 11.0.0
61
+     */
62
+    public function getPriority() {
63
+        return 50;
64
+    }
65 65
 
66
-	/**
67
-	 * @return bool True when the option can be changed for the stream
68
-	 * @since 11.0.0
69
-	 */
70
-	public function canChangeStream() {
71
-		return true;
72
-	}
66
+    /**
67
+     * @return bool True when the option can be changed for the stream
68
+     * @since 11.0.0
69
+     */
70
+    public function canChangeStream() {
71
+        return true;
72
+    }
73 73
 
74
-	/**
75
-	 * @return bool True when the option can be changed for the stream
76
-	 * @since 11.0.0
77
-	 */
78
-	public function isDefaultEnabledStream() {
79
-		return true;
80
-	}
74
+    /**
75
+     * @return bool True when the option can be changed for the stream
76
+     * @since 11.0.0
77
+     */
78
+    public function isDefaultEnabledStream() {
79
+        return true;
80
+    }
81 81
 
82
-	/**
83
-	 * @return bool True when the option can be changed for the mail
84
-	 * @since 11.0.0
85
-	 */
86
-	public function canChangeMail() {
87
-		return true;
88
-	}
82
+    /**
83
+     * @return bool True when the option can be changed for the mail
84
+     * @since 11.0.0
85
+     */
86
+    public function canChangeMail() {
87
+        return true;
88
+    }
89 89
 
90
-	/**
91
-	 * @return bool True when the option can be changed for the stream
92
-	 * @since 11.0.0
93
-	 */
94
-	public function isDefaultEnabledMail() {
95
-		return false;
96
-	}
90
+    /**
91
+     * @return bool True when the option can be changed for the stream
92
+     * @since 11.0.0
93
+     */
94
+    public function isDefaultEnabledMail() {
95
+        return false;
96
+    }
97 97
 }
98 98
 
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Publishing/PublishPlugin.php 2 patches
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -89,7 +89,7 @@  discard block
 block discarded – undo
89 89
 	 *
90 90
 	 * @return string
91 91
 	 */
92
-	public function getPluginName()	{
92
+	public function getPluginName() {
93 93
 		return 'oc-calendar-publishing';
94 94
 	}
95 95
 
@@ -107,12 +107,12 @@  discard block
 block discarded – undo
107 107
 		$this->server = $server;
108 108
 
109 109
 		$this->server->on('method:POST', [$this, 'httpPost']);
110
-		$this->server->on('propFind',    [$this, 'propFind']);
110
+		$this->server->on('propFind', [$this, 'propFind']);
111 111
 	}
112 112
 
113 113
 	public function propFind(PropFind $propFind, INode $node) {
114 114
 		if ($node instanceof Calendar) {
115
-			$propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) {
115
+			$propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function() use ($node) {
116 116
 				if ($node->getPublishStatus()) {
117 117
 					// We return the publish-url only if the calendar is published.
118 118
 					$token = $node->getPublishStatus();
Please login to merge, or discard this patch.
Indentation   +190 added lines, -190 removed lines patch added patch discarded remove patch
@@ -38,197 +38,197 @@
 block discarded – undo
38 38
 use Sabre\HTTP\ResponseInterface;
39 39
 
40 40
 class PublishPlugin extends ServerPlugin {
41
-	const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
42
-
43
-	/**
44
-	 * Reference to SabreDAV server object.
45
-	 *
46
-	 * @var \Sabre\DAV\Server
47
-	 */
48
-	protected $server;
49
-
50
-	/**
51
-	 * Config instance to get instance secret.
52
-	 *
53
-	 * @var IConfig
54
-	 */
55
-	protected $config;
56
-
57
-	/**
58
-	 * URL Generator for absolute URLs.
59
-	 *
60
-	 * @var IURLGenerator
61
-	 */
62
-	protected $urlGenerator;
63
-
64
-	/**
65
-	 * PublishPlugin constructor.
66
-	 *
67
-	 * @param IConfig $config
68
-	 * @param IURLGenerator $urlGenerator
69
-	 */
70
-	public function __construct(IConfig $config, IURLGenerator $urlGenerator) {
71
-		$this->config = $config;
72
-		$this->urlGenerator = $urlGenerator;
73
-	}
74
-
75
-	/**
76
-	 * This method should return a list of server-features.
77
-	 *
78
-	 * This is for example 'versioning' and is added to the DAV: header
79
-	 * in an OPTIONS response.
80
-	 *
81
-	 * @return string[]
82
-	 */
83
-	public function getFeatures() {
84
-		// May have to be changed to be detected
85
-		return ['oc-calendar-publishing', 'calendarserver-sharing'];
86
-	}
87
-
88
-	/**
89
-	 * Returns a plugin name.
90
-	 *
91
-	 * Using this name other plugins will be able to access other plugins
92
-	 * using Sabre\DAV\Server::getPlugin
93
-	 *
94
-	 * @return string
95
-	 */
96
-	public function getPluginName()	{
97
-		return 'oc-calendar-publishing';
98
-	}
99
-
100
-	/**
101
-	 * This initializes the plugin.
102
-	 *
103
-	 * This function is called by Sabre\DAV\Server, after
104
-	 * addPlugin is called.
105
-	 *
106
-	 * This method should set up the required event subscriptions.
107
-	 *
108
-	 * @param Server $server
109
-	 */
110
-	public function initialize(Server $server) {
111
-		$this->server = $server;
112
-
113
-		$this->server->on('method:POST', [$this, 'httpPost']);
114
-		$this->server->on('propFind',    [$this, 'propFind']);
115
-	}
116
-
117
-	public function propFind(PropFind $propFind, INode $node) {
118
-		if ($node instanceof Calendar) {
119
-			$propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) {
120
-				if ($node->getPublishStatus()) {
121
-					// We return the publish-url only if the calendar is published.
122
-					$token = $node->getPublishStatus();
123
-					$publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token;
124
-
125
-					return new Publisher($publishUrl, true);
126
-				}
127
-			});
128
-
129
-			$propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function() use ($node) {
130
-				$canShare = (!$node->isSubscription() && $node->canWrite());
131
-				$canPublish = (!$node->isSubscription() && $node->canWrite());
132
-
133
-				return new AllowedSharingModes($canShare, $canPublish);
134
-			});
135
-		}
136
-	}
137
-
138
-	/**
139
-	 * We intercept this to handle POST requests on calendars.
140
-	 *
141
-	 * @param RequestInterface $request
142
-	 * @param ResponseInterface $response
143
-	 *
144
-	 * @return void|bool
145
-	 */
146
-	public function httpPost(RequestInterface $request, ResponseInterface $response) {
147
-		$path = $request->getPath();
148
-
149
-		// Only handling xml
150
-		$contentType = $request->getHeader('Content-Type');
151
-		if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) {
152
-			return;
153
-		}
154
-
155
-		// Making sure the node exists
156
-		try {
157
-			$node = $this->server->tree->getNodeForPath($path);
158
-		} catch (NotFound $e) {
159
-			return;
160
-		}
161
-
162
-		$requestBody = $request->getBodyAsString();
163
-
164
-		// If this request handler could not deal with this POST request, it
165
-		// will return 'null' and other plugins get a chance to handle the
166
-		// request.
167
-		//
168
-		// However, we already requested the full body. This is a problem,
169
-		// because a body can only be read once. This is why we preemptively
170
-		// re-populated the request body with the existing data.
171
-		$request->setBody($requestBody);
172
-
173
-		$this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
174
-
175
-		switch ($documentType) {
176
-
177
-			case '{'.self::NS_CALENDARSERVER.'}publish-calendar' :
178
-
179
-			// We can only deal with IShareableCalendar objects
180
-			if (!$node instanceof Calendar) {
181
-				return;
182
-			}
183
-			$this->server->transactionType = 'post-publish-calendar';
184
-
185
-			// Getting ACL info
186
-			$acl = $this->server->getPlugin('acl');
187
-
188
-			// If there's no ACL support, we allow everything
189
-			if ($acl) {
190
-				$acl->checkPrivileges($path, '{DAV:}write');
191
-			}
192
-
193
-			$node->setPublishStatus(true);
194
-
195
-			// iCloud sends back the 202, so we will too.
196
-			$response->setStatus(202);
197
-
198
-			// Adding this because sending a response body may cause issues,
199
-			// and I wanted some type of indicator the response was handled.
200
-			$response->setHeader('X-Sabre-Status', 'everything-went-well');
201
-
202
-			// Breaking the event chain
203
-			return false;
204
-
205
-			case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar' :
206
-
207
-			// We can only deal with IShareableCalendar objects
208
-			if (!$node instanceof Calendar) {
209
-				return;
210
-			}
211
-			$this->server->transactionType = 'post-unpublish-calendar';
212
-
213
-			// Getting ACL info
214
-			$acl = $this->server->getPlugin('acl');
215
-
216
-			// If there's no ACL support, we allow everything
217
-			if ($acl) {
218
-				$acl->checkPrivileges($path, '{DAV:}write');
219
-			}
220
-
221
-			$node->setPublishStatus(false);
222
-
223
-			$response->setStatus(200);
41
+    const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
42
+
43
+    /**
44
+     * Reference to SabreDAV server object.
45
+     *
46
+     * @var \Sabre\DAV\Server
47
+     */
48
+    protected $server;
49
+
50
+    /**
51
+     * Config instance to get instance secret.
52
+     *
53
+     * @var IConfig
54
+     */
55
+    protected $config;
56
+
57
+    /**
58
+     * URL Generator for absolute URLs.
59
+     *
60
+     * @var IURLGenerator
61
+     */
62
+    protected $urlGenerator;
63
+
64
+    /**
65
+     * PublishPlugin constructor.
66
+     *
67
+     * @param IConfig $config
68
+     * @param IURLGenerator $urlGenerator
69
+     */
70
+    public function __construct(IConfig $config, IURLGenerator $urlGenerator) {
71
+        $this->config = $config;
72
+        $this->urlGenerator = $urlGenerator;
73
+    }
74
+
75
+    /**
76
+     * This method should return a list of server-features.
77
+     *
78
+     * This is for example 'versioning' and is added to the DAV: header
79
+     * in an OPTIONS response.
80
+     *
81
+     * @return string[]
82
+     */
83
+    public function getFeatures() {
84
+        // May have to be changed to be detected
85
+        return ['oc-calendar-publishing', 'calendarserver-sharing'];
86
+    }
87
+
88
+    /**
89
+     * Returns a plugin name.
90
+     *
91
+     * Using this name other plugins will be able to access other plugins
92
+     * using Sabre\DAV\Server::getPlugin
93
+     *
94
+     * @return string
95
+     */
96
+    public function getPluginName()	{
97
+        return 'oc-calendar-publishing';
98
+    }
99
+
100
+    /**
101
+     * This initializes the plugin.
102
+     *
103
+     * This function is called by Sabre\DAV\Server, after
104
+     * addPlugin is called.
105
+     *
106
+     * This method should set up the required event subscriptions.
107
+     *
108
+     * @param Server $server
109
+     */
110
+    public function initialize(Server $server) {
111
+        $this->server = $server;
112
+
113
+        $this->server->on('method:POST', [$this, 'httpPost']);
114
+        $this->server->on('propFind',    [$this, 'propFind']);
115
+    }
116
+
117
+    public function propFind(PropFind $propFind, INode $node) {
118
+        if ($node instanceof Calendar) {
119
+            $propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) {
120
+                if ($node->getPublishStatus()) {
121
+                    // We return the publish-url only if the calendar is published.
122
+                    $token = $node->getPublishStatus();
123
+                    $publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token;
124
+
125
+                    return new Publisher($publishUrl, true);
126
+                }
127
+            });
128
+
129
+            $propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function() use ($node) {
130
+                $canShare = (!$node->isSubscription() && $node->canWrite());
131
+                $canPublish = (!$node->isSubscription() && $node->canWrite());
132
+
133
+                return new AllowedSharingModes($canShare, $canPublish);
134
+            });
135
+        }
136
+    }
137
+
138
+    /**
139
+     * We intercept this to handle POST requests on calendars.
140
+     *
141
+     * @param RequestInterface $request
142
+     * @param ResponseInterface $response
143
+     *
144
+     * @return void|bool
145
+     */
146
+    public function httpPost(RequestInterface $request, ResponseInterface $response) {
147
+        $path = $request->getPath();
148
+
149
+        // Only handling xml
150
+        $contentType = $request->getHeader('Content-Type');
151
+        if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) {
152
+            return;
153
+        }
154
+
155
+        // Making sure the node exists
156
+        try {
157
+            $node = $this->server->tree->getNodeForPath($path);
158
+        } catch (NotFound $e) {
159
+            return;
160
+        }
161
+
162
+        $requestBody = $request->getBodyAsString();
163
+
164
+        // If this request handler could not deal with this POST request, it
165
+        // will return 'null' and other plugins get a chance to handle the
166
+        // request.
167
+        //
168
+        // However, we already requested the full body. This is a problem,
169
+        // because a body can only be read once. This is why we preemptively
170
+        // re-populated the request body with the existing data.
171
+        $request->setBody($requestBody);
172
+
173
+        $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
174
+
175
+        switch ($documentType) {
176
+
177
+            case '{'.self::NS_CALENDARSERVER.'}publish-calendar' :
178
+
179
+            // We can only deal with IShareableCalendar objects
180
+            if (!$node instanceof Calendar) {
181
+                return;
182
+            }
183
+            $this->server->transactionType = 'post-publish-calendar';
184
+
185
+            // Getting ACL info
186
+            $acl = $this->server->getPlugin('acl');
187
+
188
+            // If there's no ACL support, we allow everything
189
+            if ($acl) {
190
+                $acl->checkPrivileges($path, '{DAV:}write');
191
+            }
192
+
193
+            $node->setPublishStatus(true);
194
+
195
+            // iCloud sends back the 202, so we will too.
196
+            $response->setStatus(202);
197
+
198
+            // Adding this because sending a response body may cause issues,
199
+            // and I wanted some type of indicator the response was handled.
200
+            $response->setHeader('X-Sabre-Status', 'everything-went-well');
201
+
202
+            // Breaking the event chain
203
+            return false;
204
+
205
+            case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar' :
206
+
207
+            // We can only deal with IShareableCalendar objects
208
+            if (!$node instanceof Calendar) {
209
+                return;
210
+            }
211
+            $this->server->transactionType = 'post-unpublish-calendar';
212
+
213
+            // Getting ACL info
214
+            $acl = $this->server->getPlugin('acl');
215
+
216
+            // If there's no ACL support, we allow everything
217
+            if ($acl) {
218
+                $acl->checkPrivileges($path, '{DAV:}write');
219
+            }
220
+
221
+            $node->setPublishStatus(false);
222
+
223
+            $response->setStatus(200);
224 224
 
225
-			// Adding this because sending a response body may cause issues,
226
-			// and I wanted some type of indicator the response was handled.
227
-			$response->setHeader('X-Sabre-Status', 'everything-went-well');
225
+            // Adding this because sending a response body may cause issues,
226
+            // and I wanted some type of indicator the response was handled.
227
+            $response->setHeader('X-Sabre-Status', 'everything-went-well');
228 228
 
229
-			// Breaking the event chain
230
-			return false;
229
+            // Breaking the event chain
230
+            return false;
231 231
 
232
-		}
233
-	}
232
+        }
233
+    }
234 234
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php 1 patch
Indentation   +50 added lines, -50 removed lines patch added patch discarded remove patch
@@ -26,58 +26,58 @@
 block discarded – undo
26 26
 
27 27
 class Publisher implements XmlSerializable {
28 28
 
29
-	/**
30
-	 * @var string $publishUrl
31
-	 */
32
-	protected $publishUrl;
29
+    /**
30
+     * @var string $publishUrl
31
+     */
32
+    protected $publishUrl;
33 33
 
34
-	/**
35
-	 * @var boolean $isPublished
36
-	 */
37
-	protected $isPublished;
34
+    /**
35
+     * @var boolean $isPublished
36
+     */
37
+    protected $isPublished;
38 38
 
39
-	/**
40
-	 * @param string $publishUrl
41
-	 * @param boolean $isPublished
42
-	 */
43
-	function __construct($publishUrl, $isPublished) {
44
-		$this->publishUrl = $publishUrl;
45
-		$this->isPublished = $isPublished;
46
-	}
39
+    /**
40
+     * @param string $publishUrl
41
+     * @param boolean $isPublished
42
+     */
43
+    function __construct($publishUrl, $isPublished) {
44
+        $this->publishUrl = $publishUrl;
45
+        $this->isPublished = $isPublished;
46
+    }
47 47
 
48
-	/**
49
-	 * @return string
50
-	 */
51
-	function getValue() {
52
-		return $this->publishUrl;
53
-	}
48
+    /**
49
+     * @return string
50
+     */
51
+    function getValue() {
52
+        return $this->publishUrl;
53
+    }
54 54
 
55
-	/**
56
-	 * The xmlSerialize metod is called during xml writing.
57
-	 *
58
-	 * Use the $writer argument to write its own xml serialization.
59
-	 *
60
-	 * An important note: do _not_ create a parent element. Any element
61
-	 * implementing XmlSerializble should only ever write what's considered
62
-	 * its 'inner xml'.
63
-	 *
64
-	 * The parent of the current element is responsible for writing a
65
-	 * containing element.
66
-	 *
67
-	 * This allows serializers to be re-used for different element names.
68
-	 *
69
-	 * If you are opening new elements, you must also close them again.
70
-	 *
71
-	 * @param Writer $writer
72
-	 * @return void
73
-	 */
74
-	function xmlSerialize(Writer $writer) {
75
-		if (!$this->isPublished) {
76
-			// for pre-publish-url
77
-			$writer->write($this->publishUrl);
78
-		} else {
79
-			// for publish-url
80
-			$writer->writeElement('{DAV:}href', $this->publishUrl);
81
-		}
82
-	}
55
+    /**
56
+     * The xmlSerialize metod is called during xml writing.
57
+     *
58
+     * Use the $writer argument to write its own xml serialization.
59
+     *
60
+     * An important note: do _not_ create a parent element. Any element
61
+     * implementing XmlSerializble should only ever write what's considered
62
+     * its 'inner xml'.
63
+     *
64
+     * The parent of the current element is responsible for writing a
65
+     * containing element.
66
+     *
67
+     * This allows serializers to be re-used for different element names.
68
+     *
69
+     * If you are opening new elements, you must also close them again.
70
+     *
71
+     * @param Writer $writer
72
+     * @return void
73
+     */
74
+    function xmlSerialize(Writer $writer) {
75
+        if (!$this->isPublished) {
76
+            // for pre-publish-url
77
+            $writer->write($this->publishUrl);
78
+        } else {
79
+            // for publish-url
80
+            $writer->writeElement('{DAV:}href', $this->publishUrl);
81
+        }
82
+    }
83 83
 }
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/AddressBookImpl.php 2 patches
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
 		$update = false;
112 112
 		if (!isset($properties['URI'])) { // create a new contact
113 113
 			$uid = $this->createUid();
114
-			$uri = $uid . '.vcf';
114
+			$uri = $uid.'.vcf';
115 115
 			$vCard = $this->createEmptyVCard($uid);
116 116
 		} else { // update existing contact
117 117
 			$uri = $properties['URI'];
@@ -142,7 +142,7 @@  discard block
 block discarded – undo
142 142
 		$permissions = $this->addressBook->getACL();
143 143
 		$result = 0;
144 144
 		foreach ($permissions as $permission) {
145
-			switch($permission['privilege']) {
145
+			switch ($permission['privilege']) {
146 146
 				case '{DAV:}read':
147 147
 					$result |= Constants::PERMISSION_READ;
148 148
 					break;
@@ -187,7 +187,7 @@  discard block
 block discarded – undo
187 187
 	protected function createUid() {
188 188
 		do {
189 189
 			$uid = $this->getUid();
190
-			$contact = $this->backend->getContact($this->getKey(), $uid . '.vcf');
190
+			$contact = $this->backend->getContact($this->getKey(), $uid.'.vcf');
191 191
 		} while (!empty($contact));
192 192
 
193 193
 		return $uid;
@@ -227,15 +227,15 @@  discard block
 block discarded – undo
227 227
 		foreach ($vCard->children() as $property) {
228 228
 			if ($property->name === 'PHOTO' && $property->getValueType() === 'BINARY') {
229 229
 				$url = $this->urlGenerator->getAbsoluteURL(
230
-					$this->urlGenerator->linkTo('', 'remote.php') . '/dav/');
230
+					$this->urlGenerator->linkTo('', 'remote.php').'/dav/');
231 231
 				$url .= implode('/', [
232 232
 					'addressbooks',
233 233
 					substr($this->addressBookInfo['principaluri'], 11), //cut off 'principals/'
234 234
 					$this->addressBookInfo['uri'],
235 235
 					$uri
236
-				]) . '?photo';
236
+				]).'?photo';
237 237
 
238
-				$result['PHOTO'] = 'VALUE=uri:' . $url;
238
+				$result['PHOTO'] = 'VALUE=uri:'.$url;
239 239
 
240 240
 			} else if ($property->name === 'X-SOCIALPROFILE') {
241 241
 				$type = $this->getTypeFromProperty($property);
Please login to merge, or discard this patch.
Indentation   +283 added lines, -283 removed lines patch added patch discarded remove patch
@@ -35,287 +35,287 @@
 block discarded – undo
35 35
 
36 36
 class AddressBookImpl implements IAddressBook {
37 37
 
38
-	/** @var CardDavBackend */
39
-	private $backend;
40
-
41
-	/** @var array */
42
-	private $addressBookInfo;
43
-
44
-	/** @var AddressBook */
45
-	private $addressBook;
46
-
47
-	/** @var IURLGenerator */
48
-	private $urlGenerator;
49
-
50
-	/**
51
-	 * AddressBookImpl constructor.
52
-	 *
53
-	 * @param AddressBook $addressBook
54
-	 * @param array $addressBookInfo
55
-	 * @param CardDavBackend $backend
56
-	 * @param IUrlGenerator $urlGenerator
57
-	 */
58
-	public function __construct(
59
-			AddressBook $addressBook,
60
-			array $addressBookInfo,
61
-			CardDavBackend $backend,
62
-			IURLGenerator $urlGenerator) {
63
-
64
-		$this->addressBook = $addressBook;
65
-		$this->addressBookInfo = $addressBookInfo;
66
-		$this->backend = $backend;
67
-		$this->urlGenerator = $urlGenerator;
68
-	}
69
-
70
-	/**
71
-	 * @return string defining the technical unique key
72
-	 * @since 5.0.0
73
-	 */
74
-	public function getKey() {
75
-		return $this->addressBookInfo['id'];
76
-	}
77
-
78
-	/**
79
-	 * @return string defining the unique uri
80
-	 * @since 16.0.0
81
-	 * @return string
82
-	 */
83
-	public function getUri(): string {
84
-		return $this->addressBookInfo['uri'];
85
-	}
86
-
87
-	/**
88
-	 * In comparison to getKey() this function returns a human readable (maybe translated) name
89
-	 *
90
-	 * @return mixed
91
-	 * @since 5.0.0
92
-	 */
93
-	public function getDisplayName() {
94
-		return $this->addressBookInfo['{DAV:}displayname'];
95
-	}
96
-
97
-	/**
98
-	 * @param string $pattern which should match within the $searchProperties
99
-	 * @param array $searchProperties defines the properties within the query pattern should match
100
-	 * @param array $options Options to define the output format and search behavior
101
-	 * 	- 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
102
-	 *    example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => '[email protected]']]
103
-	 * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped
104
-	 * @return array an array of contacts which are arrays of key-value-pairs
105
-	 *  example result:
106
-	 *  [
107
-	 *		['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => '[email protected]', 'GEO' => '37.386013;-122.082932'],
108
-	 *		['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['[email protected]', '[email protected]']]
109
-	 *	]
110
-	 * @since 5.0.0
111
-	 */
112
-	public function search($pattern, $searchProperties, $options) {
113
-		$results = $this->backend->search($this->getKey(), $pattern, $searchProperties, $options);
114
-
115
-		$withTypes = \array_key_exists('types', $options) && $options['types'] === true;
116
-
117
-		$vCards = [];
118
-		foreach ($results as $result) {
119
-			$vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']), $withTypes);
120
-		}
121
-
122
-		return $vCards;
123
-	}
124
-
125
-	/**
126
-	 * @param array $properties this array if key-value-pairs defines a contact
127
-	 * @return array an array representing the contact just created or updated
128
-	 * @since 5.0.0
129
-	 */
130
-	public function createOrUpdate($properties) {
131
-		$update = false;
132
-		if (!isset($properties['URI'])) { // create a new contact
133
-			$uid = $this->createUid();
134
-			$uri = $uid . '.vcf';
135
-			$vCard = $this->createEmptyVCard($uid);
136
-		} else { // update existing contact
137
-			$uri = $properties['URI'];
138
-			$vCardData = $this->backend->getCard($this->getKey(), $uri);
139
-			$vCard = $this->readCard($vCardData['carddata']);
140
-			$update = true;
141
-		}
142
-
143
-		foreach ($properties as $key => $value) {
144
-			$vCard->$key = $vCard->createProperty($key, $value);
145
-		}
146
-
147
-		if ($update) {
148
-			$this->backend->updateCard($this->getKey(), $uri, $vCard->serialize());
149
-		} else {
150
-			$this->backend->createCard($this->getKey(), $uri, $vCard->serialize());
151
-		}
152
-
153
-		return $this->vCard2Array($uri, $vCard);
154
-
155
-	}
156
-
157
-	/**
158
-	 * @return mixed
159
-	 * @since 5.0.0
160
-	 */
161
-	public function getPermissions() {
162
-		$permissions = $this->addressBook->getACL();
163
-		$result = 0;
164
-		foreach ($permissions as $permission) {
165
-			switch($permission['privilege']) {
166
-				case '{DAV:}read':
167
-					$result |= Constants::PERMISSION_READ;
168
-					break;
169
-				case '{DAV:}write':
170
-					$result |= Constants::PERMISSION_CREATE;
171
-					$result |= Constants::PERMISSION_UPDATE;
172
-					break;
173
-				case '{DAV:}all':
174
-					$result |= Constants::PERMISSION_ALL;
175
-					break;
176
-			}
177
-		}
178
-
179
-		return $result;
180
-	}
181
-
182
-	/**
183
-	 * @param object $id the unique identifier to a contact
184
-	 * @return bool successful or not
185
-	 * @since 5.0.0
186
-	 */
187
-	public function delete($id) {
188
-		$uri = $this->backend->getCardUri($id);
189
-		return $this->backend->deleteCard($this->addressBookInfo['id'], $uri);
190
-	}
191
-
192
-	/**
193
-	 * read vCard data into a vCard object
194
-	 *
195
-	 * @param string $cardData
196
-	 * @return VCard
197
-	 */
198
-	protected function readCard($cardData) {
199
-		return  Reader::read($cardData);
200
-	}
201
-
202
-	/**
203
-	 * create UID for contact
204
-	 *
205
-	 * @return string
206
-	 */
207
-	protected function createUid() {
208
-		do {
209
-			$uid = $this->getUid();
210
-			$contact = $this->backend->getContact($this->getKey(), $uid . '.vcf');
211
-		} while (!empty($contact));
212
-
213
-		return $uid;
214
-	}
215
-
216
-	/**
217
-	 * getUid is only there for testing, use createUid instead
218
-	 */
219
-	protected function getUid() {
220
-		return UUIDUtil::getUUID();
221
-	}
222
-
223
-	/**
224
-	 * create empty vcard
225
-	 *
226
-	 * @param string $uid
227
-	 * @return VCard
228
-	 */
229
-	protected function createEmptyVCard($uid) {
230
-		$vCard = new VCard();
231
-		$vCard->UID = $uid;
232
-		return $vCard;
233
-	}
234
-
235
-	/**
236
-	 * create array with all vCard properties
237
-	 *
238
-	 * @param string $uri
239
-	 * @param VCard $vCard
240
-	 * @return array
241
-	 */
242
-	protected function vCard2Array($uri, VCard $vCard, $withTypes = false) {
243
-		$result = [
244
-			'URI' => $uri,
245
-		];
246
-
247
-		foreach ($vCard->children() as $property) {
248
-			if ($property->name === 'PHOTO' && $property->getValueType() === 'BINARY') {
249
-				$url = $this->urlGenerator->getAbsoluteURL(
250
-					$this->urlGenerator->linkTo('', 'remote.php') . '/dav/');
251
-				$url .= implode('/', [
252
-					'addressbooks',
253
-					substr($this->addressBookInfo['principaluri'], 11), //cut off 'principals/'
254
-					$this->addressBookInfo['uri'],
255
-					$uri
256
-				]) . '?photo';
257
-
258
-				$result['PHOTO'] = 'VALUE=uri:' . $url;
259
-
260
-			} else if ($property->name === 'X-SOCIALPROFILE') {
261
-				$type = $this->getTypeFromProperty($property);
262
-
263
-				// Type is the social network, when it's empty we don't need this.
264
-				if ($type !== null) {
265
-					if (!isset($result[$property->name])) {
266
-						$result[$property->name] = [];
267
-					}
268
-					$result[$property->name][$type] = $property->getValue();
269
-				}
270
-
271
-			// The following properties can be set multiple times
272
-			} else if (in_array($property->name, ['CLOUD', 'EMAIL', 'IMPP', 'TEL', 'URL', 'X-ADDRESSBOOKSERVER-MEMBER'])) {
273
-				if (!isset($result[$property->name])) {
274
-					$result[$property->name] = [];
275
-				}
276
-
277
-				$type = $this->getTypeFromProperty($property);
278
-				if ($withTypes) {
279
-					$result[$property->name][] = [
280
-						'type' => $type,
281
-						'value' => $property->getValue()
282
-						];
283
-				} else {
284
-					$result[$property->name][] = $property->getValue();
285
-				}
286
-
287
-
288
-			} else {
289
-				$result[$property->name] = $property->getValue();
290
-			}
291
-		}
292
-
293
-		if (
294
-			$this->addressBookInfo['principaluri'] === 'principals/system/system' && (
295
-				$this->addressBookInfo['uri'] === 'system' ||
296
-				$this->addressBookInfo['{DAV:}displayname'] === $this->urlGenerator->getBaseUrl()
297
-			)
298
-		) {
299
-			$result['isLocalSystemBook'] = true;
300
-		}
301
-		return $result;
302
-	}
303
-
304
-	/**
305
-	 * Get the type of the current property
306
-	 *
307
-	 * @param Property $property
308
-	 * @return null|string
309
-	 */
310
-	protected function getTypeFromProperty(Property $property) {
311
-		$parameters = $property->parameters();
312
-		// Type is the social network, when it's empty we don't need this.
313
-		if (isset($parameters['TYPE'])) {
314
-			/** @var \Sabre\VObject\Parameter $type */
315
-			$type = $parameters['TYPE'];
316
-			return $type->getValue();
317
-		}
318
-
319
-		return null;
320
-	}
38
+    /** @var CardDavBackend */
39
+    private $backend;
40
+
41
+    /** @var array */
42
+    private $addressBookInfo;
43
+
44
+    /** @var AddressBook */
45
+    private $addressBook;
46
+
47
+    /** @var IURLGenerator */
48
+    private $urlGenerator;
49
+
50
+    /**
51
+     * AddressBookImpl constructor.
52
+     *
53
+     * @param AddressBook $addressBook
54
+     * @param array $addressBookInfo
55
+     * @param CardDavBackend $backend
56
+     * @param IUrlGenerator $urlGenerator
57
+     */
58
+    public function __construct(
59
+            AddressBook $addressBook,
60
+            array $addressBookInfo,
61
+            CardDavBackend $backend,
62
+            IURLGenerator $urlGenerator) {
63
+
64
+        $this->addressBook = $addressBook;
65
+        $this->addressBookInfo = $addressBookInfo;
66
+        $this->backend = $backend;
67
+        $this->urlGenerator = $urlGenerator;
68
+    }
69
+
70
+    /**
71
+     * @return string defining the technical unique key
72
+     * @since 5.0.0
73
+     */
74
+    public function getKey() {
75
+        return $this->addressBookInfo['id'];
76
+    }
77
+
78
+    /**
79
+     * @return string defining the unique uri
80
+     * @since 16.0.0
81
+     * @return string
82
+     */
83
+    public function getUri(): string {
84
+        return $this->addressBookInfo['uri'];
85
+    }
86
+
87
+    /**
88
+     * In comparison to getKey() this function returns a human readable (maybe translated) name
89
+     *
90
+     * @return mixed
91
+     * @since 5.0.0
92
+     */
93
+    public function getDisplayName() {
94
+        return $this->addressBookInfo['{DAV:}displayname'];
95
+    }
96
+
97
+    /**
98
+     * @param string $pattern which should match within the $searchProperties
99
+     * @param array $searchProperties defines the properties within the query pattern should match
100
+     * @param array $options Options to define the output format and search behavior
101
+     * 	- 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
102
+     *    example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => '[email protected]']]
103
+     * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped
104
+     * @return array an array of contacts which are arrays of key-value-pairs
105
+     *  example result:
106
+     *  [
107
+     *		['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => '[email protected]', 'GEO' => '37.386013;-122.082932'],
108
+     *		['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['[email protected]', '[email protected]']]
109
+     *	]
110
+     * @since 5.0.0
111
+     */
112
+    public function search($pattern, $searchProperties, $options) {
113
+        $results = $this->backend->search($this->getKey(), $pattern, $searchProperties, $options);
114
+
115
+        $withTypes = \array_key_exists('types', $options) && $options['types'] === true;
116
+
117
+        $vCards = [];
118
+        foreach ($results as $result) {
119
+            $vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']), $withTypes);
120
+        }
121
+
122
+        return $vCards;
123
+    }
124
+
125
+    /**
126
+     * @param array $properties this array if key-value-pairs defines a contact
127
+     * @return array an array representing the contact just created or updated
128
+     * @since 5.0.0
129
+     */
130
+    public function createOrUpdate($properties) {
131
+        $update = false;
132
+        if (!isset($properties['URI'])) { // create a new contact
133
+            $uid = $this->createUid();
134
+            $uri = $uid . '.vcf';
135
+            $vCard = $this->createEmptyVCard($uid);
136
+        } else { // update existing contact
137
+            $uri = $properties['URI'];
138
+            $vCardData = $this->backend->getCard($this->getKey(), $uri);
139
+            $vCard = $this->readCard($vCardData['carddata']);
140
+            $update = true;
141
+        }
142
+
143
+        foreach ($properties as $key => $value) {
144
+            $vCard->$key = $vCard->createProperty($key, $value);
145
+        }
146
+
147
+        if ($update) {
148
+            $this->backend->updateCard($this->getKey(), $uri, $vCard->serialize());
149
+        } else {
150
+            $this->backend->createCard($this->getKey(), $uri, $vCard->serialize());
151
+        }
152
+
153
+        return $this->vCard2Array($uri, $vCard);
154
+
155
+    }
156
+
157
+    /**
158
+     * @return mixed
159
+     * @since 5.0.0
160
+     */
161
+    public function getPermissions() {
162
+        $permissions = $this->addressBook->getACL();
163
+        $result = 0;
164
+        foreach ($permissions as $permission) {
165
+            switch($permission['privilege']) {
166
+                case '{DAV:}read':
167
+                    $result |= Constants::PERMISSION_READ;
168
+                    break;
169
+                case '{DAV:}write':
170
+                    $result |= Constants::PERMISSION_CREATE;
171
+                    $result |= Constants::PERMISSION_UPDATE;
172
+                    break;
173
+                case '{DAV:}all':
174
+                    $result |= Constants::PERMISSION_ALL;
175
+                    break;
176
+            }
177
+        }
178
+
179
+        return $result;
180
+    }
181
+
182
+    /**
183
+     * @param object $id the unique identifier to a contact
184
+     * @return bool successful or not
185
+     * @since 5.0.0
186
+     */
187
+    public function delete($id) {
188
+        $uri = $this->backend->getCardUri($id);
189
+        return $this->backend->deleteCard($this->addressBookInfo['id'], $uri);
190
+    }
191
+
192
+    /**
193
+     * read vCard data into a vCard object
194
+     *
195
+     * @param string $cardData
196
+     * @return VCard
197
+     */
198
+    protected function readCard($cardData) {
199
+        return  Reader::read($cardData);
200
+    }
201
+
202
+    /**
203
+     * create UID for contact
204
+     *
205
+     * @return string
206
+     */
207
+    protected function createUid() {
208
+        do {
209
+            $uid = $this->getUid();
210
+            $contact = $this->backend->getContact($this->getKey(), $uid . '.vcf');
211
+        } while (!empty($contact));
212
+
213
+        return $uid;
214
+    }
215
+
216
+    /**
217
+     * getUid is only there for testing, use createUid instead
218
+     */
219
+    protected function getUid() {
220
+        return UUIDUtil::getUUID();
221
+    }
222
+
223
+    /**
224
+     * create empty vcard
225
+     *
226
+     * @param string $uid
227
+     * @return VCard
228
+     */
229
+    protected function createEmptyVCard($uid) {
230
+        $vCard = new VCard();
231
+        $vCard->UID = $uid;
232
+        return $vCard;
233
+    }
234
+
235
+    /**
236
+     * create array with all vCard properties
237
+     *
238
+     * @param string $uri
239
+     * @param VCard $vCard
240
+     * @return array
241
+     */
242
+    protected function vCard2Array($uri, VCard $vCard, $withTypes = false) {
243
+        $result = [
244
+            'URI' => $uri,
245
+        ];
246
+
247
+        foreach ($vCard->children() as $property) {
248
+            if ($property->name === 'PHOTO' && $property->getValueType() === 'BINARY') {
249
+                $url = $this->urlGenerator->getAbsoluteURL(
250
+                    $this->urlGenerator->linkTo('', 'remote.php') . '/dav/');
251
+                $url .= implode('/', [
252
+                    'addressbooks',
253
+                    substr($this->addressBookInfo['principaluri'], 11), //cut off 'principals/'
254
+                    $this->addressBookInfo['uri'],
255
+                    $uri
256
+                ]) . '?photo';
257
+
258
+                $result['PHOTO'] = 'VALUE=uri:' . $url;
259
+
260
+            } else if ($property->name === 'X-SOCIALPROFILE') {
261
+                $type = $this->getTypeFromProperty($property);
262
+
263
+                // Type is the social network, when it's empty we don't need this.
264
+                if ($type !== null) {
265
+                    if (!isset($result[$property->name])) {
266
+                        $result[$property->name] = [];
267
+                    }
268
+                    $result[$property->name][$type] = $property->getValue();
269
+                }
270
+
271
+            // The following properties can be set multiple times
272
+            } else if (in_array($property->name, ['CLOUD', 'EMAIL', 'IMPP', 'TEL', 'URL', 'X-ADDRESSBOOKSERVER-MEMBER'])) {
273
+                if (!isset($result[$property->name])) {
274
+                    $result[$property->name] = [];
275
+                }
276
+
277
+                $type = $this->getTypeFromProperty($property);
278
+                if ($withTypes) {
279
+                    $result[$property->name][] = [
280
+                        'type' => $type,
281
+                        'value' => $property->getValue()
282
+                        ];
283
+                } else {
284
+                    $result[$property->name][] = $property->getValue();
285
+                }
286
+
287
+
288
+            } else {
289
+                $result[$property->name] = $property->getValue();
290
+            }
291
+        }
292
+
293
+        if (
294
+            $this->addressBookInfo['principaluri'] === 'principals/system/system' && (
295
+                $this->addressBookInfo['uri'] === 'system' ||
296
+                $this->addressBookInfo['{DAV:}displayname'] === $this->urlGenerator->getBaseUrl()
297
+            )
298
+        ) {
299
+            $result['isLocalSystemBook'] = true;
300
+        }
301
+        return $result;
302
+    }
303
+
304
+    /**
305
+     * Get the type of the current property
306
+     *
307
+     * @param Property $property
308
+     * @return null|string
309
+     */
310
+    protected function getTypeFromProperty(Property $property) {
311
+        $parameters = $property->parameters();
312
+        // Type is the social network, when it's empty we don't need this.
313
+        if (isset($parameters['TYPE'])) {
314
+            /** @var \Sabre\VObject\Parameter $type */
315
+            $type = $parameters['TYPE'];
316
+            return $type->getValue();
317
+        }
318
+
319
+        return null;
320
+    }
321 321
 }
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/Xml/Groups.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -40,7 +40,7 @@
 block discarded – undo
40 40
 
41 41
 	function xmlSerialize(Writer $writer) {
42 42
 		foreach ($this->groups as $group) {
43
-			$writer->writeElement('{' . self::NS_OWNCLOUD . '}group', $group);
43
+			$writer->writeElement('{'.self::NS_OWNCLOUD.'}group', $group);
44 44
 		}
45 45
 	}
46 46
 }
Please login to merge, or discard this patch.
Indentation   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -26,21 +26,21 @@
 block discarded – undo
26 26
 use Sabre\Xml\XmlSerializable;
27 27
 
28 28
 class Groups implements XmlSerializable {
29
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
29
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
30 30
 
31
-	/** @var string[] of TYPE:CHECKSUM */
32
-	private $groups;
31
+    /** @var string[] of TYPE:CHECKSUM */
32
+    private $groups;
33 33
 
34
-	/**
35
-	 * @param string $groups
36
-	 */
37
-	public function __construct($groups) {
38
-		$this->groups = $groups;
39
-	}
34
+    /**
35
+     * @param string $groups
36
+     */
37
+    public function __construct($groups) {
38
+        $this->groups = $groups;
39
+    }
40 40
 
41
-	function xmlSerialize(Writer $writer) {
42
-		foreach ($this->groups as $group) {
43
-			$writer->writeElement('{' . self::NS_OWNCLOUD . '}group', $group);
44
-		}
45
-	}
41
+    function xmlSerialize(Writer $writer) {
42
+        foreach ($this->groups as $group) {
43
+            $writer->writeElement('{' . self::NS_OWNCLOUD . '}group', $group);
44
+        }
45
+    }
46 46
 }
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/CardDavBackend.php 3 patches
Braces   +3 added lines, -1 removed lines patch added patch discarded remove patch
@@ -745,7 +745,9 @@
 block discarded – undo
745 745
 		$stmt->execute([ $addressBookId ]);
746 746
 		$currentToken = $stmt->fetchColumn(0);
747 747
 
748
-		if (is_null($currentToken)) return null;
748
+		if (is_null($currentToken)) {
749
+		    return null;
750
+		}
749 751
 
750 752
 		$result = [
751 753
 			'syncToken' => $currentToken,
Please login to merge, or discard this patch.
Spacing   +50 added lines, -50 removed lines patch added patch discarded remove patch
@@ -121,7 +121,7 @@  discard block
 block discarded – undo
121 121
 			->from('addressbooks')
122 122
 			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
123 123
 
124
-		return (int)$query->execute()->fetchColumn();
124
+		return (int) $query->execute()->fetchColumn();
125 125
 	}
126 126
 
127 127
 	/**
@@ -152,15 +152,15 @@  discard block
 block discarded – undo
152 152
 		$addressBooks = [];
153 153
 
154 154
 		$result = $query->execute();
155
-		while($row = $result->fetch()) {
155
+		while ($row = $result->fetch()) {
156 156
 			$addressBooks[$row['id']] = [
157 157
 				'id'  => $row['id'],
158 158
 				'uri' => $row['uri'],
159 159
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
160 160
 				'{DAV:}displayname' => $row['displayname'],
161
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
161
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
162 162
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
163
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
163
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
164 164
 			];
165 165
 
166 166
 			$this->addOwnerPrincipal($addressBooks[$row['id']]);
@@ -174,7 +174,7 @@  discard block
 block discarded – undo
174 174
 		$principals = array_map(function($principal) {
175 175
 			return urldecode($principal);
176 176
 		}, $principals);
177
-		$principals[]= $principalUri;
177
+		$principals[] = $principalUri;
178 178
 
179 179
 		$query = $this->db->getQueryBuilder();
180 180
 		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
@@ -186,8 +186,8 @@  discard block
 block discarded – undo
186 186
 			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
187 187
 			->execute();
188 188
 
189
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
190
-		while($row = $result->fetch()) {
189
+		$readOnlyPropertyName = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only';
190
+		while ($row = $result->fetch()) {
191 191
 			if ($row['principaluri'] === $principalUri) {
192 192
 				continue;
193 193
 			}
@@ -206,18 +206,18 @@  discard block
 block discarded – undo
206 206
 			}
207 207
 
208 208
 			list(, $name) = \Sabre\Uri\split($row['principaluri']);
209
-			$uri = $row['uri'] . '_shared_by_' . $name;
210
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
209
+			$uri = $row['uri'].'_shared_by_'.$name;
210
+			$displayName = $row['displayname'].' ('.$this->getUserDisplayName($name).')';
211 211
 
212 212
 			$addressBooks[$row['id']] = [
213 213
 				'id'  => $row['id'],
214 214
 				'uri' => $uri,
215 215
 				'principaluri' => $principalUriOriginal,
216 216
 				'{DAV:}displayname' => $displayName,
217
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
217
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
218 218
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
219
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
220
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
219
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
220
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $row['principaluri'],
221 221
 				$readOnlyPropertyName => $readOnly,
222 222
 			];
223 223
 
@@ -238,15 +238,15 @@  discard block
 block discarded – undo
238 238
 		$addressBooks = [];
239 239
 
240 240
 		$result = $query->execute();
241
-		while($row = $result->fetch()) {
241
+		while ($row = $result->fetch()) {
242 242
 			$addressBooks[$row['id']] = [
243 243
 				'id'  => $row['id'],
244 244
 				'uri' => $row['uri'],
245 245
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
246 246
 				'{DAV:}displayname' => $row['displayname'],
247
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
247
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
248 248
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
249
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
249
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
250 250
 			];
251 251
 
252 252
 			$this->addOwnerPrincipal($addressBooks[$row['id']]);
@@ -291,9 +291,9 @@  discard block
 block discarded – undo
291 291
 			'uri' => $row['uri'],
292 292
 			'principaluri' => $row['principaluri'],
293 293
 			'{DAV:}displayname' => $row['displayname'],
294
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
294
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
295 295
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
296
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
296
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
297 297
 		];
298 298
 
299 299
 		$this->addOwnerPrincipal($addressBook);
@@ -325,9 +325,9 @@  discard block
 block discarded – undo
325 325
 			'uri' => $row['uri'],
326 326
 			'principaluri' => $row['principaluri'],
327 327
 			'{DAV:}displayname' => $row['displayname'],
328
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
328
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
329 329
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
330
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
330
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
331 331
 		];
332 332
 
333 333
 		$this->addOwnerPrincipal($addressBook);
@@ -354,7 +354,7 @@  discard block
 block discarded – undo
354 354
 	function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
355 355
 		$supportedProperties = [
356 356
 			'{DAV:}displayname',
357
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
357
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description',
358 358
 		];
359 359
 
360 360
 		/**
@@ -363,13 +363,13 @@  discard block
 block discarded – undo
363 363
 		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
364 364
 
365 365
 			$updates = [];
366
-			foreach($mutations as $property=>$newValue) {
366
+			foreach ($mutations as $property=>$newValue) {
367 367
 
368
-				switch($property) {
368
+				switch ($property) {
369 369
 					case '{DAV:}displayname' :
370 370
 						$updates['displayname'] = $newValue;
371 371
 						break;
372
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
372
+					case '{'.Plugin::NS_CARDDAV.'}addressbook-description' :
373 373
 						$updates['description'] = $newValue;
374 374
 						break;
375 375
 				}
@@ -377,7 +377,7 @@  discard block
 block discarded – undo
377 377
 			$query = $this->db->getQueryBuilder();
378 378
 			$query->update('addressbooks');
379 379
 
380
-			foreach($updates as $key=>$value) {
380
+			foreach ($updates as $key=>$value) {
381 381
 				$query->set($key, $query->createNamedParameter($value));
382 382
 			}
383 383
 			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
@@ -408,24 +408,24 @@  discard block
 block discarded – undo
408 408
 			'synctoken' => 1
409 409
 		];
410 410
 
411
-		foreach($properties as $property=>$newValue) {
411
+		foreach ($properties as $property=>$newValue) {
412 412
 
413
-			switch($property) {
413
+			switch ($property) {
414 414
 				case '{DAV:}displayname' :
415 415
 					$values['displayname'] = $newValue;
416 416
 					break;
417
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
417
+				case '{'.Plugin::NS_CARDDAV.'}addressbook-description' :
418 418
 					$values['description'] = $newValue;
419 419
 					break;
420 420
 				default :
421
-					throw new BadRequest('Unknown property: ' . $property);
421
+					throw new BadRequest('Unknown property: '.$property);
422 422
 			}
423 423
 
424 424
 		}
425 425
 
426 426
 		// Fallback to make sure the displayname is set. Some clients may refuse
427 427
 		// to work with addressbooks not having a displayname.
428
-		if(is_null($values['displayname'])) {
428
+		if (is_null($values['displayname'])) {
429 429
 			$values['displayname'] = $url;
430 430
 		}
431 431
 
@@ -503,8 +503,8 @@  discard block
 block discarded – undo
503 503
 		$cards = [];
504 504
 
505 505
 		$result = $query->execute();
506
-		while($row = $result->fetch()) {
507
-			$row['etag'] = '"' . $row['etag'] . '"';
506
+		while ($row = $result->fetch()) {
507
+			$row['etag'] = '"'.$row['etag'].'"';
508 508
 			$row['carddata'] = $this->readBlob($row['carddata']);
509 509
 			$cards[] = $row;
510 510
 		}
@@ -538,7 +538,7 @@  discard block
 block discarded – undo
538 538
 		if (!$row) {
539 539
 			return false;
540 540
 		}
541
-		$row['etag'] = '"' . $row['etag'] . '"';
541
+		$row['etag'] = '"'.$row['etag'].'"';
542 542
 		$row['carddata'] = $this->readBlob($row['carddata']);
543 543
 
544 544
 		return $row;
@@ -575,7 +575,7 @@  discard block
 block discarded – undo
575 575
 			$result = $query->execute();
576 576
 
577 577
 			while ($row = $result->fetch()) {
578
-				$row['etag'] = '"' . $row['etag'] . '"';
578
+				$row['etag'] = '"'.$row['etag'].'"';
579 579
 				$row['carddata'] = $this->readBlob($row['carddata']);
580 580
 				$cards[] = $row;
581 581
 			}
@@ -648,7 +648,7 @@  discard block
 block discarded – undo
648 648
 				'cardUri' => $cardUri,
649 649
 				'cardData' => $cardData]));
650 650
 
651
-		return '"' . $etag . '"';
651
+		return '"'.$etag.'"';
652 652
 	}
653 653
 
654 654
 	/**
@@ -700,7 +700,7 @@  discard block
 block discarded – undo
700 700
 				'cardUri' => $cardUri,
701 701
 				'cardData' => $cardData]));
702 702
 
703
-		return '"' . $etag . '"';
703
+		return '"'.$etag.'"';
704 704
 	}
705 705
 
706 706
 	/**
@@ -798,7 +798,7 @@  discard block
 block discarded – undo
798 798
 	function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
799 799
 		// Current synctoken
800 800
 		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
801
-		$stmt->execute([ $addressBookId ]);
801
+		$stmt->execute([$addressBookId]);
802 802
 		$currentToken = $stmt->fetchColumn(0);
803 803
 
804 804
 		if (is_null($currentToken)) return null;
@@ -813,8 +813,8 @@  discard block
 block discarded – undo
813 813
 		if ($syncToken) {
814 814
 
815 815
 			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
816
-			if ($limit>0) {
817
-				$query .= " LIMIT " . (int)$limit;
816
+			if ($limit > 0) {
817
+				$query .= " LIMIT ".(int) $limit;
818 818
 			}
819 819
 
820 820
 			// Fetching all changes
@@ -825,15 +825,15 @@  discard block
 block discarded – undo
825 825
 
826 826
 			// This loop ensures that any duplicates are overwritten, only the
827 827
 			// last change on a node is relevant.
828
-			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
828
+			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
829 829
 
830 830
 				$changes[$row['uri']] = $row['operation'];
831 831
 
832 832
 			}
833 833
 
834
-			foreach($changes as $uri => $operation) {
834
+			foreach ($changes as $uri => $operation) {
835 835
 
836
-				switch($operation) {
836
+				switch ($operation) {
837 837
 					case 1:
838 838
 						$result['added'][] = $uri;
839 839
 						break;
@@ -921,10 +921,10 @@  discard block
 block discarded – undo
921 921
 
922 922
 		// No need for like when the pattern is empty
923 923
 		if ('' !== $pattern) {
924
-			if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
924
+			if (\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
925 925
 				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
926 926
 			} else {
927
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
927
+				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%'.$this->db->escapeLikeParameter($pattern).'%')));
928 928
 			}
929 929
 		}
930 930
 
@@ -978,7 +978,7 @@  discard block
 block discarded – undo
978 978
 		$result->closeCursor();
979 979
 
980 980
 		if (!isset($uri['uri'])) {
981
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
981
+			throw new \InvalidArgumentException('Card does not exists: '.$id);
982 982
 		}
983 983
 
984 984
 		return $uri['uri'];
@@ -1050,11 +1050,11 @@  discard block
 block discarded – undo
1050 1050
 			);
1051 1051
 
1052 1052
 		foreach ($vCard->children() as $property) {
1053
-			if(!in_array($property->name, self::$indexProperties)) {
1053
+			if (!in_array($property->name, self::$indexProperties)) {
1054 1054
 				continue;
1055 1055
 			}
1056 1056
 			$preferred = 0;
1057
-			foreach($property->parameters as $parameter) {
1057
+			foreach ($property->parameters as $parameter) {
1058 1058
 				if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1059 1059
 					$preferred = 1;
1060 1060
 					break;
@@ -1109,10 +1109,10 @@  discard block
 block discarded – undo
1109 1109
 		$result->closeCursor();
1110 1110
 
1111 1111
 		if (!isset($cardIds['id'])) {
1112
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1112
+			throw new \InvalidArgumentException('Card does not exists: '.$uri);
1113 1113
 		}
1114 1114
 
1115
-		return (int)$cardIds['id'];
1115
+		return (int) $cardIds['id'];
1116 1116
 	}
1117 1117
 
1118 1118
 	/**
@@ -1137,8 +1137,8 @@  discard block
 block discarded – undo
1137 1137
 	}
1138 1138
 
1139 1139
 	private function addOwnerPrincipal(&$addressbookInfo) {
1140
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1141
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1140
+		$ownerPrincipalKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal';
1141
+		$displaynameKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD.'}owner-displayname';
1142 1142
 		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1143 1143
 			$uri = $addressbookInfo[$ownerPrincipalKey];
1144 1144
 		} else {
Please login to merge, or discard this patch.
Indentation   +1118 added lines, -1118 removed lines patch added patch discarded remove patch
@@ -55,1122 +55,1122 @@
 block discarded – undo
55 55
 
56 56
 class CardDavBackend implements BackendInterface, SyncSupport {
57 57
 
58
-	const PERSONAL_ADDRESSBOOK_URI = 'contacts';
59
-	const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
60
-
61
-	/** @var Principal */
62
-	private $principalBackend;
63
-
64
-	/** @var string */
65
-	private $dbCardsTable = 'cards';
66
-
67
-	/** @var string */
68
-	private $dbCardsPropertiesTable = 'cards_properties';
69
-
70
-	/** @var IDBConnection */
71
-	private $db;
72
-
73
-	/** @var Backend */
74
-	private $sharingBackend;
75
-
76
-	/** @var array properties to index */
77
-	public static $indexProperties = array(
78
-			'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
79
-			'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD');
80
-
81
-	/**
82
-	 * @var string[] Map of uid => display name
83
-	 */
84
-	protected $userDisplayNames;
85
-
86
-	/** @var IUserManager */
87
-	private $userManager;
88
-
89
-	/** @var EventDispatcherInterface */
90
-	private $dispatcher;
91
-
92
-	/**
93
-	 * CardDavBackend constructor.
94
-	 *
95
-	 * @param IDBConnection $db
96
-	 * @param Principal $principalBackend
97
-	 * @param IUserManager $userManager
98
-	 * @param IGroupManager $groupManager
99
-	 * @param EventDispatcherInterface $dispatcher
100
-	 */
101
-	public function __construct(IDBConnection $db,
102
-								Principal $principalBackend,
103
-								IUserManager $userManager,
104
-								IGroupManager $groupManager,
105
-								EventDispatcherInterface $dispatcher) {
106
-		$this->db = $db;
107
-		$this->principalBackend = $principalBackend;
108
-		$this->userManager = $userManager;
109
-		$this->dispatcher = $dispatcher;
110
-		$this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
111
-	}
112
-
113
-	/**
114
-	 * Return the number of address books for a principal
115
-	 *
116
-	 * @param $principalUri
117
-	 * @return int
118
-	 */
119
-	public function getAddressBooksForUserCount($principalUri) {
120
-		$principalUri = $this->convertPrincipal($principalUri, true);
121
-		$query = $this->db->getQueryBuilder();
122
-		$query->select($query->func()->count('*'))
123
-			->from('addressbooks')
124
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
125
-
126
-		return (int)$query->execute()->fetchColumn();
127
-	}
128
-
129
-	/**
130
-	 * Returns the list of address books for a specific user.
131
-	 *
132
-	 * Every addressbook should have the following properties:
133
-	 *   id - an arbitrary unique id
134
-	 *   uri - the 'basename' part of the url
135
-	 *   principaluri - Same as the passed parameter
136
-	 *
137
-	 * Any additional clark-notation property may be passed besides this. Some
138
-	 * common ones are :
139
-	 *   {DAV:}displayname
140
-	 *   {urn:ietf:params:xml:ns:carddav}addressbook-description
141
-	 *   {http://calendarserver.org/ns/}getctag
142
-	 *
143
-	 * @param string $principalUri
144
-	 * @return array
145
-	 */
146
-	function getAddressBooksForUser($principalUri) {
147
-		$principalUriOriginal = $principalUri;
148
-		$principalUri = $this->convertPrincipal($principalUri, true);
149
-		$query = $this->db->getQueryBuilder();
150
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
151
-			->from('addressbooks')
152
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
153
-
154
-		$addressBooks = [];
155
-
156
-		$result = $query->execute();
157
-		while($row = $result->fetch()) {
158
-			$addressBooks[$row['id']] = [
159
-				'id'  => $row['id'],
160
-				'uri' => $row['uri'],
161
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
162
-				'{DAV:}displayname' => $row['displayname'],
163
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
164
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
165
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
166
-			];
167
-
168
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
169
-		}
170
-		$result->closeCursor();
171
-
172
-		// query for shared addressbooks
173
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
174
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
175
-
176
-		$principals = array_map(function($principal) {
177
-			return urldecode($principal);
178
-		}, $principals);
179
-		$principals[]= $principalUri;
180
-
181
-		$query = $this->db->getQueryBuilder();
182
-		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
183
-			->from('dav_shares', 's')
184
-			->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
185
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
186
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
187
-			->setParameter('type', 'addressbook')
188
-			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
189
-			->execute();
190
-
191
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
192
-		while($row = $result->fetch()) {
193
-			if ($row['principaluri'] === $principalUri) {
194
-				continue;
195
-			}
196
-
197
-			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
198
-			if (isset($addressBooks[$row['id']])) {
199
-				if ($readOnly) {
200
-					// New share can not have more permissions then the old one.
201
-					continue;
202
-				}
203
-				if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
204
-					$addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
205
-					// Old share is already read-write, no more permissions can be gained
206
-					continue;
207
-				}
208
-			}
209
-
210
-			list(, $name) = \Sabre\Uri\split($row['principaluri']);
211
-			$uri = $row['uri'] . '_shared_by_' . $name;
212
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
213
-
214
-			$addressBooks[$row['id']] = [
215
-				'id'  => $row['id'],
216
-				'uri' => $uri,
217
-				'principaluri' => $principalUriOriginal,
218
-				'{DAV:}displayname' => $displayName,
219
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
220
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
221
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
222
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
223
-				$readOnlyPropertyName => $readOnly,
224
-			];
225
-
226
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
227
-		}
228
-		$result->closeCursor();
229
-
230
-		return array_values($addressBooks);
231
-	}
232
-
233
-	public function getUsersOwnAddressBooks($principalUri) {
234
-		$principalUri = $this->convertPrincipal($principalUri, true);
235
-		$query = $this->db->getQueryBuilder();
236
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
237
-			  ->from('addressbooks')
238
-			  ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
239
-
240
-		$addressBooks = [];
241
-
242
-		$result = $query->execute();
243
-		while($row = $result->fetch()) {
244
-			$addressBooks[$row['id']] = [
245
-				'id'  => $row['id'],
246
-				'uri' => $row['uri'],
247
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
248
-				'{DAV:}displayname' => $row['displayname'],
249
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
250
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
251
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
252
-			];
253
-
254
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
255
-		}
256
-		$result->closeCursor();
257
-
258
-		return array_values($addressBooks);
259
-	}
260
-
261
-	private function getUserDisplayName($uid) {
262
-		if (!isset($this->userDisplayNames[$uid])) {
263
-			$user = $this->userManager->get($uid);
264
-
265
-			if ($user instanceof IUser) {
266
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
267
-			} else {
268
-				$this->userDisplayNames[$uid] = $uid;
269
-			}
270
-		}
271
-
272
-		return $this->userDisplayNames[$uid];
273
-	}
274
-
275
-	/**
276
-	 * @param int $addressBookId
277
-	 */
278
-	public function getAddressBookById($addressBookId) {
279
-		$query = $this->db->getQueryBuilder();
280
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
281
-			->from('addressbooks')
282
-			->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
283
-			->execute();
284
-
285
-		$row = $result->fetch();
286
-		$result->closeCursor();
287
-		if ($row === false) {
288
-			return null;
289
-		}
290
-
291
-		$addressBook = [
292
-			'id'  => $row['id'],
293
-			'uri' => $row['uri'],
294
-			'principaluri' => $row['principaluri'],
295
-			'{DAV:}displayname' => $row['displayname'],
296
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
297
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
298
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
299
-		];
300
-
301
-		$this->addOwnerPrincipal($addressBook);
302
-
303
-		return $addressBook;
304
-	}
305
-
306
-	/**
307
-	 * @param $addressBookUri
308
-	 * @return array|null
309
-	 */
310
-	public function getAddressBooksByUri($principal, $addressBookUri) {
311
-		$query = $this->db->getQueryBuilder();
312
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
313
-			->from('addressbooks')
314
-			->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
315
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
316
-			->setMaxResults(1)
317
-			->execute();
318
-
319
-		$row = $result->fetch();
320
-		$result->closeCursor();
321
-		if ($row === false) {
322
-			return null;
323
-		}
324
-
325
-		$addressBook = [
326
-			'id'  => $row['id'],
327
-			'uri' => $row['uri'],
328
-			'principaluri' => $row['principaluri'],
329
-			'{DAV:}displayname' => $row['displayname'],
330
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
331
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
332
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
333
-		];
334
-
335
-		$this->addOwnerPrincipal($addressBook);
336
-
337
-		return $addressBook;
338
-	}
339
-
340
-	/**
341
-	 * Updates properties for an address book.
342
-	 *
343
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
344
-	 * To do the actual updates, you must tell this object which properties
345
-	 * you're going to process with the handle() method.
346
-	 *
347
-	 * Calling the handle method is like telling the PropPatch object "I
348
-	 * promise I can handle updating this property".
349
-	 *
350
-	 * Read the PropPatch documentation for more info and examples.
351
-	 *
352
-	 * @param string $addressBookId
353
-	 * @param \Sabre\DAV\PropPatch $propPatch
354
-	 * @return void
355
-	 */
356
-	function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
357
-		$supportedProperties = [
358
-			'{DAV:}displayname',
359
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
360
-		];
361
-
362
-		/**
363
-		 * @suppress SqlInjectionChecker
364
-		 */
365
-		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
366
-
367
-			$updates = [];
368
-			foreach($mutations as $property=>$newValue) {
369
-
370
-				switch($property) {
371
-					case '{DAV:}displayname' :
372
-						$updates['displayname'] = $newValue;
373
-						break;
374
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
375
-						$updates['description'] = $newValue;
376
-						break;
377
-				}
378
-			}
379
-			$query = $this->db->getQueryBuilder();
380
-			$query->update('addressbooks');
381
-
382
-			foreach($updates as $key=>$value) {
383
-				$query->set($key, $query->createNamedParameter($value));
384
-			}
385
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
386
-			->execute();
387
-
388
-			$this->addChange($addressBookId, "", 2);
389
-
390
-			return true;
391
-
392
-		});
393
-	}
394
-
395
-	/**
396
-	 * Creates a new address book
397
-	 *
398
-	 * @param string $principalUri
399
-	 * @param string $url Just the 'basename' of the url.
400
-	 * @param array $properties
401
-	 * @return int
402
-	 * @throws BadRequest
403
-	 */
404
-	function createAddressBook($principalUri, $url, array $properties) {
405
-		$values = [
406
-			'displayname' => null,
407
-			'description' => null,
408
-			'principaluri' => $principalUri,
409
-			'uri' => $url,
410
-			'synctoken' => 1
411
-		];
412
-
413
-		foreach($properties as $property=>$newValue) {
414
-
415
-			switch($property) {
416
-				case '{DAV:}displayname' :
417
-					$values['displayname'] = $newValue;
418
-					break;
419
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
420
-					$values['description'] = $newValue;
421
-					break;
422
-				default :
423
-					throw new BadRequest('Unknown property: ' . $property);
424
-			}
425
-
426
-		}
427
-
428
-		// Fallback to make sure the displayname is set. Some clients may refuse
429
-		// to work with addressbooks not having a displayname.
430
-		if(is_null($values['displayname'])) {
431
-			$values['displayname'] = $url;
432
-		}
433
-
434
-		$query = $this->db->getQueryBuilder();
435
-		$query->insert('addressbooks')
436
-			->values([
437
-				'uri' => $query->createParameter('uri'),
438
-				'displayname' => $query->createParameter('displayname'),
439
-				'description' => $query->createParameter('description'),
440
-				'principaluri' => $query->createParameter('principaluri'),
441
-				'synctoken' => $query->createParameter('synctoken'),
442
-			])
443
-			->setParameters($values)
444
-			->execute();
445
-
446
-		return $query->getLastInsertId();
447
-	}
448
-
449
-	/**
450
-	 * Deletes an entire addressbook and all its contents
451
-	 *
452
-	 * @param mixed $addressBookId
453
-	 * @return void
454
-	 */
455
-	function deleteAddressBook($addressBookId) {
456
-		$query = $this->db->getQueryBuilder();
457
-		$query->delete('cards')
458
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
459
-			->setParameter('addressbookid', $addressBookId)
460
-			->execute();
461
-
462
-		$query->delete('addressbookchanges')
463
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
464
-			->setParameter('addressbookid', $addressBookId)
465
-			->execute();
466
-
467
-		$query->delete('addressbooks')
468
-			->where($query->expr()->eq('id', $query->createParameter('id')))
469
-			->setParameter('id', $addressBookId)
470
-			->execute();
471
-
472
-		$this->sharingBackend->deleteAllShares($addressBookId);
473
-
474
-		$query->delete($this->dbCardsPropertiesTable)
475
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
476
-			->execute();
477
-
478
-	}
479
-
480
-	/**
481
-	 * Returns all cards for a specific addressbook id.
482
-	 *
483
-	 * This method should return the following properties for each card:
484
-	 *   * carddata - raw vcard data
485
-	 *   * uri - Some unique url
486
-	 *   * lastmodified - A unix timestamp
487
-	 *
488
-	 * It's recommended to also return the following properties:
489
-	 *   * etag - A unique etag. This must change every time the card changes.
490
-	 *   * size - The size of the card in bytes.
491
-	 *
492
-	 * If these last two properties are provided, less time will be spent
493
-	 * calculating them. If they are specified, you can also ommit carddata.
494
-	 * This may speed up certain requests, especially with large cards.
495
-	 *
496
-	 * @param mixed $addressBookId
497
-	 * @return array
498
-	 */
499
-	function getCards($addressBookId) {
500
-		$query = $this->db->getQueryBuilder();
501
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
502
-			->from('cards')
503
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
504
-
505
-		$cards = [];
506
-
507
-		$result = $query->execute();
508
-		while($row = $result->fetch()) {
509
-			$row['etag'] = '"' . $row['etag'] . '"';
510
-			$row['carddata'] = $this->readBlob($row['carddata']);
511
-			$cards[] = $row;
512
-		}
513
-		$result->closeCursor();
514
-
515
-		return $cards;
516
-	}
517
-
518
-	/**
519
-	 * Returns a specific card.
520
-	 *
521
-	 * The same set of properties must be returned as with getCards. The only
522
-	 * exception is that 'carddata' is absolutely required.
523
-	 *
524
-	 * If the card does not exist, you must return false.
525
-	 *
526
-	 * @param mixed $addressBookId
527
-	 * @param string $cardUri
528
-	 * @return array
529
-	 */
530
-	function getCard($addressBookId, $cardUri) {
531
-		$query = $this->db->getQueryBuilder();
532
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
533
-			->from('cards')
534
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
535
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
536
-			->setMaxResults(1);
537
-
538
-		$result = $query->execute();
539
-		$row = $result->fetch();
540
-		if (!$row) {
541
-			return false;
542
-		}
543
-		$row['etag'] = '"' . $row['etag'] . '"';
544
-		$row['carddata'] = $this->readBlob($row['carddata']);
545
-
546
-		return $row;
547
-	}
548
-
549
-	/**
550
-	 * Returns a list of cards.
551
-	 *
552
-	 * This method should work identical to getCard, but instead return all the
553
-	 * cards in the list as an array.
554
-	 *
555
-	 * If the backend supports this, it may allow for some speed-ups.
556
-	 *
557
-	 * @param mixed $addressBookId
558
-	 * @param string[] $uris
559
-	 * @return array
560
-	 */
561
-	function getMultipleCards($addressBookId, array $uris) {
562
-		if (empty($uris)) {
563
-			return [];
564
-		}
565
-
566
-		$chunks = array_chunk($uris, 100);
567
-		$cards = [];
568
-
569
-		$query = $this->db->getQueryBuilder();
570
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
571
-			->from('cards')
572
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
573
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
574
-
575
-		foreach ($chunks as $uris) {
576
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
577
-			$result = $query->execute();
578
-
579
-			while ($row = $result->fetch()) {
580
-				$row['etag'] = '"' . $row['etag'] . '"';
581
-				$row['carddata'] = $this->readBlob($row['carddata']);
582
-				$cards[] = $row;
583
-			}
584
-			$result->closeCursor();
585
-		}
586
-		return $cards;
587
-	}
588
-
589
-	/**
590
-	 * Creates a new card.
591
-	 *
592
-	 * The addressbook id will be passed as the first argument. This is the
593
-	 * same id as it is returned from the getAddressBooksForUser method.
594
-	 *
595
-	 * The cardUri is a base uri, and doesn't include the full path. The
596
-	 * cardData argument is the vcard body, and is passed as a string.
597
-	 *
598
-	 * It is possible to return an ETag from this method. This ETag is for the
599
-	 * newly created resource, and must be enclosed with double quotes (that
600
-	 * is, the string itself must contain the double quotes).
601
-	 *
602
-	 * You should only return the ETag if you store the carddata as-is. If a
603
-	 * subsequent GET request on the same card does not have the same body,
604
-	 * byte-by-byte and you did return an ETag here, clients tend to get
605
-	 * confused.
606
-	 *
607
-	 * If you don't return an ETag, you can just return null.
608
-	 *
609
-	 * @param mixed $addressBookId
610
-	 * @param string $cardUri
611
-	 * @param string $cardData
612
-	 * @return string
613
-	 */
614
-	function createCard($addressBookId, $cardUri, $cardData) {
615
-		$etag = md5($cardData);
616
-		$uid = $this->getUID($cardData);
617
-
618
-		$q = $this->db->getQueryBuilder();
619
-		$q->select('uid')
620
-			->from('cards')
621
-			->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
622
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
623
-			->setMaxResults(1);
624
-		$result = $q->execute();
625
-		$count = (bool) $result->fetchColumn();
626
-		$result->closeCursor();
627
-		if ($count) {
628
-			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
629
-		}
630
-
631
-		$query = $this->db->getQueryBuilder();
632
-		$query->insert('cards')
633
-			->values([
634
-				'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
635
-				'uri' => $query->createNamedParameter($cardUri),
636
-				'lastmodified' => $query->createNamedParameter(time()),
637
-				'addressbookid' => $query->createNamedParameter($addressBookId),
638
-				'size' => $query->createNamedParameter(strlen($cardData)),
639
-				'etag' => $query->createNamedParameter($etag),
640
-				'uid' => $query->createNamedParameter($uid),
641
-			])
642
-			->execute();
643
-
644
-		$this->addChange($addressBookId, $cardUri, 1);
645
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
646
-
647
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
648
-			new GenericEvent(null, [
649
-				'addressBookId' => $addressBookId,
650
-				'cardUri' => $cardUri,
651
-				'cardData' => $cardData]));
652
-
653
-		return '"' . $etag . '"';
654
-	}
655
-
656
-	/**
657
-	 * Updates a card.
658
-	 *
659
-	 * The addressbook id will be passed as the first argument. This is the
660
-	 * same id as it is returned from the getAddressBooksForUser method.
661
-	 *
662
-	 * The cardUri is a base uri, and doesn't include the full path. The
663
-	 * cardData argument is the vcard body, and is passed as a string.
664
-	 *
665
-	 * It is possible to return an ETag from this method. This ETag should
666
-	 * match that of the updated resource, and must be enclosed with double
667
-	 * quotes (that is: the string itself must contain the actual quotes).
668
-	 *
669
-	 * You should only return the ETag if you store the carddata as-is. If a
670
-	 * subsequent GET request on the same card does not have the same body,
671
-	 * byte-by-byte and you did return an ETag here, clients tend to get
672
-	 * confused.
673
-	 *
674
-	 * If you don't return an ETag, you can just return null.
675
-	 *
676
-	 * @param mixed $addressBookId
677
-	 * @param string $cardUri
678
-	 * @param string $cardData
679
-	 * @return string
680
-	 */
681
-	function updateCard($addressBookId, $cardUri, $cardData) {
682
-
683
-		$uid = $this->getUID($cardData);
684
-		$etag = md5($cardData);
685
-		$query = $this->db->getQueryBuilder();
686
-		$query->update('cards')
687
-			->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
688
-			->set('lastmodified', $query->createNamedParameter(time()))
689
-			->set('size', $query->createNamedParameter(strlen($cardData)))
690
-			->set('etag', $query->createNamedParameter($etag))
691
-			->set('uid', $query->createNamedParameter($uid))
692
-			->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
693
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
694
-			->execute();
695
-
696
-		$this->addChange($addressBookId, $cardUri, 2);
697
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
698
-
699
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
700
-			new GenericEvent(null, [
701
-				'addressBookId' => $addressBookId,
702
-				'cardUri' => $cardUri,
703
-				'cardData' => $cardData]));
704
-
705
-		return '"' . $etag . '"';
706
-	}
707
-
708
-	/**
709
-	 * Deletes a card
710
-	 *
711
-	 * @param mixed $addressBookId
712
-	 * @param string $cardUri
713
-	 * @return bool
714
-	 */
715
-	function deleteCard($addressBookId, $cardUri) {
716
-		try {
717
-			$cardId = $this->getCardId($addressBookId, $cardUri);
718
-		} catch (\InvalidArgumentException $e) {
719
-			$cardId = null;
720
-		}
721
-		$query = $this->db->getQueryBuilder();
722
-		$ret = $query->delete('cards')
723
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
724
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
725
-			->execute();
726
-
727
-		$this->addChange($addressBookId, $cardUri, 3);
728
-
729
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
730
-			new GenericEvent(null, [
731
-				'addressBookId' => $addressBookId,
732
-				'cardUri' => $cardUri]));
733
-
734
-		if ($ret === 1) {
735
-			if ($cardId !== null) {
736
-				$this->purgeProperties($addressBookId, $cardId);
737
-			}
738
-			return true;
739
-		}
740
-
741
-		return false;
742
-	}
743
-
744
-	/**
745
-	 * The getChanges method returns all the changes that have happened, since
746
-	 * the specified syncToken in the specified address book.
747
-	 *
748
-	 * This function should return an array, such as the following:
749
-	 *
750
-	 * [
751
-	 *   'syncToken' => 'The current synctoken',
752
-	 *   'added'   => [
753
-	 *      'new.txt',
754
-	 *   ],
755
-	 *   'modified'   => [
756
-	 *      'modified.txt',
757
-	 *   ],
758
-	 *   'deleted' => [
759
-	 *      'foo.php.bak',
760
-	 *      'old.txt'
761
-	 *   ]
762
-	 * ];
763
-	 *
764
-	 * The returned syncToken property should reflect the *current* syncToken
765
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
766
-	 * property. This is needed here too, to ensure the operation is atomic.
767
-	 *
768
-	 * If the $syncToken argument is specified as null, this is an initial
769
-	 * sync, and all members should be reported.
770
-	 *
771
-	 * The modified property is an array of nodenames that have changed since
772
-	 * the last token.
773
-	 *
774
-	 * The deleted property is an array with nodenames, that have been deleted
775
-	 * from collection.
776
-	 *
777
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
778
-	 * 1, you only have to report changes that happened only directly in
779
-	 * immediate descendants. If it's 2, it should also include changes from
780
-	 * the nodes below the child collections. (grandchildren)
781
-	 *
782
-	 * The $limit argument allows a client to specify how many results should
783
-	 * be returned at most. If the limit is not specified, it should be treated
784
-	 * as infinite.
785
-	 *
786
-	 * If the limit (infinite or not) is higher than you're willing to return,
787
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
788
-	 *
789
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
790
-	 * return null.
791
-	 *
792
-	 * The limit is 'suggestive'. You are free to ignore it.
793
-	 *
794
-	 * @param string $addressBookId
795
-	 * @param string $syncToken
796
-	 * @param int $syncLevel
797
-	 * @param int $limit
798
-	 * @return array
799
-	 */
800
-	function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
801
-		// Current synctoken
802
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
803
-		$stmt->execute([ $addressBookId ]);
804
-		$currentToken = $stmt->fetchColumn(0);
805
-
806
-		if (is_null($currentToken)) return null;
807
-
808
-		$result = [
809
-			'syncToken' => $currentToken,
810
-			'added'     => [],
811
-			'modified'  => [],
812
-			'deleted'   => [],
813
-		];
814
-
815
-		if ($syncToken) {
816
-
817
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
818
-			if ($limit>0) {
819
-				$query .= " LIMIT " . (int)$limit;
820
-			}
821
-
822
-			// Fetching all changes
823
-			$stmt = $this->db->prepare($query);
824
-			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
825
-
826
-			$changes = [];
827
-
828
-			// This loop ensures that any duplicates are overwritten, only the
829
-			// last change on a node is relevant.
830
-			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
831
-
832
-				$changes[$row['uri']] = $row['operation'];
833
-
834
-			}
835
-
836
-			foreach($changes as $uri => $operation) {
837
-
838
-				switch($operation) {
839
-					case 1:
840
-						$result['added'][] = $uri;
841
-						break;
842
-					case 2:
843
-						$result['modified'][] = $uri;
844
-						break;
845
-					case 3:
846
-						$result['deleted'][] = $uri;
847
-						break;
848
-				}
849
-
850
-			}
851
-		} else {
852
-			// No synctoken supplied, this is the initial sync.
853
-			$query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
854
-			$stmt = $this->db->prepare($query);
855
-			$stmt->execute([$addressBookId]);
856
-
857
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
858
-		}
859
-		return $result;
860
-	}
861
-
862
-	/**
863
-	 * Adds a change record to the addressbookchanges table.
864
-	 *
865
-	 * @param mixed $addressBookId
866
-	 * @param string $objectUri
867
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete
868
-	 * @return void
869
-	 */
870
-	protected function addChange($addressBookId, $objectUri, $operation) {
871
-		$sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
872
-		$stmt = $this->db->prepare($sql);
873
-		$stmt->execute([
874
-			$objectUri,
875
-			$addressBookId,
876
-			$operation,
877
-			$addressBookId
878
-		]);
879
-		$stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
880
-		$stmt->execute([
881
-			$addressBookId
882
-		]);
883
-	}
884
-
885
-	private function readBlob($cardData) {
886
-		if (is_resource($cardData)) {
887
-			return stream_get_contents($cardData);
888
-		}
889
-
890
-		return $cardData;
891
-	}
892
-
893
-	/**
894
-	 * @param IShareable $shareable
895
-	 * @param string[] $add
896
-	 * @param string[] $remove
897
-	 */
898
-	public function updateShares(IShareable $shareable, $add, $remove) {
899
-		$this->sharingBackend->updateShares($shareable, $add, $remove);
900
-	}
901
-
902
-	/**
903
-	 * search contact
904
-	 *
905
-	 * @param int $addressBookId
906
-	 * @param string $pattern which should match within the $searchProperties
907
-	 * @param array $searchProperties defines the properties within the query pattern should match
908
-	 * @param array $options = array() to define the search behavior
909
-	 * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
910
-	 * @return array an array of contacts which are arrays of key-value-pairs
911
-	 */
912
-	public function search($addressBookId, $pattern, $searchProperties, $options = array()) {
913
-		$query = $this->db->getQueryBuilder();
914
-		$query2 = $this->db->getQueryBuilder();
915
-
916
-		$query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
917
-		$query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
918
-		$or = $query2->expr()->orX();
919
-		foreach ($searchProperties as $property) {
920
-			$or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property)));
921
-		}
922
-		$query2->andWhere($or);
923
-
924
-		// No need for like when the pattern is empty
925
-		if ('' !== $pattern) {
926
-			if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
927
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
928
-			} else {
929
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
930
-			}
931
-		}
932
-
933
-		$query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
934
-			->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
935
-
936
-		$result = $query->execute();
937
-		$cards = $result->fetchAll();
938
-
939
-		$result->closeCursor();
940
-
941
-		return array_map(function($array) {
942
-			$array['carddata'] = $this->readBlob($array['carddata']);
943
-			return $array;
944
-		}, $cards);
945
-	}
946
-
947
-	/**
948
-	 * @param int $bookId
949
-	 * @param string $name
950
-	 * @return array
951
-	 */
952
-	public function collectCardProperties($bookId, $name) {
953
-		$query = $this->db->getQueryBuilder();
954
-		$result = $query->selectDistinct('value')
955
-			->from($this->dbCardsPropertiesTable)
956
-			->where($query->expr()->eq('name', $query->createNamedParameter($name)))
957
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
958
-			->execute();
959
-
960
-		$all = $result->fetchAll(PDO::FETCH_COLUMN);
961
-		$result->closeCursor();
962
-
963
-		return $all;
964
-	}
965
-
966
-	/**
967
-	 * get URI from a given contact
968
-	 *
969
-	 * @param int $id
970
-	 * @return string
971
-	 */
972
-	public function getCardUri($id) {
973
-		$query = $this->db->getQueryBuilder();
974
-		$query->select('uri')->from($this->dbCardsTable)
975
-				->where($query->expr()->eq('id', $query->createParameter('id')))
976
-				->setParameter('id', $id);
977
-
978
-		$result = $query->execute();
979
-		$uri = $result->fetch();
980
-		$result->closeCursor();
981
-
982
-		if (!isset($uri['uri'])) {
983
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
984
-		}
985
-
986
-		return $uri['uri'];
987
-	}
988
-
989
-	/**
990
-	 * return contact with the given URI
991
-	 *
992
-	 * @param int $addressBookId
993
-	 * @param string $uri
994
-	 * @returns array
995
-	 */
996
-	public function getContact($addressBookId, $uri) {
997
-		$result = [];
998
-		$query = $this->db->getQueryBuilder();
999
-		$query->select('*')->from($this->dbCardsTable)
1000
-				->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1001
-				->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1002
-		$queryResult = $query->execute();
1003
-		$contact = $queryResult->fetch();
1004
-		$queryResult->closeCursor();
1005
-
1006
-		if (is_array($contact)) {
1007
-			$result = $contact;
1008
-		}
1009
-
1010
-		return $result;
1011
-	}
1012
-
1013
-	/**
1014
-	 * Returns the list of people whom this address book is shared with.
1015
-	 *
1016
-	 * Every element in this array should have the following properties:
1017
-	 *   * href - Often a mailto: address
1018
-	 *   * commonName - Optional, for example a first + last name
1019
-	 *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1020
-	 *   * readOnly - boolean
1021
-	 *   * summary - Optional, a description for the share
1022
-	 *
1023
-	 * @return array
1024
-	 */
1025
-	public function getShares($addressBookId) {
1026
-		return $this->sharingBackend->getShares($addressBookId);
1027
-	}
1028
-
1029
-	/**
1030
-	 * update properties table
1031
-	 *
1032
-	 * @param int $addressBookId
1033
-	 * @param string $cardUri
1034
-	 * @param string $vCardSerialized
1035
-	 */
1036
-	protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1037
-		$cardId = $this->getCardId($addressBookId, $cardUri);
1038
-		$vCard = $this->readCard($vCardSerialized);
1039
-
1040
-		$this->purgeProperties($addressBookId, $cardId);
1041
-
1042
-		$query = $this->db->getQueryBuilder();
1043
-		$query->insert($this->dbCardsPropertiesTable)
1044
-			->values(
1045
-				[
1046
-					'addressbookid' => $query->createNamedParameter($addressBookId),
1047
-					'cardid' => $query->createNamedParameter($cardId),
1048
-					'name' => $query->createParameter('name'),
1049
-					'value' => $query->createParameter('value'),
1050
-					'preferred' => $query->createParameter('preferred')
1051
-				]
1052
-			);
1053
-
1054
-		foreach ($vCard->children() as $property) {
1055
-			if(!in_array($property->name, self::$indexProperties)) {
1056
-				continue;
1057
-			}
1058
-			$preferred = 0;
1059
-			foreach($property->parameters as $parameter) {
1060
-				if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1061
-					$preferred = 1;
1062
-					break;
1063
-				}
1064
-			}
1065
-			$query->setParameter('name', $property->name);
1066
-			$query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1067
-			$query->setParameter('preferred', $preferred);
1068
-			$query->execute();
1069
-		}
1070
-	}
1071
-
1072
-	/**
1073
-	 * read vCard data into a vCard object
1074
-	 *
1075
-	 * @param string $cardData
1076
-	 * @return VCard
1077
-	 */
1078
-	protected function readCard($cardData) {
1079
-		return  Reader::read($cardData);
1080
-	}
1081
-
1082
-	/**
1083
-	 * delete all properties from a given card
1084
-	 *
1085
-	 * @param int $addressBookId
1086
-	 * @param int $cardId
1087
-	 */
1088
-	protected function purgeProperties($addressBookId, $cardId) {
1089
-		$query = $this->db->getQueryBuilder();
1090
-		$query->delete($this->dbCardsPropertiesTable)
1091
-			->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1092
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1093
-		$query->execute();
1094
-	}
1095
-
1096
-	/**
1097
-	 * get ID from a given contact
1098
-	 *
1099
-	 * @param int $addressBookId
1100
-	 * @param string $uri
1101
-	 * @return int
1102
-	 */
1103
-	protected function getCardId($addressBookId, $uri) {
1104
-		$query = $this->db->getQueryBuilder();
1105
-		$query->select('id')->from($this->dbCardsTable)
1106
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1107
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1108
-
1109
-		$result = $query->execute();
1110
-		$cardIds = $result->fetch();
1111
-		$result->closeCursor();
1112
-
1113
-		if (!isset($cardIds['id'])) {
1114
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1115
-		}
1116
-
1117
-		return (int)$cardIds['id'];
1118
-	}
1119
-
1120
-	/**
1121
-	 * For shared address books the sharee is set in the ACL of the address book
1122
-	 * @param $addressBookId
1123
-	 * @param $acl
1124
-	 * @return array
1125
-	 */
1126
-	public function applyShareAcl($addressBookId, $acl) {
1127
-		return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1128
-	}
1129
-
1130
-	private function convertPrincipal($principalUri, $toV2) {
1131
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1132
-			list(, $name) = \Sabre\Uri\split($principalUri);
1133
-			if ($toV2 === true) {
1134
-				return "principals/users/$name";
1135
-			}
1136
-			return "principals/$name";
1137
-		}
1138
-		return $principalUri;
1139
-	}
1140
-
1141
-	private function addOwnerPrincipal(&$addressbookInfo) {
1142
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1143
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1144
-		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1145
-			$uri = $addressbookInfo[$ownerPrincipalKey];
1146
-		} else {
1147
-			$uri = $addressbookInfo['principaluri'];
1148
-		}
1149
-
1150
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1151
-		if (isset($principalInformation['{DAV:}displayname'])) {
1152
-			$addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1153
-		}
1154
-	}
1155
-
1156
-	/**
1157
-	 * Extract UID from vcard
1158
-	 *
1159
-	 * @param string $cardData the vcard raw data
1160
-	 * @return string the uid
1161
-	 * @throws BadRequest if no UID is available
1162
-	 */
1163
-	private function getUID($cardData) {
1164
-		if ($cardData != '') {
1165
-			$vCard = Reader::read($cardData);
1166
-			if ($vCard->UID) {
1167
-				$uid = $vCard->UID->getValue();
1168
-				return $uid;
1169
-			}
1170
-			// should already be handled, but just in case
1171
-			throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1172
-		}
1173
-		// should already be handled, but just in case
1174
-		throw new BadRequest('vCard can not be empty');
1175
-	}
58
+    const PERSONAL_ADDRESSBOOK_URI = 'contacts';
59
+    const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
60
+
61
+    /** @var Principal */
62
+    private $principalBackend;
63
+
64
+    /** @var string */
65
+    private $dbCardsTable = 'cards';
66
+
67
+    /** @var string */
68
+    private $dbCardsPropertiesTable = 'cards_properties';
69
+
70
+    /** @var IDBConnection */
71
+    private $db;
72
+
73
+    /** @var Backend */
74
+    private $sharingBackend;
75
+
76
+    /** @var array properties to index */
77
+    public static $indexProperties = array(
78
+            'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
79
+            'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD');
80
+
81
+    /**
82
+     * @var string[] Map of uid => display name
83
+     */
84
+    protected $userDisplayNames;
85
+
86
+    /** @var IUserManager */
87
+    private $userManager;
88
+
89
+    /** @var EventDispatcherInterface */
90
+    private $dispatcher;
91
+
92
+    /**
93
+     * CardDavBackend constructor.
94
+     *
95
+     * @param IDBConnection $db
96
+     * @param Principal $principalBackend
97
+     * @param IUserManager $userManager
98
+     * @param IGroupManager $groupManager
99
+     * @param EventDispatcherInterface $dispatcher
100
+     */
101
+    public function __construct(IDBConnection $db,
102
+                                Principal $principalBackend,
103
+                                IUserManager $userManager,
104
+                                IGroupManager $groupManager,
105
+                                EventDispatcherInterface $dispatcher) {
106
+        $this->db = $db;
107
+        $this->principalBackend = $principalBackend;
108
+        $this->userManager = $userManager;
109
+        $this->dispatcher = $dispatcher;
110
+        $this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
111
+    }
112
+
113
+    /**
114
+     * Return the number of address books for a principal
115
+     *
116
+     * @param $principalUri
117
+     * @return int
118
+     */
119
+    public function getAddressBooksForUserCount($principalUri) {
120
+        $principalUri = $this->convertPrincipal($principalUri, true);
121
+        $query = $this->db->getQueryBuilder();
122
+        $query->select($query->func()->count('*'))
123
+            ->from('addressbooks')
124
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
125
+
126
+        return (int)$query->execute()->fetchColumn();
127
+    }
128
+
129
+    /**
130
+     * Returns the list of address books for a specific user.
131
+     *
132
+     * Every addressbook should have the following properties:
133
+     *   id - an arbitrary unique id
134
+     *   uri - the 'basename' part of the url
135
+     *   principaluri - Same as the passed parameter
136
+     *
137
+     * Any additional clark-notation property may be passed besides this. Some
138
+     * common ones are :
139
+     *   {DAV:}displayname
140
+     *   {urn:ietf:params:xml:ns:carddav}addressbook-description
141
+     *   {http://calendarserver.org/ns/}getctag
142
+     *
143
+     * @param string $principalUri
144
+     * @return array
145
+     */
146
+    function getAddressBooksForUser($principalUri) {
147
+        $principalUriOriginal = $principalUri;
148
+        $principalUri = $this->convertPrincipal($principalUri, true);
149
+        $query = $this->db->getQueryBuilder();
150
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
151
+            ->from('addressbooks')
152
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
153
+
154
+        $addressBooks = [];
155
+
156
+        $result = $query->execute();
157
+        while($row = $result->fetch()) {
158
+            $addressBooks[$row['id']] = [
159
+                'id'  => $row['id'],
160
+                'uri' => $row['uri'],
161
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
162
+                '{DAV:}displayname' => $row['displayname'],
163
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
164
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
165
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
166
+            ];
167
+
168
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
169
+        }
170
+        $result->closeCursor();
171
+
172
+        // query for shared addressbooks
173
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
174
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
175
+
176
+        $principals = array_map(function($principal) {
177
+            return urldecode($principal);
178
+        }, $principals);
179
+        $principals[]= $principalUri;
180
+
181
+        $query = $this->db->getQueryBuilder();
182
+        $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
183
+            ->from('dav_shares', 's')
184
+            ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
185
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
186
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
187
+            ->setParameter('type', 'addressbook')
188
+            ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
189
+            ->execute();
190
+
191
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
192
+        while($row = $result->fetch()) {
193
+            if ($row['principaluri'] === $principalUri) {
194
+                continue;
195
+            }
196
+
197
+            $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
198
+            if (isset($addressBooks[$row['id']])) {
199
+                if ($readOnly) {
200
+                    // New share can not have more permissions then the old one.
201
+                    continue;
202
+                }
203
+                if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
204
+                    $addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
205
+                    // Old share is already read-write, no more permissions can be gained
206
+                    continue;
207
+                }
208
+            }
209
+
210
+            list(, $name) = \Sabre\Uri\split($row['principaluri']);
211
+            $uri = $row['uri'] . '_shared_by_' . $name;
212
+            $displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
213
+
214
+            $addressBooks[$row['id']] = [
215
+                'id'  => $row['id'],
216
+                'uri' => $uri,
217
+                'principaluri' => $principalUriOriginal,
218
+                '{DAV:}displayname' => $displayName,
219
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
220
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
221
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
222
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
223
+                $readOnlyPropertyName => $readOnly,
224
+            ];
225
+
226
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
227
+        }
228
+        $result->closeCursor();
229
+
230
+        return array_values($addressBooks);
231
+    }
232
+
233
+    public function getUsersOwnAddressBooks($principalUri) {
234
+        $principalUri = $this->convertPrincipal($principalUri, true);
235
+        $query = $this->db->getQueryBuilder();
236
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
237
+                ->from('addressbooks')
238
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
239
+
240
+        $addressBooks = [];
241
+
242
+        $result = $query->execute();
243
+        while($row = $result->fetch()) {
244
+            $addressBooks[$row['id']] = [
245
+                'id'  => $row['id'],
246
+                'uri' => $row['uri'],
247
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
248
+                '{DAV:}displayname' => $row['displayname'],
249
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
250
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
251
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
252
+            ];
253
+
254
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
255
+        }
256
+        $result->closeCursor();
257
+
258
+        return array_values($addressBooks);
259
+    }
260
+
261
+    private function getUserDisplayName($uid) {
262
+        if (!isset($this->userDisplayNames[$uid])) {
263
+            $user = $this->userManager->get($uid);
264
+
265
+            if ($user instanceof IUser) {
266
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
267
+            } else {
268
+                $this->userDisplayNames[$uid] = $uid;
269
+            }
270
+        }
271
+
272
+        return $this->userDisplayNames[$uid];
273
+    }
274
+
275
+    /**
276
+     * @param int $addressBookId
277
+     */
278
+    public function getAddressBookById($addressBookId) {
279
+        $query = $this->db->getQueryBuilder();
280
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
281
+            ->from('addressbooks')
282
+            ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
283
+            ->execute();
284
+
285
+        $row = $result->fetch();
286
+        $result->closeCursor();
287
+        if ($row === false) {
288
+            return null;
289
+        }
290
+
291
+        $addressBook = [
292
+            'id'  => $row['id'],
293
+            'uri' => $row['uri'],
294
+            'principaluri' => $row['principaluri'],
295
+            '{DAV:}displayname' => $row['displayname'],
296
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
297
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
298
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
299
+        ];
300
+
301
+        $this->addOwnerPrincipal($addressBook);
302
+
303
+        return $addressBook;
304
+    }
305
+
306
+    /**
307
+     * @param $addressBookUri
308
+     * @return array|null
309
+     */
310
+    public function getAddressBooksByUri($principal, $addressBookUri) {
311
+        $query = $this->db->getQueryBuilder();
312
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
313
+            ->from('addressbooks')
314
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
315
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
316
+            ->setMaxResults(1)
317
+            ->execute();
318
+
319
+        $row = $result->fetch();
320
+        $result->closeCursor();
321
+        if ($row === false) {
322
+            return null;
323
+        }
324
+
325
+        $addressBook = [
326
+            'id'  => $row['id'],
327
+            'uri' => $row['uri'],
328
+            'principaluri' => $row['principaluri'],
329
+            '{DAV:}displayname' => $row['displayname'],
330
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
331
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
332
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
333
+        ];
334
+
335
+        $this->addOwnerPrincipal($addressBook);
336
+
337
+        return $addressBook;
338
+    }
339
+
340
+    /**
341
+     * Updates properties for an address book.
342
+     *
343
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
344
+     * To do the actual updates, you must tell this object which properties
345
+     * you're going to process with the handle() method.
346
+     *
347
+     * Calling the handle method is like telling the PropPatch object "I
348
+     * promise I can handle updating this property".
349
+     *
350
+     * Read the PropPatch documentation for more info and examples.
351
+     *
352
+     * @param string $addressBookId
353
+     * @param \Sabre\DAV\PropPatch $propPatch
354
+     * @return void
355
+     */
356
+    function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
357
+        $supportedProperties = [
358
+            '{DAV:}displayname',
359
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description',
360
+        ];
361
+
362
+        /**
363
+         * @suppress SqlInjectionChecker
364
+         */
365
+        $propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
366
+
367
+            $updates = [];
368
+            foreach($mutations as $property=>$newValue) {
369
+
370
+                switch($property) {
371
+                    case '{DAV:}displayname' :
372
+                        $updates['displayname'] = $newValue;
373
+                        break;
374
+                    case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
375
+                        $updates['description'] = $newValue;
376
+                        break;
377
+                }
378
+            }
379
+            $query = $this->db->getQueryBuilder();
380
+            $query->update('addressbooks');
381
+
382
+            foreach($updates as $key=>$value) {
383
+                $query->set($key, $query->createNamedParameter($value));
384
+            }
385
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
386
+            ->execute();
387
+
388
+            $this->addChange($addressBookId, "", 2);
389
+
390
+            return true;
391
+
392
+        });
393
+    }
394
+
395
+    /**
396
+     * Creates a new address book
397
+     *
398
+     * @param string $principalUri
399
+     * @param string $url Just the 'basename' of the url.
400
+     * @param array $properties
401
+     * @return int
402
+     * @throws BadRequest
403
+     */
404
+    function createAddressBook($principalUri, $url, array $properties) {
405
+        $values = [
406
+            'displayname' => null,
407
+            'description' => null,
408
+            'principaluri' => $principalUri,
409
+            'uri' => $url,
410
+            'synctoken' => 1
411
+        ];
412
+
413
+        foreach($properties as $property=>$newValue) {
414
+
415
+            switch($property) {
416
+                case '{DAV:}displayname' :
417
+                    $values['displayname'] = $newValue;
418
+                    break;
419
+                case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
420
+                    $values['description'] = $newValue;
421
+                    break;
422
+                default :
423
+                    throw new BadRequest('Unknown property: ' . $property);
424
+            }
425
+
426
+        }
427
+
428
+        // Fallback to make sure the displayname is set. Some clients may refuse
429
+        // to work with addressbooks not having a displayname.
430
+        if(is_null($values['displayname'])) {
431
+            $values['displayname'] = $url;
432
+        }
433
+
434
+        $query = $this->db->getQueryBuilder();
435
+        $query->insert('addressbooks')
436
+            ->values([
437
+                'uri' => $query->createParameter('uri'),
438
+                'displayname' => $query->createParameter('displayname'),
439
+                'description' => $query->createParameter('description'),
440
+                'principaluri' => $query->createParameter('principaluri'),
441
+                'synctoken' => $query->createParameter('synctoken'),
442
+            ])
443
+            ->setParameters($values)
444
+            ->execute();
445
+
446
+        return $query->getLastInsertId();
447
+    }
448
+
449
+    /**
450
+     * Deletes an entire addressbook and all its contents
451
+     *
452
+     * @param mixed $addressBookId
453
+     * @return void
454
+     */
455
+    function deleteAddressBook($addressBookId) {
456
+        $query = $this->db->getQueryBuilder();
457
+        $query->delete('cards')
458
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
459
+            ->setParameter('addressbookid', $addressBookId)
460
+            ->execute();
461
+
462
+        $query->delete('addressbookchanges')
463
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
464
+            ->setParameter('addressbookid', $addressBookId)
465
+            ->execute();
466
+
467
+        $query->delete('addressbooks')
468
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
469
+            ->setParameter('id', $addressBookId)
470
+            ->execute();
471
+
472
+        $this->sharingBackend->deleteAllShares($addressBookId);
473
+
474
+        $query->delete($this->dbCardsPropertiesTable)
475
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
476
+            ->execute();
477
+
478
+    }
479
+
480
+    /**
481
+     * Returns all cards for a specific addressbook id.
482
+     *
483
+     * This method should return the following properties for each card:
484
+     *   * carddata - raw vcard data
485
+     *   * uri - Some unique url
486
+     *   * lastmodified - A unix timestamp
487
+     *
488
+     * It's recommended to also return the following properties:
489
+     *   * etag - A unique etag. This must change every time the card changes.
490
+     *   * size - The size of the card in bytes.
491
+     *
492
+     * If these last two properties are provided, less time will be spent
493
+     * calculating them. If they are specified, you can also ommit carddata.
494
+     * This may speed up certain requests, especially with large cards.
495
+     *
496
+     * @param mixed $addressBookId
497
+     * @return array
498
+     */
499
+    function getCards($addressBookId) {
500
+        $query = $this->db->getQueryBuilder();
501
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
502
+            ->from('cards')
503
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
504
+
505
+        $cards = [];
506
+
507
+        $result = $query->execute();
508
+        while($row = $result->fetch()) {
509
+            $row['etag'] = '"' . $row['etag'] . '"';
510
+            $row['carddata'] = $this->readBlob($row['carddata']);
511
+            $cards[] = $row;
512
+        }
513
+        $result->closeCursor();
514
+
515
+        return $cards;
516
+    }
517
+
518
+    /**
519
+     * Returns a specific card.
520
+     *
521
+     * The same set of properties must be returned as with getCards. The only
522
+     * exception is that 'carddata' is absolutely required.
523
+     *
524
+     * If the card does not exist, you must return false.
525
+     *
526
+     * @param mixed $addressBookId
527
+     * @param string $cardUri
528
+     * @return array
529
+     */
530
+    function getCard($addressBookId, $cardUri) {
531
+        $query = $this->db->getQueryBuilder();
532
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
533
+            ->from('cards')
534
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
535
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
536
+            ->setMaxResults(1);
537
+
538
+        $result = $query->execute();
539
+        $row = $result->fetch();
540
+        if (!$row) {
541
+            return false;
542
+        }
543
+        $row['etag'] = '"' . $row['etag'] . '"';
544
+        $row['carddata'] = $this->readBlob($row['carddata']);
545
+
546
+        return $row;
547
+    }
548
+
549
+    /**
550
+     * Returns a list of cards.
551
+     *
552
+     * This method should work identical to getCard, but instead return all the
553
+     * cards in the list as an array.
554
+     *
555
+     * If the backend supports this, it may allow for some speed-ups.
556
+     *
557
+     * @param mixed $addressBookId
558
+     * @param string[] $uris
559
+     * @return array
560
+     */
561
+    function getMultipleCards($addressBookId, array $uris) {
562
+        if (empty($uris)) {
563
+            return [];
564
+        }
565
+
566
+        $chunks = array_chunk($uris, 100);
567
+        $cards = [];
568
+
569
+        $query = $this->db->getQueryBuilder();
570
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
571
+            ->from('cards')
572
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
573
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
574
+
575
+        foreach ($chunks as $uris) {
576
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
577
+            $result = $query->execute();
578
+
579
+            while ($row = $result->fetch()) {
580
+                $row['etag'] = '"' . $row['etag'] . '"';
581
+                $row['carddata'] = $this->readBlob($row['carddata']);
582
+                $cards[] = $row;
583
+            }
584
+            $result->closeCursor();
585
+        }
586
+        return $cards;
587
+    }
588
+
589
+    /**
590
+     * Creates a new card.
591
+     *
592
+     * The addressbook id will be passed as the first argument. This is the
593
+     * same id as it is returned from the getAddressBooksForUser method.
594
+     *
595
+     * The cardUri is a base uri, and doesn't include the full path. The
596
+     * cardData argument is the vcard body, and is passed as a string.
597
+     *
598
+     * It is possible to return an ETag from this method. This ETag is for the
599
+     * newly created resource, and must be enclosed with double quotes (that
600
+     * is, the string itself must contain the double quotes).
601
+     *
602
+     * You should only return the ETag if you store the carddata as-is. If a
603
+     * subsequent GET request on the same card does not have the same body,
604
+     * byte-by-byte and you did return an ETag here, clients tend to get
605
+     * confused.
606
+     *
607
+     * If you don't return an ETag, you can just return null.
608
+     *
609
+     * @param mixed $addressBookId
610
+     * @param string $cardUri
611
+     * @param string $cardData
612
+     * @return string
613
+     */
614
+    function createCard($addressBookId, $cardUri, $cardData) {
615
+        $etag = md5($cardData);
616
+        $uid = $this->getUID($cardData);
617
+
618
+        $q = $this->db->getQueryBuilder();
619
+        $q->select('uid')
620
+            ->from('cards')
621
+            ->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
622
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
623
+            ->setMaxResults(1);
624
+        $result = $q->execute();
625
+        $count = (bool) $result->fetchColumn();
626
+        $result->closeCursor();
627
+        if ($count) {
628
+            throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
629
+        }
630
+
631
+        $query = $this->db->getQueryBuilder();
632
+        $query->insert('cards')
633
+            ->values([
634
+                'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
635
+                'uri' => $query->createNamedParameter($cardUri),
636
+                'lastmodified' => $query->createNamedParameter(time()),
637
+                'addressbookid' => $query->createNamedParameter($addressBookId),
638
+                'size' => $query->createNamedParameter(strlen($cardData)),
639
+                'etag' => $query->createNamedParameter($etag),
640
+                'uid' => $query->createNamedParameter($uid),
641
+            ])
642
+            ->execute();
643
+
644
+        $this->addChange($addressBookId, $cardUri, 1);
645
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
646
+
647
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
648
+            new GenericEvent(null, [
649
+                'addressBookId' => $addressBookId,
650
+                'cardUri' => $cardUri,
651
+                'cardData' => $cardData]));
652
+
653
+        return '"' . $etag . '"';
654
+    }
655
+
656
+    /**
657
+     * Updates a card.
658
+     *
659
+     * The addressbook id will be passed as the first argument. This is the
660
+     * same id as it is returned from the getAddressBooksForUser method.
661
+     *
662
+     * The cardUri is a base uri, and doesn't include the full path. The
663
+     * cardData argument is the vcard body, and is passed as a string.
664
+     *
665
+     * It is possible to return an ETag from this method. This ETag should
666
+     * match that of the updated resource, and must be enclosed with double
667
+     * quotes (that is: the string itself must contain the actual quotes).
668
+     *
669
+     * You should only return the ETag if you store the carddata as-is. If a
670
+     * subsequent GET request on the same card does not have the same body,
671
+     * byte-by-byte and you did return an ETag here, clients tend to get
672
+     * confused.
673
+     *
674
+     * If you don't return an ETag, you can just return null.
675
+     *
676
+     * @param mixed $addressBookId
677
+     * @param string $cardUri
678
+     * @param string $cardData
679
+     * @return string
680
+     */
681
+    function updateCard($addressBookId, $cardUri, $cardData) {
682
+
683
+        $uid = $this->getUID($cardData);
684
+        $etag = md5($cardData);
685
+        $query = $this->db->getQueryBuilder();
686
+        $query->update('cards')
687
+            ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
688
+            ->set('lastmodified', $query->createNamedParameter(time()))
689
+            ->set('size', $query->createNamedParameter(strlen($cardData)))
690
+            ->set('etag', $query->createNamedParameter($etag))
691
+            ->set('uid', $query->createNamedParameter($uid))
692
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
693
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
694
+            ->execute();
695
+
696
+        $this->addChange($addressBookId, $cardUri, 2);
697
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
698
+
699
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
700
+            new GenericEvent(null, [
701
+                'addressBookId' => $addressBookId,
702
+                'cardUri' => $cardUri,
703
+                'cardData' => $cardData]));
704
+
705
+        return '"' . $etag . '"';
706
+    }
707
+
708
+    /**
709
+     * Deletes a card
710
+     *
711
+     * @param mixed $addressBookId
712
+     * @param string $cardUri
713
+     * @return bool
714
+     */
715
+    function deleteCard($addressBookId, $cardUri) {
716
+        try {
717
+            $cardId = $this->getCardId($addressBookId, $cardUri);
718
+        } catch (\InvalidArgumentException $e) {
719
+            $cardId = null;
720
+        }
721
+        $query = $this->db->getQueryBuilder();
722
+        $ret = $query->delete('cards')
723
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
724
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
725
+            ->execute();
726
+
727
+        $this->addChange($addressBookId, $cardUri, 3);
728
+
729
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
730
+            new GenericEvent(null, [
731
+                'addressBookId' => $addressBookId,
732
+                'cardUri' => $cardUri]));
733
+
734
+        if ($ret === 1) {
735
+            if ($cardId !== null) {
736
+                $this->purgeProperties($addressBookId, $cardId);
737
+            }
738
+            return true;
739
+        }
740
+
741
+        return false;
742
+    }
743
+
744
+    /**
745
+     * The getChanges method returns all the changes that have happened, since
746
+     * the specified syncToken in the specified address book.
747
+     *
748
+     * This function should return an array, such as the following:
749
+     *
750
+     * [
751
+     *   'syncToken' => 'The current synctoken',
752
+     *   'added'   => [
753
+     *      'new.txt',
754
+     *   ],
755
+     *   'modified'   => [
756
+     *      'modified.txt',
757
+     *   ],
758
+     *   'deleted' => [
759
+     *      'foo.php.bak',
760
+     *      'old.txt'
761
+     *   ]
762
+     * ];
763
+     *
764
+     * The returned syncToken property should reflect the *current* syncToken
765
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
766
+     * property. This is needed here too, to ensure the operation is atomic.
767
+     *
768
+     * If the $syncToken argument is specified as null, this is an initial
769
+     * sync, and all members should be reported.
770
+     *
771
+     * The modified property is an array of nodenames that have changed since
772
+     * the last token.
773
+     *
774
+     * The deleted property is an array with nodenames, that have been deleted
775
+     * from collection.
776
+     *
777
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
778
+     * 1, you only have to report changes that happened only directly in
779
+     * immediate descendants. If it's 2, it should also include changes from
780
+     * the nodes below the child collections. (grandchildren)
781
+     *
782
+     * The $limit argument allows a client to specify how many results should
783
+     * be returned at most. If the limit is not specified, it should be treated
784
+     * as infinite.
785
+     *
786
+     * If the limit (infinite or not) is higher than you're willing to return,
787
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
788
+     *
789
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
790
+     * return null.
791
+     *
792
+     * The limit is 'suggestive'. You are free to ignore it.
793
+     *
794
+     * @param string $addressBookId
795
+     * @param string $syncToken
796
+     * @param int $syncLevel
797
+     * @param int $limit
798
+     * @return array
799
+     */
800
+    function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
801
+        // Current synctoken
802
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
803
+        $stmt->execute([ $addressBookId ]);
804
+        $currentToken = $stmt->fetchColumn(0);
805
+
806
+        if (is_null($currentToken)) return null;
807
+
808
+        $result = [
809
+            'syncToken' => $currentToken,
810
+            'added'     => [],
811
+            'modified'  => [],
812
+            'deleted'   => [],
813
+        ];
814
+
815
+        if ($syncToken) {
816
+
817
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
818
+            if ($limit>0) {
819
+                $query .= " LIMIT " . (int)$limit;
820
+            }
821
+
822
+            // Fetching all changes
823
+            $stmt = $this->db->prepare($query);
824
+            $stmt->execute([$syncToken, $currentToken, $addressBookId]);
825
+
826
+            $changes = [];
827
+
828
+            // This loop ensures that any duplicates are overwritten, only the
829
+            // last change on a node is relevant.
830
+            while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
831
+
832
+                $changes[$row['uri']] = $row['operation'];
833
+
834
+            }
835
+
836
+            foreach($changes as $uri => $operation) {
837
+
838
+                switch($operation) {
839
+                    case 1:
840
+                        $result['added'][] = $uri;
841
+                        break;
842
+                    case 2:
843
+                        $result['modified'][] = $uri;
844
+                        break;
845
+                    case 3:
846
+                        $result['deleted'][] = $uri;
847
+                        break;
848
+                }
849
+
850
+            }
851
+        } else {
852
+            // No synctoken supplied, this is the initial sync.
853
+            $query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
854
+            $stmt = $this->db->prepare($query);
855
+            $stmt->execute([$addressBookId]);
856
+
857
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
858
+        }
859
+        return $result;
860
+    }
861
+
862
+    /**
863
+     * Adds a change record to the addressbookchanges table.
864
+     *
865
+     * @param mixed $addressBookId
866
+     * @param string $objectUri
867
+     * @param int $operation 1 = add, 2 = modify, 3 = delete
868
+     * @return void
869
+     */
870
+    protected function addChange($addressBookId, $objectUri, $operation) {
871
+        $sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
872
+        $stmt = $this->db->prepare($sql);
873
+        $stmt->execute([
874
+            $objectUri,
875
+            $addressBookId,
876
+            $operation,
877
+            $addressBookId
878
+        ]);
879
+        $stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
880
+        $stmt->execute([
881
+            $addressBookId
882
+        ]);
883
+    }
884
+
885
+    private function readBlob($cardData) {
886
+        if (is_resource($cardData)) {
887
+            return stream_get_contents($cardData);
888
+        }
889
+
890
+        return $cardData;
891
+    }
892
+
893
+    /**
894
+     * @param IShareable $shareable
895
+     * @param string[] $add
896
+     * @param string[] $remove
897
+     */
898
+    public function updateShares(IShareable $shareable, $add, $remove) {
899
+        $this->sharingBackend->updateShares($shareable, $add, $remove);
900
+    }
901
+
902
+    /**
903
+     * search contact
904
+     *
905
+     * @param int $addressBookId
906
+     * @param string $pattern which should match within the $searchProperties
907
+     * @param array $searchProperties defines the properties within the query pattern should match
908
+     * @param array $options = array() to define the search behavior
909
+     * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
910
+     * @return array an array of contacts which are arrays of key-value-pairs
911
+     */
912
+    public function search($addressBookId, $pattern, $searchProperties, $options = array()) {
913
+        $query = $this->db->getQueryBuilder();
914
+        $query2 = $this->db->getQueryBuilder();
915
+
916
+        $query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
917
+        $query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
918
+        $or = $query2->expr()->orX();
919
+        foreach ($searchProperties as $property) {
920
+            $or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property)));
921
+        }
922
+        $query2->andWhere($or);
923
+
924
+        // No need for like when the pattern is empty
925
+        if ('' !== $pattern) {
926
+            if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
927
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
928
+            } else {
929
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
930
+            }
931
+        }
932
+
933
+        $query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
934
+            ->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
935
+
936
+        $result = $query->execute();
937
+        $cards = $result->fetchAll();
938
+
939
+        $result->closeCursor();
940
+
941
+        return array_map(function($array) {
942
+            $array['carddata'] = $this->readBlob($array['carddata']);
943
+            return $array;
944
+        }, $cards);
945
+    }
946
+
947
+    /**
948
+     * @param int $bookId
949
+     * @param string $name
950
+     * @return array
951
+     */
952
+    public function collectCardProperties($bookId, $name) {
953
+        $query = $this->db->getQueryBuilder();
954
+        $result = $query->selectDistinct('value')
955
+            ->from($this->dbCardsPropertiesTable)
956
+            ->where($query->expr()->eq('name', $query->createNamedParameter($name)))
957
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
958
+            ->execute();
959
+
960
+        $all = $result->fetchAll(PDO::FETCH_COLUMN);
961
+        $result->closeCursor();
962
+
963
+        return $all;
964
+    }
965
+
966
+    /**
967
+     * get URI from a given contact
968
+     *
969
+     * @param int $id
970
+     * @return string
971
+     */
972
+    public function getCardUri($id) {
973
+        $query = $this->db->getQueryBuilder();
974
+        $query->select('uri')->from($this->dbCardsTable)
975
+                ->where($query->expr()->eq('id', $query->createParameter('id')))
976
+                ->setParameter('id', $id);
977
+
978
+        $result = $query->execute();
979
+        $uri = $result->fetch();
980
+        $result->closeCursor();
981
+
982
+        if (!isset($uri['uri'])) {
983
+            throw new \InvalidArgumentException('Card does not exists: ' . $id);
984
+        }
985
+
986
+        return $uri['uri'];
987
+    }
988
+
989
+    /**
990
+     * return contact with the given URI
991
+     *
992
+     * @param int $addressBookId
993
+     * @param string $uri
994
+     * @returns array
995
+     */
996
+    public function getContact($addressBookId, $uri) {
997
+        $result = [];
998
+        $query = $this->db->getQueryBuilder();
999
+        $query->select('*')->from($this->dbCardsTable)
1000
+                ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1001
+                ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1002
+        $queryResult = $query->execute();
1003
+        $contact = $queryResult->fetch();
1004
+        $queryResult->closeCursor();
1005
+
1006
+        if (is_array($contact)) {
1007
+            $result = $contact;
1008
+        }
1009
+
1010
+        return $result;
1011
+    }
1012
+
1013
+    /**
1014
+     * Returns the list of people whom this address book is shared with.
1015
+     *
1016
+     * Every element in this array should have the following properties:
1017
+     *   * href - Often a mailto: address
1018
+     *   * commonName - Optional, for example a first + last name
1019
+     *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1020
+     *   * readOnly - boolean
1021
+     *   * summary - Optional, a description for the share
1022
+     *
1023
+     * @return array
1024
+     */
1025
+    public function getShares($addressBookId) {
1026
+        return $this->sharingBackend->getShares($addressBookId);
1027
+    }
1028
+
1029
+    /**
1030
+     * update properties table
1031
+     *
1032
+     * @param int $addressBookId
1033
+     * @param string $cardUri
1034
+     * @param string $vCardSerialized
1035
+     */
1036
+    protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1037
+        $cardId = $this->getCardId($addressBookId, $cardUri);
1038
+        $vCard = $this->readCard($vCardSerialized);
1039
+
1040
+        $this->purgeProperties($addressBookId, $cardId);
1041
+
1042
+        $query = $this->db->getQueryBuilder();
1043
+        $query->insert($this->dbCardsPropertiesTable)
1044
+            ->values(
1045
+                [
1046
+                    'addressbookid' => $query->createNamedParameter($addressBookId),
1047
+                    'cardid' => $query->createNamedParameter($cardId),
1048
+                    'name' => $query->createParameter('name'),
1049
+                    'value' => $query->createParameter('value'),
1050
+                    'preferred' => $query->createParameter('preferred')
1051
+                ]
1052
+            );
1053
+
1054
+        foreach ($vCard->children() as $property) {
1055
+            if(!in_array($property->name, self::$indexProperties)) {
1056
+                continue;
1057
+            }
1058
+            $preferred = 0;
1059
+            foreach($property->parameters as $parameter) {
1060
+                if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1061
+                    $preferred = 1;
1062
+                    break;
1063
+                }
1064
+            }
1065
+            $query->setParameter('name', $property->name);
1066
+            $query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1067
+            $query->setParameter('preferred', $preferred);
1068
+            $query->execute();
1069
+        }
1070
+    }
1071
+
1072
+    /**
1073
+     * read vCard data into a vCard object
1074
+     *
1075
+     * @param string $cardData
1076
+     * @return VCard
1077
+     */
1078
+    protected function readCard($cardData) {
1079
+        return  Reader::read($cardData);
1080
+    }
1081
+
1082
+    /**
1083
+     * delete all properties from a given card
1084
+     *
1085
+     * @param int $addressBookId
1086
+     * @param int $cardId
1087
+     */
1088
+    protected function purgeProperties($addressBookId, $cardId) {
1089
+        $query = $this->db->getQueryBuilder();
1090
+        $query->delete($this->dbCardsPropertiesTable)
1091
+            ->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1092
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1093
+        $query->execute();
1094
+    }
1095
+
1096
+    /**
1097
+     * get ID from a given contact
1098
+     *
1099
+     * @param int $addressBookId
1100
+     * @param string $uri
1101
+     * @return int
1102
+     */
1103
+    protected function getCardId($addressBookId, $uri) {
1104
+        $query = $this->db->getQueryBuilder();
1105
+        $query->select('id')->from($this->dbCardsTable)
1106
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1107
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1108
+
1109
+        $result = $query->execute();
1110
+        $cardIds = $result->fetch();
1111
+        $result->closeCursor();
1112
+
1113
+        if (!isset($cardIds['id'])) {
1114
+            throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1115
+        }
1116
+
1117
+        return (int)$cardIds['id'];
1118
+    }
1119
+
1120
+    /**
1121
+     * For shared address books the sharee is set in the ACL of the address book
1122
+     * @param $addressBookId
1123
+     * @param $acl
1124
+     * @return array
1125
+     */
1126
+    public function applyShareAcl($addressBookId, $acl) {
1127
+        return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1128
+    }
1129
+
1130
+    private function convertPrincipal($principalUri, $toV2) {
1131
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1132
+            list(, $name) = \Sabre\Uri\split($principalUri);
1133
+            if ($toV2 === true) {
1134
+                return "principals/users/$name";
1135
+            }
1136
+            return "principals/$name";
1137
+        }
1138
+        return $principalUri;
1139
+    }
1140
+
1141
+    private function addOwnerPrincipal(&$addressbookInfo) {
1142
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1143
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1144
+        if (isset($addressbookInfo[$ownerPrincipalKey])) {
1145
+            $uri = $addressbookInfo[$ownerPrincipalKey];
1146
+        } else {
1147
+            $uri = $addressbookInfo['principaluri'];
1148
+        }
1149
+
1150
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1151
+        if (isset($principalInformation['{DAV:}displayname'])) {
1152
+            $addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1153
+        }
1154
+    }
1155
+
1156
+    /**
1157
+     * Extract UID from vcard
1158
+     *
1159
+     * @param string $cardData the vcard raw data
1160
+     * @return string the uid
1161
+     * @throws BadRequest if no UID is available
1162
+     */
1163
+    private function getUID($cardData) {
1164
+        if ($cardData != '') {
1165
+            $vCard = Reader::read($cardData);
1166
+            if ($vCard->UID) {
1167
+                $uid = $vCard->UID->getValue();
1168
+                return $uid;
1169
+            }
1170
+            // should already be handled, but just in case
1171
+            throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1172
+        }
1173
+        // should already be handled, but just in case
1174
+        throw new BadRequest('vCard can not be empty');
1175
+    }
1176 1176
 }
Please login to merge, or discard this patch.