Completed
Pull Request — master (#5859)
by Robin
30:03 queued 14:10
created
apps/dav/lib/Comments/CommentsPlugin.php 3 patches
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -138,7 +138,7 @@
 block discarded – undo
138 138
 	 * This will be used in the {DAV:}supported-report-set property.
139 139
 	 *
140 140
 	 * @param string $uri
141
-	 * @return array
141
+	 * @return string[]
142 142
 	 */
143 143
 	public function getSupportedReportSet($uri) {
144 144
 		return [self::REPORT_NAME];
Please login to merge, or discard this patch.
Indentation   +206 added lines, -206 removed lines patch added patch discarded remove patch
@@ -43,210 +43,210 @@
 block discarded – undo
43 43
  * Sabre plugin to handle comments:
44 44
  */
45 45
 class CommentsPlugin extends ServerPlugin {
46
-	// namespace
47
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
48
-
49
-	const REPORT_NAME = '{http://owncloud.org/ns}filter-comments';
50
-	const REPORT_PARAM_LIMIT = '{http://owncloud.org/ns}limit';
51
-	const REPORT_PARAM_OFFSET = '{http://owncloud.org/ns}offset';
52
-	const REPORT_PARAM_TIMESTAMP = '{http://owncloud.org/ns}datetime';
53
-
54
-	/** @var ICommentsManager  */
55
-	protected $commentsManager;
56
-
57
-	/** @var \Sabre\DAV\Server $server */
58
-	private $server;
59
-
60
-	/** @var  \OCP\IUserSession */
61
-	protected $userSession;
62
-
63
-	/**
64
-	 * Comments plugin
65
-	 *
66
-	 * @param ICommentsManager $commentsManager
67
-	 * @param IUserSession $userSession
68
-	 */
69
-	public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) {
70
-		$this->commentsManager = $commentsManager;
71
-		$this->userSession = $userSession;
72
-	}
73
-
74
-	/**
75
-	 * This initializes the plugin.
76
-	 *
77
-	 * This function is called by Sabre\DAV\Server, after
78
-	 * addPlugin is called.
79
-	 *
80
-	 * This method should set up the required event subscriptions.
81
-	 *
82
-	 * @param Server $server
83
-	 * @return void
84
-	 */
85
-	public function initialize(Server $server) {
86
-		$this->server = $server;
87
-		if (strpos($this->server->getRequestUri(), 'comments/') !== 0) {
88
-			return;
89
-		}
90
-
91
-		$this->server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
92
-
93
-		$this->server->xml->classMap['DateTime'] = function (Writer $writer, \DateTime $value) {
94
-			$writer->write(\Sabre\HTTP\toDate($value));
95
-		};
96
-
97
-		$this->server->on('report', [$this, 'onReport']);
98
-		$this->server->on('method:POST', [$this, 'httpPost']);
99
-	}
100
-
101
-	/**
102
-	 * POST operation on Comments collections
103
-	 *
104
-	 * @param RequestInterface $request request object
105
-	 * @param ResponseInterface $response response object
106
-	 * @return null|false
107
-	 */
108
-	public function httpPost(RequestInterface $request, ResponseInterface $response) {
109
-		$path = $request->getPath();
110
-		$node = $this->server->tree->getNodeForPath($path);
111
-		if (!$node instanceof EntityCollection) {
112
-			return null;
113
-		}
114
-
115
-		$data = $request->getBodyAsString();
116
-		$comment = $this->createComment(
117
-			$node->getName(),
118
-			$node->getId(),
119
-			$data,
120
-			$request->getHeader('Content-Type')
121
-		);
122
-
123
-		// update read marker for the current user/poster to avoid
124
-		// having their own comments marked as unread
125
-		$node->setReadMarker(null);
126
-
127
-		$url = rtrim($request->getUrl(), '/') . '/' . urlencode($comment->getId());
128
-
129
-		$response->setHeader('Content-Location', $url);
130
-
131
-		// created
132
-		$response->setStatus(201);
133
-		return false;
134
-	}
135
-
136
-	/**
137
-	 * Returns a list of reports this plugin supports.
138
-	 *
139
-	 * This will be used in the {DAV:}supported-report-set property.
140
-	 *
141
-	 * @param string $uri
142
-	 * @return array
143
-	 */
144
-	public function getSupportedReportSet($uri) {
145
-		return [self::REPORT_NAME];
146
-	}
147
-
148
-	/**
149
-	 * REPORT operations to look for comments
150
-	 *
151
-	 * @param string $reportName
152
-	 * @param array $report
153
-	 * @param string $uri
154
-	 * @return bool
155
-	 * @throws NotFound
156
-	 * @throws ReportNotSupported
157
-	 */
158
-	public function onReport($reportName, $report, $uri) {
159
-		$node = $this->server->tree->getNodeForPath($uri);
160
-		if (!$node instanceof EntityCollection || $reportName !== self::REPORT_NAME) {
161
-			throw new ReportNotSupported();
162
-		}
163
-		$args = ['limit' => 0, 'offset' => 0, 'datetime' => null];
164
-		$acceptableParameters = [
165
-			$this::REPORT_PARAM_LIMIT,
166
-			$this::REPORT_PARAM_OFFSET,
167
-			$this::REPORT_PARAM_TIMESTAMP
168
-		];
169
-		$ns = '{' . $this::NS_OWNCLOUD . '}';
170
-		foreach ($report as $parameter) {
171
-			if (!in_array($parameter['name'], $acceptableParameters) || empty($parameter['value'])) {
172
-				continue;
173
-			}
174
-			$args[str_replace($ns, '', $parameter['name'])] = $parameter['value'];
175
-		}
176
-
177
-		if (!is_null($args['datetime'])) {
178
-			$args['datetime'] = new \DateTime($args['datetime']);
179
-		}
180
-
181
-		$results = $node->findChildren($args['limit'], $args['offset'], $args['datetime']);
182
-
183
-		$responses = [];
184
-		foreach ($results as $node) {
185
-			$nodePath = $this->server->getRequestUri() . '/' . $node->comment->getId();
186
-			$resultSet = $this->server->getPropertiesForPath($nodePath, CommentNode::getPropertyNames());
187
-			if (isset($resultSet[0]) && isset($resultSet[0][200])) {
188
-				$responses[] = new Response(
189
-					$this->server->getBaseUri() . $nodePath,
190
-					[200 => $resultSet[0][200]],
191
-					200
192
-				);
193
-			}
194
-		}
195
-
196
-		$xml = $this->server->xml->write(
197
-			'{DAV:}multistatus',
198
-			new MultiStatus($responses)
199
-		);
200
-
201
-		$this->server->httpResponse->setStatus(207);
202
-		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
203
-		$this->server->httpResponse->setBody($xml);
204
-
205
-		return false;
206
-	}
207
-
208
-	/**
209
-	 * Creates a new comment
210
-	 *
211
-	 * @param string $objectType e.g. "files"
212
-	 * @param string $objectId e.g. the file id
213
-	 * @param string $data JSON encoded string containing the properties of the tag to create
214
-	 * @param string $contentType content type of the data
215
-	 * @return IComment newly created comment
216
-	 *
217
-	 * @throws BadRequest if a field was missing
218
-	 * @throws UnsupportedMediaType if the content type is not supported
219
-	 */
220
-	private function createComment($objectType, $objectId, $data, $contentType = 'application/json') {
221
-		if (explode(';', $contentType)[0] === 'application/json') {
222
-			$data = json_decode($data, true);
223
-		} else {
224
-			throw new UnsupportedMediaType();
225
-		}
226
-
227
-		$actorType = $data['actorType'];
228
-		$actorId = null;
229
-		if ($actorType === 'users') {
230
-			$user = $this->userSession->getUser();
231
-			if (!is_null($user)) {
232
-				$actorId = $user->getUID();
233
-			}
234
-		}
235
-		if (is_null($actorId)) {
236
-			throw new BadRequest('Invalid actor "' .  $actorType .'"');
237
-		}
238
-
239
-		try {
240
-			$comment = $this->commentsManager->create($actorType, $actorId, $objectType, $objectId);
241
-			$comment->setMessage($data['message']);
242
-			$comment->setVerb($data['verb']);
243
-			$this->commentsManager->save($comment);
244
-			return $comment;
245
-		} catch (\InvalidArgumentException $e) {
246
-			throw new BadRequest('Invalid input values', 0, $e);
247
-		} catch (\OCP\Comments\MessageTooLongException $e) {
248
-			$msg = 'Message exceeds allowed character limit of ';
249
-			throw new BadRequest($msg . \OCP\Comments\IComment::MAX_MESSAGE_LENGTH, 0, $e);
250
-		}
251
-	}
46
+    // namespace
47
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
48
+
49
+    const REPORT_NAME = '{http://owncloud.org/ns}filter-comments';
50
+    const REPORT_PARAM_LIMIT = '{http://owncloud.org/ns}limit';
51
+    const REPORT_PARAM_OFFSET = '{http://owncloud.org/ns}offset';
52
+    const REPORT_PARAM_TIMESTAMP = '{http://owncloud.org/ns}datetime';
53
+
54
+    /** @var ICommentsManager  */
55
+    protected $commentsManager;
56
+
57
+    /** @var \Sabre\DAV\Server $server */
58
+    private $server;
59
+
60
+    /** @var  \OCP\IUserSession */
61
+    protected $userSession;
62
+
63
+    /**
64
+     * Comments plugin
65
+     *
66
+     * @param ICommentsManager $commentsManager
67
+     * @param IUserSession $userSession
68
+     */
69
+    public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) {
70
+        $this->commentsManager = $commentsManager;
71
+        $this->userSession = $userSession;
72
+    }
73
+
74
+    /**
75
+     * This initializes the plugin.
76
+     *
77
+     * This function is called by Sabre\DAV\Server, after
78
+     * addPlugin is called.
79
+     *
80
+     * This method should set up the required event subscriptions.
81
+     *
82
+     * @param Server $server
83
+     * @return void
84
+     */
85
+    public function initialize(Server $server) {
86
+        $this->server = $server;
87
+        if (strpos($this->server->getRequestUri(), 'comments/') !== 0) {
88
+            return;
89
+        }
90
+
91
+        $this->server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
92
+
93
+        $this->server->xml->classMap['DateTime'] = function (Writer $writer, \DateTime $value) {
94
+            $writer->write(\Sabre\HTTP\toDate($value));
95
+        };
96
+
97
+        $this->server->on('report', [$this, 'onReport']);
98
+        $this->server->on('method:POST', [$this, 'httpPost']);
99
+    }
100
+
101
+    /**
102
+     * POST operation on Comments collections
103
+     *
104
+     * @param RequestInterface $request request object
105
+     * @param ResponseInterface $response response object
106
+     * @return null|false
107
+     */
108
+    public function httpPost(RequestInterface $request, ResponseInterface $response) {
109
+        $path = $request->getPath();
110
+        $node = $this->server->tree->getNodeForPath($path);
111
+        if (!$node instanceof EntityCollection) {
112
+            return null;
113
+        }
114
+
115
+        $data = $request->getBodyAsString();
116
+        $comment = $this->createComment(
117
+            $node->getName(),
118
+            $node->getId(),
119
+            $data,
120
+            $request->getHeader('Content-Type')
121
+        );
122
+
123
+        // update read marker for the current user/poster to avoid
124
+        // having their own comments marked as unread
125
+        $node->setReadMarker(null);
126
+
127
+        $url = rtrim($request->getUrl(), '/') . '/' . urlencode($comment->getId());
128
+
129
+        $response->setHeader('Content-Location', $url);
130
+
131
+        // created
132
+        $response->setStatus(201);
133
+        return false;
134
+    }
135
+
136
+    /**
137
+     * Returns a list of reports this plugin supports.
138
+     *
139
+     * This will be used in the {DAV:}supported-report-set property.
140
+     *
141
+     * @param string $uri
142
+     * @return array
143
+     */
144
+    public function getSupportedReportSet($uri) {
145
+        return [self::REPORT_NAME];
146
+    }
147
+
148
+    /**
149
+     * REPORT operations to look for comments
150
+     *
151
+     * @param string $reportName
152
+     * @param array $report
153
+     * @param string $uri
154
+     * @return bool
155
+     * @throws NotFound
156
+     * @throws ReportNotSupported
157
+     */
158
+    public function onReport($reportName, $report, $uri) {
159
+        $node = $this->server->tree->getNodeForPath($uri);
160
+        if (!$node instanceof EntityCollection || $reportName !== self::REPORT_NAME) {
161
+            throw new ReportNotSupported();
162
+        }
163
+        $args = ['limit' => 0, 'offset' => 0, 'datetime' => null];
164
+        $acceptableParameters = [
165
+            $this::REPORT_PARAM_LIMIT,
166
+            $this::REPORT_PARAM_OFFSET,
167
+            $this::REPORT_PARAM_TIMESTAMP
168
+        ];
169
+        $ns = '{' . $this::NS_OWNCLOUD . '}';
170
+        foreach ($report as $parameter) {
171
+            if (!in_array($parameter['name'], $acceptableParameters) || empty($parameter['value'])) {
172
+                continue;
173
+            }
174
+            $args[str_replace($ns, '', $parameter['name'])] = $parameter['value'];
175
+        }
176
+
177
+        if (!is_null($args['datetime'])) {
178
+            $args['datetime'] = new \DateTime($args['datetime']);
179
+        }
180
+
181
+        $results = $node->findChildren($args['limit'], $args['offset'], $args['datetime']);
182
+
183
+        $responses = [];
184
+        foreach ($results as $node) {
185
+            $nodePath = $this->server->getRequestUri() . '/' . $node->comment->getId();
186
+            $resultSet = $this->server->getPropertiesForPath($nodePath, CommentNode::getPropertyNames());
187
+            if (isset($resultSet[0]) && isset($resultSet[0][200])) {
188
+                $responses[] = new Response(
189
+                    $this->server->getBaseUri() . $nodePath,
190
+                    [200 => $resultSet[0][200]],
191
+                    200
192
+                );
193
+            }
194
+        }
195
+
196
+        $xml = $this->server->xml->write(
197
+            '{DAV:}multistatus',
198
+            new MultiStatus($responses)
199
+        );
200
+
201
+        $this->server->httpResponse->setStatus(207);
202
+        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
203
+        $this->server->httpResponse->setBody($xml);
204
+
205
+        return false;
206
+    }
207
+
208
+    /**
209
+     * Creates a new comment
210
+     *
211
+     * @param string $objectType e.g. "files"
212
+     * @param string $objectId e.g. the file id
213
+     * @param string $data JSON encoded string containing the properties of the tag to create
214
+     * @param string $contentType content type of the data
215
+     * @return IComment newly created comment
216
+     *
217
+     * @throws BadRequest if a field was missing
218
+     * @throws UnsupportedMediaType if the content type is not supported
219
+     */
220
+    private function createComment($objectType, $objectId, $data, $contentType = 'application/json') {
221
+        if (explode(';', $contentType)[0] === 'application/json') {
222
+            $data = json_decode($data, true);
223
+        } else {
224
+            throw new UnsupportedMediaType();
225
+        }
226
+
227
+        $actorType = $data['actorType'];
228
+        $actorId = null;
229
+        if ($actorType === 'users') {
230
+            $user = $this->userSession->getUser();
231
+            if (!is_null($user)) {
232
+                $actorId = $user->getUID();
233
+            }
234
+        }
235
+        if (is_null($actorId)) {
236
+            throw new BadRequest('Invalid actor "' .  $actorType .'"');
237
+        }
238
+
239
+        try {
240
+            $comment = $this->commentsManager->create($actorType, $actorId, $objectType, $objectId);
241
+            $comment->setMessage($data['message']);
242
+            $comment->setVerb($data['verb']);
243
+            $this->commentsManager->save($comment);
244
+            return $comment;
245
+        } catch (\InvalidArgumentException $e) {
246
+            throw new BadRequest('Invalid input values', 0, $e);
247
+        } catch (\OCP\Comments\MessageTooLongException $e) {
248
+            $msg = 'Message exceeds allowed character limit of ';
249
+            throw new BadRequest($msg . \OCP\Comments\IComment::MAX_MESSAGE_LENGTH, 0, $e);
250
+        }
251
+    }
252 252
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -90,7 +90,7 @@  discard block
 block discarded – undo
90 90
 
91 91
 		$this->server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
92 92
 
93
-		$this->server->xml->classMap['DateTime'] = function (Writer $writer, \DateTime $value) {
93
+		$this->server->xml->classMap['DateTime'] = function(Writer $writer, \DateTime $value) {
94 94
 			$writer->write(\Sabre\HTTP\toDate($value));
95 95
 		};
96 96
 
@@ -124,7 +124,7 @@  discard block
 block discarded – undo
124 124
 		// having their own comments marked as unread
125 125
 		$node->setReadMarker(null);
126 126
 
127
-		$url = rtrim($request->getUrl(), '/') . '/' . urlencode($comment->getId());
127
+		$url = rtrim($request->getUrl(), '/').'/'.urlencode($comment->getId());
128 128
 
129 129
 		$response->setHeader('Content-Location', $url);
130 130
 
@@ -166,7 +166,7 @@  discard block
 block discarded – undo
166 166
 			$this::REPORT_PARAM_OFFSET,
167 167
 			$this::REPORT_PARAM_TIMESTAMP
168 168
 		];
169
-		$ns = '{' . $this::NS_OWNCLOUD . '}';
169
+		$ns = '{'.$this::NS_OWNCLOUD.'}';
170 170
 		foreach ($report as $parameter) {
171 171
 			if (!in_array($parameter['name'], $acceptableParameters) || empty($parameter['value'])) {
172 172
 				continue;
@@ -182,11 +182,11 @@  discard block
 block discarded – undo
182 182
 
183 183
 		$responses = [];
184 184
 		foreach ($results as $node) {
185
-			$nodePath = $this->server->getRequestUri() . '/' . $node->comment->getId();
185
+			$nodePath = $this->server->getRequestUri().'/'.$node->comment->getId();
186 186
 			$resultSet = $this->server->getPropertiesForPath($nodePath, CommentNode::getPropertyNames());
187 187
 			if (isset($resultSet[0]) && isset($resultSet[0][200])) {
188 188
 				$responses[] = new Response(
189
-					$this->server->getBaseUri() . $nodePath,
189
+					$this->server->getBaseUri().$nodePath,
190 190
 					[200 => $resultSet[0][200]],
191 191
 					200
192 192
 				);
@@ -233,7 +233,7 @@  discard block
 block discarded – undo
233 233
 			}
234 234
 		}
235 235
 		if (is_null($actorId)) {
236
-			throw new BadRequest('Invalid actor "' .  $actorType .'"');
236
+			throw new BadRequest('Invalid actor "'.$actorType.'"');
237 237
 		}
238 238
 
239 239
 		try {
@@ -246,7 +246,7 @@  discard block
 block discarded – undo
246 246
 			throw new BadRequest('Invalid input values', 0, $e);
247 247
 		} catch (\OCP\Comments\MessageTooLongException $e) {
248 248
 			$msg = 'Message exceeds allowed character limit of ';
249
-			throw new BadRequest($msg . \OCP\Comments\IComment::MAX_MESSAGE_LENGTH, 0, $e);
249
+			throw new BadRequest($msg.\OCP\Comments\IComment::MAX_MESSAGE_LENGTH, 0, $e);
250 250
 		}
251 251
 	}
252 252
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/CustomPropertiesBackend.php 3 patches
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -75,7 +75,7 @@
 block discarded – undo
75 75
 	private $cache = [];
76 76
 
77 77
 	/**
78
-	 * @param Tree $tree node tree
78
+	 * @param ObjectTree $tree node tree
79 79
 	 * @param IDBConnection $connection database connection
80 80
 	 * @param IUser $user owner of the tree and properties
81 81
 	 */
Please login to merge, or discard this patch.
Indentation   +321 added lines, -321 removed lines patch added patch discarded remove patch
@@ -36,325 +36,325 @@
 block discarded – undo
36 36
 
37 37
 class CustomPropertiesBackend implements BackendInterface {
38 38
 
39
-	/**
40
-	 * Ignored properties
41
-	 *
42
-	 * @var array
43
-	 */
44
-	private $ignoredProperties = [
45
-		'{DAV:}getcontentlength',
46
-		'{DAV:}getcontenttype',
47
-		'{DAV:}getetag',
48
-		'{DAV:}quota-used-bytes',
49
-		'{DAV:}quota-available-bytes',
50
-		'{DAV:}quota-available-bytes',
51
-		'{http://owncloud.org/ns}permissions',
52
-		'{http://owncloud.org/ns}downloadURL',
53
-		'{http://owncloud.org/ns}dDC',
54
-		'{http://owncloud.org/ns}size',
55
-	];
56
-
57
-	/**
58
-	 * @var Tree
59
-	 */
60
-	private $tree;
61
-
62
-	/**
63
-	 * @var IDBConnection
64
-	 */
65
-	private $connection;
66
-
67
-	/**
68
-	 * @var IUser
69
-	 */
70
-	private $user;
71
-
72
-	/**
73
-	 * Properties cache
74
-	 *
75
-	 * @var array
76
-	 */
77
-	private $cache = [];
78
-
79
-	/**
80
-	 * @param Tree $tree node tree
81
-	 * @param IDBConnection $connection database connection
82
-	 * @param IUser $user owner of the tree and properties
83
-	 */
84
-	public function __construct(
85
-		Tree $tree,
86
-		IDBConnection $connection,
87
-		IUser $user
88
-	) {
89
-		$this->tree = $tree;
90
-		$this->connection = $connection;
91
-		$this->user = $user->getUID();
92
-	}
93
-
94
-	/**
95
-	 * Fetches properties for a path.
96
-	 *
97
-	 * @param string $path
98
-	 * @param PropFind $propFind
99
-	 * @return void
100
-	 */
101
-	public function propFind($path, PropFind $propFind) {
102
-		try {
103
-			$node = $this->tree->getNodeForPath($path);
104
-			if (!($node instanceof Node)) {
105
-				return;
106
-			}
107
-		} catch (ServiceUnavailable $e) {
108
-			// might happen for unavailable mount points, skip
109
-			return;
110
-		} catch (NotFound $e) {
111
-			// in some rare (buggy) cases the node might not be found,
112
-			// we catch the exception to prevent breaking the whole list with a 404
113
-			// (soft fail)
114
-			\OC::$server->getLogger()->warning(
115
-				'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(),
116
-				['app' => 'files']
117
-			);
118
-			return;
119
-		}
120
-
121
-		$requestedProps = $propFind->get404Properties();
122
-
123
-		// these might appear
124
-		$requestedProps = array_diff(
125
-			$requestedProps,
126
-			$this->ignoredProperties
127
-		);
128
-
129
-		if (empty($requestedProps)) {
130
-			return;
131
-		}
132
-
133
-		if ($node instanceof Directory
134
-			&& $propFind->getDepth() !== 0
135
-		) {
136
-			// note: pre-fetching only supported for depth <= 1
137
-			$this->loadChildrenProperties($node, $requestedProps);
138
-		}
139
-
140
-		$props = $this->getProperties($node, $requestedProps);
141
-		foreach ($props as $propName => $propValue) {
142
-			$propFind->set($propName, $propValue);
143
-		}
144
-	}
145
-
146
-	/**
147
-	 * Updates properties for a path
148
-	 *
149
-	 * @param string $path
150
-	 * @param PropPatch $propPatch
151
-	 *
152
-	 * @return void
153
-	 */
154
-	public function propPatch($path, PropPatch $propPatch) {
155
-		$node = $this->tree->getNodeForPath($path);
156
-		if (!($node instanceof Node)) {
157
-			return;
158
-		}
159
-
160
-		$propPatch->handleRemaining(function ($changedProps) use ($node) {
161
-			return $this->updateProperties($node, $changedProps);
162
-		});
163
-	}
164
-
165
-	/**
166
-	 * This method is called after a node is deleted.
167
-	 *
168
-	 * @param string $path path of node for which to delete properties
169
-	 */
170
-	public function delete($path) {
171
-		$statement = $this->connection->prepare(
172
-			'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
173
-		);
174
-		$statement->execute([$this->user, '/' . $path]);
175
-		$statement->closeCursor();
176
-
177
-		unset($this->cache[$path]);
178
-	}
179
-
180
-	/**
181
-	 * This method is called after a successful MOVE
182
-	 *
183
-	 * @param string $source
184
-	 * @param string $destination
185
-	 *
186
-	 * @return void
187
-	 */
188
-	public function move($source, $destination) {
189
-		$statement = $this->connection->prepare(
190
-			'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
191
-			' WHERE `userid` = ? AND `propertypath` = ?'
192
-		);
193
-		$statement->execute(['/' . $destination, $this->user, '/' . $source]);
194
-		$statement->closeCursor();
195
-	}
196
-
197
-	/**
198
-	 * Returns a list of properties for this nodes.;
199
-	 * @param Node $node
200
-	 * @param array $requestedProperties requested properties or empty array for "all"
201
-	 * @return array
202
-	 * @note The properties list is a list of propertynames the client
203
-	 * requested, encoded as xmlnamespace#tagName, for example:
204
-	 * http://www.example.org/namespace#author If the array is empty, all
205
-	 * properties should be returned
206
-	 */
207
-	private function getProperties(Node $node, array $requestedProperties) {
208
-		$path = $node->getPath();
209
-		if (isset($this->cache[$path])) {
210
-			return $this->cache[$path];
211
-		}
212
-
213
-		// TODO: chunking if more than 1000 properties
214
-		$sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?';
215
-
216
-		$whereValues = [$this->user, $path];
217
-		$whereTypes = [null, null];
218
-
219
-		if (!empty($requestedProperties)) {
220
-			// request only a subset
221
-			$sql .= ' AND `propertyname` in (?)';
222
-			$whereValues[] = $requestedProperties;
223
-			$whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
224
-		}
225
-
226
-		$result = $this->connection->executeQuery(
227
-			$sql,
228
-			$whereValues,
229
-			$whereTypes
230
-		);
231
-
232
-		$props = [];
233
-		while ($row = $result->fetch()) {
234
-			$props[$row['propertyname']] = $row['propertyvalue'];
235
-		}
236
-
237
-		$result->closeCursor();
238
-
239
-		$this->cache[$path] = $props;
240
-		return $props;
241
-	}
242
-
243
-	/**
244
-	 * Update properties
245
-	 *
246
-	 * @param Node $node node for which to update properties
247
-	 * @param array $properties array of properties to update
248
-	 *
249
-	 * @return bool
250
-	 */
251
-	private function updateProperties($node, $properties) {
252
-		$path = $node->getPath();
253
-
254
-		$deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
255
-			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
256
-
257
-		$insertStatement = 'INSERT INTO `*PREFIX*properties`' .
258
-			' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
259
-
260
-		$updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
261
-			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
262
-
263
-		// TODO: use "insert or update" strategy ?
264
-		$existing = $this->getProperties($node, []);
265
-		$this->connection->beginTransaction();
266
-		foreach ($properties as $propertyName => $propertyValue) {
267
-			// If it was null, we need to delete the property
268
-			if (is_null($propertyValue)) {
269
-				if (array_key_exists($propertyName, $existing)) {
270
-					$this->connection->executeUpdate(
271
-						$deleteStatement,
272
-						[
273
-							$this->user,
274
-							$path,
275
-							$propertyName
276
-						]
277
-					);
278
-				}
279
-			} else {
280
-				if (!array_key_exists($propertyName, $existing)) {
281
-					$this->connection->executeUpdate(
282
-						$insertStatement,
283
-						[
284
-							$this->user,
285
-							$path,
286
-							$propertyName,
287
-							$propertyValue
288
-						]
289
-					);
290
-				} else {
291
-					$this->connection->executeUpdate(
292
-						$updateStatement,
293
-						[
294
-							$propertyValue,
295
-							$this->user,
296
-							$path,
297
-							$propertyName
298
-						]
299
-					);
300
-				}
301
-			}
302
-		}
303
-
304
-		$this->connection->commit();
305
-		unset($this->cache[$path]);
306
-
307
-		return true;
308
-	}
309
-
310
-	/**
311
-	 * Bulk load properties for directory children
312
-	 *
313
-	 * @param Directory $node
314
-	 * @param array $requestedProperties requested properties
315
-	 *
316
-	 * @return void
317
-	 */
318
-	private function loadChildrenProperties(Directory $node, $requestedProperties) {
319
-		$path = $node->getPath();
320
-		if (isset($this->cache[$path])) {
321
-			// we already loaded them at some point
322
-			return;
323
-		}
324
-
325
-		$childNodes = $node->getChildren();
326
-		// pre-fill cache
327
-		foreach ($childNodes as $childNode) {
328
-			$this->cache[$childNode->getPath()] = [];
329
-		}
330
-
331
-		$sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` LIKE ?';
332
-		$sql .= ' AND `propertyname` in (?) ORDER BY `propertypath`, `propertyname`';
333
-
334
-		$result = $this->connection->executeQuery(
335
-			$sql,
336
-			[$this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')) . '/%', $requestedProperties],
337
-			[null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY]
338
-		);
339
-
340
-		$oldPath = null;
341
-		$props = [];
342
-		while ($row = $result->fetch()) {
343
-			$path = $row['propertypath'];
344
-			if ($oldPath !== $path) {
345
-				// save previously gathered props
346
-				$this->cache[$oldPath] = $props;
347
-				$oldPath = $path;
348
-				// prepare props for next path
349
-				$props = [];
350
-			}
351
-			$props[$row['propertyname']] = $row['propertyvalue'];
352
-		}
353
-		if (!is_null($oldPath)) {
354
-			// save props from last run
355
-			$this->cache[$oldPath] = $props;
356
-		}
357
-
358
-		$result->closeCursor();
359
-	}
39
+    /**
40
+     * Ignored properties
41
+     *
42
+     * @var array
43
+     */
44
+    private $ignoredProperties = [
45
+        '{DAV:}getcontentlength',
46
+        '{DAV:}getcontenttype',
47
+        '{DAV:}getetag',
48
+        '{DAV:}quota-used-bytes',
49
+        '{DAV:}quota-available-bytes',
50
+        '{DAV:}quota-available-bytes',
51
+        '{http://owncloud.org/ns}permissions',
52
+        '{http://owncloud.org/ns}downloadURL',
53
+        '{http://owncloud.org/ns}dDC',
54
+        '{http://owncloud.org/ns}size',
55
+    ];
56
+
57
+    /**
58
+     * @var Tree
59
+     */
60
+    private $tree;
61
+
62
+    /**
63
+     * @var IDBConnection
64
+     */
65
+    private $connection;
66
+
67
+    /**
68
+     * @var IUser
69
+     */
70
+    private $user;
71
+
72
+    /**
73
+     * Properties cache
74
+     *
75
+     * @var array
76
+     */
77
+    private $cache = [];
78
+
79
+    /**
80
+     * @param Tree $tree node tree
81
+     * @param IDBConnection $connection database connection
82
+     * @param IUser $user owner of the tree and properties
83
+     */
84
+    public function __construct(
85
+        Tree $tree,
86
+        IDBConnection $connection,
87
+        IUser $user
88
+    ) {
89
+        $this->tree = $tree;
90
+        $this->connection = $connection;
91
+        $this->user = $user->getUID();
92
+    }
93
+
94
+    /**
95
+     * Fetches properties for a path.
96
+     *
97
+     * @param string $path
98
+     * @param PropFind $propFind
99
+     * @return void
100
+     */
101
+    public function propFind($path, PropFind $propFind) {
102
+        try {
103
+            $node = $this->tree->getNodeForPath($path);
104
+            if (!($node instanceof Node)) {
105
+                return;
106
+            }
107
+        } catch (ServiceUnavailable $e) {
108
+            // might happen for unavailable mount points, skip
109
+            return;
110
+        } catch (NotFound $e) {
111
+            // in some rare (buggy) cases the node might not be found,
112
+            // we catch the exception to prevent breaking the whole list with a 404
113
+            // (soft fail)
114
+            \OC::$server->getLogger()->warning(
115
+                'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(),
116
+                ['app' => 'files']
117
+            );
118
+            return;
119
+        }
120
+
121
+        $requestedProps = $propFind->get404Properties();
122
+
123
+        // these might appear
124
+        $requestedProps = array_diff(
125
+            $requestedProps,
126
+            $this->ignoredProperties
127
+        );
128
+
129
+        if (empty($requestedProps)) {
130
+            return;
131
+        }
132
+
133
+        if ($node instanceof Directory
134
+            && $propFind->getDepth() !== 0
135
+        ) {
136
+            // note: pre-fetching only supported for depth <= 1
137
+            $this->loadChildrenProperties($node, $requestedProps);
138
+        }
139
+
140
+        $props = $this->getProperties($node, $requestedProps);
141
+        foreach ($props as $propName => $propValue) {
142
+            $propFind->set($propName, $propValue);
143
+        }
144
+    }
145
+
146
+    /**
147
+     * Updates properties for a path
148
+     *
149
+     * @param string $path
150
+     * @param PropPatch $propPatch
151
+     *
152
+     * @return void
153
+     */
154
+    public function propPatch($path, PropPatch $propPatch) {
155
+        $node = $this->tree->getNodeForPath($path);
156
+        if (!($node instanceof Node)) {
157
+            return;
158
+        }
159
+
160
+        $propPatch->handleRemaining(function ($changedProps) use ($node) {
161
+            return $this->updateProperties($node, $changedProps);
162
+        });
163
+    }
164
+
165
+    /**
166
+     * This method is called after a node is deleted.
167
+     *
168
+     * @param string $path path of node for which to delete properties
169
+     */
170
+    public function delete($path) {
171
+        $statement = $this->connection->prepare(
172
+            'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
173
+        );
174
+        $statement->execute([$this->user, '/' . $path]);
175
+        $statement->closeCursor();
176
+
177
+        unset($this->cache[$path]);
178
+    }
179
+
180
+    /**
181
+     * This method is called after a successful MOVE
182
+     *
183
+     * @param string $source
184
+     * @param string $destination
185
+     *
186
+     * @return void
187
+     */
188
+    public function move($source, $destination) {
189
+        $statement = $this->connection->prepare(
190
+            'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
191
+            ' WHERE `userid` = ? AND `propertypath` = ?'
192
+        );
193
+        $statement->execute(['/' . $destination, $this->user, '/' . $source]);
194
+        $statement->closeCursor();
195
+    }
196
+
197
+    /**
198
+     * Returns a list of properties for this nodes.;
199
+     * @param Node $node
200
+     * @param array $requestedProperties requested properties or empty array for "all"
201
+     * @return array
202
+     * @note The properties list is a list of propertynames the client
203
+     * requested, encoded as xmlnamespace#tagName, for example:
204
+     * http://www.example.org/namespace#author If the array is empty, all
205
+     * properties should be returned
206
+     */
207
+    private function getProperties(Node $node, array $requestedProperties) {
208
+        $path = $node->getPath();
209
+        if (isset($this->cache[$path])) {
210
+            return $this->cache[$path];
211
+        }
212
+
213
+        // TODO: chunking if more than 1000 properties
214
+        $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?';
215
+
216
+        $whereValues = [$this->user, $path];
217
+        $whereTypes = [null, null];
218
+
219
+        if (!empty($requestedProperties)) {
220
+            // request only a subset
221
+            $sql .= ' AND `propertyname` in (?)';
222
+            $whereValues[] = $requestedProperties;
223
+            $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
224
+        }
225
+
226
+        $result = $this->connection->executeQuery(
227
+            $sql,
228
+            $whereValues,
229
+            $whereTypes
230
+        );
231
+
232
+        $props = [];
233
+        while ($row = $result->fetch()) {
234
+            $props[$row['propertyname']] = $row['propertyvalue'];
235
+        }
236
+
237
+        $result->closeCursor();
238
+
239
+        $this->cache[$path] = $props;
240
+        return $props;
241
+    }
242
+
243
+    /**
244
+     * Update properties
245
+     *
246
+     * @param Node $node node for which to update properties
247
+     * @param array $properties array of properties to update
248
+     *
249
+     * @return bool
250
+     */
251
+    private function updateProperties($node, $properties) {
252
+        $path = $node->getPath();
253
+
254
+        $deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
255
+            ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
256
+
257
+        $insertStatement = 'INSERT INTO `*PREFIX*properties`' .
258
+            ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
259
+
260
+        $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
261
+            ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
262
+
263
+        // TODO: use "insert or update" strategy ?
264
+        $existing = $this->getProperties($node, []);
265
+        $this->connection->beginTransaction();
266
+        foreach ($properties as $propertyName => $propertyValue) {
267
+            // If it was null, we need to delete the property
268
+            if (is_null($propertyValue)) {
269
+                if (array_key_exists($propertyName, $existing)) {
270
+                    $this->connection->executeUpdate(
271
+                        $deleteStatement,
272
+                        [
273
+                            $this->user,
274
+                            $path,
275
+                            $propertyName
276
+                        ]
277
+                    );
278
+                }
279
+            } else {
280
+                if (!array_key_exists($propertyName, $existing)) {
281
+                    $this->connection->executeUpdate(
282
+                        $insertStatement,
283
+                        [
284
+                            $this->user,
285
+                            $path,
286
+                            $propertyName,
287
+                            $propertyValue
288
+                        ]
289
+                    );
290
+                } else {
291
+                    $this->connection->executeUpdate(
292
+                        $updateStatement,
293
+                        [
294
+                            $propertyValue,
295
+                            $this->user,
296
+                            $path,
297
+                            $propertyName
298
+                        ]
299
+                    );
300
+                }
301
+            }
302
+        }
303
+
304
+        $this->connection->commit();
305
+        unset($this->cache[$path]);
306
+
307
+        return true;
308
+    }
309
+
310
+    /**
311
+     * Bulk load properties for directory children
312
+     *
313
+     * @param Directory $node
314
+     * @param array $requestedProperties requested properties
315
+     *
316
+     * @return void
317
+     */
318
+    private function loadChildrenProperties(Directory $node, $requestedProperties) {
319
+        $path = $node->getPath();
320
+        if (isset($this->cache[$path])) {
321
+            // we already loaded them at some point
322
+            return;
323
+        }
324
+
325
+        $childNodes = $node->getChildren();
326
+        // pre-fill cache
327
+        foreach ($childNodes as $childNode) {
328
+            $this->cache[$childNode->getPath()] = [];
329
+        }
330
+
331
+        $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` LIKE ?';
332
+        $sql .= ' AND `propertyname` in (?) ORDER BY `propertypath`, `propertyname`';
333
+
334
+        $result = $this->connection->executeQuery(
335
+            $sql,
336
+            [$this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')) . '/%', $requestedProperties],
337
+            [null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY]
338
+        );
339
+
340
+        $oldPath = null;
341
+        $props = [];
342
+        while ($row = $result->fetch()) {
343
+            $path = $row['propertypath'];
344
+            if ($oldPath !== $path) {
345
+                // save previously gathered props
346
+                $this->cache[$oldPath] = $props;
347
+                $oldPath = $path;
348
+                // prepare props for next path
349
+                $props = [];
350
+            }
351
+            $props[$row['propertyname']] = $row['propertyvalue'];
352
+        }
353
+        if (!is_null($oldPath)) {
354
+            // save props from last run
355
+            $this->cache[$oldPath] = $props;
356
+        }
357
+
358
+        $result->closeCursor();
359
+    }
360 360
 }
Please login to merge, or discard this patch.
Spacing   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -112,7 +112,7 @@  discard block
 block discarded – undo
112 112
 			// we catch the exception to prevent breaking the whole list with a 404
113 113
 			// (soft fail)
114 114
 			\OC::$server->getLogger()->warning(
115
-				'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(),
115
+				'Could not get node for path: \"'.$path.'\" : '.$e->getMessage(),
116 116
 				['app' => 'files']
117 117
 			);
118 118
 			return;
@@ -157,7 +157,7 @@  discard block
 block discarded – undo
157 157
 			return;
158 158
 		}
159 159
 
160
-		$propPatch->handleRemaining(function ($changedProps) use ($node) {
160
+		$propPatch->handleRemaining(function($changedProps) use ($node) {
161 161
 			return $this->updateProperties($node, $changedProps);
162 162
 		});
163 163
 	}
@@ -171,7 +171,7 @@  discard block
 block discarded – undo
171 171
 		$statement = $this->connection->prepare(
172 172
 			'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
173 173
 		);
174
-		$statement->execute([$this->user, '/' . $path]);
174
+		$statement->execute([$this->user, '/'.$path]);
175 175
 		$statement->closeCursor();
176 176
 
177 177
 		unset($this->cache[$path]);
@@ -187,10 +187,10 @@  discard block
 block discarded – undo
187 187
 	 */
188 188
 	public function move($source, $destination) {
189 189
 		$statement = $this->connection->prepare(
190
-			'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
190
+			'UPDATE `*PREFIX*properties` SET `propertypath` = ?'.
191 191
 			' WHERE `userid` = ? AND `propertypath` = ?'
192 192
 		);
193
-		$statement->execute(['/' . $destination, $this->user, '/' . $source]);
193
+		$statement->execute(['/'.$destination, $this->user, '/'.$source]);
194 194
 		$statement->closeCursor();
195 195
 	}
196 196
 
@@ -251,13 +251,13 @@  discard block
 block discarded – undo
251 251
 	private function updateProperties($node, $properties) {
252 252
 		$path = $node->getPath();
253 253
 
254
-		$deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
254
+		$deleteStatement = 'DELETE FROM `*PREFIX*properties`'.
255 255
 			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
256 256
 
257
-		$insertStatement = 'INSERT INTO `*PREFIX*properties`' .
257
+		$insertStatement = 'INSERT INTO `*PREFIX*properties`'.
258 258
 			' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
259 259
 
260
-		$updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
260
+		$updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?'.
261 261
 			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
262 262
 
263 263
 		// TODO: use "insert or update" strategy ?
@@ -333,7 +333,7 @@  discard block
 block discarded – undo
333 333
 
334 334
 		$result = $this->connection->executeQuery(
335 335
 			$sql,
336
-			[$this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')) . '/%', $requestedProperties],
336
+			[$this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')).'/%', $requestedProperties],
337 337
 			[null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY]
338 338
 		);
339 339
 
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/File.php 3 patches
Doc Comments   +3 added lines patch added patch discarded remove patch
@@ -232,6 +232,9 @@
 block discarded – undo
232 232
 		return '"' . $this->info->getEtag() . '"';
233 233
 	}
234 234
 
235
+	/**
236
+	 * @param string $path
237
+	 */
235 238
 	private function getPartFileBasePath($path) {
236 239
 		$partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
237 240
 		if ($partFileInStorage) {
Please login to merge, or discard this patch.
Indentation   +515 added lines, -515 removed lines patch added patch discarded remove patch
@@ -58,520 +58,520 @@
 block discarded – undo
58 58
 
59 59
 class File extends Node implements IFile {
60 60
 
61
-	/**
62
-	 * Updates the data
63
-	 *
64
-	 * The data argument is a readable stream resource.
65
-	 *
66
-	 * After a successful put operation, you may choose to return an ETag. The
67
-	 * etag must always be surrounded by double-quotes. These quotes must
68
-	 * appear in the actual string you're returning.
69
-	 *
70
-	 * Clients may use the ETag from a PUT request to later on make sure that
71
-	 * when they update the file, the contents haven't changed in the mean
72
-	 * time.
73
-	 *
74
-	 * If you don't plan to store the file byte-by-byte, and you return a
75
-	 * different object on a subsequent GET you are strongly recommended to not
76
-	 * return an ETag, and just return null.
77
-	 *
78
-	 * @param resource $data
79
-	 *
80
-	 * @throws Forbidden
81
-	 * @throws UnsupportedMediaType
82
-	 * @throws BadRequest
83
-	 * @throws Exception
84
-	 * @throws EntityTooLarge
85
-	 * @throws ServiceUnavailable
86
-	 * @throws FileLocked
87
-	 * @return string|null
88
-	 */
89
-	public function put($data) {
90
-		try {
91
-			$exists = $this->fileView->file_exists($this->path);
92
-			if ($this->info && $exists && !$this->info->isUpdateable()) {
93
-				throw new Forbidden();
94
-			}
95
-		} catch (StorageNotAvailableException $e) {
96
-			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
97
-		}
98
-
99
-		// verify path of the target
100
-		$this->verifyPath();
101
-
102
-		// chunked handling
103
-		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
104
-			try {
105
-				return $this->createFileChunked($data);
106
-			} catch (\Exception $e) {
107
-				$this->convertToSabreException($e);
108
-			}
109
-		}
110
-
111
-		list($partStorage) = $this->fileView->resolvePath($this->path);
112
-		$needsPartFile = $this->needsPartFile($partStorage) && (strlen($this->path) > 1);
113
-
114
-		if ($needsPartFile) {
115
-			// mark file as partial while uploading (ignored by the scanner)
116
-			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
117
-		} else {
118
-			// upload file directly as the final path
119
-			$partFilePath = $this->path;
120
-		}
121
-
122
-		// the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
123
-		/** @var \OC\Files\Storage\Storage $partStorage */
124
-		list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
125
-		/** @var \OC\Files\Storage\Storage $storage */
126
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
127
-		try {
128
-			$target = $partStorage->fopen($internalPartPath, 'wb');
129
-			if ($target === false) {
130
-				\OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::fopen() failed', \OCP\Util::ERROR);
131
-				// because we have no clue about the cause we can only throw back a 500/Internal Server Error
132
-				throw new Exception('Could not write file contents');
133
-			}
134
-			list($count, $result) = \OC_Helper::streamCopy($data, $target);
135
-			fclose($target);
136
-
137
-			if ($result === false) {
138
-				$expected = -1;
139
-				if (isset($_SERVER['CONTENT_LENGTH'])) {
140
-					$expected = $_SERVER['CONTENT_LENGTH'];
141
-				}
142
-				throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
143
-			}
144
-
145
-			// if content length is sent by client:
146
-			// double check if the file was fully received
147
-			// compare expected and actual size
148
-			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
149
-				$expected = $_SERVER['CONTENT_LENGTH'];
150
-				if ($count != $expected) {
151
-					throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
152
-				}
153
-			}
154
-		} catch (\Exception $e) {
155
-			if ($needsPartFile) {
156
-				$partStorage->unlink($internalPartPath);
157
-			}
158
-			$this->convertToSabreException($e);
159
-		}
160
-
161
-		try {
162
-			$view = \OC\Files\Filesystem::getView();
163
-			if ($view) {
164
-				$run = $this->emitPreHooks($exists);
165
-			} else {
166
-				$run = true;
167
-			}
168
-
169
-			try {
170
-				$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
171
-			} catch (LockedException $e) {
172
-				if ($needsPartFile) {
173
-					$partStorage->unlink($internalPartPath);
174
-				}
175
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
176
-			}
177
-
178
-			if ($needsPartFile) {
179
-				// rename to correct path
180
-				try {
181
-					if ($run) {
182
-						$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
183
-						$fileExists = $storage->file_exists($internalPath);
184
-					}
185
-					if (!$run || $renameOkay === false || $fileExists === false) {
186
-						\OCP\Util::writeLog('webdav', 'renaming part file to final file failed ($run: ' . ($run ? 'true' : 'false') . ', $renameOkay: '  . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', \OCP\Util::ERROR);
187
-						throw new Exception('Could not rename part file to final file');
188
-					}
189
-				} catch (ForbiddenException $ex) {
190
-					throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
191
-				} catch (\Exception $e) {
192
-					$partStorage->unlink($internalPartPath);
193
-					$this->convertToSabreException($e);
194
-				}
195
-			}
196
-
197
-			// since we skipped the view we need to scan and emit the hooks ourselves
198
-			$storage->getUpdater()->update($internalPath);
199
-
200
-			try {
201
-				$this->changeLock(ILockingProvider::LOCK_SHARED);
202
-			} catch (LockedException $e) {
203
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
204
-			}
205
-
206
-			// allow sync clients to send the mtime along in a header
207
-			$request = \OC::$server->getRequest();
208
-			if (isset($request->server['HTTP_X_OC_MTIME'])) {
209
-				$mtimeStr = $request->server['HTTP_X_OC_MTIME'];
210
-				if (!is_numeric($mtimeStr)) {
211
-					throw new \InvalidArgumentException('X-OC-Mtime header must be an integer (unix timestamp).');
212
-				}
213
-				$mtime = intval($mtimeStr);
214
-				if ($this->fileView->touch($this->path, $mtime)) {
215
-					header('X-OC-MTime: accepted');
216
-				}
217
-			}
61
+    /**
62
+     * Updates the data
63
+     *
64
+     * The data argument is a readable stream resource.
65
+     *
66
+     * After a successful put operation, you may choose to return an ETag. The
67
+     * etag must always be surrounded by double-quotes. These quotes must
68
+     * appear in the actual string you're returning.
69
+     *
70
+     * Clients may use the ETag from a PUT request to later on make sure that
71
+     * when they update the file, the contents haven't changed in the mean
72
+     * time.
73
+     *
74
+     * If you don't plan to store the file byte-by-byte, and you return a
75
+     * different object on a subsequent GET you are strongly recommended to not
76
+     * return an ETag, and just return null.
77
+     *
78
+     * @param resource $data
79
+     *
80
+     * @throws Forbidden
81
+     * @throws UnsupportedMediaType
82
+     * @throws BadRequest
83
+     * @throws Exception
84
+     * @throws EntityTooLarge
85
+     * @throws ServiceUnavailable
86
+     * @throws FileLocked
87
+     * @return string|null
88
+     */
89
+    public function put($data) {
90
+        try {
91
+            $exists = $this->fileView->file_exists($this->path);
92
+            if ($this->info && $exists && !$this->info->isUpdateable()) {
93
+                throw new Forbidden();
94
+            }
95
+        } catch (StorageNotAvailableException $e) {
96
+            throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
97
+        }
98
+
99
+        // verify path of the target
100
+        $this->verifyPath();
101
+
102
+        // chunked handling
103
+        if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
104
+            try {
105
+                return $this->createFileChunked($data);
106
+            } catch (\Exception $e) {
107
+                $this->convertToSabreException($e);
108
+            }
109
+        }
110
+
111
+        list($partStorage) = $this->fileView->resolvePath($this->path);
112
+        $needsPartFile = $this->needsPartFile($partStorage) && (strlen($this->path) > 1);
113
+
114
+        if ($needsPartFile) {
115
+            // mark file as partial while uploading (ignored by the scanner)
116
+            $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
117
+        } else {
118
+            // upload file directly as the final path
119
+            $partFilePath = $this->path;
120
+        }
121
+
122
+        // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
123
+        /** @var \OC\Files\Storage\Storage $partStorage */
124
+        list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
125
+        /** @var \OC\Files\Storage\Storage $storage */
126
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
127
+        try {
128
+            $target = $partStorage->fopen($internalPartPath, 'wb');
129
+            if ($target === false) {
130
+                \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::fopen() failed', \OCP\Util::ERROR);
131
+                // because we have no clue about the cause we can only throw back a 500/Internal Server Error
132
+                throw new Exception('Could not write file contents');
133
+            }
134
+            list($count, $result) = \OC_Helper::streamCopy($data, $target);
135
+            fclose($target);
136
+
137
+            if ($result === false) {
138
+                $expected = -1;
139
+                if (isset($_SERVER['CONTENT_LENGTH'])) {
140
+                    $expected = $_SERVER['CONTENT_LENGTH'];
141
+                }
142
+                throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
143
+            }
144
+
145
+            // if content length is sent by client:
146
+            // double check if the file was fully received
147
+            // compare expected and actual size
148
+            if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
149
+                $expected = $_SERVER['CONTENT_LENGTH'];
150
+                if ($count != $expected) {
151
+                    throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
152
+                }
153
+            }
154
+        } catch (\Exception $e) {
155
+            if ($needsPartFile) {
156
+                $partStorage->unlink($internalPartPath);
157
+            }
158
+            $this->convertToSabreException($e);
159
+        }
160
+
161
+        try {
162
+            $view = \OC\Files\Filesystem::getView();
163
+            if ($view) {
164
+                $run = $this->emitPreHooks($exists);
165
+            } else {
166
+                $run = true;
167
+            }
168
+
169
+            try {
170
+                $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
171
+            } catch (LockedException $e) {
172
+                if ($needsPartFile) {
173
+                    $partStorage->unlink($internalPartPath);
174
+                }
175
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
176
+            }
177
+
178
+            if ($needsPartFile) {
179
+                // rename to correct path
180
+                try {
181
+                    if ($run) {
182
+                        $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
183
+                        $fileExists = $storage->file_exists($internalPath);
184
+                    }
185
+                    if (!$run || $renameOkay === false || $fileExists === false) {
186
+                        \OCP\Util::writeLog('webdav', 'renaming part file to final file failed ($run: ' . ($run ? 'true' : 'false') . ', $renameOkay: '  . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', \OCP\Util::ERROR);
187
+                        throw new Exception('Could not rename part file to final file');
188
+                    }
189
+                } catch (ForbiddenException $ex) {
190
+                    throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
191
+                } catch (\Exception $e) {
192
+                    $partStorage->unlink($internalPartPath);
193
+                    $this->convertToSabreException($e);
194
+                }
195
+            }
196
+
197
+            // since we skipped the view we need to scan and emit the hooks ourselves
198
+            $storage->getUpdater()->update($internalPath);
199
+
200
+            try {
201
+                $this->changeLock(ILockingProvider::LOCK_SHARED);
202
+            } catch (LockedException $e) {
203
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
204
+            }
205
+
206
+            // allow sync clients to send the mtime along in a header
207
+            $request = \OC::$server->getRequest();
208
+            if (isset($request->server['HTTP_X_OC_MTIME'])) {
209
+                $mtimeStr = $request->server['HTTP_X_OC_MTIME'];
210
+                if (!is_numeric($mtimeStr)) {
211
+                    throw new \InvalidArgumentException('X-OC-Mtime header must be an integer (unix timestamp).');
212
+                }
213
+                $mtime = intval($mtimeStr);
214
+                if ($this->fileView->touch($this->path, $mtime)) {
215
+                    header('X-OC-MTime: accepted');
216
+                }
217
+            }
218 218
 					
219
-			if ($view) {
220
-				$this->emitPostHooks($exists);
221
-			}
222
-
223
-			$this->refreshInfo();
224
-
225
-			if (isset($request->server['HTTP_OC_CHECKSUM'])) {
226
-				$checksum = trim($request->server['HTTP_OC_CHECKSUM']);
227
-				$this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
228
-				$this->refreshInfo();
229
-			} elseif ($this->getChecksum() !== null && $this->getChecksum() !== '') {
230
-				$this->fileView->putFileInfo($this->path, ['checksum' => '']);
231
-				$this->refreshInfo();
232
-			}
233
-		} catch (StorageNotAvailableException $e) {
234
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
235
-		}
236
-
237
-		return '"' . $this->info->getEtag() . '"';
238
-	}
239
-
240
-	private function getPartFileBasePath($path) {
241
-		$partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
242
-		if ($partFileInStorage) {
243
-			return $path;
244
-		} else {
245
-			return md5($path); // will place it in the root of the view with a unique name
246
-		}
247
-	}
248
-
249
-	/**
250
-	 * @param string $path
251
-	 */
252
-	private function emitPreHooks($exists, $path = null) {
253
-		if (is_null($path)) {
254
-			$path = $this->path;
255
-		}
256
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
257
-		$run = true;
258
-
259
-		if (!$exists) {
260
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, [
261
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
262
-				\OC\Files\Filesystem::signal_param_run => &$run,
263
-			]);
264
-		} else {
265
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, [
266
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
267
-				\OC\Files\Filesystem::signal_param_run => &$run,
268
-			]);
269
-		}
270
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [
271
-			\OC\Files\Filesystem::signal_param_path => $hookPath,
272
-			\OC\Files\Filesystem::signal_param_run => &$run,
273
-		]);
274
-		return $run;
275
-	}
276
-
277
-	/**
278
-	 * @param string $path
279
-	 */
280
-	private function emitPostHooks($exists, $path = null) {
281
-		if (is_null($path)) {
282
-			$path = $this->path;
283
-		}
284
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
285
-		if (!$exists) {
286
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [
287
-				\OC\Files\Filesystem::signal_param_path => $hookPath
288
-			]);
289
-		} else {
290
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, [
291
-				\OC\Files\Filesystem::signal_param_path => $hookPath
292
-			]);
293
-		}
294
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [
295
-			\OC\Files\Filesystem::signal_param_path => $hookPath
296
-		]);
297
-	}
298
-
299
-	/**
300
-	 * Returns the data
301
-	 *
302
-	 * @return resource
303
-	 * @throws Forbidden
304
-	 * @throws ServiceUnavailable
305
-	 */
306
-	public function get() {
307
-		//throw exception if encryption is disabled but files are still encrypted
308
-		try {
309
-			if (!$this->info->isReadable()) {
310
-				// do a if the file did not exist
311
-				throw new NotFound();
312
-			}
313
-			$res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
314
-			if ($res === false) {
315
-				throw new ServiceUnavailable("Could not open file");
316
-			}
317
-			return $res;
318
-		} catch (GenericEncryptionException $e) {
319
-			// returning 503 will allow retry of the operation at a later point in time
320
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
321
-		} catch (StorageNotAvailableException $e) {
322
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
323
-		} catch (ForbiddenException $ex) {
324
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
325
-		} catch (LockedException $e) {
326
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
327
-		}
328
-	}
329
-
330
-	/**
331
-	 * Delete the current file
332
-	 *
333
-	 * @throws Forbidden
334
-	 * @throws ServiceUnavailable
335
-	 */
336
-	public function delete() {
337
-		if (!$this->info->isDeletable()) {
338
-			throw new Forbidden();
339
-		}
340
-
341
-		try {
342
-			if (!$this->fileView->unlink($this->path)) {
343
-				// assume it wasn't possible to delete due to permissions
344
-				throw new Forbidden();
345
-			}
346
-		} catch (StorageNotAvailableException $e) {
347
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
348
-		} catch (ForbiddenException $ex) {
349
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
350
-		} catch (LockedException $e) {
351
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
352
-		}
353
-	}
354
-
355
-	/**
356
-	 * Returns the mime-type for a file
357
-	 *
358
-	 * If null is returned, we'll assume application/octet-stream
359
-	 *
360
-	 * @return string
361
-	 */
362
-	public function getContentType() {
363
-		$mimeType = $this->info->getMimetype();
364
-
365
-		// PROPFIND needs to return the correct mime type, for consistency with the web UI
366
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
367
-			return $mimeType;
368
-		}
369
-		return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
370
-	}
371
-
372
-	/**
373
-	 * @return array|false
374
-	 */
375
-	public function getDirectDownload() {
376
-		if (\OCP\App::isEnabled('encryption')) {
377
-			return [];
378
-		}
379
-		/** @var \OCP\Files\Storage $storage */
380
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
381
-		if (is_null($storage)) {
382
-			return [];
383
-		}
384
-
385
-		return $storage->getDirectDownload($internalPath);
386
-	}
387
-
388
-	/**
389
-	 * @param resource $data
390
-	 * @return null|string
391
-	 * @throws Exception
392
-	 * @throws BadRequest
393
-	 * @throws NotImplemented
394
-	 * @throws ServiceUnavailable
395
-	 */
396
-	private function createFileChunked($data) {
397
-		list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($this->path);
398
-
399
-		$info = \OC_FileChunking::decodeName($name);
400
-		if (empty($info)) {
401
-			throw new NotImplemented('Invalid chunk name');
402
-		}
403
-
404
-		$chunk_handler = new \OC_FileChunking($info);
405
-		$bytesWritten = $chunk_handler->store($info['index'], $data);
406
-
407
-		//detect aborted upload
408
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
409
-			if (isset($_SERVER['CONTENT_LENGTH'])) {
410
-				$expected = $_SERVER['CONTENT_LENGTH'];
411
-				if ($bytesWritten != $expected) {
412
-					$chunk_handler->remove($info['index']);
413
-					throw new BadRequest(
414
-						'expected filesize ' . $expected . ' got ' . $bytesWritten
415
-					);
416
-				}
417
-			}
418
-		}
419
-
420
-		if ($chunk_handler->isComplete()) {
421
-			list($storage, ) = $this->fileView->resolvePath($path);
422
-			$needsPartFile = $this->needsPartFile($storage);
423
-			$partFile = null;
424
-
425
-			$targetPath = $path . '/' . $info['name'];
426
-			/** @var \OC\Files\Storage\Storage $targetStorage */
427
-			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
428
-
429
-			$exists = $this->fileView->file_exists($targetPath);
430
-
431
-			try {
432
-				$this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
433
-
434
-				$this->emitPreHooks($exists, $targetPath);
435
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
436
-				/** @var \OC\Files\Storage\Storage $targetStorage */
437
-				list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
438
-
439
-				if ($needsPartFile) {
440
-					// we first assembly the target file as a part file
441
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
442
-					/** @var \OC\Files\Storage\Storage $targetStorage */
443
-					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
444
-
445
-
446
-					$chunk_handler->file_assemble($partStorage, $partInternalPath);
447
-
448
-					// here is the final atomic rename
449
-					$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
450
-					$fileExists = $targetStorage->file_exists($targetInternalPath);
451
-					if ($renameOkay === false || $fileExists === false) {
452
-						\OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::rename() failed', \OCP\Util::ERROR);
453
-						// only delete if an error occurred and the target file was already created
454
-						if ($fileExists) {
455
-							// set to null to avoid double-deletion when handling exception
456
-							// stray part file
457
-							$partFile = null;
458
-							$targetStorage->unlink($targetInternalPath);
459
-						}
460
-						$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
461
-						throw new Exception('Could not rename part file assembled from chunks');
462
-					}
463
-				} else {
464
-					// assemble directly into the final file
465
-					$chunk_handler->file_assemble($targetStorage, $targetInternalPath);
466
-				}
467
-
468
-				// allow sync clients to send the mtime along in a header
469
-				$request = \OC::$server->getRequest();
470
-				if (isset($request->server['HTTP_X_OC_MTIME'])) {
471
-					if ($targetStorage->touch($targetInternalPath, $request->server['HTTP_X_OC_MTIME'])) {
472
-						header('X-OC-MTime: accepted');
473
-					}
474
-				}
475
-
476
-				// since we skipped the view we need to scan and emit the hooks ourselves
477
-				$targetStorage->getUpdater()->update($targetInternalPath);
478
-
479
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
480
-
481
-				$this->emitPostHooks($exists, $targetPath);
482
-
483
-				// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
484
-				$info = $this->fileView->getFileInfo($targetPath);
485
-
486
-				if (isset($request->server['HTTP_OC_CHECKSUM'])) {
487
-					$checksum = trim($request->server['HTTP_OC_CHECKSUM']);
488
-					$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
489
-				} elseif ($info->getChecksum() !== null && $info->getChecksum() !== '') {
490
-					$this->fileView->putFileInfo($this->path, ['checksum' => '']);
491
-				}
492
-
493
-				$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
494
-
495
-				return $info->getEtag();
496
-			} catch (\Exception $e) {
497
-				if ($partFile !== null) {
498
-					$targetStorage->unlink($targetInternalPath);
499
-				}
500
-				$this->convertToSabreException($e);
501
-			}
502
-		}
503
-
504
-		return null;
505
-	}
506
-
507
-	/**
508
-	 * Returns whether a part file is needed for the given storage
509
-	 * or whether the file can be assembled/uploaded directly on the
510
-	 * target storage.
511
-	 *
512
-	 * @param \OCP\Files\Storage $storage
513
-	 * @return bool true if the storage needs part file handling
514
-	 */
515
-	private function needsPartFile($storage) {
516
-		// TODO: in the future use ChunkHandler provided by storage
517
-		return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') &&
518
-			!$storage->instanceOfStorage('OC\Files\Storage\OwnCloud') &&
519
-			$storage->needsPartFile();
520
-	}
521
-
522
-	/**
523
-	 * Convert the given exception to a SabreException instance
524
-	 *
525
-	 * @param \Exception $e
526
-	 *
527
-	 * @throws \Sabre\DAV\Exception
528
-	 */
529
-	private function convertToSabreException(\Exception $e) {
530
-		if ($e instanceof \Sabre\DAV\Exception) {
531
-			throw $e;
532
-		}
533
-		if ($e instanceof NotPermittedException) {
534
-			// a more general case - due to whatever reason the content could not be written
535
-			throw new Forbidden($e->getMessage(), 0, $e);
536
-		}
537
-		if ($e instanceof ForbiddenException) {
538
-			// the path for the file was forbidden
539
-			throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
540
-		}
541
-		if ($e instanceof EntityTooLargeException) {
542
-			// the file is too big to be stored
543
-			throw new EntityTooLarge($e->getMessage(), 0, $e);
544
-		}
545
-		if ($e instanceof InvalidContentException) {
546
-			// the file content is not permitted
547
-			throw new UnsupportedMediaType($e->getMessage(), 0, $e);
548
-		}
549
-		if ($e instanceof InvalidPathException) {
550
-			// the path for the file was not valid
551
-			// TODO: find proper http status code for this case
552
-			throw new Forbidden($e->getMessage(), 0, $e);
553
-		}
554
-		if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
555
-			// the file is currently being written to by another process
556
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
557
-		}
558
-		if ($e instanceof GenericEncryptionException) {
559
-			// returning 503 will allow retry of the operation at a later point in time
560
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
561
-		}
562
-		if ($e instanceof StorageNotAvailableException) {
563
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
564
-		}
565
-
566
-		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
567
-	}
568
-
569
-	/**
570
-	 * Get the checksum for this file
571
-	 *
572
-	 * @return string
573
-	 */
574
-	public function getChecksum() {
575
-		return $this->info->getChecksum();
576
-	}
219
+            if ($view) {
220
+                $this->emitPostHooks($exists);
221
+            }
222
+
223
+            $this->refreshInfo();
224
+
225
+            if (isset($request->server['HTTP_OC_CHECKSUM'])) {
226
+                $checksum = trim($request->server['HTTP_OC_CHECKSUM']);
227
+                $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
228
+                $this->refreshInfo();
229
+            } elseif ($this->getChecksum() !== null && $this->getChecksum() !== '') {
230
+                $this->fileView->putFileInfo($this->path, ['checksum' => '']);
231
+                $this->refreshInfo();
232
+            }
233
+        } catch (StorageNotAvailableException $e) {
234
+            throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
235
+        }
236
+
237
+        return '"' . $this->info->getEtag() . '"';
238
+    }
239
+
240
+    private function getPartFileBasePath($path) {
241
+        $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
242
+        if ($partFileInStorage) {
243
+            return $path;
244
+        } else {
245
+            return md5($path); // will place it in the root of the view with a unique name
246
+        }
247
+    }
248
+
249
+    /**
250
+     * @param string $path
251
+     */
252
+    private function emitPreHooks($exists, $path = null) {
253
+        if (is_null($path)) {
254
+            $path = $this->path;
255
+        }
256
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
257
+        $run = true;
258
+
259
+        if (!$exists) {
260
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, [
261
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
262
+                \OC\Files\Filesystem::signal_param_run => &$run,
263
+            ]);
264
+        } else {
265
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, [
266
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
267
+                \OC\Files\Filesystem::signal_param_run => &$run,
268
+            ]);
269
+        }
270
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [
271
+            \OC\Files\Filesystem::signal_param_path => $hookPath,
272
+            \OC\Files\Filesystem::signal_param_run => &$run,
273
+        ]);
274
+        return $run;
275
+    }
276
+
277
+    /**
278
+     * @param string $path
279
+     */
280
+    private function emitPostHooks($exists, $path = null) {
281
+        if (is_null($path)) {
282
+            $path = $this->path;
283
+        }
284
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
285
+        if (!$exists) {
286
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [
287
+                \OC\Files\Filesystem::signal_param_path => $hookPath
288
+            ]);
289
+        } else {
290
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, [
291
+                \OC\Files\Filesystem::signal_param_path => $hookPath
292
+            ]);
293
+        }
294
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [
295
+            \OC\Files\Filesystem::signal_param_path => $hookPath
296
+        ]);
297
+    }
298
+
299
+    /**
300
+     * Returns the data
301
+     *
302
+     * @return resource
303
+     * @throws Forbidden
304
+     * @throws ServiceUnavailable
305
+     */
306
+    public function get() {
307
+        //throw exception if encryption is disabled but files are still encrypted
308
+        try {
309
+            if (!$this->info->isReadable()) {
310
+                // do a if the file did not exist
311
+                throw new NotFound();
312
+            }
313
+            $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
314
+            if ($res === false) {
315
+                throw new ServiceUnavailable("Could not open file");
316
+            }
317
+            return $res;
318
+        } catch (GenericEncryptionException $e) {
319
+            // returning 503 will allow retry of the operation at a later point in time
320
+            throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
321
+        } catch (StorageNotAvailableException $e) {
322
+            throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
323
+        } catch (ForbiddenException $ex) {
324
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
325
+        } catch (LockedException $e) {
326
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
327
+        }
328
+    }
329
+
330
+    /**
331
+     * Delete the current file
332
+     *
333
+     * @throws Forbidden
334
+     * @throws ServiceUnavailable
335
+     */
336
+    public function delete() {
337
+        if (!$this->info->isDeletable()) {
338
+            throw new Forbidden();
339
+        }
340
+
341
+        try {
342
+            if (!$this->fileView->unlink($this->path)) {
343
+                // assume it wasn't possible to delete due to permissions
344
+                throw new Forbidden();
345
+            }
346
+        } catch (StorageNotAvailableException $e) {
347
+            throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
348
+        } catch (ForbiddenException $ex) {
349
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
350
+        } catch (LockedException $e) {
351
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
352
+        }
353
+    }
354
+
355
+    /**
356
+     * Returns the mime-type for a file
357
+     *
358
+     * If null is returned, we'll assume application/octet-stream
359
+     *
360
+     * @return string
361
+     */
362
+    public function getContentType() {
363
+        $mimeType = $this->info->getMimetype();
364
+
365
+        // PROPFIND needs to return the correct mime type, for consistency with the web UI
366
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
367
+            return $mimeType;
368
+        }
369
+        return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
370
+    }
371
+
372
+    /**
373
+     * @return array|false
374
+     */
375
+    public function getDirectDownload() {
376
+        if (\OCP\App::isEnabled('encryption')) {
377
+            return [];
378
+        }
379
+        /** @var \OCP\Files\Storage $storage */
380
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
381
+        if (is_null($storage)) {
382
+            return [];
383
+        }
384
+
385
+        return $storage->getDirectDownload($internalPath);
386
+    }
387
+
388
+    /**
389
+     * @param resource $data
390
+     * @return null|string
391
+     * @throws Exception
392
+     * @throws BadRequest
393
+     * @throws NotImplemented
394
+     * @throws ServiceUnavailable
395
+     */
396
+    private function createFileChunked($data) {
397
+        list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($this->path);
398
+
399
+        $info = \OC_FileChunking::decodeName($name);
400
+        if (empty($info)) {
401
+            throw new NotImplemented('Invalid chunk name');
402
+        }
403
+
404
+        $chunk_handler = new \OC_FileChunking($info);
405
+        $bytesWritten = $chunk_handler->store($info['index'], $data);
406
+
407
+        //detect aborted upload
408
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
409
+            if (isset($_SERVER['CONTENT_LENGTH'])) {
410
+                $expected = $_SERVER['CONTENT_LENGTH'];
411
+                if ($bytesWritten != $expected) {
412
+                    $chunk_handler->remove($info['index']);
413
+                    throw new BadRequest(
414
+                        'expected filesize ' . $expected . ' got ' . $bytesWritten
415
+                    );
416
+                }
417
+            }
418
+        }
419
+
420
+        if ($chunk_handler->isComplete()) {
421
+            list($storage, ) = $this->fileView->resolvePath($path);
422
+            $needsPartFile = $this->needsPartFile($storage);
423
+            $partFile = null;
424
+
425
+            $targetPath = $path . '/' . $info['name'];
426
+            /** @var \OC\Files\Storage\Storage $targetStorage */
427
+            list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
428
+
429
+            $exists = $this->fileView->file_exists($targetPath);
430
+
431
+            try {
432
+                $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
433
+
434
+                $this->emitPreHooks($exists, $targetPath);
435
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
436
+                /** @var \OC\Files\Storage\Storage $targetStorage */
437
+                list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
438
+
439
+                if ($needsPartFile) {
440
+                    // we first assembly the target file as a part file
441
+                    $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
442
+                    /** @var \OC\Files\Storage\Storage $targetStorage */
443
+                    list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
444
+
445
+
446
+                    $chunk_handler->file_assemble($partStorage, $partInternalPath);
447
+
448
+                    // here is the final atomic rename
449
+                    $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
450
+                    $fileExists = $targetStorage->file_exists($targetInternalPath);
451
+                    if ($renameOkay === false || $fileExists === false) {
452
+                        \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::rename() failed', \OCP\Util::ERROR);
453
+                        // only delete if an error occurred and the target file was already created
454
+                        if ($fileExists) {
455
+                            // set to null to avoid double-deletion when handling exception
456
+                            // stray part file
457
+                            $partFile = null;
458
+                            $targetStorage->unlink($targetInternalPath);
459
+                        }
460
+                        $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
461
+                        throw new Exception('Could not rename part file assembled from chunks');
462
+                    }
463
+                } else {
464
+                    // assemble directly into the final file
465
+                    $chunk_handler->file_assemble($targetStorage, $targetInternalPath);
466
+                }
467
+
468
+                // allow sync clients to send the mtime along in a header
469
+                $request = \OC::$server->getRequest();
470
+                if (isset($request->server['HTTP_X_OC_MTIME'])) {
471
+                    if ($targetStorage->touch($targetInternalPath, $request->server['HTTP_X_OC_MTIME'])) {
472
+                        header('X-OC-MTime: accepted');
473
+                    }
474
+                }
475
+
476
+                // since we skipped the view we need to scan and emit the hooks ourselves
477
+                $targetStorage->getUpdater()->update($targetInternalPath);
478
+
479
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
480
+
481
+                $this->emitPostHooks($exists, $targetPath);
482
+
483
+                // FIXME: should call refreshInfo but can't because $this->path is not the of the final file
484
+                $info = $this->fileView->getFileInfo($targetPath);
485
+
486
+                if (isset($request->server['HTTP_OC_CHECKSUM'])) {
487
+                    $checksum = trim($request->server['HTTP_OC_CHECKSUM']);
488
+                    $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
489
+                } elseif ($info->getChecksum() !== null && $info->getChecksum() !== '') {
490
+                    $this->fileView->putFileInfo($this->path, ['checksum' => '']);
491
+                }
492
+
493
+                $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
494
+
495
+                return $info->getEtag();
496
+            } catch (\Exception $e) {
497
+                if ($partFile !== null) {
498
+                    $targetStorage->unlink($targetInternalPath);
499
+                }
500
+                $this->convertToSabreException($e);
501
+            }
502
+        }
503
+
504
+        return null;
505
+    }
506
+
507
+    /**
508
+     * Returns whether a part file is needed for the given storage
509
+     * or whether the file can be assembled/uploaded directly on the
510
+     * target storage.
511
+     *
512
+     * @param \OCP\Files\Storage $storage
513
+     * @return bool true if the storage needs part file handling
514
+     */
515
+    private function needsPartFile($storage) {
516
+        // TODO: in the future use ChunkHandler provided by storage
517
+        return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') &&
518
+            !$storage->instanceOfStorage('OC\Files\Storage\OwnCloud') &&
519
+            $storage->needsPartFile();
520
+    }
521
+
522
+    /**
523
+     * Convert the given exception to a SabreException instance
524
+     *
525
+     * @param \Exception $e
526
+     *
527
+     * @throws \Sabre\DAV\Exception
528
+     */
529
+    private function convertToSabreException(\Exception $e) {
530
+        if ($e instanceof \Sabre\DAV\Exception) {
531
+            throw $e;
532
+        }
533
+        if ($e instanceof NotPermittedException) {
534
+            // a more general case - due to whatever reason the content could not be written
535
+            throw new Forbidden($e->getMessage(), 0, $e);
536
+        }
537
+        if ($e instanceof ForbiddenException) {
538
+            // the path for the file was forbidden
539
+            throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
540
+        }
541
+        if ($e instanceof EntityTooLargeException) {
542
+            // the file is too big to be stored
543
+            throw new EntityTooLarge($e->getMessage(), 0, $e);
544
+        }
545
+        if ($e instanceof InvalidContentException) {
546
+            // the file content is not permitted
547
+            throw new UnsupportedMediaType($e->getMessage(), 0, $e);
548
+        }
549
+        if ($e instanceof InvalidPathException) {
550
+            // the path for the file was not valid
551
+            // TODO: find proper http status code for this case
552
+            throw new Forbidden($e->getMessage(), 0, $e);
553
+        }
554
+        if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
555
+            // the file is currently being written to by another process
556
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
557
+        }
558
+        if ($e instanceof GenericEncryptionException) {
559
+            // returning 503 will allow retry of the operation at a later point in time
560
+            throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
561
+        }
562
+        if ($e instanceof StorageNotAvailableException) {
563
+            throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
564
+        }
565
+
566
+        throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
567
+    }
568
+
569
+    /**
570
+     * Get the checksum for this file
571
+     *
572
+     * @return string
573
+     */
574
+    public function getChecksum() {
575
+        return $this->info->getChecksum();
576
+    }
577 577
 }
Please login to merge, or discard this patch.
Spacing   +16 added lines, -16 removed lines patch added patch discarded remove patch
@@ -93,7 +93,7 @@  discard block
 block discarded – undo
93 93
 				throw new Forbidden();
94 94
 			}
95 95
 		} catch (StorageNotAvailableException $e) {
96
-			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
96
+			throw new ServiceUnavailable("File is not updatable: ".$e->getMessage());
97 97
 		}
98 98
 
99 99
 		// verify path of the target
@@ -113,7 +113,7 @@  discard block
 block discarded – undo
113 113
 
114 114
 		if ($needsPartFile) {
115 115
 			// mark file as partial while uploading (ignored by the scanner)
116
-			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
116
+			$partFilePath = $this->getPartFileBasePath($this->path).'.ocTransferId'.rand().'.part';
117 117
 		} else {
118 118
 			// upload file directly as the final path
119 119
 			$partFilePath = $this->path;
@@ -139,7 +139,7 @@  discard block
 block discarded – undo
139 139
 				if (isset($_SERVER['CONTENT_LENGTH'])) {
140 140
 					$expected = $_SERVER['CONTENT_LENGTH'];
141 141
 				}
142
-				throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
142
+				throw new Exception('Error while copying file to target location (copied bytes: '.$count.', expected filesize: '.$expected.' )');
143 143
 			}
144 144
 
145 145
 			// if content length is sent by client:
@@ -148,7 +148,7 @@  discard block
 block discarded – undo
148 148
 			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
149 149
 				$expected = $_SERVER['CONTENT_LENGTH'];
150 150
 				if ($count != $expected) {
151
-					throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
151
+					throw new BadRequest('expected filesize '.$expected.' got '.$count);
152 152
 				}
153 153
 			}
154 154
 		} catch (\Exception $e) {
@@ -183,7 +183,7 @@  discard block
 block discarded – undo
183 183
 						$fileExists = $storage->file_exists($internalPath);
184 184
 					}
185 185
 					if (!$run || $renameOkay === false || $fileExists === false) {
186
-						\OCP\Util::writeLog('webdav', 'renaming part file to final file failed ($run: ' . ($run ? 'true' : 'false') . ', $renameOkay: '  . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', \OCP\Util::ERROR);
186
+						\OCP\Util::writeLog('webdav', 'renaming part file to final file failed ($run: '.($run ? 'true' : 'false').', $renameOkay: '.($renameOkay ? 'true' : 'false').', $fileExists: '.($fileExists ? 'true' : 'false').')', \OCP\Util::ERROR);
187 187
 						throw new Exception('Could not rename part file to final file');
188 188
 					}
189 189
 				} catch (ForbiddenException $ex) {
@@ -231,10 +231,10 @@  discard block
 block discarded – undo
231 231
 				$this->refreshInfo();
232 232
 			}
233 233
 		} catch (StorageNotAvailableException $e) {
234
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
234
+			throw new ServiceUnavailable("Failed to check file size: ".$e->getMessage());
235 235
 		}
236 236
 
237
-		return '"' . $this->info->getEtag() . '"';
237
+		return '"'.$this->info->getEtag().'"';
238 238
 	}
239 239
 
240 240
 	private function getPartFileBasePath($path) {
@@ -317,9 +317,9 @@  discard block
 block discarded – undo
317 317
 			return $res;
318 318
 		} catch (GenericEncryptionException $e) {
319 319
 			// returning 503 will allow retry of the operation at a later point in time
320
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
320
+			throw new ServiceUnavailable("Encryption not ready: ".$e->getMessage());
321 321
 		} catch (StorageNotAvailableException $e) {
322
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
322
+			throw new ServiceUnavailable("Failed to open file: ".$e->getMessage());
323 323
 		} catch (ForbiddenException $ex) {
324 324
 			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
325 325
 		} catch (LockedException $e) {
@@ -344,7 +344,7 @@  discard block
 block discarded – undo
344 344
 				throw new Forbidden();
345 345
 			}
346 346
 		} catch (StorageNotAvailableException $e) {
347
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
347
+			throw new ServiceUnavailable("Failed to unlink: ".$e->getMessage());
348 348
 		} catch (ForbiddenException $ex) {
349 349
 			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
350 350
 		} catch (LockedException $e) {
@@ -411,18 +411,18 @@  discard block
 block discarded – undo
411 411
 				if ($bytesWritten != $expected) {
412 412
 					$chunk_handler->remove($info['index']);
413 413
 					throw new BadRequest(
414
-						'expected filesize ' . $expected . ' got ' . $bytesWritten
414
+						'expected filesize '.$expected.' got '.$bytesWritten
415 415
 					);
416 416
 				}
417 417
 			}
418 418
 		}
419 419
 
420 420
 		if ($chunk_handler->isComplete()) {
421
-			list($storage, ) = $this->fileView->resolvePath($path);
421
+			list($storage,) = $this->fileView->resolvePath($path);
422 422
 			$needsPartFile = $this->needsPartFile($storage);
423 423
 			$partFile = null;
424 424
 
425
-			$targetPath = $path . '/' . $info['name'];
425
+			$targetPath = $path.'/'.$info['name'];
426 426
 			/** @var \OC\Files\Storage\Storage $targetStorage */
427 427
 			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
428 428
 
@@ -438,7 +438,7 @@  discard block
 block discarded – undo
438 438
 
439 439
 				if ($needsPartFile) {
440 440
 					// we first assembly the target file as a part file
441
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
441
+					$partFile = $this->getPartFileBasePath($path.'/'.$info['name']).'.ocTransferId'.$info['transferid'].'.part';
442 442
 					/** @var \OC\Files\Storage\Storage $targetStorage */
443 443
 					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
444 444
 
@@ -557,10 +557,10 @@  discard block
 block discarded – undo
557 557
 		}
558 558
 		if ($e instanceof GenericEncryptionException) {
559 559
 			// returning 503 will allow retry of the operation at a later point in time
560
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
560
+			throw new ServiceUnavailable('Encryption not ready: '.$e->getMessage(), 0, $e);
561 561
 		}
562 562
 		if ($e instanceof StorageNotAvailableException) {
563
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
563
+			throw new ServiceUnavailable('Failed to write file contents: '.$e->getMessage(), 0, $e);
564 564
 		}
565 565
 
566 566
 		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
Please login to merge, or discard this patch.
apps/encryption/lib/KeyManager.php 3 patches
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -488,7 +488,7 @@
 block discarded – undo
488 488
 
489 489
 
490 490
 	/**
491
-	 * @param $path
491
+	 * @param string $path
492 492
 	 * @param $uid
493 493
 	 * @return mixed
494 494
 	 */
Please login to merge, or discard this patch.
Indentation   +702 added lines, -702 removed lines patch added patch discarded remove patch
@@ -38,706 +38,706 @@
 block discarded – undo
38 38
 
39 39
 class KeyManager {
40 40
 
41
-	/**
42
-	 * @var Session
43
-	 */
44
-	protected $session;
45
-	/**
46
-	 * @var IStorage
47
-	 */
48
-	private $keyStorage;
49
-	/**
50
-	 * @var Crypt
51
-	 */
52
-	private $crypt;
53
-	/**
54
-	 * @var string
55
-	 */
56
-	private $recoveryKeyId;
57
-	/**
58
-	 * @var string
59
-	 */
60
-	private $publicShareKeyId;
61
-	/**
62
-	 * @var string
63
-	 */
64
-	private $masterKeyId;
65
-	/**
66
-	 * @var string UserID
67
-	 */
68
-	private $keyId;
69
-	/**
70
-	 * @var string
71
-	 */
72
-	private $publicKeyId = 'publicKey';
73
-	/**
74
-	 * @var string
75
-	 */
76
-	private $privateKeyId = 'privateKey';
77
-
78
-	/**
79
-	 * @var string
80
-	 */
81
-	private $shareKeyId = 'shareKey';
82
-
83
-	/**
84
-	 * @var string
85
-	 */
86
-	private $fileKeyId = 'fileKey';
87
-	/**
88
-	 * @var IConfig
89
-	 */
90
-	private $config;
91
-	/**
92
-	 * @var ILogger
93
-	 */
94
-	private $log;
95
-	/**
96
-	 * @var Util
97
-	 */
98
-	private $util;
99
-
100
-	/**
101
-	 * @param IStorage $keyStorage
102
-	 * @param Crypt $crypt
103
-	 * @param IConfig $config
104
-	 * @param IUserSession $userSession
105
-	 * @param Session $session
106
-	 * @param ILogger $log
107
-	 * @param Util $util
108
-	 */
109
-	public function __construct(
110
-		IStorage $keyStorage,
111
-		Crypt $crypt,
112
-		IConfig $config,
113
-		IUserSession $userSession,
114
-		Session $session,
115
-		ILogger $log,
116
-		Util $util
117
-	) {
118
-		$this->util = $util;
119
-		$this->session = $session;
120
-		$this->keyStorage = $keyStorage;
121
-		$this->crypt = $crypt;
122
-		$this->config = $config;
123
-		$this->log = $log;
124
-
125
-		$this->recoveryKeyId = $this->config->getAppValue(
126
-			'encryption',
127
-			'recoveryKeyId'
128
-		);
129
-		if (empty($this->recoveryKeyId)) {
130
-			$this->recoveryKeyId = 'recoveryKey_' . substr(md5(time()), 0, 8);
131
-			$this->config->setAppValue(
132
-				'encryption',
133
-				'recoveryKeyId',
134
-				$this->recoveryKeyId
135
-			);
136
-		}
137
-
138
-		$this->publicShareKeyId = $this->config->getAppValue(
139
-			'encryption',
140
-			'publicShareKeyId'
141
-		);
142
-		if (empty($this->publicShareKeyId)) {
143
-			$this->publicShareKeyId = 'pubShare_' . substr(md5(time()), 0, 8);
144
-			$this->config->setAppValue('encryption', 'publicShareKeyId', $this->publicShareKeyId);
145
-		}
146
-
147
-		$this->masterKeyId = $this->config->getAppValue(
148
-			'encryption',
149
-			'masterKeyId'
150
-		);
151
-		if (empty($this->masterKeyId)) {
152
-			$this->masterKeyId = 'master_' . substr(md5(time()), 0, 8);
153
-			$this->config->setAppValue('encryption', 'masterKeyId', $this->masterKeyId);
154
-		}
155
-
156
-		$this->keyId = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : false;
157
-		$this->log = $log;
158
-	}
159
-
160
-	/**
161
-	 * check if key pair for public link shares exists, if not we create one
162
-	 */
163
-	public function validateShareKey() {
164
-		$shareKey = $this->getPublicShareKey();
165
-		if (empty($shareKey)) {
166
-			$keyPair = $this->crypt->createKeyPair();
167
-
168
-			// Save public key
169
-			$this->keyStorage->setSystemUserKey(
170
-				$this->publicShareKeyId . '.publicKey',
171
-				$keyPair['publicKey'],
172
-				Encryption::ID
173
-			);
174
-
175
-			// Encrypt private key empty passphrase
176
-			$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], '');
177
-			$header = $this->crypt->generateHeader();
178
-			$this->setSystemPrivateKey($this->publicShareKeyId, $header . $encryptedKey);
179
-		}
180
-	}
181
-
182
-	/**
183
-	 * check if a key pair for the master key exists, if not we create one
184
-	 */
185
-	public function validateMasterKey() {
186
-		if ($this->util->isMasterKeyEnabled() === false) {
187
-			return;
188
-		}
189
-
190
-		$publicMasterKey = $this->getPublicMasterKey();
191
-		if (empty($publicMasterKey)) {
192
-			$keyPair = $this->crypt->createKeyPair();
193
-
194
-			// Save public key
195
-			$this->keyStorage->setSystemUserKey(
196
-				$this->masterKeyId . '.publicKey',
197
-				$keyPair['publicKey'],
198
-				Encryption::ID
199
-			);
200
-
201
-			// Encrypt private key with system password
202
-			$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $this->getMasterKeyPassword(), $this->masterKeyId);
203
-			$header = $this->crypt->generateHeader();
204
-			$this->setSystemPrivateKey($this->masterKeyId, $header . $encryptedKey);
205
-		}
206
-
207
-		if (!$this->session->isPrivateKeySet()) {
208
-			$masterKey = $this->getSystemPrivateKey($this->masterKeyId);
209
-			$decryptedMasterKey = $this->crypt->decryptPrivateKey($masterKey, $this->getMasterKeyPassword(), $this->masterKeyId);
210
-			$this->session->setPrivateKey($decryptedMasterKey);
211
-		}
212
-
213
-		// after the encryption key is available we are ready to go
214
-		$this->session->setStatus(Session::INIT_SUCCESSFUL);
215
-	}
216
-
217
-	/**
218
-	 * @return bool
219
-	 */
220
-	public function recoveryKeyExists() {
221
-		$key = $this->getRecoveryKey();
222
-		return (!empty($key));
223
-	}
224
-
225
-	/**
226
-	 * get recovery key
227
-	 *
228
-	 * @return string
229
-	 */
230
-	public function getRecoveryKey() {
231
-		return $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.publicKey', Encryption::ID);
232
-	}
233
-
234
-	/**
235
-	 * get recovery key ID
236
-	 *
237
-	 * @return string
238
-	 */
239
-	public function getRecoveryKeyId() {
240
-		return $this->recoveryKeyId;
241
-	}
242
-
243
-	/**
244
-	 * @param string $password
245
-	 * @return bool
246
-	 */
247
-	public function checkRecoveryPassword($password) {
248
-		$recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.privateKey', Encryption::ID);
249
-		$decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $password);
250
-
251
-		if ($decryptedRecoveryKey) {
252
-			return true;
253
-		}
254
-		return false;
255
-	}
256
-
257
-	/**
258
-	 * @param string $uid
259
-	 * @param string $password
260
-	 * @param string $keyPair
261
-	 * @return bool
262
-	 */
263
-	public function storeKeyPair($uid, $password, $keyPair) {
264
-		// Save Public Key
265
-		$this->setPublicKey($uid, $keyPair['publicKey']);
266
-
267
-		$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password, $uid);
268
-
269
-		$header = $this->crypt->generateHeader();
270
-
271
-		if ($encryptedKey) {
272
-			$this->setPrivateKey($uid, $header . $encryptedKey);
273
-			return true;
274
-		}
275
-		return false;
276
-	}
277
-
278
-	/**
279
-	 * @param string $password
280
-	 * @param array $keyPair
281
-	 * @return bool
282
-	 */
283
-	public function setRecoveryKey($password, $keyPair) {
284
-		// Save Public Key
285
-		$this->keyStorage->setSystemUserKey(
286
-			$this->getRecoveryKeyId().
287
-			'.publicKey',
288
-			$keyPair['publicKey'],
289
-			Encryption::ID
290
-		);
291
-
292
-		$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password);
293
-		$header = $this->crypt->generateHeader();
294
-
295
-		if ($encryptedKey) {
296
-			$this->setSystemPrivateKey($this->getRecoveryKeyId(), $header . $encryptedKey);
297
-			return true;
298
-		}
299
-		return false;
300
-	}
301
-
302
-	/**
303
-	 * @param $userId
304
-	 * @param $key
305
-	 * @return bool
306
-	 */
307
-	public function setPublicKey($userId, $key) {
308
-		return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key, Encryption::ID);
309
-	}
310
-
311
-	/**
312
-	 * @param $userId
313
-	 * @param string $key
314
-	 * @return bool
315
-	 */
316
-	public function setPrivateKey($userId, $key) {
317
-		return $this->keyStorage->setUserKey(
318
-			$userId,
319
-			$this->privateKeyId,
320
-			$key,
321
-			Encryption::ID
322
-		);
323
-	}
324
-
325
-	/**
326
-	 * write file key to key storage
327
-	 *
328
-	 * @param string $path
329
-	 * @param string $key
330
-	 * @return boolean
331
-	 */
332
-	public function setFileKey($path, $key) {
333
-		return $this->keyStorage->setFileKey($path, $this->fileKeyId, $key, Encryption::ID);
334
-	}
335
-
336
-	/**
337
-	 * set all file keys (the file key and the corresponding share keys)
338
-	 *
339
-	 * @param string $path
340
-	 * @param array $keys
341
-	 */
342
-	public function setAllFileKeys($path, $keys) {
343
-		$this->setFileKey($path, $keys['data']);
344
-		foreach ($keys['keys'] as $uid => $keyFile) {
345
-			$this->setShareKey($path, $uid, $keyFile);
346
-		}
347
-	}
348
-
349
-	/**
350
-	 * write share key to the key storage
351
-	 *
352
-	 * @param string $path
353
-	 * @param string $uid
354
-	 * @param string $key
355
-	 * @return boolean
356
-	 */
357
-	public function setShareKey($path, $uid, $key) {
358
-		$keyId = $uid . '.' . $this->shareKeyId;
359
-		return $this->keyStorage->setFileKey($path, $keyId, $key, Encryption::ID);
360
-	}
361
-
362
-	/**
363
-	 * Decrypt private key and store it
364
-	 *
365
-	 * @param string $uid user id
366
-	 * @param string $passPhrase users password
367
-	 * @return boolean
368
-	 */
369
-	public function init($uid, $passPhrase) {
370
-		$this->session->setStatus(Session::INIT_EXECUTED);
371
-
372
-		try {
373
-			if ($this->util->isMasterKeyEnabled()) {
374
-				$uid = $this->getMasterKeyId();
375
-				$passPhrase = $this->getMasterKeyPassword();
376
-				$privateKey = $this->getSystemPrivateKey($uid);
377
-			} else {
378
-				$privateKey = $this->getPrivateKey($uid);
379
-			}
380
-			$privateKey = $this->crypt->decryptPrivateKey($privateKey, $passPhrase, $uid);
381
-		} catch (PrivateKeyMissingException $e) {
382
-			return false;
383
-		} catch (DecryptionFailedException $e) {
384
-			return false;
385
-		} catch (\Exception $e) {
386
-			$this->log->warning(
387
-				'Could not decrypt the private key from user "' . $uid . '"" during login. ' .
388
-				'Assume password change on the user back-end. Error message: '
389
-				. $e->getMessage()
390
-			);
391
-			return false;
392
-		}
393
-
394
-		if ($privateKey) {
395
-			$this->session->setPrivateKey($privateKey);
396
-			$this->session->setStatus(Session::INIT_SUCCESSFUL);
397
-			return true;
398
-		}
399
-
400
-		return false;
401
-	}
402
-
403
-	/**
404
-	 * @param $userId
405
-	 * @return string
406
-	 * @throws PrivateKeyMissingException
407
-	 */
408
-	public function getPrivateKey($userId) {
409
-		$privateKey = $this->keyStorage->getUserKey(
410
-			$userId,
411
-			$this->privateKeyId,
412
-			Encryption::ID
413
-		);
414
-
415
-		if (strlen($privateKey) !== 0) {
416
-			return $privateKey;
417
-		}
418
-		throw new PrivateKeyMissingException($userId);
419
-	}
420
-
421
-	/**
422
-	 * @param string $path
423
-	 * @param $uid
424
-	 * @return string
425
-	 */
426
-	public function getFileKey($path, $uid) {
427
-		if ($uid === '') {
428
-			$uid = null;
429
-		}
430
-		$publicAccess = is_null($uid);
431
-		$encryptedFileKey = $this->keyStorage->getFileKey($path, $this->fileKeyId, Encryption::ID);
432
-
433
-		if (empty($encryptedFileKey)) {
434
-			return '';
435
-		}
436
-
437
-		if ($this->util->isMasterKeyEnabled()) {
438
-			$uid = $this->getMasterKeyId();
439
-			$shareKey = $this->getShareKey($path, $uid);
440
-			if ($publicAccess) {
441
-				$privateKey = $this->getSystemPrivateKey($uid);
442
-				$privateKey = $this->crypt->decryptPrivateKey($privateKey, $this->getMasterKeyPassword(), $uid);
443
-			} else {
444
-				// when logged in, the master key is already decrypted in the session
445
-				$privateKey = $this->session->getPrivateKey();
446
-			}
447
-		} elseif ($publicAccess) {
448
-			// use public share key for public links
449
-			$uid = $this->getPublicShareKeyId();
450
-			$shareKey = $this->getShareKey($path, $uid);
451
-			$privateKey = $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.privateKey', Encryption::ID);
452
-			$privateKey = $this->crypt->decryptPrivateKey($privateKey);
453
-		} else {
454
-			$shareKey = $this->getShareKey($path, $uid);
455
-			$privateKey = $this->session->getPrivateKey();
456
-		}
457
-
458
-		if ($encryptedFileKey && $shareKey && $privateKey) {
459
-			return $this->crypt->multiKeyDecrypt(
460
-				$encryptedFileKey,
461
-				$shareKey,
462
-				$privateKey
463
-			);
464
-		}
465
-
466
-		return '';
467
-	}
468
-
469
-	/**
470
-	 * Get the current version of a file
471
-	 *
472
-	 * @param string $path
473
-	 * @param View $view
474
-	 * @return int
475
-	 */
476
-	public function getVersion($path, View $view) {
477
-		$fileInfo = $view->getFileInfo($path);
478
-		if ($fileInfo === false) {
479
-			return 0;
480
-		}
481
-		return $fileInfo->getEncryptedVersion();
482
-	}
483
-
484
-	/**
485
-	 * Set the current version of a file
486
-	 *
487
-	 * @param string $path
488
-	 * @param int $version
489
-	 * @param View $view
490
-	 */
491
-	public function setVersion($path, $version, View $view) {
492
-		$fileInfo = $view->getFileInfo($path);
493
-
494
-		if ($fileInfo !== false) {
495
-			$cache = $fileInfo->getStorage()->getCache();
496
-			$cache->update($fileInfo->getId(), ['encrypted' => $version, 'encryptedVersion' => $version]);
497
-		}
498
-	}
499
-
500
-	/**
501
-	 * get the encrypted file key
502
-	 *
503
-	 * @param string $path
504
-	 * @return string
505
-	 */
506
-	public function getEncryptedFileKey($path) {
507
-		$encryptedFileKey = $this->keyStorage->getFileKey(
508
-			$path,
509
-			$this->fileKeyId,
510
-			Encryption::ID
511
-		);
512
-
513
-		return $encryptedFileKey;
514
-	}
515
-
516
-	/**
517
-	 * delete share key
518
-	 *
519
-	 * @param string $path
520
-	 * @param string $keyId
521
-	 * @return boolean
522
-	 */
523
-	public function deleteShareKey($path, $keyId) {
524
-		return $this->keyStorage->deleteFileKey(
525
-			$path,
526
-			$keyId . '.' . $this->shareKeyId,
527
-			Encryption::ID
528
-		);
529
-	}
530
-
531
-
532
-	/**
533
-	 * @param $path
534
-	 * @param $uid
535
-	 * @return mixed
536
-	 */
537
-	public function getShareKey($path, $uid) {
538
-		$keyId = $uid . '.' . $this->shareKeyId;
539
-		return $this->keyStorage->getFileKey($path, $keyId, Encryption::ID);
540
-	}
541
-
542
-	/**
543
-	 * check if user has a private and a public key
544
-	 *
545
-	 * @param string $userId
546
-	 * @return bool
547
-	 * @throws PrivateKeyMissingException
548
-	 * @throws PublicKeyMissingException
549
-	 */
550
-	public function userHasKeys($userId) {
551
-		$privateKey = $publicKey = true;
552
-		$exception = null;
553
-
554
-		try {
555
-			$this->getPrivateKey($userId);
556
-		} catch (PrivateKeyMissingException $e) {
557
-			$privateKey = false;
558
-			$exception = $e;
559
-		}
560
-		try {
561
-			$this->getPublicKey($userId);
562
-		} catch (PublicKeyMissingException $e) {
563
-			$publicKey = false;
564
-			$exception = $e;
565
-		}
566
-
567
-		if ($privateKey && $publicKey) {
568
-			return true;
569
-		} elseif (!$privateKey && !$publicKey) {
570
-			return false;
571
-		} else {
572
-			throw $exception;
573
-		}
574
-	}
575
-
576
-	/**
577
-	 * @param $userId
578
-	 * @return mixed
579
-	 * @throws PublicKeyMissingException
580
-	 */
581
-	public function getPublicKey($userId) {
582
-		$publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId, Encryption::ID);
583
-
584
-		if (strlen($publicKey) !== 0) {
585
-			return $publicKey;
586
-		}
587
-		throw new PublicKeyMissingException($userId);
588
-	}
589
-
590
-	public function getPublicShareKeyId() {
591
-		return $this->publicShareKeyId;
592
-	}
593
-
594
-	/**
595
-	 * get public key for public link shares
596
-	 *
597
-	 * @return string
598
-	 */
599
-	public function getPublicShareKey() {
600
-		return $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.publicKey', Encryption::ID);
601
-	}
602
-
603
-	/**
604
-	 * @param string $purpose
605
-	 * @param string $uid
606
-	 */
607
-	public function backupUserKeys($purpose, $uid) {
608
-		$this->keyStorage->backupUserKeys(Encryption::ID, $purpose, $uid);
609
-	}
610
-
611
-	/**
612
-	 * creat a backup of the users private and public key and then  delete it
613
-	 *
614
-	 * @param string $uid
615
-	 */
616
-	public function deleteUserKeys($uid) {
617
-		$this->deletePublicKey($uid);
618
-		$this->deletePrivateKey($uid);
619
-	}
620
-
621
-	/**
622
-	 * @param $uid
623
-	 * @return bool
624
-	 */
625
-	public function deletePublicKey($uid) {
626
-		return $this->keyStorage->deleteUserKey($uid, $this->publicKeyId, Encryption::ID);
627
-	}
628
-
629
-	/**
630
-	 * @param string $uid
631
-	 * @return bool
632
-	 */
633
-	private function deletePrivateKey($uid) {
634
-		return $this->keyStorage->deleteUserKey($uid, $this->privateKeyId, Encryption::ID);
635
-	}
636
-
637
-	/**
638
-	 * @param string $path
639
-	 * @return bool
640
-	 */
641
-	public function deleteAllFileKeys($path) {
642
-		return $this->keyStorage->deleteAllFileKeys($path);
643
-	}
644
-
645
-	/**
646
-	 * @param array $userIds
647
-	 * @return array
648
-	 * @throws PublicKeyMissingException
649
-	 */
650
-	public function getPublicKeys(array $userIds) {
651
-		$keys = [];
652
-
653
-		foreach ($userIds as $userId) {
654
-			try {
655
-				$keys[$userId] = $this->getPublicKey($userId);
656
-			} catch (PublicKeyMissingException $e) {
657
-				continue;
658
-			}
659
-		}
660
-
661
-		return $keys;
662
-	}
663
-
664
-	/**
665
-	 * @param string $keyId
666
-	 * @return string returns openssl key
667
-	 */
668
-	public function getSystemPrivateKey($keyId) {
669
-		return $this->keyStorage->getSystemUserKey($keyId . '.' . $this->privateKeyId, Encryption::ID);
670
-	}
671
-
672
-	/**
673
-	 * @param string $keyId
674
-	 * @param string $key
675
-	 * @return string returns openssl key
676
-	 */
677
-	public function setSystemPrivateKey($keyId, $key) {
678
-		return $this->keyStorage->setSystemUserKey(
679
-			$keyId . '.' . $this->privateKeyId,
680
-			$key,
681
-			Encryption::ID
682
-		);
683
-	}
684
-
685
-	/**
686
-	 * add system keys such as the public share key and the recovery key
687
-	 *
688
-	 * @param array $accessList
689
-	 * @param array $publicKeys
690
-	 * @param string $uid
691
-	 * @return array
692
-	 * @throws PublicKeyMissingException
693
-	 */
694
-	public function addSystemKeys(array $accessList, array $publicKeys, $uid) {
695
-		if (!empty($accessList['public'])) {
696
-			$publicShareKey = $this->getPublicShareKey();
697
-			if (empty($publicShareKey)) {
698
-				throw new PublicKeyMissingException($this->getPublicShareKeyId());
699
-			}
700
-			$publicKeys[$this->getPublicShareKeyId()] = $publicShareKey;
701
-		}
702
-
703
-		if ($this->recoveryKeyExists() &&
704
-			$this->util->isRecoveryEnabledForUser($uid)) {
705
-			$publicKeys[$this->getRecoveryKeyId()] = $this->getRecoveryKey();
706
-		}
707
-
708
-		return $publicKeys;
709
-	}
710
-
711
-	/**
712
-	 * get master key password
713
-	 *
714
-	 * @return string
715
-	 * @throws \Exception
716
-	 */
717
-	public function getMasterKeyPassword() {
718
-		$password = $this->config->getSystemValue('secret');
719
-		if (empty($password)) {
720
-			throw new \Exception('Can not get secret from Nextcloud instance');
721
-		}
722
-
723
-		return $password;
724
-	}
725
-
726
-	/**
727
-	 * return master key id
728
-	 *
729
-	 * @return string
730
-	 */
731
-	public function getMasterKeyId() {
732
-		return $this->masterKeyId;
733
-	}
734
-
735
-	/**
736
-	 * get public master key
737
-	 *
738
-	 * @return string
739
-	 */
740
-	public function getPublicMasterKey() {
741
-		return $this->keyStorage->getSystemUserKey($this->masterKeyId . '.publicKey', Encryption::ID);
742
-	}
41
+    /**
42
+     * @var Session
43
+     */
44
+    protected $session;
45
+    /**
46
+     * @var IStorage
47
+     */
48
+    private $keyStorage;
49
+    /**
50
+     * @var Crypt
51
+     */
52
+    private $crypt;
53
+    /**
54
+     * @var string
55
+     */
56
+    private $recoveryKeyId;
57
+    /**
58
+     * @var string
59
+     */
60
+    private $publicShareKeyId;
61
+    /**
62
+     * @var string
63
+     */
64
+    private $masterKeyId;
65
+    /**
66
+     * @var string UserID
67
+     */
68
+    private $keyId;
69
+    /**
70
+     * @var string
71
+     */
72
+    private $publicKeyId = 'publicKey';
73
+    /**
74
+     * @var string
75
+     */
76
+    private $privateKeyId = 'privateKey';
77
+
78
+    /**
79
+     * @var string
80
+     */
81
+    private $shareKeyId = 'shareKey';
82
+
83
+    /**
84
+     * @var string
85
+     */
86
+    private $fileKeyId = 'fileKey';
87
+    /**
88
+     * @var IConfig
89
+     */
90
+    private $config;
91
+    /**
92
+     * @var ILogger
93
+     */
94
+    private $log;
95
+    /**
96
+     * @var Util
97
+     */
98
+    private $util;
99
+
100
+    /**
101
+     * @param IStorage $keyStorage
102
+     * @param Crypt $crypt
103
+     * @param IConfig $config
104
+     * @param IUserSession $userSession
105
+     * @param Session $session
106
+     * @param ILogger $log
107
+     * @param Util $util
108
+     */
109
+    public function __construct(
110
+        IStorage $keyStorage,
111
+        Crypt $crypt,
112
+        IConfig $config,
113
+        IUserSession $userSession,
114
+        Session $session,
115
+        ILogger $log,
116
+        Util $util
117
+    ) {
118
+        $this->util = $util;
119
+        $this->session = $session;
120
+        $this->keyStorage = $keyStorage;
121
+        $this->crypt = $crypt;
122
+        $this->config = $config;
123
+        $this->log = $log;
124
+
125
+        $this->recoveryKeyId = $this->config->getAppValue(
126
+            'encryption',
127
+            'recoveryKeyId'
128
+        );
129
+        if (empty($this->recoveryKeyId)) {
130
+            $this->recoveryKeyId = 'recoveryKey_' . substr(md5(time()), 0, 8);
131
+            $this->config->setAppValue(
132
+                'encryption',
133
+                'recoveryKeyId',
134
+                $this->recoveryKeyId
135
+            );
136
+        }
137
+
138
+        $this->publicShareKeyId = $this->config->getAppValue(
139
+            'encryption',
140
+            'publicShareKeyId'
141
+        );
142
+        if (empty($this->publicShareKeyId)) {
143
+            $this->publicShareKeyId = 'pubShare_' . substr(md5(time()), 0, 8);
144
+            $this->config->setAppValue('encryption', 'publicShareKeyId', $this->publicShareKeyId);
145
+        }
146
+
147
+        $this->masterKeyId = $this->config->getAppValue(
148
+            'encryption',
149
+            'masterKeyId'
150
+        );
151
+        if (empty($this->masterKeyId)) {
152
+            $this->masterKeyId = 'master_' . substr(md5(time()), 0, 8);
153
+            $this->config->setAppValue('encryption', 'masterKeyId', $this->masterKeyId);
154
+        }
155
+
156
+        $this->keyId = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : false;
157
+        $this->log = $log;
158
+    }
159
+
160
+    /**
161
+     * check if key pair for public link shares exists, if not we create one
162
+     */
163
+    public function validateShareKey() {
164
+        $shareKey = $this->getPublicShareKey();
165
+        if (empty($shareKey)) {
166
+            $keyPair = $this->crypt->createKeyPair();
167
+
168
+            // Save public key
169
+            $this->keyStorage->setSystemUserKey(
170
+                $this->publicShareKeyId . '.publicKey',
171
+                $keyPair['publicKey'],
172
+                Encryption::ID
173
+            );
174
+
175
+            // Encrypt private key empty passphrase
176
+            $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], '');
177
+            $header = $this->crypt->generateHeader();
178
+            $this->setSystemPrivateKey($this->publicShareKeyId, $header . $encryptedKey);
179
+        }
180
+    }
181
+
182
+    /**
183
+     * check if a key pair for the master key exists, if not we create one
184
+     */
185
+    public function validateMasterKey() {
186
+        if ($this->util->isMasterKeyEnabled() === false) {
187
+            return;
188
+        }
189
+
190
+        $publicMasterKey = $this->getPublicMasterKey();
191
+        if (empty($publicMasterKey)) {
192
+            $keyPair = $this->crypt->createKeyPair();
193
+
194
+            // Save public key
195
+            $this->keyStorage->setSystemUserKey(
196
+                $this->masterKeyId . '.publicKey',
197
+                $keyPair['publicKey'],
198
+                Encryption::ID
199
+            );
200
+
201
+            // Encrypt private key with system password
202
+            $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $this->getMasterKeyPassword(), $this->masterKeyId);
203
+            $header = $this->crypt->generateHeader();
204
+            $this->setSystemPrivateKey($this->masterKeyId, $header . $encryptedKey);
205
+        }
206
+
207
+        if (!$this->session->isPrivateKeySet()) {
208
+            $masterKey = $this->getSystemPrivateKey($this->masterKeyId);
209
+            $decryptedMasterKey = $this->crypt->decryptPrivateKey($masterKey, $this->getMasterKeyPassword(), $this->masterKeyId);
210
+            $this->session->setPrivateKey($decryptedMasterKey);
211
+        }
212
+
213
+        // after the encryption key is available we are ready to go
214
+        $this->session->setStatus(Session::INIT_SUCCESSFUL);
215
+    }
216
+
217
+    /**
218
+     * @return bool
219
+     */
220
+    public function recoveryKeyExists() {
221
+        $key = $this->getRecoveryKey();
222
+        return (!empty($key));
223
+    }
224
+
225
+    /**
226
+     * get recovery key
227
+     *
228
+     * @return string
229
+     */
230
+    public function getRecoveryKey() {
231
+        return $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.publicKey', Encryption::ID);
232
+    }
233
+
234
+    /**
235
+     * get recovery key ID
236
+     *
237
+     * @return string
238
+     */
239
+    public function getRecoveryKeyId() {
240
+        return $this->recoveryKeyId;
241
+    }
242
+
243
+    /**
244
+     * @param string $password
245
+     * @return bool
246
+     */
247
+    public function checkRecoveryPassword($password) {
248
+        $recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.privateKey', Encryption::ID);
249
+        $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $password);
250
+
251
+        if ($decryptedRecoveryKey) {
252
+            return true;
253
+        }
254
+        return false;
255
+    }
256
+
257
+    /**
258
+     * @param string $uid
259
+     * @param string $password
260
+     * @param string $keyPair
261
+     * @return bool
262
+     */
263
+    public function storeKeyPair($uid, $password, $keyPair) {
264
+        // Save Public Key
265
+        $this->setPublicKey($uid, $keyPair['publicKey']);
266
+
267
+        $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password, $uid);
268
+
269
+        $header = $this->crypt->generateHeader();
270
+
271
+        if ($encryptedKey) {
272
+            $this->setPrivateKey($uid, $header . $encryptedKey);
273
+            return true;
274
+        }
275
+        return false;
276
+    }
277
+
278
+    /**
279
+     * @param string $password
280
+     * @param array $keyPair
281
+     * @return bool
282
+     */
283
+    public function setRecoveryKey($password, $keyPair) {
284
+        // Save Public Key
285
+        $this->keyStorage->setSystemUserKey(
286
+            $this->getRecoveryKeyId().
287
+            '.publicKey',
288
+            $keyPair['publicKey'],
289
+            Encryption::ID
290
+        );
291
+
292
+        $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password);
293
+        $header = $this->crypt->generateHeader();
294
+
295
+        if ($encryptedKey) {
296
+            $this->setSystemPrivateKey($this->getRecoveryKeyId(), $header . $encryptedKey);
297
+            return true;
298
+        }
299
+        return false;
300
+    }
301
+
302
+    /**
303
+     * @param $userId
304
+     * @param $key
305
+     * @return bool
306
+     */
307
+    public function setPublicKey($userId, $key) {
308
+        return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key, Encryption::ID);
309
+    }
310
+
311
+    /**
312
+     * @param $userId
313
+     * @param string $key
314
+     * @return bool
315
+     */
316
+    public function setPrivateKey($userId, $key) {
317
+        return $this->keyStorage->setUserKey(
318
+            $userId,
319
+            $this->privateKeyId,
320
+            $key,
321
+            Encryption::ID
322
+        );
323
+    }
324
+
325
+    /**
326
+     * write file key to key storage
327
+     *
328
+     * @param string $path
329
+     * @param string $key
330
+     * @return boolean
331
+     */
332
+    public function setFileKey($path, $key) {
333
+        return $this->keyStorage->setFileKey($path, $this->fileKeyId, $key, Encryption::ID);
334
+    }
335
+
336
+    /**
337
+     * set all file keys (the file key and the corresponding share keys)
338
+     *
339
+     * @param string $path
340
+     * @param array $keys
341
+     */
342
+    public function setAllFileKeys($path, $keys) {
343
+        $this->setFileKey($path, $keys['data']);
344
+        foreach ($keys['keys'] as $uid => $keyFile) {
345
+            $this->setShareKey($path, $uid, $keyFile);
346
+        }
347
+    }
348
+
349
+    /**
350
+     * write share key to the key storage
351
+     *
352
+     * @param string $path
353
+     * @param string $uid
354
+     * @param string $key
355
+     * @return boolean
356
+     */
357
+    public function setShareKey($path, $uid, $key) {
358
+        $keyId = $uid . '.' . $this->shareKeyId;
359
+        return $this->keyStorage->setFileKey($path, $keyId, $key, Encryption::ID);
360
+    }
361
+
362
+    /**
363
+     * Decrypt private key and store it
364
+     *
365
+     * @param string $uid user id
366
+     * @param string $passPhrase users password
367
+     * @return boolean
368
+     */
369
+    public function init($uid, $passPhrase) {
370
+        $this->session->setStatus(Session::INIT_EXECUTED);
371
+
372
+        try {
373
+            if ($this->util->isMasterKeyEnabled()) {
374
+                $uid = $this->getMasterKeyId();
375
+                $passPhrase = $this->getMasterKeyPassword();
376
+                $privateKey = $this->getSystemPrivateKey($uid);
377
+            } else {
378
+                $privateKey = $this->getPrivateKey($uid);
379
+            }
380
+            $privateKey = $this->crypt->decryptPrivateKey($privateKey, $passPhrase, $uid);
381
+        } catch (PrivateKeyMissingException $e) {
382
+            return false;
383
+        } catch (DecryptionFailedException $e) {
384
+            return false;
385
+        } catch (\Exception $e) {
386
+            $this->log->warning(
387
+                'Could not decrypt the private key from user "' . $uid . '"" during login. ' .
388
+                'Assume password change on the user back-end. Error message: '
389
+                . $e->getMessage()
390
+            );
391
+            return false;
392
+        }
393
+
394
+        if ($privateKey) {
395
+            $this->session->setPrivateKey($privateKey);
396
+            $this->session->setStatus(Session::INIT_SUCCESSFUL);
397
+            return true;
398
+        }
399
+
400
+        return false;
401
+    }
402
+
403
+    /**
404
+     * @param $userId
405
+     * @return string
406
+     * @throws PrivateKeyMissingException
407
+     */
408
+    public function getPrivateKey($userId) {
409
+        $privateKey = $this->keyStorage->getUserKey(
410
+            $userId,
411
+            $this->privateKeyId,
412
+            Encryption::ID
413
+        );
414
+
415
+        if (strlen($privateKey) !== 0) {
416
+            return $privateKey;
417
+        }
418
+        throw new PrivateKeyMissingException($userId);
419
+    }
420
+
421
+    /**
422
+     * @param string $path
423
+     * @param $uid
424
+     * @return string
425
+     */
426
+    public function getFileKey($path, $uid) {
427
+        if ($uid === '') {
428
+            $uid = null;
429
+        }
430
+        $publicAccess = is_null($uid);
431
+        $encryptedFileKey = $this->keyStorage->getFileKey($path, $this->fileKeyId, Encryption::ID);
432
+
433
+        if (empty($encryptedFileKey)) {
434
+            return '';
435
+        }
436
+
437
+        if ($this->util->isMasterKeyEnabled()) {
438
+            $uid = $this->getMasterKeyId();
439
+            $shareKey = $this->getShareKey($path, $uid);
440
+            if ($publicAccess) {
441
+                $privateKey = $this->getSystemPrivateKey($uid);
442
+                $privateKey = $this->crypt->decryptPrivateKey($privateKey, $this->getMasterKeyPassword(), $uid);
443
+            } else {
444
+                // when logged in, the master key is already decrypted in the session
445
+                $privateKey = $this->session->getPrivateKey();
446
+            }
447
+        } elseif ($publicAccess) {
448
+            // use public share key for public links
449
+            $uid = $this->getPublicShareKeyId();
450
+            $shareKey = $this->getShareKey($path, $uid);
451
+            $privateKey = $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.privateKey', Encryption::ID);
452
+            $privateKey = $this->crypt->decryptPrivateKey($privateKey);
453
+        } else {
454
+            $shareKey = $this->getShareKey($path, $uid);
455
+            $privateKey = $this->session->getPrivateKey();
456
+        }
457
+
458
+        if ($encryptedFileKey && $shareKey && $privateKey) {
459
+            return $this->crypt->multiKeyDecrypt(
460
+                $encryptedFileKey,
461
+                $shareKey,
462
+                $privateKey
463
+            );
464
+        }
465
+
466
+        return '';
467
+    }
468
+
469
+    /**
470
+     * Get the current version of a file
471
+     *
472
+     * @param string $path
473
+     * @param View $view
474
+     * @return int
475
+     */
476
+    public function getVersion($path, View $view) {
477
+        $fileInfo = $view->getFileInfo($path);
478
+        if ($fileInfo === false) {
479
+            return 0;
480
+        }
481
+        return $fileInfo->getEncryptedVersion();
482
+    }
483
+
484
+    /**
485
+     * Set the current version of a file
486
+     *
487
+     * @param string $path
488
+     * @param int $version
489
+     * @param View $view
490
+     */
491
+    public function setVersion($path, $version, View $view) {
492
+        $fileInfo = $view->getFileInfo($path);
493
+
494
+        if ($fileInfo !== false) {
495
+            $cache = $fileInfo->getStorage()->getCache();
496
+            $cache->update($fileInfo->getId(), ['encrypted' => $version, 'encryptedVersion' => $version]);
497
+        }
498
+    }
499
+
500
+    /**
501
+     * get the encrypted file key
502
+     *
503
+     * @param string $path
504
+     * @return string
505
+     */
506
+    public function getEncryptedFileKey($path) {
507
+        $encryptedFileKey = $this->keyStorage->getFileKey(
508
+            $path,
509
+            $this->fileKeyId,
510
+            Encryption::ID
511
+        );
512
+
513
+        return $encryptedFileKey;
514
+    }
515
+
516
+    /**
517
+     * delete share key
518
+     *
519
+     * @param string $path
520
+     * @param string $keyId
521
+     * @return boolean
522
+     */
523
+    public function deleteShareKey($path, $keyId) {
524
+        return $this->keyStorage->deleteFileKey(
525
+            $path,
526
+            $keyId . '.' . $this->shareKeyId,
527
+            Encryption::ID
528
+        );
529
+    }
530
+
531
+
532
+    /**
533
+     * @param $path
534
+     * @param $uid
535
+     * @return mixed
536
+     */
537
+    public function getShareKey($path, $uid) {
538
+        $keyId = $uid . '.' . $this->shareKeyId;
539
+        return $this->keyStorage->getFileKey($path, $keyId, Encryption::ID);
540
+    }
541
+
542
+    /**
543
+     * check if user has a private and a public key
544
+     *
545
+     * @param string $userId
546
+     * @return bool
547
+     * @throws PrivateKeyMissingException
548
+     * @throws PublicKeyMissingException
549
+     */
550
+    public function userHasKeys($userId) {
551
+        $privateKey = $publicKey = true;
552
+        $exception = null;
553
+
554
+        try {
555
+            $this->getPrivateKey($userId);
556
+        } catch (PrivateKeyMissingException $e) {
557
+            $privateKey = false;
558
+            $exception = $e;
559
+        }
560
+        try {
561
+            $this->getPublicKey($userId);
562
+        } catch (PublicKeyMissingException $e) {
563
+            $publicKey = false;
564
+            $exception = $e;
565
+        }
566
+
567
+        if ($privateKey && $publicKey) {
568
+            return true;
569
+        } elseif (!$privateKey && !$publicKey) {
570
+            return false;
571
+        } else {
572
+            throw $exception;
573
+        }
574
+    }
575
+
576
+    /**
577
+     * @param $userId
578
+     * @return mixed
579
+     * @throws PublicKeyMissingException
580
+     */
581
+    public function getPublicKey($userId) {
582
+        $publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId, Encryption::ID);
583
+
584
+        if (strlen($publicKey) !== 0) {
585
+            return $publicKey;
586
+        }
587
+        throw new PublicKeyMissingException($userId);
588
+    }
589
+
590
+    public function getPublicShareKeyId() {
591
+        return $this->publicShareKeyId;
592
+    }
593
+
594
+    /**
595
+     * get public key for public link shares
596
+     *
597
+     * @return string
598
+     */
599
+    public function getPublicShareKey() {
600
+        return $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.publicKey', Encryption::ID);
601
+    }
602
+
603
+    /**
604
+     * @param string $purpose
605
+     * @param string $uid
606
+     */
607
+    public function backupUserKeys($purpose, $uid) {
608
+        $this->keyStorage->backupUserKeys(Encryption::ID, $purpose, $uid);
609
+    }
610
+
611
+    /**
612
+     * creat a backup of the users private and public key and then  delete it
613
+     *
614
+     * @param string $uid
615
+     */
616
+    public function deleteUserKeys($uid) {
617
+        $this->deletePublicKey($uid);
618
+        $this->deletePrivateKey($uid);
619
+    }
620
+
621
+    /**
622
+     * @param $uid
623
+     * @return bool
624
+     */
625
+    public function deletePublicKey($uid) {
626
+        return $this->keyStorage->deleteUserKey($uid, $this->publicKeyId, Encryption::ID);
627
+    }
628
+
629
+    /**
630
+     * @param string $uid
631
+     * @return bool
632
+     */
633
+    private function deletePrivateKey($uid) {
634
+        return $this->keyStorage->deleteUserKey($uid, $this->privateKeyId, Encryption::ID);
635
+    }
636
+
637
+    /**
638
+     * @param string $path
639
+     * @return bool
640
+     */
641
+    public function deleteAllFileKeys($path) {
642
+        return $this->keyStorage->deleteAllFileKeys($path);
643
+    }
644
+
645
+    /**
646
+     * @param array $userIds
647
+     * @return array
648
+     * @throws PublicKeyMissingException
649
+     */
650
+    public function getPublicKeys(array $userIds) {
651
+        $keys = [];
652
+
653
+        foreach ($userIds as $userId) {
654
+            try {
655
+                $keys[$userId] = $this->getPublicKey($userId);
656
+            } catch (PublicKeyMissingException $e) {
657
+                continue;
658
+            }
659
+        }
660
+
661
+        return $keys;
662
+    }
663
+
664
+    /**
665
+     * @param string $keyId
666
+     * @return string returns openssl key
667
+     */
668
+    public function getSystemPrivateKey($keyId) {
669
+        return $this->keyStorage->getSystemUserKey($keyId . '.' . $this->privateKeyId, Encryption::ID);
670
+    }
671
+
672
+    /**
673
+     * @param string $keyId
674
+     * @param string $key
675
+     * @return string returns openssl key
676
+     */
677
+    public function setSystemPrivateKey($keyId, $key) {
678
+        return $this->keyStorage->setSystemUserKey(
679
+            $keyId . '.' . $this->privateKeyId,
680
+            $key,
681
+            Encryption::ID
682
+        );
683
+    }
684
+
685
+    /**
686
+     * add system keys such as the public share key and the recovery key
687
+     *
688
+     * @param array $accessList
689
+     * @param array $publicKeys
690
+     * @param string $uid
691
+     * @return array
692
+     * @throws PublicKeyMissingException
693
+     */
694
+    public function addSystemKeys(array $accessList, array $publicKeys, $uid) {
695
+        if (!empty($accessList['public'])) {
696
+            $publicShareKey = $this->getPublicShareKey();
697
+            if (empty($publicShareKey)) {
698
+                throw new PublicKeyMissingException($this->getPublicShareKeyId());
699
+            }
700
+            $publicKeys[$this->getPublicShareKeyId()] = $publicShareKey;
701
+        }
702
+
703
+        if ($this->recoveryKeyExists() &&
704
+            $this->util->isRecoveryEnabledForUser($uid)) {
705
+            $publicKeys[$this->getRecoveryKeyId()] = $this->getRecoveryKey();
706
+        }
707
+
708
+        return $publicKeys;
709
+    }
710
+
711
+    /**
712
+     * get master key password
713
+     *
714
+     * @return string
715
+     * @throws \Exception
716
+     */
717
+    public function getMasterKeyPassword() {
718
+        $password = $this->config->getSystemValue('secret');
719
+        if (empty($password)) {
720
+            throw new \Exception('Can not get secret from Nextcloud instance');
721
+        }
722
+
723
+        return $password;
724
+    }
725
+
726
+    /**
727
+     * return master key id
728
+     *
729
+     * @return string
730
+     */
731
+    public function getMasterKeyId() {
732
+        return $this->masterKeyId;
733
+    }
734
+
735
+    /**
736
+     * get public master key
737
+     *
738
+     * @return string
739
+     */
740
+    public function getPublicMasterKey() {
741
+        return $this->keyStorage->getSystemUserKey($this->masterKeyId . '.publicKey', Encryption::ID);
742
+    }
743 743
 }
Please login to merge, or discard this patch.
Spacing   +20 added lines, -20 removed lines patch added patch discarded remove patch
@@ -127,7 +127,7 @@  discard block
 block discarded – undo
127 127
 			'recoveryKeyId'
128 128
 		);
129 129
 		if (empty($this->recoveryKeyId)) {
130
-			$this->recoveryKeyId = 'recoveryKey_' . substr(md5(time()), 0, 8);
130
+			$this->recoveryKeyId = 'recoveryKey_'.substr(md5(time()), 0, 8);
131 131
 			$this->config->setAppValue(
132 132
 				'encryption',
133 133
 				'recoveryKeyId',
@@ -140,7 +140,7 @@  discard block
 block discarded – undo
140 140
 			'publicShareKeyId'
141 141
 		);
142 142
 		if (empty($this->publicShareKeyId)) {
143
-			$this->publicShareKeyId = 'pubShare_' . substr(md5(time()), 0, 8);
143
+			$this->publicShareKeyId = 'pubShare_'.substr(md5(time()), 0, 8);
144 144
 			$this->config->setAppValue('encryption', 'publicShareKeyId', $this->publicShareKeyId);
145 145
 		}
146 146
 
@@ -149,7 +149,7 @@  discard block
 block discarded – undo
149 149
 			'masterKeyId'
150 150
 		);
151 151
 		if (empty($this->masterKeyId)) {
152
-			$this->masterKeyId = 'master_' . substr(md5(time()), 0, 8);
152
+			$this->masterKeyId = 'master_'.substr(md5(time()), 0, 8);
153 153
 			$this->config->setAppValue('encryption', 'masterKeyId', $this->masterKeyId);
154 154
 		}
155 155
 
@@ -167,7 +167,7 @@  discard block
 block discarded – undo
167 167
 
168 168
 			// Save public key
169 169
 			$this->keyStorage->setSystemUserKey(
170
-				$this->publicShareKeyId . '.publicKey',
170
+				$this->publicShareKeyId.'.publicKey',
171 171
 				$keyPair['publicKey'],
172 172
 				Encryption::ID
173 173
 			);
@@ -175,7 +175,7 @@  discard block
 block discarded – undo
175 175
 			// Encrypt private key empty passphrase
176 176
 			$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], '');
177 177
 			$header = $this->crypt->generateHeader();
178
-			$this->setSystemPrivateKey($this->publicShareKeyId, $header . $encryptedKey);
178
+			$this->setSystemPrivateKey($this->publicShareKeyId, $header.$encryptedKey);
179 179
 		}
180 180
 	}
181 181
 
@@ -193,7 +193,7 @@  discard block
 block discarded – undo
193 193
 
194 194
 			// Save public key
195 195
 			$this->keyStorage->setSystemUserKey(
196
-				$this->masterKeyId . '.publicKey',
196
+				$this->masterKeyId.'.publicKey',
197 197
 				$keyPair['publicKey'],
198 198
 				Encryption::ID
199 199
 			);
@@ -201,7 +201,7 @@  discard block
 block discarded – undo
201 201
 			// Encrypt private key with system password
202 202
 			$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $this->getMasterKeyPassword(), $this->masterKeyId);
203 203
 			$header = $this->crypt->generateHeader();
204
-			$this->setSystemPrivateKey($this->masterKeyId, $header . $encryptedKey);
204
+			$this->setSystemPrivateKey($this->masterKeyId, $header.$encryptedKey);
205 205
 		}
206 206
 
207 207
 		if (!$this->session->isPrivateKeySet()) {
@@ -228,7 +228,7 @@  discard block
 block discarded – undo
228 228
 	 * @return string
229 229
 	 */
230 230
 	public function getRecoveryKey() {
231
-		return $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.publicKey', Encryption::ID);
231
+		return $this->keyStorage->getSystemUserKey($this->recoveryKeyId.'.publicKey', Encryption::ID);
232 232
 	}
233 233
 
234 234
 	/**
@@ -245,7 +245,7 @@  discard block
 block discarded – undo
245 245
 	 * @return bool
246 246
 	 */
247 247
 	public function checkRecoveryPassword($password) {
248
-		$recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.privateKey', Encryption::ID);
248
+		$recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId.'.privateKey', Encryption::ID);
249 249
 		$decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $password);
250 250
 
251 251
 		if ($decryptedRecoveryKey) {
@@ -269,7 +269,7 @@  discard block
 block discarded – undo
269 269
 		$header = $this->crypt->generateHeader();
270 270
 
271 271
 		if ($encryptedKey) {
272
-			$this->setPrivateKey($uid, $header . $encryptedKey);
272
+			$this->setPrivateKey($uid, $header.$encryptedKey);
273 273
 			return true;
274 274
 		}
275 275
 		return false;
@@ -293,7 +293,7 @@  discard block
 block discarded – undo
293 293
 		$header = $this->crypt->generateHeader();
294 294
 
295 295
 		if ($encryptedKey) {
296
-			$this->setSystemPrivateKey($this->getRecoveryKeyId(), $header . $encryptedKey);
296
+			$this->setSystemPrivateKey($this->getRecoveryKeyId(), $header.$encryptedKey);
297 297
 			return true;
298 298
 		}
299 299
 		return false;
@@ -355,7 +355,7 @@  discard block
 block discarded – undo
355 355
 	 * @return boolean
356 356
 	 */
357 357
 	public function setShareKey($path, $uid, $key) {
358
-		$keyId = $uid . '.' . $this->shareKeyId;
358
+		$keyId = $uid.'.'.$this->shareKeyId;
359 359
 		return $this->keyStorage->setFileKey($path, $keyId, $key, Encryption::ID);
360 360
 	}
361 361
 
@@ -384,7 +384,7 @@  discard block
 block discarded – undo
384 384
 			return false;
385 385
 		} catch (\Exception $e) {
386 386
 			$this->log->warning(
387
-				'Could not decrypt the private key from user "' . $uid . '"" during login. ' .
387
+				'Could not decrypt the private key from user "'.$uid.'"" during login. '.
388 388
 				'Assume password change on the user back-end. Error message: '
389 389
 				. $e->getMessage()
390 390
 			);
@@ -448,7 +448,7 @@  discard block
 block discarded – undo
448 448
 			// use public share key for public links
449 449
 			$uid = $this->getPublicShareKeyId();
450 450
 			$shareKey = $this->getShareKey($path, $uid);
451
-			$privateKey = $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.privateKey', Encryption::ID);
451
+			$privateKey = $this->keyStorage->getSystemUserKey($this->publicShareKeyId.'.privateKey', Encryption::ID);
452 452
 			$privateKey = $this->crypt->decryptPrivateKey($privateKey);
453 453
 		} else {
454 454
 			$shareKey = $this->getShareKey($path, $uid);
@@ -523,7 +523,7 @@  discard block
 block discarded – undo
523 523
 	public function deleteShareKey($path, $keyId) {
524 524
 		return $this->keyStorage->deleteFileKey(
525 525
 			$path,
526
-			$keyId . '.' . $this->shareKeyId,
526
+			$keyId.'.'.$this->shareKeyId,
527 527
 			Encryption::ID
528 528
 		);
529 529
 	}
@@ -535,7 +535,7 @@  discard block
 block discarded – undo
535 535
 	 * @return mixed
536 536
 	 */
537 537
 	public function getShareKey($path, $uid) {
538
-		$keyId = $uid . '.' . $this->shareKeyId;
538
+		$keyId = $uid.'.'.$this->shareKeyId;
539 539
 		return $this->keyStorage->getFileKey($path, $keyId, Encryption::ID);
540 540
 	}
541 541
 
@@ -597,7 +597,7 @@  discard block
 block discarded – undo
597 597
 	 * @return string
598 598
 	 */
599 599
 	public function getPublicShareKey() {
600
-		return $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.publicKey', Encryption::ID);
600
+		return $this->keyStorage->getSystemUserKey($this->publicShareKeyId.'.publicKey', Encryption::ID);
601 601
 	}
602 602
 
603 603
 	/**
@@ -666,7 +666,7 @@  discard block
 block discarded – undo
666 666
 	 * @return string returns openssl key
667 667
 	 */
668 668
 	public function getSystemPrivateKey($keyId) {
669
-		return $this->keyStorage->getSystemUserKey($keyId . '.' . $this->privateKeyId, Encryption::ID);
669
+		return $this->keyStorage->getSystemUserKey($keyId.'.'.$this->privateKeyId, Encryption::ID);
670 670
 	}
671 671
 
672 672
 	/**
@@ -676,7 +676,7 @@  discard block
 block discarded – undo
676 676
 	 */
677 677
 	public function setSystemPrivateKey($keyId, $key) {
678 678
 		return $this->keyStorage->setSystemUserKey(
679
-			$keyId . '.' . $this->privateKeyId,
679
+			$keyId.'.'.$this->privateKeyId,
680 680
 			$key,
681 681
 			Encryption::ID
682 682
 		);
@@ -738,6 +738,6 @@  discard block
 block discarded – undo
738 738
 	 * @return string
739 739
 	 */
740 740
 	public function getPublicMasterKey() {
741
-		return $this->keyStorage->getSystemUserKey($this->masterKeyId . '.publicKey', Encryption::ID);
741
+		return $this->keyStorage->getSystemUserKey($this->masterKeyId.'.publicKey', Encryption::ID);
742 742
 	}
743 743
 }
Please login to merge, or discard this patch.
apps/federatedfilesharing/lib/FederatedShareProvider.php 3 patches
Doc Comments   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -391,7 +391,7 @@  discard block
 block discarded – undo
391 391
 	/**
392 392
 	 * store remote ID in federated reShare table
393 393
 	 *
394
-	 * @param $shareId
394
+	 * @param integer $shareId
395 395
 	 * @param $remoteId
396 396
 	 */
397 397
 	public function storeRemoteId($shareId, $remoteId) {
@@ -729,7 +729,7 @@  discard block
 block discarded – undo
729 729
 	/**
730 730
 	 * get database row of a give share
731 731
 	 *
732
-	 * @param $id
732
+	 * @param integer $id
733 733
 	 * @return array
734 734
 	 * @throws ShareNotFound
735 735
 	 */
Please login to merge, or discard this patch.
Indentation   +970 added lines, -970 removed lines patch added patch discarded remove patch
@@ -49,984 +49,984 @@
 block discarded – undo
49 49
  * @package OCA\FederatedFileSharing
50 50
  */
51 51
 class FederatedShareProvider implements IShareProvider {
52
-	const SHARE_TYPE_REMOTE = 6;
53
-
54
-	/** @var IDBConnection */
55
-	private $dbConnection;
56
-
57
-	/** @var AddressHandler */
58
-	private $addressHandler;
59
-
60
-	/** @var Notifications */
61
-	private $notifications;
62
-
63
-	/** @var TokenHandler */
64
-	private $tokenHandler;
65
-
66
-	/** @var IL10N */
67
-	private $l;
68
-
69
-	/** @var ILogger */
70
-	private $logger;
71
-
72
-	/** @var IRootFolder */
73
-	private $rootFolder;
74
-
75
-	/** @var IConfig */
76
-	private $config;
77
-
78
-	/** @var string */
79
-	private $externalShareTable = 'share_external';
80
-
81
-	/** @var IUserManager */
82
-	private $userManager;
83
-
84
-	/** @var ICloudIdManager */
85
-	private $cloudIdManager;
86
-
87
-	/** @var \OCP\GlobalScale\IConfig */
88
-	private $gsConfig;
89
-
90
-	/**
91
-	 * DefaultShareProvider constructor.
92
-	 *
93
-	 * @param IDBConnection $connection
94
-	 * @param AddressHandler $addressHandler
95
-	 * @param Notifications $notifications
96
-	 * @param TokenHandler $tokenHandler
97
-	 * @param IL10N $l10n
98
-	 * @param ILogger $logger
99
-	 * @param IRootFolder $rootFolder
100
-	 * @param IConfig $config
101
-	 * @param IUserManager $userManager
102
-	 * @param ICloudIdManager $cloudIdManager
103
-	 * @param \OCP\GlobalScale\IConfig $globalScaleConfig
104
-	 */
105
-	public function __construct(
106
-			IDBConnection $connection,
107
-			AddressHandler $addressHandler,
108
-			Notifications $notifications,
109
-			TokenHandler $tokenHandler,
110
-			IL10N $l10n,
111
-			ILogger $logger,
112
-			IRootFolder $rootFolder,
113
-			IConfig $config,
114
-			IUserManager $userManager,
115
-			ICloudIdManager $cloudIdManager,
116
-			\OCP\GlobalScale\IConfig $globalScaleConfig
117
-	) {
118
-		$this->dbConnection = $connection;
119
-		$this->addressHandler = $addressHandler;
120
-		$this->notifications = $notifications;
121
-		$this->tokenHandler = $tokenHandler;
122
-		$this->l = $l10n;
123
-		$this->logger = $logger;
124
-		$this->rootFolder = $rootFolder;
125
-		$this->config = $config;
126
-		$this->userManager = $userManager;
127
-		$this->cloudIdManager = $cloudIdManager;
128
-		$this->gsConfig = $globalScaleConfig;
129
-	}
130
-
131
-	/**
132
-	 * Return the identifier of this provider.
133
-	 *
134
-	 * @return string Containing only [a-zA-Z0-9]
135
-	 */
136
-	public function identifier() {
137
-		return 'ocFederatedSharing';
138
-	}
139
-
140
-	/**
141
-	 * Share a path
142
-	 *
143
-	 * @param IShare $share
144
-	 * @return IShare The share object
145
-	 * @throws ShareNotFound
146
-	 * @throws \Exception
147
-	 */
148
-	public function create(IShare $share) {
149
-		$shareWith = $share->getSharedWith();
150
-		$itemSource = $share->getNodeId();
151
-		$itemType = $share->getNodeType();
152
-		$permissions = $share->getPermissions();
153
-		$sharedBy = $share->getSharedBy();
154
-
155
-		/*
52
+    const SHARE_TYPE_REMOTE = 6;
53
+
54
+    /** @var IDBConnection */
55
+    private $dbConnection;
56
+
57
+    /** @var AddressHandler */
58
+    private $addressHandler;
59
+
60
+    /** @var Notifications */
61
+    private $notifications;
62
+
63
+    /** @var TokenHandler */
64
+    private $tokenHandler;
65
+
66
+    /** @var IL10N */
67
+    private $l;
68
+
69
+    /** @var ILogger */
70
+    private $logger;
71
+
72
+    /** @var IRootFolder */
73
+    private $rootFolder;
74
+
75
+    /** @var IConfig */
76
+    private $config;
77
+
78
+    /** @var string */
79
+    private $externalShareTable = 'share_external';
80
+
81
+    /** @var IUserManager */
82
+    private $userManager;
83
+
84
+    /** @var ICloudIdManager */
85
+    private $cloudIdManager;
86
+
87
+    /** @var \OCP\GlobalScale\IConfig */
88
+    private $gsConfig;
89
+
90
+    /**
91
+     * DefaultShareProvider constructor.
92
+     *
93
+     * @param IDBConnection $connection
94
+     * @param AddressHandler $addressHandler
95
+     * @param Notifications $notifications
96
+     * @param TokenHandler $tokenHandler
97
+     * @param IL10N $l10n
98
+     * @param ILogger $logger
99
+     * @param IRootFolder $rootFolder
100
+     * @param IConfig $config
101
+     * @param IUserManager $userManager
102
+     * @param ICloudIdManager $cloudIdManager
103
+     * @param \OCP\GlobalScale\IConfig $globalScaleConfig
104
+     */
105
+    public function __construct(
106
+            IDBConnection $connection,
107
+            AddressHandler $addressHandler,
108
+            Notifications $notifications,
109
+            TokenHandler $tokenHandler,
110
+            IL10N $l10n,
111
+            ILogger $logger,
112
+            IRootFolder $rootFolder,
113
+            IConfig $config,
114
+            IUserManager $userManager,
115
+            ICloudIdManager $cloudIdManager,
116
+            \OCP\GlobalScale\IConfig $globalScaleConfig
117
+    ) {
118
+        $this->dbConnection = $connection;
119
+        $this->addressHandler = $addressHandler;
120
+        $this->notifications = $notifications;
121
+        $this->tokenHandler = $tokenHandler;
122
+        $this->l = $l10n;
123
+        $this->logger = $logger;
124
+        $this->rootFolder = $rootFolder;
125
+        $this->config = $config;
126
+        $this->userManager = $userManager;
127
+        $this->cloudIdManager = $cloudIdManager;
128
+        $this->gsConfig = $globalScaleConfig;
129
+    }
130
+
131
+    /**
132
+     * Return the identifier of this provider.
133
+     *
134
+     * @return string Containing only [a-zA-Z0-9]
135
+     */
136
+    public function identifier() {
137
+        return 'ocFederatedSharing';
138
+    }
139
+
140
+    /**
141
+     * Share a path
142
+     *
143
+     * @param IShare $share
144
+     * @return IShare The share object
145
+     * @throws ShareNotFound
146
+     * @throws \Exception
147
+     */
148
+    public function create(IShare $share) {
149
+        $shareWith = $share->getSharedWith();
150
+        $itemSource = $share->getNodeId();
151
+        $itemType = $share->getNodeType();
152
+        $permissions = $share->getPermissions();
153
+        $sharedBy = $share->getSharedBy();
154
+
155
+        /*
156 156
 		 * Check if file is not already shared with the remote user
157 157
 		 */
158
-		$alreadyShared = $this->getSharedWith($shareWith, self::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);
159
-		if (!empty($alreadyShared)) {
160
-			$message = 'Sharing %s failed, because this item is already shared with %s';
161
-			$message_t = $this->l->t('Sharing %s failed, because this item is already shared with %s', [$share->getNode()->getName(), $shareWith]);
162
-			$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
163
-			throw new \Exception($message_t);
164
-		}
165
-
166
-
167
-		// don't allow federated shares if source and target server are the same
168
-		$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
169
-		$currentServer = $this->addressHandler->generateRemoteURL();
170
-		$currentUser = $sharedBy;
171
-		if ($this->addressHandler->compareAddresses($cloudId->getUser(), $cloudId->getRemote(), $currentUser, $currentServer)) {
172
-			$message = 'Not allowed to create a federated share with the same user.';
173
-			$message_t = $this->l->t('Not allowed to create a federated share with the same user');
174
-			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
175
-			throw new \Exception($message_t);
176
-		}
177
-
178
-
179
-		$share->setSharedWith($cloudId->getId());
180
-
181
-		try {
182
-			$remoteShare = $this->getShareFromExternalShareTable($share);
183
-		} catch (ShareNotFound $e) {
184
-			$remoteShare = null;
185
-		}
186
-
187
-		if ($remoteShare) {
188
-			try {
189
-				$ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
190
-				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time());
191
-				$share->setId($shareId);
192
-				list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
193
-				// remote share was create successfully if we get a valid token as return
194
-				$send = is_string($token) && $token !== '';
195
-			} catch (\Exception $e) {
196
-				// fall back to old re-share behavior if the remote server
197
-				// doesn't support flat re-shares (was introduced with Nextcloud 9.1)
198
-				$this->removeShareFromTable($share);
199
-				$shareId = $this->createFederatedShare($share);
200
-			}
201
-			if ($send) {
202
-				$this->updateSuccessfulReshare($shareId, $token);
203
-				$this->storeRemoteId($shareId, $remoteId);
204
-			} else {
205
-				$this->removeShareFromTable($share);
206
-				$message_t = $this->l->t('File is already shared with %s', [$shareWith]);
207
-				throw new \Exception($message_t);
208
-			}
209
-		} else {
210
-			$shareId = $this->createFederatedShare($share);
211
-		}
212
-
213
-		$data = $this->getRawShare($shareId);
214
-		return $this->createShareObject($data);
215
-	}
216
-
217
-	/**
218
-	 * create federated share and inform the recipient
219
-	 *
220
-	 * @param IShare $share
221
-	 * @return int
222
-	 * @throws ShareNotFound
223
-	 * @throws \Exception
224
-	 */
225
-	protected function createFederatedShare(IShare $share) {
226
-		$token = $this->tokenHandler->generateToken();
227
-		$shareId = $this->addShareToDB(
228
-			$share->getNodeId(),
229
-			$share->getNodeType(),
230
-			$share->getSharedWith(),
231
-			$share->getSharedBy(),
232
-			$share->getShareOwner(),
233
-			$share->getPermissions(),
234
-			$token
235
-		);
236
-
237
-		$failure = false;
238
-
239
-		try {
240
-			$sharedByFederatedId = $share->getSharedBy();
241
-			if ($this->userManager->userExists($sharedByFederatedId)) {
242
-				$cloudId = $this->cloudIdManager->getCloudId($sharedByFederatedId, $this->addressHandler->generateRemoteURL());
243
-				$sharedByFederatedId = $cloudId->getId();
244
-			}
245
-			$ownerCloudId = $this->cloudIdManager->getCloudId($share->getShareOwner(), $this->addressHandler->generateRemoteURL());
246
-			$send = $this->notifications->sendRemoteShare(
247
-				$token,
248
-				$share->getSharedWith(),
249
-				$share->getNode()->getName(),
250
-				$shareId,
251
-				$share->getShareOwner(),
252
-				$ownerCloudId->getId(),
253
-				$share->getSharedBy(),
254
-				$sharedByFederatedId
255
-			);
256
-
257
-			if ($send === false) {
258
-				$failure = true;
259
-			}
260
-		} catch (\Exception $e) {
261
-			$this->logger->error('Failed to notify remote server of federated share, removing share (' . $e->getMessage() . ')');
262
-			$failure = true;
263
-		}
264
-
265
-		if ($failure) {
266
-			$this->removeShareFromTableById($shareId);
267
-			$message_t = $this->l->t(
268
-				'Sharing %s failed, could not find %s, maybe the server is currently unreachable or uses a self-signed certificate.',
269
-				[$share->getNode()->getName(), $share->getSharedWith()]
270
-			);
271
-			throw new \Exception($message_t);
272
-		}
273
-
274
-		return $shareId;
275
-	}
276
-
277
-	/**
278
-	 * @param string $shareWith
279
-	 * @param IShare $share
280
-	 * @param string $shareId internal share Id
281
-	 * @return array
282
-	 * @throws \Exception
283
-	 */
284
-	protected function askOwnerToReShare($shareWith, IShare $share, $shareId) {
285
-		$remoteShare = $this->getShareFromExternalShareTable($share);
286
-		$token = $remoteShare['share_token'];
287
-		$remoteId = $remoteShare['remote_id'];
288
-		$remote = $remoteShare['remote'];
289
-
290
-		list($token, $remoteId) = $this->notifications->requestReShare(
291
-			$token,
292
-			$remoteId,
293
-			$shareId,
294
-			$remote,
295
-			$shareWith,
296
-			$share->getPermissions()
297
-		);
298
-
299
-		return [$token, $remoteId];
300
-	}
301
-
302
-	/**
303
-	 * get federated share from the share_external table but exclude mounted link shares
304
-	 *
305
-	 * @param IShare $share
306
-	 * @return array
307
-	 * @throws ShareNotFound
308
-	 */
309
-	protected function getShareFromExternalShareTable(IShare $share) {
310
-		$query = $this->dbConnection->getQueryBuilder();
311
-		$query->select('*')->from($this->externalShareTable)
312
-			->where($query->expr()->eq('user', $query->createNamedParameter($share->getShareOwner())))
313
-			->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget())));
314
-		$result = $query->execute()->fetchAll();
315
-
316
-		if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
317
-			return $result[0];
318
-		}
319
-
320
-		throw new ShareNotFound('share not found in share_external table');
321
-	}
322
-
323
-	/**
324
-	 * add share to the database and return the ID
325
-	 *
326
-	 * @param int $itemSource
327
-	 * @param string $itemType
328
-	 * @param string $shareWith
329
-	 * @param string $sharedBy
330
-	 * @param string $uidOwner
331
-	 * @param int $permissions
332
-	 * @param string $token
333
-	 * @return int
334
-	 */
335
-	private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token) {
336
-		$qb = $this->dbConnection->getQueryBuilder();
337
-		$qb->insert('share')
338
-			->setValue('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE))
339
-			->setValue('item_type', $qb->createNamedParameter($itemType))
340
-			->setValue('item_source', $qb->createNamedParameter($itemSource))
341
-			->setValue('file_source', $qb->createNamedParameter($itemSource))
342
-			->setValue('share_with', $qb->createNamedParameter($shareWith))
343
-			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
344
-			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
345
-			->setValue('permissions', $qb->createNamedParameter($permissions))
346
-			->setValue('token', $qb->createNamedParameter($token))
347
-			->setValue('stime', $qb->createNamedParameter(time()));
348
-
349
-		/*
158
+        $alreadyShared = $this->getSharedWith($shareWith, self::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);
159
+        if (!empty($alreadyShared)) {
160
+            $message = 'Sharing %s failed, because this item is already shared with %s';
161
+            $message_t = $this->l->t('Sharing %s failed, because this item is already shared with %s', [$share->getNode()->getName(), $shareWith]);
162
+            $this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
163
+            throw new \Exception($message_t);
164
+        }
165
+
166
+
167
+        // don't allow federated shares if source and target server are the same
168
+        $cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
169
+        $currentServer = $this->addressHandler->generateRemoteURL();
170
+        $currentUser = $sharedBy;
171
+        if ($this->addressHandler->compareAddresses($cloudId->getUser(), $cloudId->getRemote(), $currentUser, $currentServer)) {
172
+            $message = 'Not allowed to create a federated share with the same user.';
173
+            $message_t = $this->l->t('Not allowed to create a federated share with the same user');
174
+            $this->logger->debug($message, ['app' => 'Federated File Sharing']);
175
+            throw new \Exception($message_t);
176
+        }
177
+
178
+
179
+        $share->setSharedWith($cloudId->getId());
180
+
181
+        try {
182
+            $remoteShare = $this->getShareFromExternalShareTable($share);
183
+        } catch (ShareNotFound $e) {
184
+            $remoteShare = null;
185
+        }
186
+
187
+        if ($remoteShare) {
188
+            try {
189
+                $ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
190
+                $shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time());
191
+                $share->setId($shareId);
192
+                list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
193
+                // remote share was create successfully if we get a valid token as return
194
+                $send = is_string($token) && $token !== '';
195
+            } catch (\Exception $e) {
196
+                // fall back to old re-share behavior if the remote server
197
+                // doesn't support flat re-shares (was introduced with Nextcloud 9.1)
198
+                $this->removeShareFromTable($share);
199
+                $shareId = $this->createFederatedShare($share);
200
+            }
201
+            if ($send) {
202
+                $this->updateSuccessfulReshare($shareId, $token);
203
+                $this->storeRemoteId($shareId, $remoteId);
204
+            } else {
205
+                $this->removeShareFromTable($share);
206
+                $message_t = $this->l->t('File is already shared with %s', [$shareWith]);
207
+                throw new \Exception($message_t);
208
+            }
209
+        } else {
210
+            $shareId = $this->createFederatedShare($share);
211
+        }
212
+
213
+        $data = $this->getRawShare($shareId);
214
+        return $this->createShareObject($data);
215
+    }
216
+
217
+    /**
218
+     * create federated share and inform the recipient
219
+     *
220
+     * @param IShare $share
221
+     * @return int
222
+     * @throws ShareNotFound
223
+     * @throws \Exception
224
+     */
225
+    protected function createFederatedShare(IShare $share) {
226
+        $token = $this->tokenHandler->generateToken();
227
+        $shareId = $this->addShareToDB(
228
+            $share->getNodeId(),
229
+            $share->getNodeType(),
230
+            $share->getSharedWith(),
231
+            $share->getSharedBy(),
232
+            $share->getShareOwner(),
233
+            $share->getPermissions(),
234
+            $token
235
+        );
236
+
237
+        $failure = false;
238
+
239
+        try {
240
+            $sharedByFederatedId = $share->getSharedBy();
241
+            if ($this->userManager->userExists($sharedByFederatedId)) {
242
+                $cloudId = $this->cloudIdManager->getCloudId($sharedByFederatedId, $this->addressHandler->generateRemoteURL());
243
+                $sharedByFederatedId = $cloudId->getId();
244
+            }
245
+            $ownerCloudId = $this->cloudIdManager->getCloudId($share->getShareOwner(), $this->addressHandler->generateRemoteURL());
246
+            $send = $this->notifications->sendRemoteShare(
247
+                $token,
248
+                $share->getSharedWith(),
249
+                $share->getNode()->getName(),
250
+                $shareId,
251
+                $share->getShareOwner(),
252
+                $ownerCloudId->getId(),
253
+                $share->getSharedBy(),
254
+                $sharedByFederatedId
255
+            );
256
+
257
+            if ($send === false) {
258
+                $failure = true;
259
+            }
260
+        } catch (\Exception $e) {
261
+            $this->logger->error('Failed to notify remote server of federated share, removing share (' . $e->getMessage() . ')');
262
+            $failure = true;
263
+        }
264
+
265
+        if ($failure) {
266
+            $this->removeShareFromTableById($shareId);
267
+            $message_t = $this->l->t(
268
+                'Sharing %s failed, could not find %s, maybe the server is currently unreachable or uses a self-signed certificate.',
269
+                [$share->getNode()->getName(), $share->getSharedWith()]
270
+            );
271
+            throw new \Exception($message_t);
272
+        }
273
+
274
+        return $shareId;
275
+    }
276
+
277
+    /**
278
+     * @param string $shareWith
279
+     * @param IShare $share
280
+     * @param string $shareId internal share Id
281
+     * @return array
282
+     * @throws \Exception
283
+     */
284
+    protected function askOwnerToReShare($shareWith, IShare $share, $shareId) {
285
+        $remoteShare = $this->getShareFromExternalShareTable($share);
286
+        $token = $remoteShare['share_token'];
287
+        $remoteId = $remoteShare['remote_id'];
288
+        $remote = $remoteShare['remote'];
289
+
290
+        list($token, $remoteId) = $this->notifications->requestReShare(
291
+            $token,
292
+            $remoteId,
293
+            $shareId,
294
+            $remote,
295
+            $shareWith,
296
+            $share->getPermissions()
297
+        );
298
+
299
+        return [$token, $remoteId];
300
+    }
301
+
302
+    /**
303
+     * get federated share from the share_external table but exclude mounted link shares
304
+     *
305
+     * @param IShare $share
306
+     * @return array
307
+     * @throws ShareNotFound
308
+     */
309
+    protected function getShareFromExternalShareTable(IShare $share) {
310
+        $query = $this->dbConnection->getQueryBuilder();
311
+        $query->select('*')->from($this->externalShareTable)
312
+            ->where($query->expr()->eq('user', $query->createNamedParameter($share->getShareOwner())))
313
+            ->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget())));
314
+        $result = $query->execute()->fetchAll();
315
+
316
+        if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
317
+            return $result[0];
318
+        }
319
+
320
+        throw new ShareNotFound('share not found in share_external table');
321
+    }
322
+
323
+    /**
324
+     * add share to the database and return the ID
325
+     *
326
+     * @param int $itemSource
327
+     * @param string $itemType
328
+     * @param string $shareWith
329
+     * @param string $sharedBy
330
+     * @param string $uidOwner
331
+     * @param int $permissions
332
+     * @param string $token
333
+     * @return int
334
+     */
335
+    private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token) {
336
+        $qb = $this->dbConnection->getQueryBuilder();
337
+        $qb->insert('share')
338
+            ->setValue('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE))
339
+            ->setValue('item_type', $qb->createNamedParameter($itemType))
340
+            ->setValue('item_source', $qb->createNamedParameter($itemSource))
341
+            ->setValue('file_source', $qb->createNamedParameter($itemSource))
342
+            ->setValue('share_with', $qb->createNamedParameter($shareWith))
343
+            ->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
344
+            ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
345
+            ->setValue('permissions', $qb->createNamedParameter($permissions))
346
+            ->setValue('token', $qb->createNamedParameter($token))
347
+            ->setValue('stime', $qb->createNamedParameter(time()));
348
+
349
+        /*
350 350
 		 * Added to fix https://github.com/owncloud/core/issues/22215
351 351
 		 * Can be removed once we get rid of ajax/share.php
352 352
 		 */
353
-		$qb->setValue('file_target', $qb->createNamedParameter(''));
354
-
355
-		$qb->execute();
356
-		$id = $qb->getLastInsertId();
357
-
358
-		return (int)$id;
359
-	}
360
-
361
-	/**
362
-	 * Update a share
363
-	 *
364
-	 * @param IShare $share
365
-	 * @return IShare The share object
366
-	 */
367
-	public function update(IShare $share) {
368
-		/*
353
+        $qb->setValue('file_target', $qb->createNamedParameter(''));
354
+
355
+        $qb->execute();
356
+        $id = $qb->getLastInsertId();
357
+
358
+        return (int)$id;
359
+    }
360
+
361
+    /**
362
+     * Update a share
363
+     *
364
+     * @param IShare $share
365
+     * @return IShare The share object
366
+     */
367
+    public function update(IShare $share) {
368
+        /*
369 369
 		 * We allow updating the permissions of federated shares
370 370
 		 */
371
-		$qb = $this->dbConnection->getQueryBuilder();
372
-		$qb->update('share')
373
-				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
374
-				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
375
-				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
376
-				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
377
-				->execute();
378
-
379
-		// send the updated permission to the owner/initiator, if they are not the same
380
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
381
-			$this->sendPermissionUpdate($share);
382
-		}
383
-
384
-		return $share;
385
-	}
386
-
387
-	/**
388
-	 * send the updated permission to the owner/initiator, if they are not the same
389
-	 *
390
-	 * @param IShare $share
391
-	 * @throws ShareNotFound
392
-	 * @throws \OC\HintException
393
-	 */
394
-	protected function sendPermissionUpdate(IShare $share) {
395
-		$remoteId = $this->getRemoteId($share);
396
-		// if the local user is the owner we send the permission change to the initiator
397
-		if ($this->userManager->userExists($share->getShareOwner())) {
398
-			list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
399
-		} else { // ... if not we send the permission change to the owner
400
-			list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
401
-		}
402
-		$this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions());
403
-	}
404
-
405
-
406
-	/**
407
-	 * update successful reShare with the correct token
408
-	 *
409
-	 * @param int $shareId
410
-	 * @param string $token
411
-	 */
412
-	protected function updateSuccessfulReShare($shareId, $token) {
413
-		$query = $this->dbConnection->getQueryBuilder();
414
-		$query->update('share')
415
-			->where($query->expr()->eq('id', $query->createNamedParameter($shareId)))
416
-			->set('token', $query->createNamedParameter($token))
417
-			->execute();
418
-	}
419
-
420
-	/**
421
-	 * store remote ID in federated reShare table
422
-	 *
423
-	 * @param $shareId
424
-	 * @param $remoteId
425
-	 */
426
-	public function storeRemoteId($shareId, $remoteId) {
427
-		$query = $this->dbConnection->getQueryBuilder();
428
-		$query->insert('federated_reshares')
429
-			->values(
430
-				[
431
-					'share_id' => $query->createNamedParameter($shareId),
432
-					'remote_id' => $query->createNamedParameter($remoteId),
433
-				]
434
-			);
435
-		$query->execute();
436
-	}
437
-
438
-	/**
439
-	 * get share ID on remote server for federated re-shares
440
-	 *
441
-	 * @param IShare $share
442
-	 * @return int
443
-	 * @throws ShareNotFound
444
-	 */
445
-	public function getRemoteId(IShare $share) {
446
-		$query = $this->dbConnection->getQueryBuilder();
447
-		$query->select('remote_id')->from('federated_reshares')
448
-			->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
449
-		$data = $query->execute()->fetch();
450
-
451
-		if (!is_array($data) || !isset($data['remote_id'])) {
452
-			throw new ShareNotFound();
453
-		}
454
-
455
-		return (int)$data['remote_id'];
456
-	}
457
-
458
-	/**
459
-	 * @inheritdoc
460
-	 */
461
-	public function move(IShare $share, $recipient) {
462
-		/*
371
+        $qb = $this->dbConnection->getQueryBuilder();
372
+        $qb->update('share')
373
+                ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
374
+                ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
375
+                ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
376
+                ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
377
+                ->execute();
378
+
379
+        // send the updated permission to the owner/initiator, if they are not the same
380
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
381
+            $this->sendPermissionUpdate($share);
382
+        }
383
+
384
+        return $share;
385
+    }
386
+
387
+    /**
388
+     * send the updated permission to the owner/initiator, if they are not the same
389
+     *
390
+     * @param IShare $share
391
+     * @throws ShareNotFound
392
+     * @throws \OC\HintException
393
+     */
394
+    protected function sendPermissionUpdate(IShare $share) {
395
+        $remoteId = $this->getRemoteId($share);
396
+        // if the local user is the owner we send the permission change to the initiator
397
+        if ($this->userManager->userExists($share->getShareOwner())) {
398
+            list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
399
+        } else { // ... if not we send the permission change to the owner
400
+            list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
401
+        }
402
+        $this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions());
403
+    }
404
+
405
+
406
+    /**
407
+     * update successful reShare with the correct token
408
+     *
409
+     * @param int $shareId
410
+     * @param string $token
411
+     */
412
+    protected function updateSuccessfulReShare($shareId, $token) {
413
+        $query = $this->dbConnection->getQueryBuilder();
414
+        $query->update('share')
415
+            ->where($query->expr()->eq('id', $query->createNamedParameter($shareId)))
416
+            ->set('token', $query->createNamedParameter($token))
417
+            ->execute();
418
+    }
419
+
420
+    /**
421
+     * store remote ID in federated reShare table
422
+     *
423
+     * @param $shareId
424
+     * @param $remoteId
425
+     */
426
+    public function storeRemoteId($shareId, $remoteId) {
427
+        $query = $this->dbConnection->getQueryBuilder();
428
+        $query->insert('federated_reshares')
429
+            ->values(
430
+                [
431
+                    'share_id' => $query->createNamedParameter($shareId),
432
+                    'remote_id' => $query->createNamedParameter($remoteId),
433
+                ]
434
+            );
435
+        $query->execute();
436
+    }
437
+
438
+    /**
439
+     * get share ID on remote server for federated re-shares
440
+     *
441
+     * @param IShare $share
442
+     * @return int
443
+     * @throws ShareNotFound
444
+     */
445
+    public function getRemoteId(IShare $share) {
446
+        $query = $this->dbConnection->getQueryBuilder();
447
+        $query->select('remote_id')->from('federated_reshares')
448
+            ->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
449
+        $data = $query->execute()->fetch();
450
+
451
+        if (!is_array($data) || !isset($data['remote_id'])) {
452
+            throw new ShareNotFound();
453
+        }
454
+
455
+        return (int)$data['remote_id'];
456
+    }
457
+
458
+    /**
459
+     * @inheritdoc
460
+     */
461
+    public function move(IShare $share, $recipient) {
462
+        /*
463 463
 		 * This function does nothing yet as it is just for outgoing
464 464
 		 * federated shares.
465 465
 		 */
466
-		return $share;
467
-	}
468
-
469
-	/**
470
-	 * Get all children of this share
471
-	 *
472
-	 * @param IShare $parent
473
-	 * @return IShare[]
474
-	 */
475
-	public function getChildren(IShare $parent) {
476
-		$children = [];
477
-
478
-		$qb = $this->dbConnection->getQueryBuilder();
479
-		$qb->select('*')
480
-			->from('share')
481
-			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
482
-			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
483
-			->orderBy('id');
484
-
485
-		$cursor = $qb->execute();
486
-		while ($data = $cursor->fetch()) {
487
-			$children[] = $this->createShareObject($data);
488
-		}
489
-		$cursor->closeCursor();
490
-
491
-		return $children;
492
-	}
493
-
494
-	/**
495
-	 * Delete a share (owner unShares the file)
496
-	 *
497
-	 * @param IShare $share
498
-	 */
499
-	public function delete(IShare $share) {
500
-		list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
501
-
502
-		$isOwner = false;
503
-
504
-		$this->removeShareFromTable($share);
505
-
506
-		// if the local user is the owner we can send the unShare request directly...
507
-		if ($this->userManager->userExists($share->getShareOwner())) {
508
-			$this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
509
-			$this->revokeShare($share, true);
510
-			$isOwner = true;
511
-		} else { // ... if not we need to correct ID for the unShare request
512
-			$remoteId = $this->getRemoteId($share);
513
-			$this->notifications->sendRemoteUnShare($remote, $remoteId, $share->getToken());
514
-			$this->revokeShare($share, false);
515
-		}
516
-
517
-		// send revoke notification to the other user, if initiator and owner are not the same user
518
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
519
-			$remoteId = $this->getRemoteId($share);
520
-			if ($isOwner) {
521
-				list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
522
-			} else {
523
-				list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
524
-			}
525
-			$this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
526
-		}
527
-	}
528
-
529
-	/**
530
-	 * in case of a re-share we need to send the other use (initiator or owner)
531
-	 * a message that the file was unshared
532
-	 *
533
-	 * @param IShare $share
534
-	 * @param bool $isOwner the user can either be the owner or the user who re-sahred it
535
-	 * @throws ShareNotFound
536
-	 * @throws \OC\HintException
537
-	 */
538
-	protected function revokeShare($share, $isOwner) {
539
-		// also send a unShare request to the initiator, if this is a different user than the owner
540
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
541
-			if ($isOwner) {
542
-				list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
543
-			} else {
544
-				list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
545
-			}
546
-			$remoteId = $this->getRemoteId($share);
547
-			$this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
548
-		}
549
-	}
550
-
551
-	/**
552
-	 * remove share from table
553
-	 *
554
-	 * @param IShare $share
555
-	 */
556
-	public function removeShareFromTable(IShare $share) {
557
-		$this->removeShareFromTableById($share->getId());
558
-	}
559
-
560
-	/**
561
-	 * remove share from table
562
-	 *
563
-	 * @param string $shareId
564
-	 */
565
-	private function removeShareFromTableById($shareId) {
566
-		$qb = $this->dbConnection->getQueryBuilder();
567
-		$qb->delete('share')
568
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
569
-		$qb->execute();
570
-
571
-		$qb->delete('federated_reshares')
572
-			->where($qb->expr()->eq('share_id', $qb->createNamedParameter($shareId)));
573
-		$qb->execute();
574
-	}
575
-
576
-	/**
577
-	 * @inheritdoc
578
-	 */
579
-	public function deleteFromSelf(IShare $share, $recipient) {
580
-		// nothing to do here. Technically deleteFromSelf in the context of federated
581
-		// shares is a umount of a external storage. This is handled here
582
-		// apps/files_sharing/lib/external/manager.php
583
-		// TODO move this code over to this app
584
-		return;
585
-	}
586
-
587
-
588
-	public function getSharesInFolder($userId, Folder $node, $reshares) {
589
-		$qb = $this->dbConnection->getQueryBuilder();
590
-		$qb->select('*')
591
-			->from('share', 's')
592
-			->andWhere($qb->expr()->orX(
593
-				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
594
-				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
595
-			))
596
-			->andWhere(
597
-				$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE))
598
-			);
599
-
600
-		/**
601
-		 * Reshares for this user are shares where they are the owner.
602
-		 */
603
-		if ($reshares === false) {
604
-			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
605
-		} else {
606
-			$qb->andWhere(
607
-				$qb->expr()->orX(
608
-					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
609
-					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
610
-				)
611
-			);
612
-		}
613
-
614
-		$qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
615
-		$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
616
-
617
-		$qb->orderBy('id');
618
-
619
-		$cursor = $qb->execute();
620
-		$shares = [];
621
-		while ($data = $cursor->fetch()) {
622
-			$shares[$data['fileid']][] = $this->createShareObject($data);
623
-		}
624
-		$cursor->closeCursor();
625
-
626
-		return $shares;
627
-	}
628
-
629
-	/**
630
-	 * @inheritdoc
631
-	 */
632
-	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
633
-		$qb = $this->dbConnection->getQueryBuilder();
634
-		$qb->select('*')
635
-			->from('share');
636
-
637
-		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
638
-
639
-		/**
640
-		 * Reshares for this user are shares where they are the owner.
641
-		 */
642
-		if ($reshares === false) {
643
-			//Special case for old shares created via the web UI
644
-			$or1 = $qb->expr()->andX(
645
-				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
646
-				$qb->expr()->isNull('uid_initiator')
647
-			);
648
-
649
-			$qb->andWhere(
650
-				$qb->expr()->orX(
651
-					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
652
-					$or1
653
-				)
654
-			);
655
-		} else {
656
-			$qb->andWhere(
657
-				$qb->expr()->orX(
658
-					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
659
-					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
660
-				)
661
-			);
662
-		}
663
-
664
-		if ($node !== null) {
665
-			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
666
-		}
667
-
668
-		if ($limit !== -1) {
669
-			$qb->setMaxResults($limit);
670
-		}
671
-
672
-		$qb->setFirstResult($offset);
673
-		$qb->orderBy('id');
674
-
675
-		$cursor = $qb->execute();
676
-		$shares = [];
677
-		while ($data = $cursor->fetch()) {
678
-			$shares[] = $this->createShareObject($data);
679
-		}
680
-		$cursor->closeCursor();
681
-
682
-		return $shares;
683
-	}
684
-
685
-	/**
686
-	 * @inheritdoc
687
-	 */
688
-	public function getShareById($id, $recipientId = null) {
689
-		$qb = $this->dbConnection->getQueryBuilder();
690
-
691
-		$qb->select('*')
692
-			->from('share')
693
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
694
-			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
695
-
696
-		$cursor = $qb->execute();
697
-		$data = $cursor->fetch();
698
-		$cursor->closeCursor();
699
-
700
-		if ($data === false) {
701
-			throw new ShareNotFound();
702
-		}
703
-
704
-		try {
705
-			$share = $this->createShareObject($data);
706
-		} catch (InvalidShare $e) {
707
-			throw new ShareNotFound();
708
-		}
709
-
710
-		return $share;
711
-	}
712
-
713
-	/**
714
-	 * Get shares for a given path
715
-	 *
716
-	 * @param \OCP\Files\Node $path
717
-	 * @return IShare[]
718
-	 */
719
-	public function getSharesByPath(Node $path) {
720
-		$qb = $this->dbConnection->getQueryBuilder();
721
-
722
-		$cursor = $qb->select('*')
723
-			->from('share')
724
-			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
725
-			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
726
-			->execute();
727
-
728
-		$shares = [];
729
-		while ($data = $cursor->fetch()) {
730
-			$shares[] = $this->createShareObject($data);
731
-		}
732
-		$cursor->closeCursor();
733
-
734
-		return $shares;
735
-	}
736
-
737
-	/**
738
-	 * @inheritdoc
739
-	 */
740
-	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
741
-		/** @var IShare[] $shares */
742
-		$shares = [];
743
-
744
-		//Get shares directly with this user
745
-		$qb = $this->dbConnection->getQueryBuilder();
746
-		$qb->select('*')
747
-			->from('share');
748
-
749
-		// Order by id
750
-		$qb->orderBy('id');
751
-
752
-		// Set limit and offset
753
-		if ($limit !== -1) {
754
-			$qb->setMaxResults($limit);
755
-		}
756
-		$qb->setFirstResult($offset);
757
-
758
-		$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
759
-		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
760
-
761
-		// Filter by node if provided
762
-		if ($node !== null) {
763
-			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
764
-		}
765
-
766
-		$cursor = $qb->execute();
767
-
768
-		while ($data = $cursor->fetch()) {
769
-			$shares[] = $this->createShareObject($data);
770
-		}
771
-		$cursor->closeCursor();
772
-
773
-
774
-		return $shares;
775
-	}
776
-
777
-	/**
778
-	 * Get a share by token
779
-	 *
780
-	 * @param string $token
781
-	 * @return IShare
782
-	 * @throws ShareNotFound
783
-	 */
784
-	public function getShareByToken($token) {
785
-		$qb = $this->dbConnection->getQueryBuilder();
786
-
787
-		$cursor = $qb->select('*')
788
-			->from('share')
789
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
790
-			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
791
-			->execute();
792
-
793
-		$data = $cursor->fetch();
794
-
795
-		if ($data === false) {
796
-			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
797
-		}
798
-
799
-		try {
800
-			$share = $this->createShareObject($data);
801
-		} catch (InvalidShare $e) {
802
-			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
803
-		}
804
-
805
-		return $share;
806
-	}
807
-
808
-	/**
809
-	 * get database row of a give share
810
-	 *
811
-	 * @param $id
812
-	 * @return array
813
-	 * @throws ShareNotFound
814
-	 */
815
-	private function getRawShare($id) {
816
-
817
-		// Now fetch the inserted share and create a complete share object
818
-		$qb = $this->dbConnection->getQueryBuilder();
819
-		$qb->select('*')
820
-			->from('share')
821
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
822
-
823
-		$cursor = $qb->execute();
824
-		$data = $cursor->fetch();
825
-		$cursor->closeCursor();
826
-
827
-		if ($data === false) {
828
-			throw new ShareNotFound;
829
-		}
830
-
831
-		return $data;
832
-	}
833
-
834
-	/**
835
-	 * Create a share object from an database row
836
-	 *
837
-	 * @param array $data
838
-	 * @return IShare
839
-	 * @throws InvalidShare
840
-	 * @throws ShareNotFound
841
-	 */
842
-	private function createShareObject($data) {
843
-		$share = new Share($this->rootFolder, $this->userManager);
844
-		$share->setId((int)$data['id'])
845
-			->setShareType((int)$data['share_type'])
846
-			->setPermissions((int)$data['permissions'])
847
-			->setTarget($data['file_target'])
848
-			->setMailSend((bool)$data['mail_send'])
849
-			->setToken($data['token']);
850
-
851
-		$shareTime = new \DateTime();
852
-		$shareTime->setTimestamp((int)$data['stime']);
853
-		$share->setShareTime($shareTime);
854
-		$share->setSharedWith($data['share_with']);
855
-
856
-		if ($data['uid_initiator'] !== null) {
857
-			$share->setShareOwner($data['uid_owner']);
858
-			$share->setSharedBy($data['uid_initiator']);
859
-		} else {
860
-			//OLD SHARE
861
-			$share->setSharedBy($data['uid_owner']);
862
-			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
863
-
864
-			$owner = $path->getOwner();
865
-			$share->setShareOwner($owner->getUID());
866
-		}
867
-
868
-		$share->setNodeId((int)$data['file_source']);
869
-		$share->setNodeType($data['item_type']);
870
-
871
-		$share->setProviderId($this->identifier());
872
-
873
-		return $share;
874
-	}
875
-
876
-	/**
877
-	 * Get the node with file $id for $user
878
-	 *
879
-	 * @param string $userId
880
-	 * @param int $id
881
-	 * @return \OCP\Files\File|\OCP\Files\Folder
882
-	 * @throws InvalidShare
883
-	 */
884
-	private function getNode($userId, $id) {
885
-		try {
886
-			$userFolder = $this->rootFolder->getUserFolder($userId);
887
-		} catch (NotFoundException $e) {
888
-			throw new InvalidShare();
889
-		}
890
-
891
-		$nodes = $userFolder->getById($id);
892
-
893
-		if (empty($nodes)) {
894
-			throw new InvalidShare();
895
-		}
896
-
897
-		return $nodes[0];
898
-	}
899
-
900
-	/**
901
-	 * A user is deleted from the system
902
-	 * So clean up the relevant shares.
903
-	 *
904
-	 * @param string $uid
905
-	 * @param int $shareType
906
-	 */
907
-	public function userDeleted($uid, $shareType) {
908
-		//TODO: probabaly a good idea to send unshare info to remote servers
909
-
910
-		$qb = $this->dbConnection->getQueryBuilder();
911
-
912
-		$qb->delete('share')
913
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
914
-			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
915
-			->execute();
916
-	}
917
-
918
-	/**
919
-	 * This provider does not handle groups
920
-	 *
921
-	 * @param string $gid
922
-	 */
923
-	public function groupDeleted($gid) {
924
-		// We don't handle groups here
925
-		return;
926
-	}
927
-
928
-	/**
929
-	 * This provider does not handle groups
930
-	 *
931
-	 * @param string $uid
932
-	 * @param string $gid
933
-	 */
934
-	public function userDeletedFromGroup($uid, $gid) {
935
-		// We don't handle groups here
936
-		return;
937
-	}
938
-
939
-	/**
940
-	 * check if users from other Nextcloud instances are allowed to mount public links share by this instance
941
-	 *
942
-	 * @return bool
943
-	 */
944
-	public function isOutgoingServer2serverShareEnabled() {
945
-		if ($this->gsConfig->onlyInternalFederation()) {
946
-			return false;
947
-		}
948
-		$result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes');
949
-		return ($result === 'yes');
950
-	}
951
-
952
-	/**
953
-	 * check if users are allowed to mount public links from other Nextclouds
954
-	 *
955
-	 * @return bool
956
-	 */
957
-	public function isIncomingServer2serverShareEnabled() {
958
-		if ($this->gsConfig->onlyInternalFederation()) {
959
-			return false;
960
-		}
961
-		$result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes');
962
-		return ($result === 'yes');
963
-	}
964
-
965
-	/**
966
-	 * Check if querying sharees on the lookup server is enabled
967
-	 *
968
-	 * @return bool
969
-	 */
970
-	public function isLookupServerQueriesEnabled() {
971
-		// in a global scale setup we should always query the lookup server
972
-		if ($this->gsConfig->isGlobalScaleEnabled()) {
973
-			return true;
974
-		}
975
-		$result = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no');
976
-		return ($result === 'yes');
977
-	}
978
-
979
-
980
-	/**
981
-	 * Check if it is allowed to publish user specific data to the lookup server
982
-	 *
983
-	 * @return bool
984
-	 */
985
-	public function isLookupServerUploadEnabled() {
986
-		// in a global scale setup the admin is responsible to keep the lookup server up-to-date
987
-		if ($this->gsConfig->isGlobalScaleEnabled()) {
988
-			return false;
989
-		}
990
-		$result = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes');
991
-		return ($result === 'yes');
992
-	}
993
-
994
-	/**
995
-	 * @inheritdoc
996
-	 */
997
-	public function getAccessList($nodes, $currentAccess) {
998
-		$ids = [];
999
-		foreach ($nodes as $node) {
1000
-			$ids[] = $node->getId();
1001
-		}
1002
-
1003
-		$qb = $this->dbConnection->getQueryBuilder();
1004
-		$qb->select('share_with', 'token', 'file_source')
1005
-			->from('share')
1006
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
1007
-			->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1008
-			->andWhere($qb->expr()->orX(
1009
-				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1010
-				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1011
-			));
1012
-		$cursor = $qb->execute();
1013
-
1014
-		if ($currentAccess === false) {
1015
-			$remote = $cursor->fetch() !== false;
1016
-			$cursor->closeCursor();
1017
-
1018
-			return ['remote' => $remote];
1019
-		}
1020
-
1021
-		$remote = [];
1022
-		while ($row = $cursor->fetch()) {
1023
-			$remote[$row['share_with']] = [
1024
-				'node_id' => $row['file_source'],
1025
-				'token' => $row['token'],
1026
-			];
1027
-		}
1028
-		$cursor->closeCursor();
1029
-
1030
-		return ['remote' => $remote];
1031
-	}
466
+        return $share;
467
+    }
468
+
469
+    /**
470
+     * Get all children of this share
471
+     *
472
+     * @param IShare $parent
473
+     * @return IShare[]
474
+     */
475
+    public function getChildren(IShare $parent) {
476
+        $children = [];
477
+
478
+        $qb = $this->dbConnection->getQueryBuilder();
479
+        $qb->select('*')
480
+            ->from('share')
481
+            ->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
482
+            ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
483
+            ->orderBy('id');
484
+
485
+        $cursor = $qb->execute();
486
+        while ($data = $cursor->fetch()) {
487
+            $children[] = $this->createShareObject($data);
488
+        }
489
+        $cursor->closeCursor();
490
+
491
+        return $children;
492
+    }
493
+
494
+    /**
495
+     * Delete a share (owner unShares the file)
496
+     *
497
+     * @param IShare $share
498
+     */
499
+    public function delete(IShare $share) {
500
+        list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
501
+
502
+        $isOwner = false;
503
+
504
+        $this->removeShareFromTable($share);
505
+
506
+        // if the local user is the owner we can send the unShare request directly...
507
+        if ($this->userManager->userExists($share->getShareOwner())) {
508
+            $this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
509
+            $this->revokeShare($share, true);
510
+            $isOwner = true;
511
+        } else { // ... if not we need to correct ID for the unShare request
512
+            $remoteId = $this->getRemoteId($share);
513
+            $this->notifications->sendRemoteUnShare($remote, $remoteId, $share->getToken());
514
+            $this->revokeShare($share, false);
515
+        }
516
+
517
+        // send revoke notification to the other user, if initiator and owner are not the same user
518
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
519
+            $remoteId = $this->getRemoteId($share);
520
+            if ($isOwner) {
521
+                list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
522
+            } else {
523
+                list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
524
+            }
525
+            $this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
526
+        }
527
+    }
528
+
529
+    /**
530
+     * in case of a re-share we need to send the other use (initiator or owner)
531
+     * a message that the file was unshared
532
+     *
533
+     * @param IShare $share
534
+     * @param bool $isOwner the user can either be the owner or the user who re-sahred it
535
+     * @throws ShareNotFound
536
+     * @throws \OC\HintException
537
+     */
538
+    protected function revokeShare($share, $isOwner) {
539
+        // also send a unShare request to the initiator, if this is a different user than the owner
540
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
541
+            if ($isOwner) {
542
+                list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
543
+            } else {
544
+                list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
545
+            }
546
+            $remoteId = $this->getRemoteId($share);
547
+            $this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
548
+        }
549
+    }
550
+
551
+    /**
552
+     * remove share from table
553
+     *
554
+     * @param IShare $share
555
+     */
556
+    public function removeShareFromTable(IShare $share) {
557
+        $this->removeShareFromTableById($share->getId());
558
+    }
559
+
560
+    /**
561
+     * remove share from table
562
+     *
563
+     * @param string $shareId
564
+     */
565
+    private function removeShareFromTableById($shareId) {
566
+        $qb = $this->dbConnection->getQueryBuilder();
567
+        $qb->delete('share')
568
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
569
+        $qb->execute();
570
+
571
+        $qb->delete('federated_reshares')
572
+            ->where($qb->expr()->eq('share_id', $qb->createNamedParameter($shareId)));
573
+        $qb->execute();
574
+    }
575
+
576
+    /**
577
+     * @inheritdoc
578
+     */
579
+    public function deleteFromSelf(IShare $share, $recipient) {
580
+        // nothing to do here. Technically deleteFromSelf in the context of federated
581
+        // shares is a umount of a external storage. This is handled here
582
+        // apps/files_sharing/lib/external/manager.php
583
+        // TODO move this code over to this app
584
+        return;
585
+    }
586
+
587
+
588
+    public function getSharesInFolder($userId, Folder $node, $reshares) {
589
+        $qb = $this->dbConnection->getQueryBuilder();
590
+        $qb->select('*')
591
+            ->from('share', 's')
592
+            ->andWhere($qb->expr()->orX(
593
+                $qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
594
+                $qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
595
+            ))
596
+            ->andWhere(
597
+                $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE))
598
+            );
599
+
600
+        /**
601
+         * Reshares for this user are shares where they are the owner.
602
+         */
603
+        if ($reshares === false) {
604
+            $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
605
+        } else {
606
+            $qb->andWhere(
607
+                $qb->expr()->orX(
608
+                    $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
609
+                    $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
610
+                )
611
+            );
612
+        }
613
+
614
+        $qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
615
+        $qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
616
+
617
+        $qb->orderBy('id');
618
+
619
+        $cursor = $qb->execute();
620
+        $shares = [];
621
+        while ($data = $cursor->fetch()) {
622
+            $shares[$data['fileid']][] = $this->createShareObject($data);
623
+        }
624
+        $cursor->closeCursor();
625
+
626
+        return $shares;
627
+    }
628
+
629
+    /**
630
+     * @inheritdoc
631
+     */
632
+    public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
633
+        $qb = $this->dbConnection->getQueryBuilder();
634
+        $qb->select('*')
635
+            ->from('share');
636
+
637
+        $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
638
+
639
+        /**
640
+         * Reshares for this user are shares where they are the owner.
641
+         */
642
+        if ($reshares === false) {
643
+            //Special case for old shares created via the web UI
644
+            $or1 = $qb->expr()->andX(
645
+                $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
646
+                $qb->expr()->isNull('uid_initiator')
647
+            );
648
+
649
+            $qb->andWhere(
650
+                $qb->expr()->orX(
651
+                    $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
652
+                    $or1
653
+                )
654
+            );
655
+        } else {
656
+            $qb->andWhere(
657
+                $qb->expr()->orX(
658
+                    $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
659
+                    $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
660
+                )
661
+            );
662
+        }
663
+
664
+        if ($node !== null) {
665
+            $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
666
+        }
667
+
668
+        if ($limit !== -1) {
669
+            $qb->setMaxResults($limit);
670
+        }
671
+
672
+        $qb->setFirstResult($offset);
673
+        $qb->orderBy('id');
674
+
675
+        $cursor = $qb->execute();
676
+        $shares = [];
677
+        while ($data = $cursor->fetch()) {
678
+            $shares[] = $this->createShareObject($data);
679
+        }
680
+        $cursor->closeCursor();
681
+
682
+        return $shares;
683
+    }
684
+
685
+    /**
686
+     * @inheritdoc
687
+     */
688
+    public function getShareById($id, $recipientId = null) {
689
+        $qb = $this->dbConnection->getQueryBuilder();
690
+
691
+        $qb->select('*')
692
+            ->from('share')
693
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
694
+            ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
695
+
696
+        $cursor = $qb->execute();
697
+        $data = $cursor->fetch();
698
+        $cursor->closeCursor();
699
+
700
+        if ($data === false) {
701
+            throw new ShareNotFound();
702
+        }
703
+
704
+        try {
705
+            $share = $this->createShareObject($data);
706
+        } catch (InvalidShare $e) {
707
+            throw new ShareNotFound();
708
+        }
709
+
710
+        return $share;
711
+    }
712
+
713
+    /**
714
+     * Get shares for a given path
715
+     *
716
+     * @param \OCP\Files\Node $path
717
+     * @return IShare[]
718
+     */
719
+    public function getSharesByPath(Node $path) {
720
+        $qb = $this->dbConnection->getQueryBuilder();
721
+
722
+        $cursor = $qb->select('*')
723
+            ->from('share')
724
+            ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
725
+            ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
726
+            ->execute();
727
+
728
+        $shares = [];
729
+        while ($data = $cursor->fetch()) {
730
+            $shares[] = $this->createShareObject($data);
731
+        }
732
+        $cursor->closeCursor();
733
+
734
+        return $shares;
735
+    }
736
+
737
+    /**
738
+     * @inheritdoc
739
+     */
740
+    public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
741
+        /** @var IShare[] $shares */
742
+        $shares = [];
743
+
744
+        //Get shares directly with this user
745
+        $qb = $this->dbConnection->getQueryBuilder();
746
+        $qb->select('*')
747
+            ->from('share');
748
+
749
+        // Order by id
750
+        $qb->orderBy('id');
751
+
752
+        // Set limit and offset
753
+        if ($limit !== -1) {
754
+            $qb->setMaxResults($limit);
755
+        }
756
+        $qb->setFirstResult($offset);
757
+
758
+        $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
759
+        $qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
760
+
761
+        // Filter by node if provided
762
+        if ($node !== null) {
763
+            $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
764
+        }
765
+
766
+        $cursor = $qb->execute();
767
+
768
+        while ($data = $cursor->fetch()) {
769
+            $shares[] = $this->createShareObject($data);
770
+        }
771
+        $cursor->closeCursor();
772
+
773
+
774
+        return $shares;
775
+    }
776
+
777
+    /**
778
+     * Get a share by token
779
+     *
780
+     * @param string $token
781
+     * @return IShare
782
+     * @throws ShareNotFound
783
+     */
784
+    public function getShareByToken($token) {
785
+        $qb = $this->dbConnection->getQueryBuilder();
786
+
787
+        $cursor = $qb->select('*')
788
+            ->from('share')
789
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
790
+            ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
791
+            ->execute();
792
+
793
+        $data = $cursor->fetch();
794
+
795
+        if ($data === false) {
796
+            throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
797
+        }
798
+
799
+        try {
800
+            $share = $this->createShareObject($data);
801
+        } catch (InvalidShare $e) {
802
+            throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
803
+        }
804
+
805
+        return $share;
806
+    }
807
+
808
+    /**
809
+     * get database row of a give share
810
+     *
811
+     * @param $id
812
+     * @return array
813
+     * @throws ShareNotFound
814
+     */
815
+    private function getRawShare($id) {
816
+
817
+        // Now fetch the inserted share and create a complete share object
818
+        $qb = $this->dbConnection->getQueryBuilder();
819
+        $qb->select('*')
820
+            ->from('share')
821
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
822
+
823
+        $cursor = $qb->execute();
824
+        $data = $cursor->fetch();
825
+        $cursor->closeCursor();
826
+
827
+        if ($data === false) {
828
+            throw new ShareNotFound;
829
+        }
830
+
831
+        return $data;
832
+    }
833
+
834
+    /**
835
+     * Create a share object from an database row
836
+     *
837
+     * @param array $data
838
+     * @return IShare
839
+     * @throws InvalidShare
840
+     * @throws ShareNotFound
841
+     */
842
+    private function createShareObject($data) {
843
+        $share = new Share($this->rootFolder, $this->userManager);
844
+        $share->setId((int)$data['id'])
845
+            ->setShareType((int)$data['share_type'])
846
+            ->setPermissions((int)$data['permissions'])
847
+            ->setTarget($data['file_target'])
848
+            ->setMailSend((bool)$data['mail_send'])
849
+            ->setToken($data['token']);
850
+
851
+        $shareTime = new \DateTime();
852
+        $shareTime->setTimestamp((int)$data['stime']);
853
+        $share->setShareTime($shareTime);
854
+        $share->setSharedWith($data['share_with']);
855
+
856
+        if ($data['uid_initiator'] !== null) {
857
+            $share->setShareOwner($data['uid_owner']);
858
+            $share->setSharedBy($data['uid_initiator']);
859
+        } else {
860
+            //OLD SHARE
861
+            $share->setSharedBy($data['uid_owner']);
862
+            $path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
863
+
864
+            $owner = $path->getOwner();
865
+            $share->setShareOwner($owner->getUID());
866
+        }
867
+
868
+        $share->setNodeId((int)$data['file_source']);
869
+        $share->setNodeType($data['item_type']);
870
+
871
+        $share->setProviderId($this->identifier());
872
+
873
+        return $share;
874
+    }
875
+
876
+    /**
877
+     * Get the node with file $id for $user
878
+     *
879
+     * @param string $userId
880
+     * @param int $id
881
+     * @return \OCP\Files\File|\OCP\Files\Folder
882
+     * @throws InvalidShare
883
+     */
884
+    private function getNode($userId, $id) {
885
+        try {
886
+            $userFolder = $this->rootFolder->getUserFolder($userId);
887
+        } catch (NotFoundException $e) {
888
+            throw new InvalidShare();
889
+        }
890
+
891
+        $nodes = $userFolder->getById($id);
892
+
893
+        if (empty($nodes)) {
894
+            throw new InvalidShare();
895
+        }
896
+
897
+        return $nodes[0];
898
+    }
899
+
900
+    /**
901
+     * A user is deleted from the system
902
+     * So clean up the relevant shares.
903
+     *
904
+     * @param string $uid
905
+     * @param int $shareType
906
+     */
907
+    public function userDeleted($uid, $shareType) {
908
+        //TODO: probabaly a good idea to send unshare info to remote servers
909
+
910
+        $qb = $this->dbConnection->getQueryBuilder();
911
+
912
+        $qb->delete('share')
913
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
914
+            ->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
915
+            ->execute();
916
+    }
917
+
918
+    /**
919
+     * This provider does not handle groups
920
+     *
921
+     * @param string $gid
922
+     */
923
+    public function groupDeleted($gid) {
924
+        // We don't handle groups here
925
+        return;
926
+    }
927
+
928
+    /**
929
+     * This provider does not handle groups
930
+     *
931
+     * @param string $uid
932
+     * @param string $gid
933
+     */
934
+    public function userDeletedFromGroup($uid, $gid) {
935
+        // We don't handle groups here
936
+        return;
937
+    }
938
+
939
+    /**
940
+     * check if users from other Nextcloud instances are allowed to mount public links share by this instance
941
+     *
942
+     * @return bool
943
+     */
944
+    public function isOutgoingServer2serverShareEnabled() {
945
+        if ($this->gsConfig->onlyInternalFederation()) {
946
+            return false;
947
+        }
948
+        $result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes');
949
+        return ($result === 'yes');
950
+    }
951
+
952
+    /**
953
+     * check if users are allowed to mount public links from other Nextclouds
954
+     *
955
+     * @return bool
956
+     */
957
+    public function isIncomingServer2serverShareEnabled() {
958
+        if ($this->gsConfig->onlyInternalFederation()) {
959
+            return false;
960
+        }
961
+        $result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes');
962
+        return ($result === 'yes');
963
+    }
964
+
965
+    /**
966
+     * Check if querying sharees on the lookup server is enabled
967
+     *
968
+     * @return bool
969
+     */
970
+    public function isLookupServerQueriesEnabled() {
971
+        // in a global scale setup we should always query the lookup server
972
+        if ($this->gsConfig->isGlobalScaleEnabled()) {
973
+            return true;
974
+        }
975
+        $result = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no');
976
+        return ($result === 'yes');
977
+    }
978
+
979
+
980
+    /**
981
+     * Check if it is allowed to publish user specific data to the lookup server
982
+     *
983
+     * @return bool
984
+     */
985
+    public function isLookupServerUploadEnabled() {
986
+        // in a global scale setup the admin is responsible to keep the lookup server up-to-date
987
+        if ($this->gsConfig->isGlobalScaleEnabled()) {
988
+            return false;
989
+        }
990
+        $result = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes');
991
+        return ($result === 'yes');
992
+    }
993
+
994
+    /**
995
+     * @inheritdoc
996
+     */
997
+    public function getAccessList($nodes, $currentAccess) {
998
+        $ids = [];
999
+        foreach ($nodes as $node) {
1000
+            $ids[] = $node->getId();
1001
+        }
1002
+
1003
+        $qb = $this->dbConnection->getQueryBuilder();
1004
+        $qb->select('share_with', 'token', 'file_source')
1005
+            ->from('share')
1006
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
1007
+            ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1008
+            ->andWhere($qb->expr()->orX(
1009
+                $qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1010
+                $qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1011
+            ));
1012
+        $cursor = $qb->execute();
1013
+
1014
+        if ($currentAccess === false) {
1015
+            $remote = $cursor->fetch() !== false;
1016
+            $cursor->closeCursor();
1017
+
1018
+            return ['remote' => $remote];
1019
+        }
1020
+
1021
+        $remote = [];
1022
+        while ($row = $cursor->fetch()) {
1023
+            $remote[$row['share_with']] = [
1024
+                'node_id' => $row['file_source'],
1025
+                'token' => $row['token'],
1026
+            ];
1027
+        }
1028
+        $cursor->closeCursor();
1029
+
1030
+        return ['remote' => $remote];
1031
+    }
1032 1032
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -187,7 +187,7 @@  discard block
 block discarded – undo
187 187
 		if ($remoteShare) {
188 188
 			try {
189 189
 				$ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
190
-				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time());
190
+				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_'.time());
191 191
 				$share->setId($shareId);
192 192
 				list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
193 193
 				// remote share was create successfully if we get a valid token as return
@@ -258,7 +258,7 @@  discard block
 block discarded – undo
258 258
 				$failure = true;
259 259
 			}
260 260
 		} catch (\Exception $e) {
261
-			$this->logger->error('Failed to notify remote server of federated share, removing share (' . $e->getMessage() . ')');
261
+			$this->logger->error('Failed to notify remote server of federated share, removing share ('.$e->getMessage().')');
262 262
 			$failure = true;
263 263
 		}
264 264
 
@@ -313,7 +313,7 @@  discard block
 block discarded – undo
313 313
 			->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget())));
314 314
 		$result = $query->execute()->fetchAll();
315 315
 
316
-		if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
316
+		if (isset($result[0]) && (int) $result[0]['remote_id'] > 0) {
317 317
 			return $result[0];
318 318
 		}
319 319
 
@@ -355,7 +355,7 @@  discard block
 block discarded – undo
355 355
 		$qb->execute();
356 356
 		$id = $qb->getLastInsertId();
357 357
 
358
-		return (int)$id;
358
+		return (int) $id;
359 359
 	}
360 360
 
361 361
 	/**
@@ -445,14 +445,14 @@  discard block
 block discarded – undo
445 445
 	public function getRemoteId(IShare $share) {
446 446
 		$query = $this->dbConnection->getQueryBuilder();
447 447
 		$query->select('remote_id')->from('federated_reshares')
448
-			->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
448
+			->where($query->expr()->eq('share_id', $query->createNamedParameter((int) $share->getId())));
449 449
 		$data = $query->execute()->fetch();
450 450
 
451 451
 		if (!is_array($data) || !isset($data['remote_id'])) {
452 452
 			throw new ShareNotFound();
453 453
 		}
454 454
 
455
-		return (int)$data['remote_id'];
455
+		return (int) $data['remote_id'];
456 456
 	}
457 457
 
458 458
 	/**
@@ -841,15 +841,15 @@  discard block
 block discarded – undo
841 841
 	 */
842 842
 	private function createShareObject($data) {
843 843
 		$share = new Share($this->rootFolder, $this->userManager);
844
-		$share->setId((int)$data['id'])
845
-			->setShareType((int)$data['share_type'])
846
-			->setPermissions((int)$data['permissions'])
844
+		$share->setId((int) $data['id'])
845
+			->setShareType((int) $data['share_type'])
846
+			->setPermissions((int) $data['permissions'])
847 847
 			->setTarget($data['file_target'])
848
-			->setMailSend((bool)$data['mail_send'])
848
+			->setMailSend((bool) $data['mail_send'])
849 849
 			->setToken($data['token']);
850 850
 
851 851
 		$shareTime = new \DateTime();
852
-		$shareTime->setTimestamp((int)$data['stime']);
852
+		$shareTime->setTimestamp((int) $data['stime']);
853 853
 		$share->setShareTime($shareTime);
854 854
 		$share->setSharedWith($data['share_with']);
855 855
 
@@ -859,13 +859,13 @@  discard block
 block discarded – undo
859 859
 		} else {
860 860
 			//OLD SHARE
861 861
 			$share->setSharedBy($data['uid_owner']);
862
-			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
862
+			$path = $this->getNode($share->getSharedBy(), (int) $data['file_source']);
863 863
 
864 864
 			$owner = $path->getOwner();
865 865
 			$share->setShareOwner($owner->getUID());
866 866
 		}
867 867
 
868
-		$share->setNodeId((int)$data['file_source']);
868
+		$share->setNodeId((int) $data['file_source']);
869 869
 		$share->setNodeType($data['item_type']);
870 870
 
871 871
 		$share->setProviderId($this->identifier());
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Trashbin.php 3 patches
Doc Comments   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -671,7 +671,7 @@  discard block
 block discarded – undo
671 671
 	 * if the size limit for the trash bin is reached, we delete the oldest
672 672
 	 * files in the trash bin until we meet the limit again
673 673
 	 *
674
-	 * @param array $files
674
+	 * @param \OCP\Files\FileInfo[] $files
675 675
 	 * @param string $user
676 676
 	 * @param int $availableSpace available disc space
677 677
 	 * @return int size of deleted files
@@ -699,7 +699,7 @@  discard block
 block discarded – undo
699 699
 	/**
700 700
 	 * delete files older then max storage time
701 701
 	 *
702
-	 * @param array $files list of files sorted by mtime
702
+	 * @param \OCP\Files\FileInfo[] $files list of files sorted by mtime
703 703
 	 * @param string $user
704 704
 	 * @return integer[] size of deleted files and number of deleted files
705 705
 	 */
Please login to merge, or discard this patch.
Indentation   +931 added lines, -931 removed lines patch added patch discarded remove patch
@@ -47,935 +47,935 @@
 block discarded – undo
47 47
 
48 48
 class Trashbin {
49 49
 
50
-	// unit: percentage; 50% of available disk space/quota
51
-	const DEFAULTMAXSIZE = 50;
52
-
53
-	/**
54
-	 * Whether versions have already be rescanned during this PHP request
55
-	 *
56
-	 * @var bool
57
-	 */
58
-	private static $scannedVersions = false;
59
-
60
-	/**
61
-	 * Ensure we don't need to scan the file during the move to trash
62
-	 * by triggering the scan in the pre-hook
63
-	 *
64
-	 * @param array $params
65
-	 */
66
-	public static function ensureFileScannedHook($params) {
67
-		try {
68
-			self::getUidAndFilename($params['path']);
69
-		} catch (NotFoundException $e) {
70
-			// nothing to scan for non existing files
71
-		}
72
-	}
73
-
74
-	/**
75
-	 * get the UID of the owner of the file and the path to the file relative to
76
-	 * owners files folder
77
-	 *
78
-	 * @param string $filename
79
-	 * @return array
80
-	 * @throws \OC\User\NoUserException
81
-	 */
82
-	public static function getUidAndFilename($filename) {
83
-		$uid = Filesystem::getOwner($filename);
84
-		$userManager = \OC::$server->getUserManager();
85
-		// if the user with the UID doesn't exists, e.g. because the UID points
86
-		// to a remote user with a federated cloud ID we use the current logged-in
87
-		// user. We need a valid local user to move the file to the right trash bin
88
-		if (!$userManager->userExists($uid)) {
89
-			$uid = User::getUser();
90
-		}
91
-		if (!$uid) {
92
-			// no owner, usually because of share link from ext storage
93
-			return [null, null];
94
-		}
95
-		Filesystem::initMountPoints($uid);
96
-		if ($uid != User::getUser()) {
97
-			$info = Filesystem::getFileInfo($filename);
98
-			$ownerView = new View('/' . $uid . '/files');
99
-			try {
100
-				$filename = $ownerView->getPath($info['fileid']);
101
-			} catch (NotFoundException $e) {
102
-				$filename = null;
103
-			}
104
-		}
105
-		return [$uid, $filename];
106
-	}
107
-
108
-	/**
109
-	 * get original location of files for user
110
-	 *
111
-	 * @param string $user
112
-	 * @return array (filename => array (timestamp => original location))
113
-	 */
114
-	public static function getLocations($user) {
115
-		$query = \OC_DB::prepare('SELECT `id`, `timestamp`, `location`'
116
-			. ' FROM `*PREFIX*files_trash` WHERE `user`=?');
117
-		$result = $query->execute([$user]);
118
-		$array = [];
119
-		while ($row = $result->fetchRow()) {
120
-			if (isset($array[$row['id']])) {
121
-				$array[$row['id']][$row['timestamp']] = $row['location'];
122
-			} else {
123
-				$array[$row['id']] = [$row['timestamp'] => $row['location']];
124
-			}
125
-		}
126
-		return $array;
127
-	}
128
-
129
-	/**
130
-	 * get original location of file
131
-	 *
132
-	 * @param string $user
133
-	 * @param string $filename
134
-	 * @param string $timestamp
135
-	 * @return string original location
136
-	 */
137
-	public static function getLocation($user, $filename, $timestamp) {
138
-		$query = \OC_DB::prepare('SELECT `location` FROM `*PREFIX*files_trash`'
139
-			. ' WHERE `user`=? AND `id`=? AND `timestamp`=?');
140
-		$result = $query->execute([$user, $filename, $timestamp])->fetchAll();
141
-		if (isset($result[0]['location'])) {
142
-			return $result[0]['location'];
143
-		} else {
144
-			return false;
145
-		}
146
-	}
147
-
148
-	private static function setUpTrash($user) {
149
-		$view = new View('/' . $user);
150
-		if (!$view->is_dir('files_trashbin')) {
151
-			$view->mkdir('files_trashbin');
152
-		}
153
-		if (!$view->is_dir('files_trashbin/files')) {
154
-			$view->mkdir('files_trashbin/files');
155
-		}
156
-		if (!$view->is_dir('files_trashbin/versions')) {
157
-			$view->mkdir('files_trashbin/versions');
158
-		}
159
-		if (!$view->is_dir('files_trashbin/keys')) {
160
-			$view->mkdir('files_trashbin/keys');
161
-		}
162
-	}
163
-
164
-
165
-	/**
166
-	 * copy file to owners trash
167
-	 *
168
-	 * @param string $sourcePath
169
-	 * @param string $owner
170
-	 * @param string $targetPath
171
-	 * @param $user
172
-	 * @param integer $timestamp
173
-	 */
174
-	private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
175
-		self::setUpTrash($owner);
176
-
177
-		$targetFilename = basename($targetPath);
178
-		$targetLocation = dirname($targetPath);
179
-
180
-		$sourceFilename = basename($sourcePath);
181
-
182
-		$view = new View('/');
183
-
184
-		$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
185
-		$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
186
-		self::copy_recursive($source, $target, $view);
187
-
188
-
189
-		if ($view->file_exists($target)) {
190
-			$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
191
-			$result = $query->execute([$targetFilename, $timestamp, $targetLocation, $user]);
192
-			if (!$result) {
193
-				\OCP\Util::writeLog('files_trashbin', 'trash bin database couldn\'t be updated for the files owner', \OCP\Util::ERROR);
194
-			}
195
-		}
196
-	}
197
-
198
-
199
-	/**
200
-	 * move file to the trash bin
201
-	 *
202
-	 * @param string $file_path path to the deleted file/directory relative to the files root directory
203
-	 * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
204
-	 *
205
-	 * @return bool
206
-	 */
207
-	public static function move2trash($file_path, $ownerOnly = false) {
208
-		// get the user for which the filesystem is setup
209
-		$root = Filesystem::getRoot();
210
-		list(, $user) = explode('/', $root);
211
-		list($owner, $ownerPath) = self::getUidAndFilename($file_path);
212
-
213
-		// if no owner found (ex: ext storage + share link), will use the current user's trashbin then
214
-		if (is_null($owner)) {
215
-			$owner = $user;
216
-			$ownerPath = $file_path;
217
-		}
218
-
219
-		$ownerView = new View('/' . $owner);
220
-		// file has been deleted in between
221
-		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
222
-			return true;
223
-		}
224
-
225
-		self::setUpTrash($user);
226
-		if ($owner !== $user) {
227
-			// also setup for owner
228
-			self::setUpTrash($owner);
229
-		}
230
-
231
-		$path_parts = pathinfo($ownerPath);
232
-
233
-		$filename = $path_parts['basename'];
234
-		$location = $path_parts['dirname'];
235
-		$timestamp = time();
236
-
237
-		// disable proxy to prevent recursive calls
238
-		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
239
-
240
-		/** @var \OC\Files\Storage\Storage $trashStorage */
241
-		list($trashStorage, $trashInternalPath) = $ownerView->resolvePath($trashPath);
242
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
243
-		list($sourceStorage, $sourceInternalPath) = $ownerView->resolvePath('/files/' . $ownerPath);
244
-		try {
245
-			$moveSuccessful = true;
246
-			if ($trashStorage->file_exists($trashInternalPath)) {
247
-				$trashStorage->unlink($trashInternalPath);
248
-			}
249
-			$trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
250
-		} catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
251
-			$moveSuccessful = false;
252
-			if ($trashStorage->file_exists($trashInternalPath)) {
253
-				$trashStorage->unlink($trashInternalPath);
254
-			}
255
-			\OCP\Util::writeLog('files_trashbin', 'Couldn\'t move ' . $file_path . ' to the trash bin', \OCP\Util::ERROR);
256
-		}
257
-
258
-		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
259
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
260
-				$sourceStorage->rmdir($sourceInternalPath);
261
-			} else {
262
-				$sourceStorage->unlink($sourceInternalPath);
263
-			}
264
-			return false;
265
-		}
266
-
267
-		$trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
268
-
269
-		if ($moveSuccessful) {
270
-			$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
271
-			$result = $query->execute([$filename, $timestamp, $location, $owner]);
272
-			if (!$result) {
273
-				\OCP\Util::writeLog('files_trashbin', 'trash bin database couldn\'t be updated', \OCP\Util::ERROR);
274
-			}
275
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
276
-				'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
277
-
278
-			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
279
-
280
-			// if owner !== user we need to also add a copy to the users trash
281
-			if ($user !== $owner && $ownerOnly === false) {
282
-				self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
283
-			}
284
-		}
285
-
286
-		self::scheduleExpire($user);
287
-
288
-		// if owner !== user we also need to update the owners trash size
289
-		if ($owner !== $user) {
290
-			self::scheduleExpire($owner);
291
-		}
292
-
293
-		return $moveSuccessful;
294
-	}
295
-
296
-	/**
297
-	 * Move file versions to trash so that they can be restored later
298
-	 *
299
-	 * @param string $filename of deleted file
300
-	 * @param string $owner owner user id
301
-	 * @param string $ownerPath path relative to the owner's home storage
302
-	 * @param integer $timestamp when the file was deleted
303
-	 */
304
-	private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
305
-		if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
306
-			$user = User::getUser();
307
-			$rootView = new View('/');
308
-
309
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
310
-				if ($owner !== $user) {
311
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
312
-				}
313
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
314
-			} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
315
-				foreach ($versions as $v) {
316
-					if ($owner !== $user) {
317
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
318
-					}
319
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
320
-				}
321
-			}
322
-		}
323
-	}
324
-
325
-	/**
326
-	 * Move a file or folder on storage level
327
-	 *
328
-	 * @param View $view
329
-	 * @param string $source
330
-	 * @param string $target
331
-	 * @return bool
332
-	 */
333
-	private static function move(View $view, $source, $target) {
334
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
335
-		list($sourceStorage, $sourceInternalPath) = $view->resolvePath($source);
336
-		/** @var \OC\Files\Storage\Storage $targetStorage */
337
-		list($targetStorage, $targetInternalPath) = $view->resolvePath($target);
338
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
339
-
340
-		$result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
341
-		if ($result) {
342
-			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
343
-		}
344
-		return $result;
345
-	}
346
-
347
-	/**
348
-	 * Copy a file or folder on storage level
349
-	 *
350
-	 * @param View $view
351
-	 * @param string $source
352
-	 * @param string $target
353
-	 * @return bool
354
-	 */
355
-	private static function copy(View $view, $source, $target) {
356
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
357
-		list($sourceStorage, $sourceInternalPath) = $view->resolvePath($source);
358
-		/** @var \OC\Files\Storage\Storage $targetStorage */
359
-		list($targetStorage, $targetInternalPath) = $view->resolvePath($target);
360
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
361
-
362
-		$result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
363
-		if ($result) {
364
-			$targetStorage->getUpdater()->update($targetInternalPath);
365
-		}
366
-		return $result;
367
-	}
368
-
369
-	/**
370
-	 * Restore a file or folder from trash bin
371
-	 *
372
-	 * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
373
-	 * including the timestamp suffix ".d12345678"
374
-	 * @param string $filename name of the file/folder
375
-	 * @param int $timestamp time when the file/folder was deleted
376
-	 *
377
-	 * @return bool true on success, false otherwise
378
-	 */
379
-	public static function restore($file, $filename, $timestamp) {
380
-		$user = User::getUser();
381
-		$view = new View('/' . $user);
382
-
383
-		$location = '';
384
-		if ($timestamp) {
385
-			$location = self::getLocation($user, $filename, $timestamp);
386
-			if ($location === false) {
387
-				\OCP\Util::writeLog('files_trashbin', 'trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', \OCP\Util::ERROR);
388
-			} else {
389
-				// if location no longer exists, restore file in the root directory
390
-				if ($location !== '/' &&
391
-					(!$view->is_dir('files/' . $location) ||
392
-						!$view->isCreatable('files/' . $location))
393
-				) {
394
-					$location = '';
395
-				}
396
-			}
397
-		}
398
-
399
-		// we need a  extension in case a file/dir with the same name already exists
400
-		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
401
-
402
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
403
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
404
-		if (!$view->file_exists($source)) {
405
-			return false;
406
-		}
407
-		$mtime = $view->filemtime($source);
408
-
409
-		// restore file
410
-		$restoreResult = $view->rename($source, $target);
411
-
412
-		// handle the restore result
413
-		if ($restoreResult) {
414
-			$fakeRoot = $view->getRoot();
415
-			$view->chroot('/' . $user . '/files');
416
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
417
-			$view->chroot($fakeRoot);
418
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
419
-				'trashPath' => Filesystem::normalizePath($file)]);
420
-
421
-			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
422
-
423
-			if ($timestamp) {
424
-				$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
425
-				$query->execute([$user, $filename, $timestamp]);
426
-			}
427
-
428
-			return true;
429
-		}
430
-
431
-		return false;
432
-	}
433
-
434
-	/**
435
-	 * restore versions from trash bin
436
-	 *
437
-	 * @param View $view file view
438
-	 * @param string $file complete path to file
439
-	 * @param string $filename name of file once it was deleted
440
-	 * @param string $uniqueFilename new file name to restore the file without overwriting existing files
441
-	 * @param string $location location if file
442
-	 * @param int $timestamp deletion time
443
-	 * @return false|null
444
-	 */
445
-	private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
446
-		if (\OCP\App::isEnabled('files_versions')) {
447
-			$user = User::getUser();
448
-			$rootView = new View('/');
449
-
450
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
451
-
452
-			list($owner, $ownerPath) = self::getUidAndFilename($target);
453
-
454
-			// file has been deleted in between
455
-			if (empty($ownerPath)) {
456
-				return false;
457
-			}
458
-
459
-			if ($timestamp) {
460
-				$versionedFile = $filename;
461
-			} else {
462
-				$versionedFile = $file;
463
-			}
464
-
465
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
466
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
467
-			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
468
-				foreach ($versions as $v) {
469
-					if ($timestamp) {
470
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
471
-					} else {
472
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
473
-					}
474
-				}
475
-			}
476
-		}
477
-	}
478
-
479
-	/**
480
-	 * delete all files from the trash
481
-	 */
482
-	public static function deleteAll() {
483
-		$user = User::getUser();
484
-		$view = new View('/' . $user);
485
-		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
486
-
487
-		// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
488
-		$filePaths = [];
489
-		foreach ($fileInfos as $fileInfo) {
490
-			$filePaths[] = $view->getRelativePath($fileInfo->getPath());
491
-		}
492
-		unset($fileInfos); // save memory
493
-
494
-		// Bulk PreDelete-Hook
495
-		\OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
496
-
497
-		// Single-File Hooks
498
-		foreach ($filePaths as $path) {
499
-			self::emitTrashbinPreDelete($path);
500
-		}
501
-
502
-		// actual file deletion
503
-		$view->deleteAll('files_trashbin');
504
-		$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
505
-		$query->execute([$user]);
506
-
507
-		// Bulk PostDelete-Hook
508
-		\OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
509
-
510
-		// Single-File Hooks
511
-		foreach ($filePaths as $path) {
512
-			self::emitTrashbinPostDelete($path);
513
-		}
514
-
515
-		$view->mkdir('files_trashbin');
516
-		$view->mkdir('files_trashbin/files');
517
-
518
-		return true;
519
-	}
520
-
521
-	/**
522
-	 * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
523
-	 * @param string $path
524
-	 */
525
-	protected static function emitTrashbinPreDelete($path) {
526
-		\OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
527
-	}
528
-
529
-	/**
530
-	 * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
531
-	 * @param string $path
532
-	 */
533
-	protected static function emitTrashbinPostDelete($path) {
534
-		\OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
535
-	}
536
-
537
-	/**
538
-	 * delete file from trash bin permanently
539
-	 *
540
-	 * @param string $filename path to the file
541
-	 * @param string $user
542
-	 * @param int $timestamp of deletion time
543
-	 *
544
-	 * @return int size of deleted files
545
-	 */
546
-	public static function delete($filename, $user, $timestamp = null) {
547
-		$view = new View('/' . $user);
548
-		$size = 0;
549
-
550
-		if ($timestamp) {
551
-			$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
552
-			$query->execute([$user, $filename, $timestamp]);
553
-			$file = $filename . '.d' . $timestamp;
554
-		} else {
555
-			$file = $filename;
556
-		}
557
-
558
-		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
559
-
560
-		if ($view->is_dir('/files_trashbin/files/' . $file)) {
561
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
562
-		} else {
563
-			$size += $view->filesize('/files_trashbin/files/' . $file);
564
-		}
565
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
566
-		$view->unlink('/files_trashbin/files/' . $file);
567
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
568
-
569
-		return $size;
570
-	}
571
-
572
-	/**
573
-	 * @param View $view
574
-	 * @param string $file
575
-	 * @param string $filename
576
-	 * @param integer|null $timestamp
577
-	 * @param string $user
578
-	 * @return int
579
-	 */
580
-	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
581
-		$size = 0;
582
-		if (\OCP\App::isEnabled('files_versions')) {
583
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
584
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
585
-				$view->unlink('files_trashbin/versions/' . $file);
586
-			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
587
-				foreach ($versions as $v) {
588
-					if ($timestamp) {
589
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
590
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
591
-					} else {
592
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
593
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
594
-					}
595
-				}
596
-			}
597
-		}
598
-		return $size;
599
-	}
600
-
601
-	/**
602
-	 * check to see whether a file exists in trashbin
603
-	 *
604
-	 * @param string $filename path to the file
605
-	 * @param int $timestamp of deletion time
606
-	 * @return bool true if file exists, otherwise false
607
-	 */
608
-	public static function file_exists($filename, $timestamp = null) {
609
-		$user = User::getUser();
610
-		$view = new View('/' . $user);
611
-
612
-		if ($timestamp) {
613
-			$filename = $filename . '.d' . $timestamp;
614
-		} else {
615
-			$filename = $filename;
616
-		}
617
-
618
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
619
-		return $view->file_exists($target);
620
-	}
621
-
622
-	/**
623
-	 * deletes used space for trash bin in db if user was deleted
624
-	 *
625
-	 * @param string $uid id of deleted user
626
-	 * @return bool result of db delete operation
627
-	 */
628
-	public static function deleteUser($uid) {
629
-		$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
630
-		return $query->execute([$uid]);
631
-	}
632
-
633
-	/**
634
-	 * calculate remaining free space for trash bin
635
-	 *
636
-	 * @param integer $trashbinSize current size of the trash bin
637
-	 * @param string $user
638
-	 * @return int available free space for trash bin
639
-	 */
640
-	private static function calculateFreeSpace($trashbinSize, $user) {
641
-		$softQuota = true;
642
-		$userObject = \OC::$server->getUserManager()->get($user);
643
-		if (is_null($userObject)) {
644
-			return 0;
645
-		}
646
-		$quota = $userObject->getQuota();
647
-		if ($quota === null || $quota === 'none') {
648
-			$quota = Filesystem::free_space('/');
649
-			$softQuota = false;
650
-			// inf or unknown free space
651
-			if ($quota < 0) {
652
-				$quota = PHP_INT_MAX;
653
-			}
654
-		} else {
655
-			$quota = \OCP\Util::computerFileSize($quota);
656
-		}
657
-
658
-		// calculate available space for trash bin
659
-		// subtract size of files and current trash bin size from quota
660
-		if ($softQuota) {
661
-			$userFolder = \OC::$server->getUserFolder($user);
662
-			if (is_null($userFolder)) {
663
-				return 0;
664
-			}
665
-			$free = $quota - $userFolder->getSize(); // remaining free space for user
666
-			if ($free > 0) {
667
-				$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
668
-			} else {
669
-				$availableSpace = $free - $trashbinSize;
670
-			}
671
-		} else {
672
-			$availableSpace = $quota;
673
-		}
674
-
675
-		return $availableSpace;
676
-	}
677
-
678
-	/**
679
-	 * resize trash bin if necessary after a new file was added to Nextcloud
680
-	 *
681
-	 * @param string $user user id
682
-	 */
683
-	public static function resizeTrash($user) {
684
-		$size = self::getTrashbinSize($user);
685
-
686
-		$freeSpace = self::calculateFreeSpace($size, $user);
687
-
688
-		if ($freeSpace < 0) {
689
-			self::scheduleExpire($user);
690
-		}
691
-	}
692
-
693
-	/**
694
-	 * clean up the trash bin
695
-	 *
696
-	 * @param string $user
697
-	 */
698
-	public static function expire($user) {
699
-		$trashBinSize = self::getTrashbinSize($user);
700
-		$availableSpace = self::calculateFreeSpace($trashBinSize, $user);
701
-
702
-		$dirContent = Helper::getTrashFiles('/', $user, 'mtime');
703
-
704
-		// delete all files older then $retention_obligation
705
-		list($delSize, $count) = self::deleteExpiredFiles($dirContent, $user);
706
-
707
-		$availableSpace += $delSize;
708
-
709
-		// delete files from trash until we meet the trash bin size limit again
710
-		self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
711
-	}
712
-
713
-	/**
714
-	 * @param string $user
715
-	 */
716
-	private static function scheduleExpire($user) {
717
-		// let the admin disable auto expire
718
-		$application = new Application();
719
-		$expiration = $application->getContainer()->query('Expiration');
720
-		if ($expiration->isEnabled()) {
721
-			\OC::$server->getCommandBus()->push(new Expire($user));
722
-		}
723
-	}
724
-
725
-	/**
726
-	 * if the size limit for the trash bin is reached, we delete the oldest
727
-	 * files in the trash bin until we meet the limit again
728
-	 *
729
-	 * @param array $files
730
-	 * @param string $user
731
-	 * @param int $availableSpace available disc space
732
-	 * @return int size of deleted files
733
-	 */
734
-	protected static function deleteFiles($files, $user, $availableSpace) {
735
-		$application = new Application();
736
-		$expiration = $application->getContainer()->query('Expiration');
737
-		$size = 0;
738
-
739
-		if ($availableSpace < 0) {
740
-			foreach ($files as $file) {
741
-				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
742
-					$tmp = self::delete($file['name'], $user, $file['mtime']);
743
-					\OCP\Util::writeLog('files_trashbin', 'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', \OCP\Util::INFO);
744
-					$availableSpace += $tmp;
745
-					$size += $tmp;
746
-				} else {
747
-					break;
748
-				}
749
-			}
750
-		}
751
-		return $size;
752
-	}
753
-
754
-	/**
755
-	 * delete files older then max storage time
756
-	 *
757
-	 * @param array $files list of files sorted by mtime
758
-	 * @param string $user
759
-	 * @return integer[] size of deleted files and number of deleted files
760
-	 */
761
-	public static function deleteExpiredFiles($files, $user) {
762
-		$application = new Application();
763
-		$expiration = $application->getContainer()->query('Expiration');
764
-		$size = 0;
765
-		$count = 0;
766
-		foreach ($files as $file) {
767
-			$timestamp = $file['mtime'];
768
-			$filename = $file['name'];
769
-			if ($expiration->isExpired($timestamp)) {
770
-				$count++;
771
-				$size += self::delete($filename, $user, $timestamp);
772
-				\OC::$server->getLogger()->info(
773
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
774
-					['app' => 'files_trashbin']
775
-				);
776
-			} else {
777
-				break;
778
-			}
779
-		}
780
-
781
-		return [$size, $count];
782
-	}
783
-
784
-	/**
785
-	 * recursive copy to copy a whole directory
786
-	 *
787
-	 * @param string $source source path, relative to the users files directory
788
-	 * @param string $destination destination path relative to the users root directoy
789
-	 * @param View $view file view for the users root directory
790
-	 * @return int
791
-	 * @throws Exceptions\CopyRecursiveException
792
-	 */
793
-	private static function copy_recursive($source, $destination, View $view) {
794
-		$size = 0;
795
-		if ($view->is_dir($source)) {
796
-			$view->mkdir($destination);
797
-			$view->touch($destination, $view->filemtime($source));
798
-			foreach ($view->getDirectoryContent($source) as $i) {
799
-				$pathDir = $source . '/' . $i['name'];
800
-				if ($view->is_dir($pathDir)) {
801
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
802
-				} else {
803
-					$size += $view->filesize($pathDir);
804
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
805
-					if (!$result) {
806
-						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
807
-					}
808
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
809
-				}
810
-			}
811
-		} else {
812
-			$size += $view->filesize($source);
813
-			$result = $view->copy($source, $destination);
814
-			if (!$result) {
815
-				throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
816
-			}
817
-			$view->touch($destination, $view->filemtime($source));
818
-		}
819
-		return $size;
820
-	}
821
-
822
-	/**
823
-	 * find all versions which belong to the file we want to restore
824
-	 *
825
-	 * @param string $filename name of the file which should be restored
826
-	 * @param int $timestamp timestamp when the file was deleted
827
-	 * @return array
828
-	 */
829
-	private static function getVersionsFromTrash($filename, $timestamp, $user) {
830
-		$view = new View('/' . $user . '/files_trashbin/versions');
831
-		$versions = [];
832
-
833
-		//force rescan of versions, local storage may not have updated the cache
834
-		if (!self::$scannedVersions) {
835
-			/** @var \OC\Files\Storage\Storage $storage */
836
-			list($storage, ) = $view->resolvePath('/');
837
-			$storage->getScanner()->scan('files_trashbin/versions');
838
-			self::$scannedVersions = true;
839
-		}
840
-
841
-		if ($timestamp) {
842
-			// fetch for old versions
843
-			$matches = $view->searchRaw($filename . '.v%.d' . $timestamp);
844
-			$offset = -strlen($timestamp) - 2;
845
-		} else {
846
-			$matches = $view->searchRaw($filename . '.v%');
847
-		}
848
-
849
-		if (is_array($matches)) {
850
-			foreach ($matches as $ma) {
851
-				if ($timestamp) {
852
-					$parts = explode('.v', substr($ma['path'], 0, $offset));
853
-					$versions[] = (end($parts));
854
-				} else {
855
-					$parts = explode('.v', $ma);
856
-					$versions[] = (end($parts));
857
-				}
858
-			}
859
-		}
860
-		return $versions;
861
-	}
862
-
863
-	/**
864
-	 * find unique extension for restored file if a file with the same name already exists
865
-	 *
866
-	 * @param string $location where the file should be restored
867
-	 * @param string $filename name of the file
868
-	 * @param View $view filesystem view relative to users root directory
869
-	 * @return string with unique extension
870
-	 */
871
-	private static function getUniqueFilename($location, $filename, View $view) {
872
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
873
-		$name = pathinfo($filename, PATHINFO_FILENAME);
874
-		$l = \OC::$server->getL10N('files_trashbin');
875
-
876
-		$location = '/' . trim($location, '/');
877
-
878
-		// if extension is not empty we set a dot in front of it
879
-		if ($ext !== '') {
880
-			$ext = '.' . $ext;
881
-		}
882
-
883
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
884
-			$i = 2;
885
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
886
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
887
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
888
-				$i++;
889
-			}
890
-
891
-			return $uniqueName;
892
-		}
893
-
894
-		return $filename;
895
-	}
896
-
897
-	/**
898
-	 * get the size from a given root folder
899
-	 *
900
-	 * @param View $view file view on the root folder
901
-	 * @return integer size of the folder
902
-	 */
903
-	private static function calculateSize($view) {
904
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
905
-		if (!file_exists($root)) {
906
-			return 0;
907
-		}
908
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
909
-		$size = 0;
910
-
911
-		/**
912
-		 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
913
-		 * This bug is fixed in PHP 5.5.9 or before
914
-		 * See #8376
915
-		 */
916
-		$iterator->rewind();
917
-		while ($iterator->valid()) {
918
-			$path = $iterator->current();
919
-			$relpath = substr($path, strlen($root) - 1);
920
-			if (!$view->is_dir($relpath)) {
921
-				$size += $view->filesize($relpath);
922
-			}
923
-			$iterator->next();
924
-		}
925
-		return $size;
926
-	}
927
-
928
-	/**
929
-	 * get current size of trash bin from a given user
930
-	 *
931
-	 * @param string $user user who owns the trash bin
932
-	 * @return integer trash bin size
933
-	 */
934
-	private static function getTrashbinSize($user) {
935
-		$view = new View('/' . $user);
936
-		$fileInfo = $view->getFileInfo('/files_trashbin');
937
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
938
-	}
939
-
940
-	/**
941
-	 * register hooks
942
-	 */
943
-	public static function registerHooks() {
944
-		// create storage wrapper on setup
945
-		\OCP\Util::connectHook('OC_Filesystem', 'preSetup', 'OCA\Files_Trashbin\Storage', 'setupStorage');
946
-		//Listen to delete user signal
947
-		\OCP\Util::connectHook('OC_User', 'pre_deleteUser', 'OCA\Files_Trashbin\Hooks', 'deleteUser_hook');
948
-		//Listen to post write hook
949
-		\OCP\Util::connectHook('OC_Filesystem', 'post_write', 'OCA\Files_Trashbin\Hooks', 'post_write_hook');
950
-		// pre and post-rename, disable trash logic for the copy+unlink case
951
-		\OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Trashbin\Trashbin', 'ensureFileScannedHook');
952
-		\OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Trashbin\Storage', 'preRenameHook');
953
-		\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Files_Trashbin\Storage', 'postRenameHook');
954
-	}
955
-
956
-	/**
957
-	 * check if trash bin is empty for a given user
958
-	 *
959
-	 * @param string $user
960
-	 * @return bool
961
-	 */
962
-	public static function isEmpty($user) {
963
-		$view = new View('/' . $user . '/files_trashbin');
964
-		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
965
-			while ($file = readdir($dh)) {
966
-				if (!Filesystem::isIgnoredDir($file)) {
967
-					return false;
968
-				}
969
-			}
970
-		}
971
-		return true;
972
-	}
973
-
974
-	/**
975
-	 * @param $path
976
-	 * @return string
977
-	 */
978
-	public static function preview_icon($path) {
979
-		return \OCP\Util::linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
980
-	}
50
+    // unit: percentage; 50% of available disk space/quota
51
+    const DEFAULTMAXSIZE = 50;
52
+
53
+    /**
54
+     * Whether versions have already be rescanned during this PHP request
55
+     *
56
+     * @var bool
57
+     */
58
+    private static $scannedVersions = false;
59
+
60
+    /**
61
+     * Ensure we don't need to scan the file during the move to trash
62
+     * by triggering the scan in the pre-hook
63
+     *
64
+     * @param array $params
65
+     */
66
+    public static function ensureFileScannedHook($params) {
67
+        try {
68
+            self::getUidAndFilename($params['path']);
69
+        } catch (NotFoundException $e) {
70
+            // nothing to scan for non existing files
71
+        }
72
+    }
73
+
74
+    /**
75
+     * get the UID of the owner of the file and the path to the file relative to
76
+     * owners files folder
77
+     *
78
+     * @param string $filename
79
+     * @return array
80
+     * @throws \OC\User\NoUserException
81
+     */
82
+    public static function getUidAndFilename($filename) {
83
+        $uid = Filesystem::getOwner($filename);
84
+        $userManager = \OC::$server->getUserManager();
85
+        // if the user with the UID doesn't exists, e.g. because the UID points
86
+        // to a remote user with a federated cloud ID we use the current logged-in
87
+        // user. We need a valid local user to move the file to the right trash bin
88
+        if (!$userManager->userExists($uid)) {
89
+            $uid = User::getUser();
90
+        }
91
+        if (!$uid) {
92
+            // no owner, usually because of share link from ext storage
93
+            return [null, null];
94
+        }
95
+        Filesystem::initMountPoints($uid);
96
+        if ($uid != User::getUser()) {
97
+            $info = Filesystem::getFileInfo($filename);
98
+            $ownerView = new View('/' . $uid . '/files');
99
+            try {
100
+                $filename = $ownerView->getPath($info['fileid']);
101
+            } catch (NotFoundException $e) {
102
+                $filename = null;
103
+            }
104
+        }
105
+        return [$uid, $filename];
106
+    }
107
+
108
+    /**
109
+     * get original location of files for user
110
+     *
111
+     * @param string $user
112
+     * @return array (filename => array (timestamp => original location))
113
+     */
114
+    public static function getLocations($user) {
115
+        $query = \OC_DB::prepare('SELECT `id`, `timestamp`, `location`'
116
+            . ' FROM `*PREFIX*files_trash` WHERE `user`=?');
117
+        $result = $query->execute([$user]);
118
+        $array = [];
119
+        while ($row = $result->fetchRow()) {
120
+            if (isset($array[$row['id']])) {
121
+                $array[$row['id']][$row['timestamp']] = $row['location'];
122
+            } else {
123
+                $array[$row['id']] = [$row['timestamp'] => $row['location']];
124
+            }
125
+        }
126
+        return $array;
127
+    }
128
+
129
+    /**
130
+     * get original location of file
131
+     *
132
+     * @param string $user
133
+     * @param string $filename
134
+     * @param string $timestamp
135
+     * @return string original location
136
+     */
137
+    public static function getLocation($user, $filename, $timestamp) {
138
+        $query = \OC_DB::prepare('SELECT `location` FROM `*PREFIX*files_trash`'
139
+            . ' WHERE `user`=? AND `id`=? AND `timestamp`=?');
140
+        $result = $query->execute([$user, $filename, $timestamp])->fetchAll();
141
+        if (isset($result[0]['location'])) {
142
+            return $result[0]['location'];
143
+        } else {
144
+            return false;
145
+        }
146
+    }
147
+
148
+    private static function setUpTrash($user) {
149
+        $view = new View('/' . $user);
150
+        if (!$view->is_dir('files_trashbin')) {
151
+            $view->mkdir('files_trashbin');
152
+        }
153
+        if (!$view->is_dir('files_trashbin/files')) {
154
+            $view->mkdir('files_trashbin/files');
155
+        }
156
+        if (!$view->is_dir('files_trashbin/versions')) {
157
+            $view->mkdir('files_trashbin/versions');
158
+        }
159
+        if (!$view->is_dir('files_trashbin/keys')) {
160
+            $view->mkdir('files_trashbin/keys');
161
+        }
162
+    }
163
+
164
+
165
+    /**
166
+     * copy file to owners trash
167
+     *
168
+     * @param string $sourcePath
169
+     * @param string $owner
170
+     * @param string $targetPath
171
+     * @param $user
172
+     * @param integer $timestamp
173
+     */
174
+    private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
175
+        self::setUpTrash($owner);
176
+
177
+        $targetFilename = basename($targetPath);
178
+        $targetLocation = dirname($targetPath);
179
+
180
+        $sourceFilename = basename($sourcePath);
181
+
182
+        $view = new View('/');
183
+
184
+        $target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
185
+        $source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
186
+        self::copy_recursive($source, $target, $view);
187
+
188
+
189
+        if ($view->file_exists($target)) {
190
+            $query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
191
+            $result = $query->execute([$targetFilename, $timestamp, $targetLocation, $user]);
192
+            if (!$result) {
193
+                \OCP\Util::writeLog('files_trashbin', 'trash bin database couldn\'t be updated for the files owner', \OCP\Util::ERROR);
194
+            }
195
+        }
196
+    }
197
+
198
+
199
+    /**
200
+     * move file to the trash bin
201
+     *
202
+     * @param string $file_path path to the deleted file/directory relative to the files root directory
203
+     * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
204
+     *
205
+     * @return bool
206
+     */
207
+    public static function move2trash($file_path, $ownerOnly = false) {
208
+        // get the user for which the filesystem is setup
209
+        $root = Filesystem::getRoot();
210
+        list(, $user) = explode('/', $root);
211
+        list($owner, $ownerPath) = self::getUidAndFilename($file_path);
212
+
213
+        // if no owner found (ex: ext storage + share link), will use the current user's trashbin then
214
+        if (is_null($owner)) {
215
+            $owner = $user;
216
+            $ownerPath = $file_path;
217
+        }
218
+
219
+        $ownerView = new View('/' . $owner);
220
+        // file has been deleted in between
221
+        if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
222
+            return true;
223
+        }
224
+
225
+        self::setUpTrash($user);
226
+        if ($owner !== $user) {
227
+            // also setup for owner
228
+            self::setUpTrash($owner);
229
+        }
230
+
231
+        $path_parts = pathinfo($ownerPath);
232
+
233
+        $filename = $path_parts['basename'];
234
+        $location = $path_parts['dirname'];
235
+        $timestamp = time();
236
+
237
+        // disable proxy to prevent recursive calls
238
+        $trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
239
+
240
+        /** @var \OC\Files\Storage\Storage $trashStorage */
241
+        list($trashStorage, $trashInternalPath) = $ownerView->resolvePath($trashPath);
242
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
243
+        list($sourceStorage, $sourceInternalPath) = $ownerView->resolvePath('/files/' . $ownerPath);
244
+        try {
245
+            $moveSuccessful = true;
246
+            if ($trashStorage->file_exists($trashInternalPath)) {
247
+                $trashStorage->unlink($trashInternalPath);
248
+            }
249
+            $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
250
+        } catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
251
+            $moveSuccessful = false;
252
+            if ($trashStorage->file_exists($trashInternalPath)) {
253
+                $trashStorage->unlink($trashInternalPath);
254
+            }
255
+            \OCP\Util::writeLog('files_trashbin', 'Couldn\'t move ' . $file_path . ' to the trash bin', \OCP\Util::ERROR);
256
+        }
257
+
258
+        if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
259
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
260
+                $sourceStorage->rmdir($sourceInternalPath);
261
+            } else {
262
+                $sourceStorage->unlink($sourceInternalPath);
263
+            }
264
+            return false;
265
+        }
266
+
267
+        $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
268
+
269
+        if ($moveSuccessful) {
270
+            $query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
271
+            $result = $query->execute([$filename, $timestamp, $location, $owner]);
272
+            if (!$result) {
273
+                \OCP\Util::writeLog('files_trashbin', 'trash bin database couldn\'t be updated', \OCP\Util::ERROR);
274
+            }
275
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
276
+                'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
277
+
278
+            self::retainVersions($filename, $owner, $ownerPath, $timestamp);
279
+
280
+            // if owner !== user we need to also add a copy to the users trash
281
+            if ($user !== $owner && $ownerOnly === false) {
282
+                self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
283
+            }
284
+        }
285
+
286
+        self::scheduleExpire($user);
287
+
288
+        // if owner !== user we also need to update the owners trash size
289
+        if ($owner !== $user) {
290
+            self::scheduleExpire($owner);
291
+        }
292
+
293
+        return $moveSuccessful;
294
+    }
295
+
296
+    /**
297
+     * Move file versions to trash so that they can be restored later
298
+     *
299
+     * @param string $filename of deleted file
300
+     * @param string $owner owner user id
301
+     * @param string $ownerPath path relative to the owner's home storage
302
+     * @param integer $timestamp when the file was deleted
303
+     */
304
+    private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
305
+        if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
306
+            $user = User::getUser();
307
+            $rootView = new View('/');
308
+
309
+            if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
310
+                if ($owner !== $user) {
311
+                    self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
312
+                }
313
+                self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
314
+            } elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
315
+                foreach ($versions as $v) {
316
+                    if ($owner !== $user) {
317
+                        self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
318
+                    }
319
+                    self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
320
+                }
321
+            }
322
+        }
323
+    }
324
+
325
+    /**
326
+     * Move a file or folder on storage level
327
+     *
328
+     * @param View $view
329
+     * @param string $source
330
+     * @param string $target
331
+     * @return bool
332
+     */
333
+    private static function move(View $view, $source, $target) {
334
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
335
+        list($sourceStorage, $sourceInternalPath) = $view->resolvePath($source);
336
+        /** @var \OC\Files\Storage\Storage $targetStorage */
337
+        list($targetStorage, $targetInternalPath) = $view->resolvePath($target);
338
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
339
+
340
+        $result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
341
+        if ($result) {
342
+            $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
343
+        }
344
+        return $result;
345
+    }
346
+
347
+    /**
348
+     * Copy a file or folder on storage level
349
+     *
350
+     * @param View $view
351
+     * @param string $source
352
+     * @param string $target
353
+     * @return bool
354
+     */
355
+    private static function copy(View $view, $source, $target) {
356
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
357
+        list($sourceStorage, $sourceInternalPath) = $view->resolvePath($source);
358
+        /** @var \OC\Files\Storage\Storage $targetStorage */
359
+        list($targetStorage, $targetInternalPath) = $view->resolvePath($target);
360
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
361
+
362
+        $result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
363
+        if ($result) {
364
+            $targetStorage->getUpdater()->update($targetInternalPath);
365
+        }
366
+        return $result;
367
+    }
368
+
369
+    /**
370
+     * Restore a file or folder from trash bin
371
+     *
372
+     * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
373
+     * including the timestamp suffix ".d12345678"
374
+     * @param string $filename name of the file/folder
375
+     * @param int $timestamp time when the file/folder was deleted
376
+     *
377
+     * @return bool true on success, false otherwise
378
+     */
379
+    public static function restore($file, $filename, $timestamp) {
380
+        $user = User::getUser();
381
+        $view = new View('/' . $user);
382
+
383
+        $location = '';
384
+        if ($timestamp) {
385
+            $location = self::getLocation($user, $filename, $timestamp);
386
+            if ($location === false) {
387
+                \OCP\Util::writeLog('files_trashbin', 'trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', \OCP\Util::ERROR);
388
+            } else {
389
+                // if location no longer exists, restore file in the root directory
390
+                if ($location !== '/' &&
391
+                    (!$view->is_dir('files/' . $location) ||
392
+                        !$view->isCreatable('files/' . $location))
393
+                ) {
394
+                    $location = '';
395
+                }
396
+            }
397
+        }
398
+
399
+        // we need a  extension in case a file/dir with the same name already exists
400
+        $uniqueFilename = self::getUniqueFilename($location, $filename, $view);
401
+
402
+        $source = Filesystem::normalizePath('files_trashbin/files/' . $file);
403
+        $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
404
+        if (!$view->file_exists($source)) {
405
+            return false;
406
+        }
407
+        $mtime = $view->filemtime($source);
408
+
409
+        // restore file
410
+        $restoreResult = $view->rename($source, $target);
411
+
412
+        // handle the restore result
413
+        if ($restoreResult) {
414
+            $fakeRoot = $view->getRoot();
415
+            $view->chroot('/' . $user . '/files');
416
+            $view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
417
+            $view->chroot($fakeRoot);
418
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
419
+                'trashPath' => Filesystem::normalizePath($file)]);
420
+
421
+            self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
422
+
423
+            if ($timestamp) {
424
+                $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
425
+                $query->execute([$user, $filename, $timestamp]);
426
+            }
427
+
428
+            return true;
429
+        }
430
+
431
+        return false;
432
+    }
433
+
434
+    /**
435
+     * restore versions from trash bin
436
+     *
437
+     * @param View $view file view
438
+     * @param string $file complete path to file
439
+     * @param string $filename name of file once it was deleted
440
+     * @param string $uniqueFilename new file name to restore the file without overwriting existing files
441
+     * @param string $location location if file
442
+     * @param int $timestamp deletion time
443
+     * @return false|null
444
+     */
445
+    private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
446
+        if (\OCP\App::isEnabled('files_versions')) {
447
+            $user = User::getUser();
448
+            $rootView = new View('/');
449
+
450
+            $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
451
+
452
+            list($owner, $ownerPath) = self::getUidAndFilename($target);
453
+
454
+            // file has been deleted in between
455
+            if (empty($ownerPath)) {
456
+                return false;
457
+            }
458
+
459
+            if ($timestamp) {
460
+                $versionedFile = $filename;
461
+            } else {
462
+                $versionedFile = $file;
463
+            }
464
+
465
+            if ($view->is_dir('/files_trashbin/versions/' . $file)) {
466
+                $rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
467
+            } elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
468
+                foreach ($versions as $v) {
469
+                    if ($timestamp) {
470
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
471
+                    } else {
472
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
473
+                    }
474
+                }
475
+            }
476
+        }
477
+    }
478
+
479
+    /**
480
+     * delete all files from the trash
481
+     */
482
+    public static function deleteAll() {
483
+        $user = User::getUser();
484
+        $view = new View('/' . $user);
485
+        $fileInfos = $view->getDirectoryContent('files_trashbin/files');
486
+
487
+        // Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
488
+        $filePaths = [];
489
+        foreach ($fileInfos as $fileInfo) {
490
+            $filePaths[] = $view->getRelativePath($fileInfo->getPath());
491
+        }
492
+        unset($fileInfos); // save memory
493
+
494
+        // Bulk PreDelete-Hook
495
+        \OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', ['paths' => $filePaths]);
496
+
497
+        // Single-File Hooks
498
+        foreach ($filePaths as $path) {
499
+            self::emitTrashbinPreDelete($path);
500
+        }
501
+
502
+        // actual file deletion
503
+        $view->deleteAll('files_trashbin');
504
+        $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
505
+        $query->execute([$user]);
506
+
507
+        // Bulk PostDelete-Hook
508
+        \OC_Hook::emit('\OCP\Trashbin', 'deleteAll', ['paths' => $filePaths]);
509
+
510
+        // Single-File Hooks
511
+        foreach ($filePaths as $path) {
512
+            self::emitTrashbinPostDelete($path);
513
+        }
514
+
515
+        $view->mkdir('files_trashbin');
516
+        $view->mkdir('files_trashbin/files');
517
+
518
+        return true;
519
+    }
520
+
521
+    /**
522
+     * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
523
+     * @param string $path
524
+     */
525
+    protected static function emitTrashbinPreDelete($path) {
526
+        \OC_Hook::emit('\OCP\Trashbin', 'preDelete', ['path' => $path]);
527
+    }
528
+
529
+    /**
530
+     * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
531
+     * @param string $path
532
+     */
533
+    protected static function emitTrashbinPostDelete($path) {
534
+        \OC_Hook::emit('\OCP\Trashbin', 'delete', ['path' => $path]);
535
+    }
536
+
537
+    /**
538
+     * delete file from trash bin permanently
539
+     *
540
+     * @param string $filename path to the file
541
+     * @param string $user
542
+     * @param int $timestamp of deletion time
543
+     *
544
+     * @return int size of deleted files
545
+     */
546
+    public static function delete($filename, $user, $timestamp = null) {
547
+        $view = new View('/' . $user);
548
+        $size = 0;
549
+
550
+        if ($timestamp) {
551
+            $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
552
+            $query->execute([$user, $filename, $timestamp]);
553
+            $file = $filename . '.d' . $timestamp;
554
+        } else {
555
+            $file = $filename;
556
+        }
557
+
558
+        $size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
559
+
560
+        if ($view->is_dir('/files_trashbin/files/' . $file)) {
561
+            $size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
562
+        } else {
563
+            $size += $view->filesize('/files_trashbin/files/' . $file);
564
+        }
565
+        self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
566
+        $view->unlink('/files_trashbin/files/' . $file);
567
+        self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
568
+
569
+        return $size;
570
+    }
571
+
572
+    /**
573
+     * @param View $view
574
+     * @param string $file
575
+     * @param string $filename
576
+     * @param integer|null $timestamp
577
+     * @param string $user
578
+     * @return int
579
+     */
580
+    private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
581
+        $size = 0;
582
+        if (\OCP\App::isEnabled('files_versions')) {
583
+            if ($view->is_dir('files_trashbin/versions/' . $file)) {
584
+                $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
585
+                $view->unlink('files_trashbin/versions/' . $file);
586
+            } elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
587
+                foreach ($versions as $v) {
588
+                    if ($timestamp) {
589
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
590
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
591
+                    } else {
592
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
593
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
594
+                    }
595
+                }
596
+            }
597
+        }
598
+        return $size;
599
+    }
600
+
601
+    /**
602
+     * check to see whether a file exists in trashbin
603
+     *
604
+     * @param string $filename path to the file
605
+     * @param int $timestamp of deletion time
606
+     * @return bool true if file exists, otherwise false
607
+     */
608
+    public static function file_exists($filename, $timestamp = null) {
609
+        $user = User::getUser();
610
+        $view = new View('/' . $user);
611
+
612
+        if ($timestamp) {
613
+            $filename = $filename . '.d' . $timestamp;
614
+        } else {
615
+            $filename = $filename;
616
+        }
617
+
618
+        $target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
619
+        return $view->file_exists($target);
620
+    }
621
+
622
+    /**
623
+     * deletes used space for trash bin in db if user was deleted
624
+     *
625
+     * @param string $uid id of deleted user
626
+     * @return bool result of db delete operation
627
+     */
628
+    public static function deleteUser($uid) {
629
+        $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
630
+        return $query->execute([$uid]);
631
+    }
632
+
633
+    /**
634
+     * calculate remaining free space for trash bin
635
+     *
636
+     * @param integer $trashbinSize current size of the trash bin
637
+     * @param string $user
638
+     * @return int available free space for trash bin
639
+     */
640
+    private static function calculateFreeSpace($trashbinSize, $user) {
641
+        $softQuota = true;
642
+        $userObject = \OC::$server->getUserManager()->get($user);
643
+        if (is_null($userObject)) {
644
+            return 0;
645
+        }
646
+        $quota = $userObject->getQuota();
647
+        if ($quota === null || $quota === 'none') {
648
+            $quota = Filesystem::free_space('/');
649
+            $softQuota = false;
650
+            // inf or unknown free space
651
+            if ($quota < 0) {
652
+                $quota = PHP_INT_MAX;
653
+            }
654
+        } else {
655
+            $quota = \OCP\Util::computerFileSize($quota);
656
+        }
657
+
658
+        // calculate available space for trash bin
659
+        // subtract size of files and current trash bin size from quota
660
+        if ($softQuota) {
661
+            $userFolder = \OC::$server->getUserFolder($user);
662
+            if (is_null($userFolder)) {
663
+                return 0;
664
+            }
665
+            $free = $quota - $userFolder->getSize(); // remaining free space for user
666
+            if ($free > 0) {
667
+                $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
668
+            } else {
669
+                $availableSpace = $free - $trashbinSize;
670
+            }
671
+        } else {
672
+            $availableSpace = $quota;
673
+        }
674
+
675
+        return $availableSpace;
676
+    }
677
+
678
+    /**
679
+     * resize trash bin if necessary after a new file was added to Nextcloud
680
+     *
681
+     * @param string $user user id
682
+     */
683
+    public static function resizeTrash($user) {
684
+        $size = self::getTrashbinSize($user);
685
+
686
+        $freeSpace = self::calculateFreeSpace($size, $user);
687
+
688
+        if ($freeSpace < 0) {
689
+            self::scheduleExpire($user);
690
+        }
691
+    }
692
+
693
+    /**
694
+     * clean up the trash bin
695
+     *
696
+     * @param string $user
697
+     */
698
+    public static function expire($user) {
699
+        $trashBinSize = self::getTrashbinSize($user);
700
+        $availableSpace = self::calculateFreeSpace($trashBinSize, $user);
701
+
702
+        $dirContent = Helper::getTrashFiles('/', $user, 'mtime');
703
+
704
+        // delete all files older then $retention_obligation
705
+        list($delSize, $count) = self::deleteExpiredFiles($dirContent, $user);
706
+
707
+        $availableSpace += $delSize;
708
+
709
+        // delete files from trash until we meet the trash bin size limit again
710
+        self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
711
+    }
712
+
713
+    /**
714
+     * @param string $user
715
+     */
716
+    private static function scheduleExpire($user) {
717
+        // let the admin disable auto expire
718
+        $application = new Application();
719
+        $expiration = $application->getContainer()->query('Expiration');
720
+        if ($expiration->isEnabled()) {
721
+            \OC::$server->getCommandBus()->push(new Expire($user));
722
+        }
723
+    }
724
+
725
+    /**
726
+     * if the size limit for the trash bin is reached, we delete the oldest
727
+     * files in the trash bin until we meet the limit again
728
+     *
729
+     * @param array $files
730
+     * @param string $user
731
+     * @param int $availableSpace available disc space
732
+     * @return int size of deleted files
733
+     */
734
+    protected static function deleteFiles($files, $user, $availableSpace) {
735
+        $application = new Application();
736
+        $expiration = $application->getContainer()->query('Expiration');
737
+        $size = 0;
738
+
739
+        if ($availableSpace < 0) {
740
+            foreach ($files as $file) {
741
+                if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
742
+                    $tmp = self::delete($file['name'], $user, $file['mtime']);
743
+                    \OCP\Util::writeLog('files_trashbin', 'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', \OCP\Util::INFO);
744
+                    $availableSpace += $tmp;
745
+                    $size += $tmp;
746
+                } else {
747
+                    break;
748
+                }
749
+            }
750
+        }
751
+        return $size;
752
+    }
753
+
754
+    /**
755
+     * delete files older then max storage time
756
+     *
757
+     * @param array $files list of files sorted by mtime
758
+     * @param string $user
759
+     * @return integer[] size of deleted files and number of deleted files
760
+     */
761
+    public static function deleteExpiredFiles($files, $user) {
762
+        $application = new Application();
763
+        $expiration = $application->getContainer()->query('Expiration');
764
+        $size = 0;
765
+        $count = 0;
766
+        foreach ($files as $file) {
767
+            $timestamp = $file['mtime'];
768
+            $filename = $file['name'];
769
+            if ($expiration->isExpired($timestamp)) {
770
+                $count++;
771
+                $size += self::delete($filename, $user, $timestamp);
772
+                \OC::$server->getLogger()->info(
773
+                    'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
774
+                    ['app' => 'files_trashbin']
775
+                );
776
+            } else {
777
+                break;
778
+            }
779
+        }
780
+
781
+        return [$size, $count];
782
+    }
783
+
784
+    /**
785
+     * recursive copy to copy a whole directory
786
+     *
787
+     * @param string $source source path, relative to the users files directory
788
+     * @param string $destination destination path relative to the users root directoy
789
+     * @param View $view file view for the users root directory
790
+     * @return int
791
+     * @throws Exceptions\CopyRecursiveException
792
+     */
793
+    private static function copy_recursive($source, $destination, View $view) {
794
+        $size = 0;
795
+        if ($view->is_dir($source)) {
796
+            $view->mkdir($destination);
797
+            $view->touch($destination, $view->filemtime($source));
798
+            foreach ($view->getDirectoryContent($source) as $i) {
799
+                $pathDir = $source . '/' . $i['name'];
800
+                if ($view->is_dir($pathDir)) {
801
+                    $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
802
+                } else {
803
+                    $size += $view->filesize($pathDir);
804
+                    $result = $view->copy($pathDir, $destination . '/' . $i['name']);
805
+                    if (!$result) {
806
+                        throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
807
+                    }
808
+                    $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
809
+                }
810
+            }
811
+        } else {
812
+            $size += $view->filesize($source);
813
+            $result = $view->copy($source, $destination);
814
+            if (!$result) {
815
+                throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
816
+            }
817
+            $view->touch($destination, $view->filemtime($source));
818
+        }
819
+        return $size;
820
+    }
821
+
822
+    /**
823
+     * find all versions which belong to the file we want to restore
824
+     *
825
+     * @param string $filename name of the file which should be restored
826
+     * @param int $timestamp timestamp when the file was deleted
827
+     * @return array
828
+     */
829
+    private static function getVersionsFromTrash($filename, $timestamp, $user) {
830
+        $view = new View('/' . $user . '/files_trashbin/versions');
831
+        $versions = [];
832
+
833
+        //force rescan of versions, local storage may not have updated the cache
834
+        if (!self::$scannedVersions) {
835
+            /** @var \OC\Files\Storage\Storage $storage */
836
+            list($storage, ) = $view->resolvePath('/');
837
+            $storage->getScanner()->scan('files_trashbin/versions');
838
+            self::$scannedVersions = true;
839
+        }
840
+
841
+        if ($timestamp) {
842
+            // fetch for old versions
843
+            $matches = $view->searchRaw($filename . '.v%.d' . $timestamp);
844
+            $offset = -strlen($timestamp) - 2;
845
+        } else {
846
+            $matches = $view->searchRaw($filename . '.v%');
847
+        }
848
+
849
+        if (is_array($matches)) {
850
+            foreach ($matches as $ma) {
851
+                if ($timestamp) {
852
+                    $parts = explode('.v', substr($ma['path'], 0, $offset));
853
+                    $versions[] = (end($parts));
854
+                } else {
855
+                    $parts = explode('.v', $ma);
856
+                    $versions[] = (end($parts));
857
+                }
858
+            }
859
+        }
860
+        return $versions;
861
+    }
862
+
863
+    /**
864
+     * find unique extension for restored file if a file with the same name already exists
865
+     *
866
+     * @param string $location where the file should be restored
867
+     * @param string $filename name of the file
868
+     * @param View $view filesystem view relative to users root directory
869
+     * @return string with unique extension
870
+     */
871
+    private static function getUniqueFilename($location, $filename, View $view) {
872
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
873
+        $name = pathinfo($filename, PATHINFO_FILENAME);
874
+        $l = \OC::$server->getL10N('files_trashbin');
875
+
876
+        $location = '/' . trim($location, '/');
877
+
878
+        // if extension is not empty we set a dot in front of it
879
+        if ($ext !== '') {
880
+            $ext = '.' . $ext;
881
+        }
882
+
883
+        if ($view->file_exists('files' . $location . '/' . $filename)) {
884
+            $i = 2;
885
+            $uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
886
+            while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
887
+                $uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
888
+                $i++;
889
+            }
890
+
891
+            return $uniqueName;
892
+        }
893
+
894
+        return $filename;
895
+    }
896
+
897
+    /**
898
+     * get the size from a given root folder
899
+     *
900
+     * @param View $view file view on the root folder
901
+     * @return integer size of the folder
902
+     */
903
+    private static function calculateSize($view) {
904
+        $root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
905
+        if (!file_exists($root)) {
906
+            return 0;
907
+        }
908
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
909
+        $size = 0;
910
+
911
+        /**
912
+         * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
913
+         * This bug is fixed in PHP 5.5.9 or before
914
+         * See #8376
915
+         */
916
+        $iterator->rewind();
917
+        while ($iterator->valid()) {
918
+            $path = $iterator->current();
919
+            $relpath = substr($path, strlen($root) - 1);
920
+            if (!$view->is_dir($relpath)) {
921
+                $size += $view->filesize($relpath);
922
+            }
923
+            $iterator->next();
924
+        }
925
+        return $size;
926
+    }
927
+
928
+    /**
929
+     * get current size of trash bin from a given user
930
+     *
931
+     * @param string $user user who owns the trash bin
932
+     * @return integer trash bin size
933
+     */
934
+    private static function getTrashbinSize($user) {
935
+        $view = new View('/' . $user);
936
+        $fileInfo = $view->getFileInfo('/files_trashbin');
937
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
938
+    }
939
+
940
+    /**
941
+     * register hooks
942
+     */
943
+    public static function registerHooks() {
944
+        // create storage wrapper on setup
945
+        \OCP\Util::connectHook('OC_Filesystem', 'preSetup', 'OCA\Files_Trashbin\Storage', 'setupStorage');
946
+        //Listen to delete user signal
947
+        \OCP\Util::connectHook('OC_User', 'pre_deleteUser', 'OCA\Files_Trashbin\Hooks', 'deleteUser_hook');
948
+        //Listen to post write hook
949
+        \OCP\Util::connectHook('OC_Filesystem', 'post_write', 'OCA\Files_Trashbin\Hooks', 'post_write_hook');
950
+        // pre and post-rename, disable trash logic for the copy+unlink case
951
+        \OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Trashbin\Trashbin', 'ensureFileScannedHook');
952
+        \OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Trashbin\Storage', 'preRenameHook');
953
+        \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Files_Trashbin\Storage', 'postRenameHook');
954
+    }
955
+
956
+    /**
957
+     * check if trash bin is empty for a given user
958
+     *
959
+     * @param string $user
960
+     * @return bool
961
+     */
962
+    public static function isEmpty($user) {
963
+        $view = new View('/' . $user . '/files_trashbin');
964
+        if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
965
+            while ($file = readdir($dh)) {
966
+                if (!Filesystem::isIgnoredDir($file)) {
967
+                    return false;
968
+                }
969
+            }
970
+        }
971
+        return true;
972
+    }
973
+
974
+    /**
975
+     * @param $path
976
+     * @return string
977
+     */
978
+    public static function preview_icon($path) {
979
+        return \OCP\Util::linkToRoute('core_ajax_trashbin_preview', ['x' => 32, 'y' => 32, 'file' => $path]);
980
+    }
981 981
 }
Please login to merge, or discard this patch.
Spacing   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -95,7 +95,7 @@  discard block
 block discarded – undo
95 95
 		Filesystem::initMountPoints($uid);
96 96
 		if ($uid != User::getUser()) {
97 97
 			$info = Filesystem::getFileInfo($filename);
98
-			$ownerView = new View('/' . $uid . '/files');
98
+			$ownerView = new View('/'.$uid.'/files');
99 99
 			try {
100 100
 				$filename = $ownerView->getPath($info['fileid']);
101 101
 			} catch (NotFoundException $e) {
@@ -146,7 +146,7 @@  discard block
 block discarded – undo
146 146
 	}
147 147
 
148 148
 	private static function setUpTrash($user) {
149
-		$view = new View('/' . $user);
149
+		$view = new View('/'.$user);
150 150
 		if (!$view->is_dir('files_trashbin')) {
151 151
 			$view->mkdir('files_trashbin');
152 152
 		}
@@ -181,8 +181,8 @@  discard block
 block discarded – undo
181 181
 
182 182
 		$view = new View('/');
183 183
 
184
-		$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
185
-		$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
184
+		$target = $user.'/files_trashbin/files/'.$targetFilename.'.d'.$timestamp;
185
+		$source = $owner.'/files_trashbin/files/'.$sourceFilename.'.d'.$timestamp;
186 186
 		self::copy_recursive($source, $target, $view);
187 187
 
188 188
 
@@ -216,9 +216,9 @@  discard block
 block discarded – undo
216 216
 			$ownerPath = $file_path;
217 217
 		}
218 218
 
219
-		$ownerView = new View('/' . $owner);
219
+		$ownerView = new View('/'.$owner);
220 220
 		// file has been deleted in between
221
-		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
221
+		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/'.$ownerPath)) {
222 222
 			return true;
223 223
 		}
224 224
 
@@ -235,12 +235,12 @@  discard block
 block discarded – undo
235 235
 		$timestamp = time();
236 236
 
237 237
 		// disable proxy to prevent recursive calls
238
-		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
238
+		$trashPath = '/files_trashbin/files/'.$filename.'.d'.$timestamp;
239 239
 
240 240
 		/** @var \OC\Files\Storage\Storage $trashStorage */
241 241
 		list($trashStorage, $trashInternalPath) = $ownerView->resolvePath($trashPath);
242 242
 		/** @var \OC\Files\Storage\Storage $sourceStorage */
243
-		list($sourceStorage, $sourceInternalPath) = $ownerView->resolvePath('/files/' . $ownerPath);
243
+		list($sourceStorage, $sourceInternalPath) = $ownerView->resolvePath('/files/'.$ownerPath);
244 244
 		try {
245 245
 			$moveSuccessful = true;
246 246
 			if ($trashStorage->file_exists($trashInternalPath)) {
@@ -252,7 +252,7 @@  discard block
 block discarded – undo
252 252
 			if ($trashStorage->file_exists($trashInternalPath)) {
253 253
 				$trashStorage->unlink($trashInternalPath);
254 254
 			}
255
-			\OCP\Util::writeLog('files_trashbin', 'Couldn\'t move ' . $file_path . ' to the trash bin', \OCP\Util::ERROR);
255
+			\OCP\Util::writeLog('files_trashbin', 'Couldn\'t move '.$file_path.' to the trash bin', \OCP\Util::ERROR);
256 256
 		}
257 257
 
258 258
 		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
@@ -273,7 +273,7 @@  discard block
 block discarded – undo
273 273
 				\OCP\Util::writeLog('files_trashbin', 'trash bin database couldn\'t be updated', \OCP\Util::ERROR);
274 274
 			}
275 275
 			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
276
-				'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)]);
276
+				'trashPath' => Filesystem::normalizePath($filename.'.d'.$timestamp)]);
277 277
 
278 278
 			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
279 279
 
@@ -306,17 +306,17 @@  discard block
 block discarded – undo
306 306
 			$user = User::getUser();
307 307
 			$rootView = new View('/');
308 308
 
309
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
309
+			if ($rootView->is_dir($owner.'/files_versions/'.$ownerPath)) {
310 310
 				if ($owner !== $user) {
311
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
311
+					self::copy_recursive($owner.'/files_versions/'.$ownerPath, $owner.'/files_trashbin/versions/'.basename($ownerPath).'.d'.$timestamp, $rootView);
312 312
 				}
313
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
313
+				self::move($rootView, $owner.'/files_versions/'.$ownerPath, $user.'/files_trashbin/versions/'.$filename.'.d'.$timestamp);
314 314
 			} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
315 315
 				foreach ($versions as $v) {
316 316
 					if ($owner !== $user) {
317
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
317
+						self::copy($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $owner.'/files_trashbin/versions/'.$v['name'].'.v'.$v['version'].'.d'.$timestamp);
318 318
 					}
319
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
319
+					self::move($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $user.'/files_trashbin/versions/'.$filename.'.v'.$v['version'].'.d'.$timestamp);
320 320
 				}
321 321
 			}
322 322
 		}
@@ -378,18 +378,18 @@  discard block
 block discarded – undo
378 378
 	 */
379 379
 	public static function restore($file, $filename, $timestamp) {
380 380
 		$user = User::getUser();
381
-		$view = new View('/' . $user);
381
+		$view = new View('/'.$user);
382 382
 
383 383
 		$location = '';
384 384
 		if ($timestamp) {
385 385
 			$location = self::getLocation($user, $filename, $timestamp);
386 386
 			if ($location === false) {
387
-				\OCP\Util::writeLog('files_trashbin', 'trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', \OCP\Util::ERROR);
387
+				\OCP\Util::writeLog('files_trashbin', 'trash bin database inconsistent! ($user: '.$user.' $filename: '.$filename.', $timestamp: '.$timestamp.')', \OCP\Util::ERROR);
388 388
 			} else {
389 389
 				// if location no longer exists, restore file in the root directory
390 390
 				if ($location !== '/' &&
391
-					(!$view->is_dir('files/' . $location) ||
392
-						!$view->isCreatable('files/' . $location))
391
+					(!$view->is_dir('files/'.$location) ||
392
+						!$view->isCreatable('files/'.$location))
393 393
 				) {
394 394
 					$location = '';
395 395
 				}
@@ -399,8 +399,8 @@  discard block
 block discarded – undo
399 399
 		// we need a  extension in case a file/dir with the same name already exists
400 400
 		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
401 401
 
402
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
403
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
402
+		$source = Filesystem::normalizePath('files_trashbin/files/'.$file);
403
+		$target = Filesystem::normalizePath('files/'.$location.'/'.$uniqueFilename);
404 404
 		if (!$view->file_exists($source)) {
405 405
 			return false;
406 406
 		}
@@ -412,10 +412,10 @@  discard block
 block discarded – undo
412 412
 		// handle the restore result
413 413
 		if ($restoreResult) {
414 414
 			$fakeRoot = $view->getRoot();
415
-			$view->chroot('/' . $user . '/files');
416
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
415
+			$view->chroot('/'.$user.'/files');
416
+			$view->touch('/'.$location.'/'.$uniqueFilename, $mtime);
417 417
 			$view->chroot($fakeRoot);
418
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
418
+			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename),
419 419
 				'trashPath' => Filesystem::normalizePath($file)]);
420 420
 
421 421
 			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
@@ -447,7 +447,7 @@  discard block
 block discarded – undo
447 447
 			$user = User::getUser();
448 448
 			$rootView = new View('/');
449 449
 
450
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
450
+			$target = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename);
451 451
 
452 452
 			list($owner, $ownerPath) = self::getUidAndFilename($target);
453 453
 
@@ -462,14 +462,14 @@  discard block
 block discarded – undo
462 462
 				$versionedFile = $file;
463 463
 			}
464 464
 
465
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
466
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
465
+			if ($view->is_dir('/files_trashbin/versions/'.$file)) {
466
+				$rootView->rename(Filesystem::normalizePath($user.'/files_trashbin/versions/'.$file), Filesystem::normalizePath($owner.'/files_versions/'.$ownerPath));
467 467
 			} elseif ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
468 468
 				foreach ($versions as $v) {
469 469
 					if ($timestamp) {
470
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
470
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v.'.d'.$timestamp, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
471 471
 					} else {
472
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
472
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
473 473
 					}
474 474
 				}
475 475
 			}
@@ -481,7 +481,7 @@  discard block
 block discarded – undo
481 481
 	 */
482 482
 	public static function deleteAll() {
483 483
 		$user = User::getUser();
484
-		$view = new View('/' . $user);
484
+		$view = new View('/'.$user);
485 485
 		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
486 486
 
487 487
 		// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
@@ -544,27 +544,27 @@  discard block
 block discarded – undo
544 544
 	 * @return int size of deleted files
545 545
 	 */
546 546
 	public static function delete($filename, $user, $timestamp = null) {
547
-		$view = new View('/' . $user);
547
+		$view = new View('/'.$user);
548 548
 		$size = 0;
549 549
 
550 550
 		if ($timestamp) {
551 551
 			$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
552 552
 			$query->execute([$user, $filename, $timestamp]);
553
-			$file = $filename . '.d' . $timestamp;
553
+			$file = $filename.'.d'.$timestamp;
554 554
 		} else {
555 555
 			$file = $filename;
556 556
 		}
557 557
 
558 558
 		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
559 559
 
560
-		if ($view->is_dir('/files_trashbin/files/' . $file)) {
561
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
560
+		if ($view->is_dir('/files_trashbin/files/'.$file)) {
561
+			$size += self::calculateSize(new View('/'.$user.'/files_trashbin/files/'.$file));
562 562
 		} else {
563
-			$size += $view->filesize('/files_trashbin/files/' . $file);
563
+			$size += $view->filesize('/files_trashbin/files/'.$file);
564 564
 		}
565
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
566
-		$view->unlink('/files_trashbin/files/' . $file);
567
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
565
+		self::emitTrashbinPreDelete('/files_trashbin/files/'.$file);
566
+		$view->unlink('/files_trashbin/files/'.$file);
567
+		self::emitTrashbinPostDelete('/files_trashbin/files/'.$file);
568 568
 
569 569
 		return $size;
570 570
 	}
@@ -580,17 +580,17 @@  discard block
 block discarded – undo
580 580
 	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
581 581
 		$size = 0;
582 582
 		if (\OCP\App::isEnabled('files_versions')) {
583
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
584
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
585
-				$view->unlink('files_trashbin/versions/' . $file);
583
+			if ($view->is_dir('files_trashbin/versions/'.$file)) {
584
+				$size += self::calculateSize(new View('/'.$user.'/files_trashbin/versions/'.$file));
585
+				$view->unlink('files_trashbin/versions/'.$file);
586 586
 			} elseif ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
587 587
 				foreach ($versions as $v) {
588 588
 					if ($timestamp) {
589
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
590
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
589
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp);
590
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp);
591 591
 					} else {
592
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
593
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
592
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v);
593
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v);
594 594
 					}
595 595
 				}
596 596
 			}
@@ -607,15 +607,15 @@  discard block
 block discarded – undo
607 607
 	 */
608 608
 	public static function file_exists($filename, $timestamp = null) {
609 609
 		$user = User::getUser();
610
-		$view = new View('/' . $user);
610
+		$view = new View('/'.$user);
611 611
 
612 612
 		if ($timestamp) {
613
-			$filename = $filename . '.d' . $timestamp;
613
+			$filename = $filename.'.d'.$timestamp;
614 614
 		} else {
615 615
 			$filename = $filename;
616 616
 		}
617 617
 
618
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
618
+		$target = Filesystem::normalizePath('files_trashbin/files/'.$filename);
619 619
 		return $view->file_exists($target);
620 620
 	}
621 621
 
@@ -740,7 +740,7 @@  discard block
 block discarded – undo
740 740
 			foreach ($files as $file) {
741 741
 				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
742 742
 					$tmp = self::delete($file['name'], $user, $file['mtime']);
743
-					\OCP\Util::writeLog('files_trashbin', 'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', \OCP\Util::INFO);
743
+					\OCP\Util::writeLog('files_trashbin', 'remove "'.$file['name'].'" ('.$tmp.'B) to meet the limit of trash bin size (50% of available quota)', \OCP\Util::INFO);
744 744
 					$availableSpace += $tmp;
745 745
 					$size += $tmp;
746 746
 				} else {
@@ -770,7 +770,7 @@  discard block
 block discarded – undo
770 770
 				$count++;
771 771
 				$size += self::delete($filename, $user, $timestamp);
772 772
 				\OC::$server->getLogger()->info(
773
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
773
+					'Remove "'.$filename.'" from trashbin because it exceeds max retention obligation term.',
774 774
 					['app' => 'files_trashbin']
775 775
 				);
776 776
 			} else {
@@ -796,16 +796,16 @@  discard block
 block discarded – undo
796 796
 			$view->mkdir($destination);
797 797
 			$view->touch($destination, $view->filemtime($source));
798 798
 			foreach ($view->getDirectoryContent($source) as $i) {
799
-				$pathDir = $source . '/' . $i['name'];
799
+				$pathDir = $source.'/'.$i['name'];
800 800
 				if ($view->is_dir($pathDir)) {
801
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
801
+					$size += self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view);
802 802
 				} else {
803 803
 					$size += $view->filesize($pathDir);
804
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
804
+					$result = $view->copy($pathDir, $destination.'/'.$i['name']);
805 805
 					if (!$result) {
806 806
 						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
807 807
 					}
808
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
808
+					$view->touch($destination.'/'.$i['name'], $view->filemtime($pathDir));
809 809
 				}
810 810
 			}
811 811
 		} else {
@@ -827,23 +827,23 @@  discard block
 block discarded – undo
827 827
 	 * @return array
828 828
 	 */
829 829
 	private static function getVersionsFromTrash($filename, $timestamp, $user) {
830
-		$view = new View('/' . $user . '/files_trashbin/versions');
830
+		$view = new View('/'.$user.'/files_trashbin/versions');
831 831
 		$versions = [];
832 832
 
833 833
 		//force rescan of versions, local storage may not have updated the cache
834 834
 		if (!self::$scannedVersions) {
835 835
 			/** @var \OC\Files\Storage\Storage $storage */
836
-			list($storage, ) = $view->resolvePath('/');
836
+			list($storage,) = $view->resolvePath('/');
837 837
 			$storage->getScanner()->scan('files_trashbin/versions');
838 838
 			self::$scannedVersions = true;
839 839
 		}
840 840
 
841 841
 		if ($timestamp) {
842 842
 			// fetch for old versions
843
-			$matches = $view->searchRaw($filename . '.v%.d' . $timestamp);
843
+			$matches = $view->searchRaw($filename.'.v%.d'.$timestamp);
844 844
 			$offset = -strlen($timestamp) - 2;
845 845
 		} else {
846
-			$matches = $view->searchRaw($filename . '.v%');
846
+			$matches = $view->searchRaw($filename.'.v%');
847 847
 		}
848 848
 
849 849
 		if (is_array($matches)) {
@@ -873,18 +873,18 @@  discard block
 block discarded – undo
873 873
 		$name = pathinfo($filename, PATHINFO_FILENAME);
874 874
 		$l = \OC::$server->getL10N('files_trashbin');
875 875
 
876
-		$location = '/' . trim($location, '/');
876
+		$location = '/'.trim($location, '/');
877 877
 
878 878
 		// if extension is not empty we set a dot in front of it
879 879
 		if ($ext !== '') {
880
-			$ext = '.' . $ext;
880
+			$ext = '.'.$ext;
881 881
 		}
882 882
 
883
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
883
+		if ($view->file_exists('files'.$location.'/'.$filename)) {
884 884
 			$i = 2;
885
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
886
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
887
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
885
+			$uniqueName = $name." (".$l->t("restored").")".$ext;
886
+			while ($view->file_exists('files'.$location.'/'.$uniqueName)) {
887
+				$uniqueName = $name." (".$l->t("restored")." ".$i.")".$ext;
888 888
 				$i++;
889 889
 			}
890 890
 
@@ -901,7 +901,7 @@  discard block
 block discarded – undo
901 901
 	 * @return integer size of the folder
902 902
 	 */
903 903
 	private static function calculateSize($view) {
904
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
904
+		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').$view->getAbsolutePath('');
905 905
 		if (!file_exists($root)) {
906 906
 			return 0;
907 907
 		}
@@ -932,7 +932,7 @@  discard block
 block discarded – undo
932 932
 	 * @return integer trash bin size
933 933
 	 */
934 934
 	private static function getTrashbinSize($user) {
935
-		$view = new View('/' . $user);
935
+		$view = new View('/'.$user);
936 936
 		$fileInfo = $view->getFileInfo('/files_trashbin');
937 937
 		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
938 938
 	}
@@ -960,7 +960,7 @@  discard block
 block discarded – undo
960 960
 	 * @return bool
961 961
 	 */
962 962
 	public static function isEmpty($user) {
963
-		$view = new View('/' . $user . '/files_trashbin');
963
+		$view = new View('/'.$user.'/files_trashbin');
964 964
 		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
965 965
 			while ($file = readdir($dh)) {
966 966
 				if (!Filesystem::isIgnoredDir($file)) {
Please login to merge, or discard this patch.
apps/updatenotification/lib/Notification/Notifier.php 3 patches
Doc Comments   +3 added lines patch added patch discarded remove patch
@@ -102,6 +102,9 @@
 block discarded – undo
102 102
 		return \OC_App::getAppVersions();
103 103
 	}
104 104
 
105
+	/**
106
+	 * @param string $appId
107
+	 */
105 108
 	protected function getAppInfo($appId) {
106 109
 		return \OC_App::getAppInfo($appId);
107 110
 	}
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -91,7 +91,7 @@  discard block
 block discarded – undo
91 91
 			$notification->setParsedSubject($l->t('Update to %1$s is available.', [$parameters['version']]));
92 92
 
93 93
 			if ($this->isAdmin()) {
94
-				$notification->setLink($this->url->linkToRouteAbsolute('settings.AdminSettings.index') . '#updater');
94
+				$notification->setLink($this->url->linkToRouteAbsolute('settings.AdminSettings.index').'#updater');
95 95
 			}
96 96
 		} else {
97 97
 			$appInfo = $this->getAppInfo($notification->getObjectType());
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
 				]);
112 112
 
113 113
 			if ($this->isAdmin()) {
114
-				$notification->setLink($this->url->linkToRouteAbsolute('settings.AppSettings.viewApps') . '#app-' . $notification->getObjectType());
114
+				$notification->setLink($this->url->linkToRouteAbsolute('settings.AppSettings.viewApps').'#app-'.$notification->getObjectType());
115 115
 			}
116 116
 		}
117 117
 
Please login to merge, or discard this patch.
Indentation   +137 added lines, -137 removed lines patch added patch discarded remove patch
@@ -36,141 +36,141 @@
 block discarded – undo
36 36
 
37 37
 class Notifier implements INotifier {
38 38
 
39
-	/** @var IURLGenerator */
40
-	protected $url;
41
-
42
-	/** @var IConfig */
43
-	protected $config;
44
-
45
-	/** @var IManager */
46
-	protected $notificationManager;
47
-
48
-	/** @var IFactory */
49
-	protected $l10NFactory;
50
-
51
-	/** @var IUserSession */
52
-	protected $userSession;
53
-
54
-	/** @var IGroupManager */
55
-	protected $groupManager;
56
-
57
-	/** @var string[] */
58
-	protected $appVersions;
59
-
60
-	/**
61
-	 * Notifier constructor.
62
-	 *
63
-	 * @param IURLGenerator $url
64
-	 * @param IConfig $config
65
-	 * @param IManager $notificationManager
66
-	 * @param IFactory $l10NFactory
67
-	 * @param IUserSession $userSession
68
-	 * @param IGroupManager $groupManager
69
-	 */
70
-	public function __construct(IURLGenerator $url, IConfig $config, IManager $notificationManager, IFactory $l10NFactory, IUserSession $userSession, IGroupManager $groupManager) {
71
-		$this->url = $url;
72
-		$this->notificationManager = $notificationManager;
73
-		$this->config = $config;
74
-		$this->l10NFactory = $l10NFactory;
75
-		$this->userSession = $userSession;
76
-		$this->groupManager = $groupManager;
77
-		$this->appVersions = $this->getAppVersions();
78
-	}
79
-
80
-	/**
81
-	 * @param INotification $notification
82
-	 * @param string $languageCode The code of the language that should be used to prepare the notification
83
-	 * @return INotification
84
-	 * @throws \InvalidArgumentException When the notification was not prepared by a notifier
85
-	 * @since 9.0.0
86
-	 */
87
-	public function prepare(INotification $notification, $languageCode) {
88
-		if ($notification->getApp() !== 'updatenotification') {
89
-			throw new \InvalidArgumentException();
90
-		}
91
-
92
-		$l = $this->l10NFactory->get('updatenotification', $languageCode);
93
-		if ($notification->getSubject() === 'connection_error') {
94
-			$errors = (int) $this->config->getAppValue('updatenotification', 'update_check_errors', 0);
95
-			if ($errors === 0) {
96
-				$this->notificationManager->markProcessed($notification);
97
-				throw new \InvalidArgumentException();
98
-			}
99
-
100
-			$notification->setParsedSubject($l->t('The update server could not be reached since %d days to check for new updates.', [$errors]))
101
-				->setParsedMessage($l->t('Please check the Nextcloud and server log files for errors.'));
102
-		} elseif ($notification->getObjectType() === 'core') {
103
-			$this->updateAlreadyInstalledCheck($notification, $this->getCoreVersions());
104
-
105
-			$parameters = $notification->getSubjectParameters();
106
-			$notification->setParsedSubject($l->t('Update to %1$s is available.', [$parameters['version']]));
107
-
108
-			if ($this->isAdmin()) {
109
-				$notification->setLink($this->url->linkToRouteAbsolute('settings.AdminSettings.index') . '#updater');
110
-			}
111
-		} else {
112
-			$appInfo = $this->getAppInfo($notification->getObjectType());
113
-			$appName = ($appInfo === null) ? $notification->getObjectType() : $appInfo['name'];
114
-
115
-			if (isset($this->appVersions[$notification->getObjectType()])) {
116
-				$this->updateAlreadyInstalledCheck($notification, $this->appVersions[$notification->getObjectType()]);
117
-			}
118
-
119
-			$notification->setParsedSubject($l->t('Update for %1$s to version %2$s is available.', [$appName, $notification->getObjectId()]))
120
-				->setRichSubject($l->t('Update for {app} to version %s is available.', $notification->getObjectId()), [
121
-					'app' => [
122
-						'type' => 'app',
123
-						'id' => $notification->getObjectType(),
124
-						'name' => $appName,
125
-					]
126
-				]);
127
-
128
-			if ($this->isAdmin()) {
129
-				$notification->setLink($this->url->linkToRouteAbsolute('settings.AppSettings.viewApps') . '#app-' . $notification->getObjectType());
130
-			}
131
-		}
132
-
133
-		$notification->setIcon($this->url->getAbsoluteURL($this->url->imagePath('updatenotification', 'notification.svg')));
134
-
135
-		return $notification;
136
-	}
137
-
138
-	/**
139
-	 * Remove the notification and prevent rendering, when the update is installed
140
-	 *
141
-	 * @param INotification $notification
142
-	 * @param string $installedVersion
143
-	 * @throws \InvalidArgumentException When the update is already installed
144
-	 */
145
-	protected function updateAlreadyInstalledCheck(INotification $notification, $installedVersion) {
146
-		if (version_compare($notification->getObjectId(), $installedVersion, '<=')) {
147
-			$this->notificationManager->markProcessed($notification);
148
-			throw new \InvalidArgumentException();
149
-		}
150
-	}
151
-
152
-	/**
153
-	 * @return bool
154
-	 */
155
-	protected function isAdmin() {
156
-		$user = $this->userSession->getUser();
157
-
158
-		if ($user instanceof IUser) {
159
-			return $this->groupManager->isAdmin($user->getUID());
160
-		}
161
-
162
-		return false;
163
-	}
164
-
165
-	protected function getCoreVersions() {
166
-		return implode('.', \OCP\Util::getVersion());
167
-	}
168
-
169
-	protected function getAppVersions() {
170
-		return \OC_App::getAppVersions();
171
-	}
172
-
173
-	protected function getAppInfo($appId) {
174
-		return \OC_App::getAppInfo($appId);
175
-	}
39
+    /** @var IURLGenerator */
40
+    protected $url;
41
+
42
+    /** @var IConfig */
43
+    protected $config;
44
+
45
+    /** @var IManager */
46
+    protected $notificationManager;
47
+
48
+    /** @var IFactory */
49
+    protected $l10NFactory;
50
+
51
+    /** @var IUserSession */
52
+    protected $userSession;
53
+
54
+    /** @var IGroupManager */
55
+    protected $groupManager;
56
+
57
+    /** @var string[] */
58
+    protected $appVersions;
59
+
60
+    /**
61
+     * Notifier constructor.
62
+     *
63
+     * @param IURLGenerator $url
64
+     * @param IConfig $config
65
+     * @param IManager $notificationManager
66
+     * @param IFactory $l10NFactory
67
+     * @param IUserSession $userSession
68
+     * @param IGroupManager $groupManager
69
+     */
70
+    public function __construct(IURLGenerator $url, IConfig $config, IManager $notificationManager, IFactory $l10NFactory, IUserSession $userSession, IGroupManager $groupManager) {
71
+        $this->url = $url;
72
+        $this->notificationManager = $notificationManager;
73
+        $this->config = $config;
74
+        $this->l10NFactory = $l10NFactory;
75
+        $this->userSession = $userSession;
76
+        $this->groupManager = $groupManager;
77
+        $this->appVersions = $this->getAppVersions();
78
+    }
79
+
80
+    /**
81
+     * @param INotification $notification
82
+     * @param string $languageCode The code of the language that should be used to prepare the notification
83
+     * @return INotification
84
+     * @throws \InvalidArgumentException When the notification was not prepared by a notifier
85
+     * @since 9.0.0
86
+     */
87
+    public function prepare(INotification $notification, $languageCode) {
88
+        if ($notification->getApp() !== 'updatenotification') {
89
+            throw new \InvalidArgumentException();
90
+        }
91
+
92
+        $l = $this->l10NFactory->get('updatenotification', $languageCode);
93
+        if ($notification->getSubject() === 'connection_error') {
94
+            $errors = (int) $this->config->getAppValue('updatenotification', 'update_check_errors', 0);
95
+            if ($errors === 0) {
96
+                $this->notificationManager->markProcessed($notification);
97
+                throw new \InvalidArgumentException();
98
+            }
99
+
100
+            $notification->setParsedSubject($l->t('The update server could not be reached since %d days to check for new updates.', [$errors]))
101
+                ->setParsedMessage($l->t('Please check the Nextcloud and server log files for errors.'));
102
+        } elseif ($notification->getObjectType() === 'core') {
103
+            $this->updateAlreadyInstalledCheck($notification, $this->getCoreVersions());
104
+
105
+            $parameters = $notification->getSubjectParameters();
106
+            $notification->setParsedSubject($l->t('Update to %1$s is available.', [$parameters['version']]));
107
+
108
+            if ($this->isAdmin()) {
109
+                $notification->setLink($this->url->linkToRouteAbsolute('settings.AdminSettings.index') . '#updater');
110
+            }
111
+        } else {
112
+            $appInfo = $this->getAppInfo($notification->getObjectType());
113
+            $appName = ($appInfo === null) ? $notification->getObjectType() : $appInfo['name'];
114
+
115
+            if (isset($this->appVersions[$notification->getObjectType()])) {
116
+                $this->updateAlreadyInstalledCheck($notification, $this->appVersions[$notification->getObjectType()]);
117
+            }
118
+
119
+            $notification->setParsedSubject($l->t('Update for %1$s to version %2$s is available.', [$appName, $notification->getObjectId()]))
120
+                ->setRichSubject($l->t('Update for {app} to version %s is available.', $notification->getObjectId()), [
121
+                    'app' => [
122
+                        'type' => 'app',
123
+                        'id' => $notification->getObjectType(),
124
+                        'name' => $appName,
125
+                    ]
126
+                ]);
127
+
128
+            if ($this->isAdmin()) {
129
+                $notification->setLink($this->url->linkToRouteAbsolute('settings.AppSettings.viewApps') . '#app-' . $notification->getObjectType());
130
+            }
131
+        }
132
+
133
+        $notification->setIcon($this->url->getAbsoluteURL($this->url->imagePath('updatenotification', 'notification.svg')));
134
+
135
+        return $notification;
136
+    }
137
+
138
+    /**
139
+     * Remove the notification and prevent rendering, when the update is installed
140
+     *
141
+     * @param INotification $notification
142
+     * @param string $installedVersion
143
+     * @throws \InvalidArgumentException When the update is already installed
144
+     */
145
+    protected function updateAlreadyInstalledCheck(INotification $notification, $installedVersion) {
146
+        if (version_compare($notification->getObjectId(), $installedVersion, '<=')) {
147
+            $this->notificationManager->markProcessed($notification);
148
+            throw new \InvalidArgumentException();
149
+        }
150
+    }
151
+
152
+    /**
153
+     * @return bool
154
+     */
155
+    protected function isAdmin() {
156
+        $user = $this->userSession->getUser();
157
+
158
+        if ($user instanceof IUser) {
159
+            return $this->groupManager->isAdmin($user->getUID());
160
+        }
161
+
162
+        return false;
163
+    }
164
+
165
+    protected function getCoreVersions() {
166
+        return implode('.', \OCP\Util::getVersion());
167
+    }
168
+
169
+    protected function getAppVersions() {
170
+        return \OC_App::getAppVersions();
171
+    }
172
+
173
+    protected function getAppInfo($appId) {
174
+        return \OC_App::getAppInfo($appId);
175
+    }
176 176
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Command/SetConfig.php 2 patches
Doc Comments   -2 removed lines patch added patch discarded remove patch
@@ -74,8 +74,6 @@
 block discarded – undo
74 74
 	/**
75 75
 	 * save the configuration value as provided
76 76
 	 * @param string $configID
77
-	 * @param string $configKey
78
-	 * @param string $configValue
79 77
 	 */
80 78
 	protected function setValue($configID, $key, $value) {
81 79
 		$configHolder = new Configuration($configID);
Please login to merge, or discard this patch.
Indentation   +46 added lines, -46 removed lines patch added patch discarded remove patch
@@ -33,53 +33,53 @@
 block discarded – undo
33 33
 use OCA\User_LDAP\Configuration;
34 34
 
35 35
 class SetConfig extends Command {
36
-	protected function configure() {
37
-		$this
38
-			->setName('ldap:set-config')
39
-			->setDescription('modifies an LDAP configuration')
40
-			->addArgument(
41
-					'configID',
42
-					InputArgument::REQUIRED,
43
-					'the configuration ID'
44
-					 )
45
-			->addArgument(
46
-					'configKey',
47
-					InputArgument::REQUIRED,
48
-					'the configuration key'
49
-					 )
50
-			->addArgument(
51
-					'configValue',
52
-					InputArgument::REQUIRED,
53
-					'the new configuration value'
54
-					 )
55
-		;
56
-	}
36
+    protected function configure() {
37
+        $this
38
+            ->setName('ldap:set-config')
39
+            ->setDescription('modifies an LDAP configuration')
40
+            ->addArgument(
41
+                    'configID',
42
+                    InputArgument::REQUIRED,
43
+                    'the configuration ID'
44
+                        )
45
+            ->addArgument(
46
+                    'configKey',
47
+                    InputArgument::REQUIRED,
48
+                    'the configuration key'
49
+                        )
50
+            ->addArgument(
51
+                    'configValue',
52
+                    InputArgument::REQUIRED,
53
+                    'the new configuration value'
54
+                        )
55
+        ;
56
+    }
57 57
 
58
-	protected function execute(InputInterface $input, OutputInterface $output) {
59
-		$helper = new Helper(\OC::$server->getConfig());
60
-		$availableConfigs = $helper->getServerConfigurationPrefixes();
61
-		$configID = $input->getArgument('configID');
62
-		if (!in_array($configID, $availableConfigs)) {
63
-			$output->writeln("Invalid configID");
64
-			return;
65
-		}
58
+    protected function execute(InputInterface $input, OutputInterface $output) {
59
+        $helper = new Helper(\OC::$server->getConfig());
60
+        $availableConfigs = $helper->getServerConfigurationPrefixes();
61
+        $configID = $input->getArgument('configID');
62
+        if (!in_array($configID, $availableConfigs)) {
63
+            $output->writeln("Invalid configID");
64
+            return;
65
+        }
66 66
 
67
-		$this->setValue(
68
-			$configID,
69
-			$input->getArgument('configKey'),
70
-			$input->getArgument('configValue')
71
-		);
72
-	}
67
+        $this->setValue(
68
+            $configID,
69
+            $input->getArgument('configKey'),
70
+            $input->getArgument('configValue')
71
+        );
72
+    }
73 73
 
74
-	/**
75
-	 * save the configuration value as provided
76
-	 * @param string $configID
77
-	 * @param string $configKey
78
-	 * @param string $configValue
79
-	 */
80
-	protected function setValue($configID, $key, $value) {
81
-		$configHolder = new Configuration($configID);
82
-		$configHolder->$key = $value;
83
-		$configHolder->saveConfiguration();
84
-	}
74
+    /**
75
+     * save the configuration value as provided
76
+     * @param string $configID
77
+     * @param string $configKey
78
+     * @param string $configValue
79
+     */
80
+    protected function setValue($configID, $key, $value) {
81
+        $configHolder = new Configuration($configID);
82
+        $configHolder->$key = $value;
83
+        $configHolder->saveConfiguration();
84
+    }
85 85
 }
Please login to merge, or discard this patch.
lib/private/AllConfig.php 3 patches
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -112,7 +112,7 @@
 block discarded – undo
112 112
 	 * Looks up a system wide defined value
113 113
 	 *
114 114
 	 * @param string $key the key of the value, under which it was saved
115
-	 * @param mixed $default the default value to be returned if the value isn't set
115
+	 * @param string $default the default value to be returned if the value isn't set
116 116
 	 * @return mixed the value or $default
117 117
 	 */
118 118
 	public function getSystemValue($key, $default = '') {
Please login to merge, or discard this patch.
Indentation   +423 added lines, -423 removed lines patch added patch discarded remove patch
@@ -38,427 +38,427 @@
 block discarded – undo
38 38
  * Class to combine all the configuration options ownCloud offers
39 39
  */
40 40
 class AllConfig implements \OCP\IConfig {
41
-	/** @var SystemConfig */
42
-	private $systemConfig;
43
-
44
-	/** @var IDBConnection */
45
-	private $connection;
46
-
47
-	/**
48
-	 * 3 dimensional array with the following structure:
49
-	 * [ $userId =>
50
-	 *     [ $appId =>
51
-	 *         [ $key => $value ]
52
-	 *     ]
53
-	 * ]
54
-	 *
55
-	 * database table: preferences
56
-	 *
57
-	 * methods that use this:
58
-	 *   - setUserValue
59
-	 *   - getUserValue
60
-	 *   - getUserKeys
61
-	 *   - deleteUserValue
62
-	 *   - deleteAllUserValues
63
-	 *   - deleteAppFromAllUsers
64
-	 *
65
-	 * @var CappedMemoryCache $userCache
66
-	 */
67
-	private $userCache;
68
-
69
-	/**
70
-	 * @param SystemConfig $systemConfig
71
-	 */
72
-	public function __construct(SystemConfig $systemConfig) {
73
-		$this->userCache = new CappedMemoryCache();
74
-		$this->systemConfig = $systemConfig;
75
-	}
76
-
77
-	/**
78
-	 * TODO - FIXME This fixes an issue with base.php that cause cyclic
79
-	 * dependencies, especially with autoconfig setup
80
-	 *
81
-	 * Replace this by properly injected database connection. Currently the
82
-	 * base.php triggers the getDatabaseConnection too early which causes in
83
-	 * autoconfig setup case a too early distributed database connection and
84
-	 * the autoconfig then needs to reinit all already initialized dependencies
85
-	 * that use the database connection.
86
-	 *
87
-	 * otherwise a SQLite database is created in the wrong directory
88
-	 * because the database connection was created with an uninitialized config
89
-	 */
90
-	private function fixDIInit() {
91
-		if ($this->connection === null) {
92
-			$this->connection = \OC::$server->getDatabaseConnection();
93
-		}
94
-	}
95
-
96
-	/**
97
-	 * Sets and deletes system wide values
98
-	 *
99
-	 * @param array $configs Associative array with `key => value` pairs
100
-	 *                       If value is null, the config key will be deleted
101
-	 */
102
-	public function setSystemValues(array $configs) {
103
-		$this->systemConfig->setValues($configs);
104
-	}
105
-
106
-	/**
107
-	 * Sets a new system wide value
108
-	 *
109
-	 * @param string $key the key of the value, under which will be saved
110
-	 * @param mixed $value the value that should be stored
111
-	 */
112
-	public function setSystemValue($key, $value) {
113
-		$this->systemConfig->setValue($key, $value);
114
-	}
115
-
116
-	/**
117
-	 * Looks up a system wide defined value
118
-	 *
119
-	 * @param string $key the key of the value, under which it was saved
120
-	 * @param mixed $default the default value to be returned if the value isn't set
121
-	 * @return mixed the value or $default
122
-	 */
123
-	public function getSystemValue($key, $default = '') {
124
-		return $this->systemConfig->getValue($key, $default);
125
-	}
126
-
127
-	/**
128
-	 * Looks up a system wide defined value and filters out sensitive data
129
-	 *
130
-	 * @param string $key the key of the value, under which it was saved
131
-	 * @param mixed $default the default value to be returned if the value isn't set
132
-	 * @return mixed the value or $default
133
-	 */
134
-	public function getFilteredSystemValue($key, $default = '') {
135
-		return $this->systemConfig->getFilteredValue($key, $default);
136
-	}
137
-
138
-	/**
139
-	 * Delete a system wide defined value
140
-	 *
141
-	 * @param string $key the key of the value, under which it was saved
142
-	 */
143
-	public function deleteSystemValue($key) {
144
-		$this->systemConfig->deleteValue($key);
145
-	}
146
-
147
-	/**
148
-	 * Get all keys stored for an app
149
-	 *
150
-	 * @param string $appName the appName that we stored the value under
151
-	 * @return string[] the keys stored for the app
152
-	 */
153
-	public function getAppKeys($appName) {
154
-		return \OC::$server->getAppConfig()->getKeys($appName);
155
-	}
156
-
157
-	/**
158
-	 * Writes a new app wide value
159
-	 *
160
-	 * @param string $appName the appName that we want to store the value under
161
-	 * @param string $key the key of the value, under which will be saved
162
-	 * @param string|float|int $value the value that should be stored
163
-	 */
164
-	public function setAppValue($appName, $key, $value) {
165
-		\OC::$server->getAppConfig()->setValue($appName, $key, $value);
166
-	}
167
-
168
-	/**
169
-	 * Looks up an app wide defined value
170
-	 *
171
-	 * @param string $appName the appName that we stored the value under
172
-	 * @param string $key the key of the value, under which it was saved
173
-	 * @param string $default the default value to be returned if the value isn't set
174
-	 * @return string the saved value
175
-	 */
176
-	public function getAppValue($appName, $key, $default = '') {
177
-		return \OC::$server->getAppConfig()->getValue($appName, $key, $default);
178
-	}
179
-
180
-	/**
181
-	 * Delete an app wide defined value
182
-	 *
183
-	 * @param string $appName the appName that we stored the value under
184
-	 * @param string $key the key of the value, under which it was saved
185
-	 */
186
-	public function deleteAppValue($appName, $key) {
187
-		\OC::$server->getAppConfig()->deleteKey($appName, $key);
188
-	}
189
-
190
-	/**
191
-	 * Removes all keys in appconfig belonging to the app
192
-	 *
193
-	 * @param string $appName the appName the configs are stored under
194
-	 */
195
-	public function deleteAppValues($appName) {
196
-		\OC::$server->getAppConfig()->deleteApp($appName);
197
-	}
198
-
199
-
200
-	/**
201
-	 * Set a user defined value
202
-	 *
203
-	 * @param string $userId the userId of the user that we want to store the value under
204
-	 * @param string $appName the appName that we want to store the value under
205
-	 * @param string $key the key under which the value is being stored
206
-	 * @param string|float|int $value the value that you want to store
207
-	 * @param string $preCondition only update if the config value was previously the value passed as $preCondition
208
-	 * @throws \OCP\PreConditionNotMetException if a precondition is specified and is not met
209
-	 * @throws \UnexpectedValueException when trying to store an unexpected value
210
-	 */
211
-	public function setUserValue($userId, $appName, $key, $value, $preCondition = null) {
212
-		if (!is_int($value) && !is_float($value) && !is_string($value)) {
213
-			throw new \UnexpectedValueException('Only integers, floats and strings are allowed as value');
214
-		}
215
-
216
-		// TODO - FIXME
217
-		$this->fixDIInit();
218
-
219
-		$prevValue = $this->getUserValue($userId, $appName, $key, null);
220
-
221
-		if ($prevValue !== null) {
222
-			if ($prevValue === (string)$value) {
223
-				return;
224
-			} elseif ($preCondition !== null && $prevValue !== (string)$preCondition) {
225
-				throw new PreConditionNotMetException();
226
-			} else {
227
-				$qb = $this->connection->getQueryBuilder();
228
-				$qb->update('preferences')
229
-					->set('configvalue', $qb->createNamedParameter($value))
230
-					->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId)))
231
-					->andWhere($qb->expr()->eq('appid', $qb->createNamedParameter($appName)))
232
-					->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
233
-				$qb->execute();
234
-
235
-				$this->userCache[$userId][$appName][$key] = $value;
236
-				return;
237
-			}
238
-		}
239
-
240
-		$preconditionArray = [];
241
-		if (isset($preCondition)) {
242
-			$preconditionArray = [
243
-				'configvalue' => $preCondition,
244
-			];
245
-		}
246
-
247
-		$this->connection->setValues('preferences', [
248
-			'userid' => $userId,
249
-			'appid' => $appName,
250
-			'configkey' => $key,
251
-		], [
252
-			'configvalue' => $value,
253
-		], $preconditionArray);
254
-
255
-		// only add to the cache if we already loaded data for the user
256
-		if (isset($this->userCache[$userId])) {
257
-			if (!isset($this->userCache[$userId][$appName])) {
258
-				$this->userCache[$userId][$appName] = [];
259
-			}
260
-			$this->userCache[$userId][$appName][$key] = $value;
261
-		}
262
-	}
263
-
264
-	/**
265
-	 * Getting a user defined value
266
-	 *
267
-	 * @param string $userId the userId of the user that we want to store the value under
268
-	 * @param string $appName the appName that we stored the value under
269
-	 * @param string $key the key under which the value is being stored
270
-	 * @param mixed $default the default value to be returned if the value isn't set
271
-	 * @return string
272
-	 */
273
-	public function getUserValue($userId, $appName, $key, $default = '') {
274
-		$data = $this->getUserValues($userId);
275
-		if (isset($data[$appName]) and isset($data[$appName][$key])) {
276
-			return $data[$appName][$key];
277
-		} else {
278
-			return $default;
279
-		}
280
-	}
281
-
282
-	/**
283
-	 * Get the keys of all stored by an app for the user
284
-	 *
285
-	 * @param string $userId the userId of the user that we want to store the value under
286
-	 * @param string $appName the appName that we stored the value under
287
-	 * @return string[]
288
-	 */
289
-	public function getUserKeys($userId, $appName) {
290
-		$data = $this->getUserValues($userId);
291
-		if (isset($data[$appName])) {
292
-			return array_keys($data[$appName]);
293
-		} else {
294
-			return [];
295
-		}
296
-	}
297
-
298
-	/**
299
-	 * Delete a user value
300
-	 *
301
-	 * @param string $userId the userId of the user that we want to store the value under
302
-	 * @param string $appName the appName that we stored the value under
303
-	 * @param string $key the key under which the value is being stored
304
-	 */
305
-	public function deleteUserValue($userId, $appName, $key) {
306
-		// TODO - FIXME
307
-		$this->fixDIInit();
308
-
309
-		$sql = 'DELETE FROM `*PREFIX*preferences` '.
310
-				'WHERE `userid` = ? AND `appid` = ? AND `configkey` = ?';
311
-		$this->connection->executeUpdate($sql, [$userId, $appName, $key]);
312
-
313
-		if (isset($this->userCache[$userId]) and isset($this->userCache[$userId][$appName])) {
314
-			unset($this->userCache[$userId][$appName][$key]);
315
-		}
316
-	}
317
-
318
-	/**
319
-	 * Delete all user values
320
-	 *
321
-	 * @param string $userId the userId of the user that we want to remove all values from
322
-	 */
323
-	public function deleteAllUserValues($userId) {
324
-		// TODO - FIXME
325
-		$this->fixDIInit();
326
-
327
-		$sql = 'DELETE FROM `*PREFIX*preferences` '.
328
-			'WHERE `userid` = ?';
329
-		$this->connection->executeUpdate($sql, [$userId]);
330
-
331
-		unset($this->userCache[$userId]);
332
-	}
333
-
334
-	/**
335
-	 * Delete all user related values of one app
336
-	 *
337
-	 * @param string $appName the appName of the app that we want to remove all values from
338
-	 */
339
-	public function deleteAppFromAllUsers($appName) {
340
-		// TODO - FIXME
341
-		$this->fixDIInit();
342
-
343
-		$sql = 'DELETE FROM `*PREFIX*preferences` '.
344
-				'WHERE `appid` = ?';
345
-		$this->connection->executeUpdate($sql, [$appName]);
346
-
347
-		foreach ($this->userCache as &$userCache) {
348
-			unset($userCache[$appName]);
349
-		}
350
-	}
351
-
352
-	/**
353
-	 * Returns all user configs sorted by app of one user
354
-	 *
355
-	 * @param string $userId the user ID to get the app configs from
356
-	 * @return array[] - 2 dimensional array with the following structure:
357
-	 *     [ $appId =>
358
-	 *         [ $key => $value ]
359
-	 *     ]
360
-	 */
361
-	private function getUserValues($userId) {
362
-		if (isset($this->userCache[$userId])) {
363
-			return $this->userCache[$userId];
364
-		}
365
-		if ($userId === null || $userId === '') {
366
-			$this->userCache[$userId] = [];
367
-			return $this->userCache[$userId];
368
-		}
369
-
370
-		// TODO - FIXME
371
-		$this->fixDIInit();
372
-
373
-		$data = [];
374
-		$query = 'SELECT `appid`, `configkey`, `configvalue` FROM `*PREFIX*preferences` WHERE `userid` = ?';
375
-		$result = $this->connection->executeQuery($query, [$userId]);
376
-		while ($row = $result->fetch()) {
377
-			$appId = $row['appid'];
378
-			if (!isset($data[$appId])) {
379
-				$data[$appId] = [];
380
-			}
381
-			$data[$appId][$row['configkey']] = $row['configvalue'];
382
-		}
383
-		$this->userCache[$userId] = $data;
384
-		return $data;
385
-	}
386
-
387
-	/**
388
-	 * Fetches a mapped list of userId -> value, for a specified app and key and a list of user IDs.
389
-	 *
390
-	 * @param string $appName app to get the value for
391
-	 * @param string $key the key to get the value for
392
-	 * @param array $userIds the user IDs to fetch the values for
393
-	 * @return array Mapped values: userId => value
394
-	 */
395
-	public function getUserValueForUsers($appName, $key, $userIds) {
396
-		// TODO - FIXME
397
-		$this->fixDIInit();
398
-
399
-		if (empty($userIds) || !is_array($userIds)) {
400
-			return [];
401
-		}
402
-
403
-		$chunkedUsers = array_chunk($userIds, 50, true);
404
-		$placeholders50 = implode(',', array_fill(0, 50, '?'));
405
-
406
-		$userValues = [];
407
-		foreach ($chunkedUsers as $chunk) {
408
-			$queryParams = $chunk;
409
-			// create [$app, $key, $chunkedUsers]
410
-			array_unshift($queryParams, $key);
411
-			array_unshift($queryParams, $appName);
412
-
413
-			$placeholders = (count($chunk) === 50) ? $placeholders50 :  implode(',', array_fill(0, count($chunk), '?'));
414
-
415
-			$query = 'SELECT `userid`, `configvalue` ' .
416
-						'FROM `*PREFIX*preferences` ' .
417
-						'WHERE `appid` = ? AND `configkey` = ? ' .
418
-						'AND `userid` IN (' . $placeholders . ')';
419
-			$result = $this->connection->executeQuery($query, $queryParams);
420
-
421
-			while ($row = $result->fetch()) {
422
-				$userValues[$row['userid']] = $row['configvalue'];
423
-			}
424
-		}
425
-
426
-		return $userValues;
427
-	}
428
-
429
-	/**
430
-	 * Determines the users that have the given value set for a specific app-key-pair
431
-	 *
432
-	 * @param string $appName the app to get the user for
433
-	 * @param string $key the key to get the user for
434
-	 * @param string $value the value to get the user for
435
-	 * @return array of user IDs
436
-	 */
437
-	public function getUsersForUserValue($appName, $key, $value) {
438
-		// TODO - FIXME
439
-		$this->fixDIInit();
440
-
441
-		$sql = 'SELECT `userid` FROM `*PREFIX*preferences` ' .
442
-				'WHERE `appid` = ? AND `configkey` = ? ';
443
-
444
-		if ($this->getSystemValue('dbtype', 'sqlite') === 'oci') {
445
-			//oracle hack: need to explicitly cast CLOB to CHAR for comparison
446
-			$sql .= 'AND to_char(`configvalue`) = ?';
447
-		} else {
448
-			$sql .= 'AND `configvalue` = ?';
449
-		}
450
-
451
-		$result = $this->connection->executeQuery($sql, [$appName, $key, $value]);
452
-
453
-		$userIDs = [];
454
-		while ($row = $result->fetch()) {
455
-			$userIDs[] = $row['userid'];
456
-		}
457
-
458
-		return $userIDs;
459
-	}
460
-
461
-	public function getSystemConfig() {
462
-		return $this->systemConfig;
463
-	}
41
+    /** @var SystemConfig */
42
+    private $systemConfig;
43
+
44
+    /** @var IDBConnection */
45
+    private $connection;
46
+
47
+    /**
48
+     * 3 dimensional array with the following structure:
49
+     * [ $userId =>
50
+     *     [ $appId =>
51
+     *         [ $key => $value ]
52
+     *     ]
53
+     * ]
54
+     *
55
+     * database table: preferences
56
+     *
57
+     * methods that use this:
58
+     *   - setUserValue
59
+     *   - getUserValue
60
+     *   - getUserKeys
61
+     *   - deleteUserValue
62
+     *   - deleteAllUserValues
63
+     *   - deleteAppFromAllUsers
64
+     *
65
+     * @var CappedMemoryCache $userCache
66
+     */
67
+    private $userCache;
68
+
69
+    /**
70
+     * @param SystemConfig $systemConfig
71
+     */
72
+    public function __construct(SystemConfig $systemConfig) {
73
+        $this->userCache = new CappedMemoryCache();
74
+        $this->systemConfig = $systemConfig;
75
+    }
76
+
77
+    /**
78
+     * TODO - FIXME This fixes an issue with base.php that cause cyclic
79
+     * dependencies, especially with autoconfig setup
80
+     *
81
+     * Replace this by properly injected database connection. Currently the
82
+     * base.php triggers the getDatabaseConnection too early which causes in
83
+     * autoconfig setup case a too early distributed database connection and
84
+     * the autoconfig then needs to reinit all already initialized dependencies
85
+     * that use the database connection.
86
+     *
87
+     * otherwise a SQLite database is created in the wrong directory
88
+     * because the database connection was created with an uninitialized config
89
+     */
90
+    private function fixDIInit() {
91
+        if ($this->connection === null) {
92
+            $this->connection = \OC::$server->getDatabaseConnection();
93
+        }
94
+    }
95
+
96
+    /**
97
+     * Sets and deletes system wide values
98
+     *
99
+     * @param array $configs Associative array with `key => value` pairs
100
+     *                       If value is null, the config key will be deleted
101
+     */
102
+    public function setSystemValues(array $configs) {
103
+        $this->systemConfig->setValues($configs);
104
+    }
105
+
106
+    /**
107
+     * Sets a new system wide value
108
+     *
109
+     * @param string $key the key of the value, under which will be saved
110
+     * @param mixed $value the value that should be stored
111
+     */
112
+    public function setSystemValue($key, $value) {
113
+        $this->systemConfig->setValue($key, $value);
114
+    }
115
+
116
+    /**
117
+     * Looks up a system wide defined value
118
+     *
119
+     * @param string $key the key of the value, under which it was saved
120
+     * @param mixed $default the default value to be returned if the value isn't set
121
+     * @return mixed the value or $default
122
+     */
123
+    public function getSystemValue($key, $default = '') {
124
+        return $this->systemConfig->getValue($key, $default);
125
+    }
126
+
127
+    /**
128
+     * Looks up a system wide defined value and filters out sensitive data
129
+     *
130
+     * @param string $key the key of the value, under which it was saved
131
+     * @param mixed $default the default value to be returned if the value isn't set
132
+     * @return mixed the value or $default
133
+     */
134
+    public function getFilteredSystemValue($key, $default = '') {
135
+        return $this->systemConfig->getFilteredValue($key, $default);
136
+    }
137
+
138
+    /**
139
+     * Delete a system wide defined value
140
+     *
141
+     * @param string $key the key of the value, under which it was saved
142
+     */
143
+    public function deleteSystemValue($key) {
144
+        $this->systemConfig->deleteValue($key);
145
+    }
146
+
147
+    /**
148
+     * Get all keys stored for an app
149
+     *
150
+     * @param string $appName the appName that we stored the value under
151
+     * @return string[] the keys stored for the app
152
+     */
153
+    public function getAppKeys($appName) {
154
+        return \OC::$server->getAppConfig()->getKeys($appName);
155
+    }
156
+
157
+    /**
158
+     * Writes a new app wide value
159
+     *
160
+     * @param string $appName the appName that we want to store the value under
161
+     * @param string $key the key of the value, under which will be saved
162
+     * @param string|float|int $value the value that should be stored
163
+     */
164
+    public function setAppValue($appName, $key, $value) {
165
+        \OC::$server->getAppConfig()->setValue($appName, $key, $value);
166
+    }
167
+
168
+    /**
169
+     * Looks up an app wide defined value
170
+     *
171
+     * @param string $appName the appName that we stored the value under
172
+     * @param string $key the key of the value, under which it was saved
173
+     * @param string $default the default value to be returned if the value isn't set
174
+     * @return string the saved value
175
+     */
176
+    public function getAppValue($appName, $key, $default = '') {
177
+        return \OC::$server->getAppConfig()->getValue($appName, $key, $default);
178
+    }
179
+
180
+    /**
181
+     * Delete an app wide defined value
182
+     *
183
+     * @param string $appName the appName that we stored the value under
184
+     * @param string $key the key of the value, under which it was saved
185
+     */
186
+    public function deleteAppValue($appName, $key) {
187
+        \OC::$server->getAppConfig()->deleteKey($appName, $key);
188
+    }
189
+
190
+    /**
191
+     * Removes all keys in appconfig belonging to the app
192
+     *
193
+     * @param string $appName the appName the configs are stored under
194
+     */
195
+    public function deleteAppValues($appName) {
196
+        \OC::$server->getAppConfig()->deleteApp($appName);
197
+    }
198
+
199
+
200
+    /**
201
+     * Set a user defined value
202
+     *
203
+     * @param string $userId the userId of the user that we want to store the value under
204
+     * @param string $appName the appName that we want to store the value under
205
+     * @param string $key the key under which the value is being stored
206
+     * @param string|float|int $value the value that you want to store
207
+     * @param string $preCondition only update if the config value was previously the value passed as $preCondition
208
+     * @throws \OCP\PreConditionNotMetException if a precondition is specified and is not met
209
+     * @throws \UnexpectedValueException when trying to store an unexpected value
210
+     */
211
+    public function setUserValue($userId, $appName, $key, $value, $preCondition = null) {
212
+        if (!is_int($value) && !is_float($value) && !is_string($value)) {
213
+            throw new \UnexpectedValueException('Only integers, floats and strings are allowed as value');
214
+        }
215
+
216
+        // TODO - FIXME
217
+        $this->fixDIInit();
218
+
219
+        $prevValue = $this->getUserValue($userId, $appName, $key, null);
220
+
221
+        if ($prevValue !== null) {
222
+            if ($prevValue === (string)$value) {
223
+                return;
224
+            } elseif ($preCondition !== null && $prevValue !== (string)$preCondition) {
225
+                throw new PreConditionNotMetException();
226
+            } else {
227
+                $qb = $this->connection->getQueryBuilder();
228
+                $qb->update('preferences')
229
+                    ->set('configvalue', $qb->createNamedParameter($value))
230
+                    ->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId)))
231
+                    ->andWhere($qb->expr()->eq('appid', $qb->createNamedParameter($appName)))
232
+                    ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
233
+                $qb->execute();
234
+
235
+                $this->userCache[$userId][$appName][$key] = $value;
236
+                return;
237
+            }
238
+        }
239
+
240
+        $preconditionArray = [];
241
+        if (isset($preCondition)) {
242
+            $preconditionArray = [
243
+                'configvalue' => $preCondition,
244
+            ];
245
+        }
246
+
247
+        $this->connection->setValues('preferences', [
248
+            'userid' => $userId,
249
+            'appid' => $appName,
250
+            'configkey' => $key,
251
+        ], [
252
+            'configvalue' => $value,
253
+        ], $preconditionArray);
254
+
255
+        // only add to the cache if we already loaded data for the user
256
+        if (isset($this->userCache[$userId])) {
257
+            if (!isset($this->userCache[$userId][$appName])) {
258
+                $this->userCache[$userId][$appName] = [];
259
+            }
260
+            $this->userCache[$userId][$appName][$key] = $value;
261
+        }
262
+    }
263
+
264
+    /**
265
+     * Getting a user defined value
266
+     *
267
+     * @param string $userId the userId of the user that we want to store the value under
268
+     * @param string $appName the appName that we stored the value under
269
+     * @param string $key the key under which the value is being stored
270
+     * @param mixed $default the default value to be returned if the value isn't set
271
+     * @return string
272
+     */
273
+    public function getUserValue($userId, $appName, $key, $default = '') {
274
+        $data = $this->getUserValues($userId);
275
+        if (isset($data[$appName]) and isset($data[$appName][$key])) {
276
+            return $data[$appName][$key];
277
+        } else {
278
+            return $default;
279
+        }
280
+    }
281
+
282
+    /**
283
+     * Get the keys of all stored by an app for the user
284
+     *
285
+     * @param string $userId the userId of the user that we want to store the value under
286
+     * @param string $appName the appName that we stored the value under
287
+     * @return string[]
288
+     */
289
+    public function getUserKeys($userId, $appName) {
290
+        $data = $this->getUserValues($userId);
291
+        if (isset($data[$appName])) {
292
+            return array_keys($data[$appName]);
293
+        } else {
294
+            return [];
295
+        }
296
+    }
297
+
298
+    /**
299
+     * Delete a user value
300
+     *
301
+     * @param string $userId the userId of the user that we want to store the value under
302
+     * @param string $appName the appName that we stored the value under
303
+     * @param string $key the key under which the value is being stored
304
+     */
305
+    public function deleteUserValue($userId, $appName, $key) {
306
+        // TODO - FIXME
307
+        $this->fixDIInit();
308
+
309
+        $sql = 'DELETE FROM `*PREFIX*preferences` '.
310
+                'WHERE `userid` = ? AND `appid` = ? AND `configkey` = ?';
311
+        $this->connection->executeUpdate($sql, [$userId, $appName, $key]);
312
+
313
+        if (isset($this->userCache[$userId]) and isset($this->userCache[$userId][$appName])) {
314
+            unset($this->userCache[$userId][$appName][$key]);
315
+        }
316
+    }
317
+
318
+    /**
319
+     * Delete all user values
320
+     *
321
+     * @param string $userId the userId of the user that we want to remove all values from
322
+     */
323
+    public function deleteAllUserValues($userId) {
324
+        // TODO - FIXME
325
+        $this->fixDIInit();
326
+
327
+        $sql = 'DELETE FROM `*PREFIX*preferences` '.
328
+            'WHERE `userid` = ?';
329
+        $this->connection->executeUpdate($sql, [$userId]);
330
+
331
+        unset($this->userCache[$userId]);
332
+    }
333
+
334
+    /**
335
+     * Delete all user related values of one app
336
+     *
337
+     * @param string $appName the appName of the app that we want to remove all values from
338
+     */
339
+    public function deleteAppFromAllUsers($appName) {
340
+        // TODO - FIXME
341
+        $this->fixDIInit();
342
+
343
+        $sql = 'DELETE FROM `*PREFIX*preferences` '.
344
+                'WHERE `appid` = ?';
345
+        $this->connection->executeUpdate($sql, [$appName]);
346
+
347
+        foreach ($this->userCache as &$userCache) {
348
+            unset($userCache[$appName]);
349
+        }
350
+    }
351
+
352
+    /**
353
+     * Returns all user configs sorted by app of one user
354
+     *
355
+     * @param string $userId the user ID to get the app configs from
356
+     * @return array[] - 2 dimensional array with the following structure:
357
+     *     [ $appId =>
358
+     *         [ $key => $value ]
359
+     *     ]
360
+     */
361
+    private function getUserValues($userId) {
362
+        if (isset($this->userCache[$userId])) {
363
+            return $this->userCache[$userId];
364
+        }
365
+        if ($userId === null || $userId === '') {
366
+            $this->userCache[$userId] = [];
367
+            return $this->userCache[$userId];
368
+        }
369
+
370
+        // TODO - FIXME
371
+        $this->fixDIInit();
372
+
373
+        $data = [];
374
+        $query = 'SELECT `appid`, `configkey`, `configvalue` FROM `*PREFIX*preferences` WHERE `userid` = ?';
375
+        $result = $this->connection->executeQuery($query, [$userId]);
376
+        while ($row = $result->fetch()) {
377
+            $appId = $row['appid'];
378
+            if (!isset($data[$appId])) {
379
+                $data[$appId] = [];
380
+            }
381
+            $data[$appId][$row['configkey']] = $row['configvalue'];
382
+        }
383
+        $this->userCache[$userId] = $data;
384
+        return $data;
385
+    }
386
+
387
+    /**
388
+     * Fetches a mapped list of userId -> value, for a specified app and key and a list of user IDs.
389
+     *
390
+     * @param string $appName app to get the value for
391
+     * @param string $key the key to get the value for
392
+     * @param array $userIds the user IDs to fetch the values for
393
+     * @return array Mapped values: userId => value
394
+     */
395
+    public function getUserValueForUsers($appName, $key, $userIds) {
396
+        // TODO - FIXME
397
+        $this->fixDIInit();
398
+
399
+        if (empty($userIds) || !is_array($userIds)) {
400
+            return [];
401
+        }
402
+
403
+        $chunkedUsers = array_chunk($userIds, 50, true);
404
+        $placeholders50 = implode(',', array_fill(0, 50, '?'));
405
+
406
+        $userValues = [];
407
+        foreach ($chunkedUsers as $chunk) {
408
+            $queryParams = $chunk;
409
+            // create [$app, $key, $chunkedUsers]
410
+            array_unshift($queryParams, $key);
411
+            array_unshift($queryParams, $appName);
412
+
413
+            $placeholders = (count($chunk) === 50) ? $placeholders50 :  implode(',', array_fill(0, count($chunk), '?'));
414
+
415
+            $query = 'SELECT `userid`, `configvalue` ' .
416
+                        'FROM `*PREFIX*preferences` ' .
417
+                        'WHERE `appid` = ? AND `configkey` = ? ' .
418
+                        'AND `userid` IN (' . $placeholders . ')';
419
+            $result = $this->connection->executeQuery($query, $queryParams);
420
+
421
+            while ($row = $result->fetch()) {
422
+                $userValues[$row['userid']] = $row['configvalue'];
423
+            }
424
+        }
425
+
426
+        return $userValues;
427
+    }
428
+
429
+    /**
430
+     * Determines the users that have the given value set for a specific app-key-pair
431
+     *
432
+     * @param string $appName the app to get the user for
433
+     * @param string $key the key to get the user for
434
+     * @param string $value the value to get the user for
435
+     * @return array of user IDs
436
+     */
437
+    public function getUsersForUserValue($appName, $key, $value) {
438
+        // TODO - FIXME
439
+        $this->fixDIInit();
440
+
441
+        $sql = 'SELECT `userid` FROM `*PREFIX*preferences` ' .
442
+                'WHERE `appid` = ? AND `configkey` = ? ';
443
+
444
+        if ($this->getSystemValue('dbtype', 'sqlite') === 'oci') {
445
+            //oracle hack: need to explicitly cast CLOB to CHAR for comparison
446
+            $sql .= 'AND to_char(`configvalue`) = ?';
447
+        } else {
448
+            $sql .= 'AND `configvalue` = ?';
449
+        }
450
+
451
+        $result = $this->connection->executeQuery($sql, [$appName, $key, $value]);
452
+
453
+        $userIDs = [];
454
+        while ($row = $result->fetch()) {
455
+            $userIDs[] = $row['userid'];
456
+        }
457
+
458
+        return $userIDs;
459
+    }
460
+
461
+    public function getSystemConfig() {
462
+        return $this->systemConfig;
463
+    }
464 464
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -219,9 +219,9 @@  discard block
 block discarded – undo
219 219
 		$prevValue = $this->getUserValue($userId, $appName, $key, null);
220 220
 
221 221
 		if ($prevValue !== null) {
222
-			if ($prevValue === (string)$value) {
222
+			if ($prevValue === (string) $value) {
223 223
 				return;
224
-			} elseif ($preCondition !== null && $prevValue !== (string)$preCondition) {
224
+			} elseif ($preCondition !== null && $prevValue !== (string) $preCondition) {
225 225
 				throw new PreConditionNotMetException();
226 226
 			} else {
227 227
 				$qb = $this->connection->getQueryBuilder();
@@ -410,12 +410,12 @@  discard block
 block discarded – undo
410 410
 			array_unshift($queryParams, $key);
411 411
 			array_unshift($queryParams, $appName);
412 412
 
413
-			$placeholders = (count($chunk) === 50) ? $placeholders50 :  implode(',', array_fill(0, count($chunk), '?'));
413
+			$placeholders = (count($chunk) === 50) ? $placeholders50 : implode(',', array_fill(0, count($chunk), '?'));
414 414
 
415
-			$query = 'SELECT `userid`, `configvalue` ' .
416
-						'FROM `*PREFIX*preferences` ' .
417
-						'WHERE `appid` = ? AND `configkey` = ? ' .
418
-						'AND `userid` IN (' . $placeholders . ')';
415
+			$query = 'SELECT `userid`, `configvalue` '.
416
+						'FROM `*PREFIX*preferences` '.
417
+						'WHERE `appid` = ? AND `configkey` = ? '.
418
+						'AND `userid` IN ('.$placeholders.')';
419 419
 			$result = $this->connection->executeQuery($query, $queryParams);
420 420
 
421 421
 			while ($row = $result->fetch()) {
@@ -438,7 +438,7 @@  discard block
 block discarded – undo
438 438
 		// TODO - FIXME
439 439
 		$this->fixDIInit();
440 440
 
441
-		$sql = 'SELECT `userid` FROM `*PREFIX*preferences` ' .
441
+		$sql = 'SELECT `userid` FROM `*PREFIX*preferences` '.
442 442
 				'WHERE `appid` = ? AND `configkey` = ? ';
443 443
 
444 444
 		if ($this->getSystemValue('dbtype', 'sqlite') === 'oci') {
Please login to merge, or discard this patch.