Completed
Pull Request — master (#8061)
by Morris
109:58 queued 95:16
created
apps/dav/lib/Connector/Sabre/CommentPropertiesPlugin.php 2 patches
Indentation   +127 added lines, -127 removed lines patch added patch discarded remove patch
@@ -30,132 +30,132 @@
 block discarded – undo
30 30
 
31 31
 class CommentPropertiesPlugin extends ServerPlugin {
32 32
 
33
-	const PROPERTY_NAME_HREF   = '{http://owncloud.org/ns}comments-href';
34
-	const PROPERTY_NAME_COUNT  = '{http://owncloud.org/ns}comments-count';
35
-	const PROPERTY_NAME_UNREAD = '{http://owncloud.org/ns}comments-unread';
36
-
37
-	/** @var  \Sabre\DAV\Server */
38
-	protected $server;
39
-
40
-	/** @var ICommentsManager */
41
-	private $commentsManager;
42
-
43
-	/** @var IUserSession */
44
-	private $userSession;
45
-
46
-	private $cachedUnreadCount = [];
47
-
48
-	private $cachedFolders = [];
49
-
50
-	public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) {
51
-		$this->commentsManager = $commentsManager;
52
-		$this->userSession = $userSession;
53
-	}
54
-
55
-	/**
56
-	 * This initializes the plugin.
57
-	 *
58
-	 * This function is called by Sabre\DAV\Server, after
59
-	 * addPlugin is called.
60
-	 *
61
-	 * This method should set up the required event subscriptions.
62
-	 *
63
-	 * @param \Sabre\DAV\Server $server
64
-	 * @return void
65
-	 */
66
-	function initialize(\Sabre\DAV\Server $server) {
67
-		$this->server = $server;
68
-		$this->server->on('propFind', array($this, 'handleGetProperties'));
69
-	}
70
-
71
-	/**
72
-	 * Adds tags and favorites properties to the response,
73
-	 * if requested.
74
-	 *
75
-	 * @param PropFind $propFind
76
-	 * @param \Sabre\DAV\INode $node
77
-	 * @return void
78
-	 */
79
-	public function handleGetProperties(
80
-		PropFind $propFind,
81
-		\Sabre\DAV\INode $node
82
-	) {
83
-		if (!($node instanceof File) && !($node instanceof Directory)) {
84
-			return;
85
-		}
86
-
87
-		// need prefetch ?
88
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Directory
89
-			&& $propFind->getDepth() !== 0
90
-			&& !is_null($propFind->getStatus(self::PROPERTY_NAME_UNREAD))
91
-		) {
92
-			$unreadCounts = $this->commentsManager->getNumberOfUnreadCommentsForFolder($node->getId(), $this->userSession->getUser());
93
-			$this->cachedFolders[] = $node->getPath();
94
-			foreach ($unreadCounts as $id => $count) {
95
-				$this->cachedUnreadCount[$id] = $count;
96
-			}
97
-		}
98
-
99
-		$propFind->handle(self::PROPERTY_NAME_COUNT, function() use ($node) {
100
-			return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId());
101
-		});
102
-
103
-		$propFind->handle(self::PROPERTY_NAME_HREF, function() use ($node) {
104
-			return $this->getCommentsLink($node);
105
-		});
106
-
107
-		$propFind->handle(self::PROPERTY_NAME_UNREAD, function() use ($node) {
108
-			if (isset($this->cachedUnreadCount[$node->getId()])) {
109
-				return $this->cachedUnreadCount[$node->getId()];
110
-			} else {
111
-				list($parentPath,) = \Sabre\Uri\split($node->getPath());
112
-				if ($parentPath === '') {
113
-					$parentPath = '/';
114
-				}
115
-				// if we already cached the folder this file is in we know there are no shares for this file
116
-				if (array_search($parentPath, $this->cachedFolders) === false) {
117
-					return $this->getUnreadCount($node);
118
-				} else {
119
-					return 0;
120
-				}
121
-			}
122
-		});
123
-	}
124
-
125
-	/**
126
-	 * returns a reference to the comments node
127
-	 *
128
-	 * @param Node $node
129
-	 * @return mixed|string
130
-	 */
131
-	public function getCommentsLink(Node $node) {
132
-		$href =  $this->server->getBaseUri();
133
-		$entryPoint = strpos($href, '/remote.php/');
134
-		if($entryPoint === false) {
135
-			// in case we end up somewhere else, unexpectedly.
136
-			return null;
137
-		}
138
-		$commentsPart = 'dav/comments/files/' . rawurldecode($node->getId());
139
-		$href = substr_replace($href, $commentsPart, $entryPoint + strlen('/remote.php/'));
140
-		return $href;
141
-	}
142
-
143
-	/**
144
-	 * returns the number of unread comments for the currently logged in user
145
-	 * on the given file or directory node
146
-	 *
147
-	 * @param Node $node
148
-	 * @return Int|null
149
-	 */
150
-	public function getUnreadCount(Node $node) {
151
-		$user = $this->userSession->getUser();
152
-		if(is_null($user)) {
153
-			return null;
154
-		}
155
-
156
-		$lastRead = $this->commentsManager->getReadMark('files', (string)$node->getId(), $user);
157
-
158
-		return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId(), $lastRead);
159
-	}
33
+    const PROPERTY_NAME_HREF   = '{http://owncloud.org/ns}comments-href';
34
+    const PROPERTY_NAME_COUNT  = '{http://owncloud.org/ns}comments-count';
35
+    const PROPERTY_NAME_UNREAD = '{http://owncloud.org/ns}comments-unread';
36
+
37
+    /** @var  \Sabre\DAV\Server */
38
+    protected $server;
39
+
40
+    /** @var ICommentsManager */
41
+    private $commentsManager;
42
+
43
+    /** @var IUserSession */
44
+    private $userSession;
45
+
46
+    private $cachedUnreadCount = [];
47
+
48
+    private $cachedFolders = [];
49
+
50
+    public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) {
51
+        $this->commentsManager = $commentsManager;
52
+        $this->userSession = $userSession;
53
+    }
54
+
55
+    /**
56
+     * This initializes the plugin.
57
+     *
58
+     * This function is called by Sabre\DAV\Server, after
59
+     * addPlugin is called.
60
+     *
61
+     * This method should set up the required event subscriptions.
62
+     *
63
+     * @param \Sabre\DAV\Server $server
64
+     * @return void
65
+     */
66
+    function initialize(\Sabre\DAV\Server $server) {
67
+        $this->server = $server;
68
+        $this->server->on('propFind', array($this, 'handleGetProperties'));
69
+    }
70
+
71
+    /**
72
+     * Adds tags and favorites properties to the response,
73
+     * if requested.
74
+     *
75
+     * @param PropFind $propFind
76
+     * @param \Sabre\DAV\INode $node
77
+     * @return void
78
+     */
79
+    public function handleGetProperties(
80
+        PropFind $propFind,
81
+        \Sabre\DAV\INode $node
82
+    ) {
83
+        if (!($node instanceof File) && !($node instanceof Directory)) {
84
+            return;
85
+        }
86
+
87
+        // need prefetch ?
88
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Directory
89
+            && $propFind->getDepth() !== 0
90
+            && !is_null($propFind->getStatus(self::PROPERTY_NAME_UNREAD))
91
+        ) {
92
+            $unreadCounts = $this->commentsManager->getNumberOfUnreadCommentsForFolder($node->getId(), $this->userSession->getUser());
93
+            $this->cachedFolders[] = $node->getPath();
94
+            foreach ($unreadCounts as $id => $count) {
95
+                $this->cachedUnreadCount[$id] = $count;
96
+            }
97
+        }
98
+
99
+        $propFind->handle(self::PROPERTY_NAME_COUNT, function() use ($node) {
100
+            return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId());
101
+        });
102
+
103
+        $propFind->handle(self::PROPERTY_NAME_HREF, function() use ($node) {
104
+            return $this->getCommentsLink($node);
105
+        });
106
+
107
+        $propFind->handle(self::PROPERTY_NAME_UNREAD, function() use ($node) {
108
+            if (isset($this->cachedUnreadCount[$node->getId()])) {
109
+                return $this->cachedUnreadCount[$node->getId()];
110
+            } else {
111
+                list($parentPath,) = \Sabre\Uri\split($node->getPath());
112
+                if ($parentPath === '') {
113
+                    $parentPath = '/';
114
+                }
115
+                // if we already cached the folder this file is in we know there are no shares for this file
116
+                if (array_search($parentPath, $this->cachedFolders) === false) {
117
+                    return $this->getUnreadCount($node);
118
+                } else {
119
+                    return 0;
120
+                }
121
+            }
122
+        });
123
+    }
124
+
125
+    /**
126
+     * returns a reference to the comments node
127
+     *
128
+     * @param Node $node
129
+     * @return mixed|string
130
+     */
131
+    public function getCommentsLink(Node $node) {
132
+        $href =  $this->server->getBaseUri();
133
+        $entryPoint = strpos($href, '/remote.php/');
134
+        if($entryPoint === false) {
135
+            // in case we end up somewhere else, unexpectedly.
136
+            return null;
137
+        }
138
+        $commentsPart = 'dav/comments/files/' . rawurldecode($node->getId());
139
+        $href = substr_replace($href, $commentsPart, $entryPoint + strlen('/remote.php/'));
140
+        return $href;
141
+    }
142
+
143
+    /**
144
+     * returns the number of unread comments for the currently logged in user
145
+     * on the given file or directory node
146
+     *
147
+     * @param Node $node
148
+     * @return Int|null
149
+     */
150
+    public function getUnreadCount(Node $node) {
151
+        $user = $this->userSession->getUser();
152
+        if(is_null($user)) {
153
+            return null;
154
+        }
155
+
156
+        $lastRead = $this->commentsManager->getReadMark('files', (string)$node->getId(), $user);
157
+
158
+        return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId(), $lastRead);
159
+    }
160 160
 
161 161
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -97,7 +97,7 @@  discard block
 block discarded – undo
97 97
 		}
98 98
 
99 99
 		$propFind->handle(self::PROPERTY_NAME_COUNT, function() use ($node) {
100
-			return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId());
100
+			return $this->commentsManager->getNumberOfCommentsForObject('files', (string) $node->getId());
101 101
 		});
102 102
 
103 103
 		$propFind->handle(self::PROPERTY_NAME_HREF, function() use ($node) {
@@ -129,13 +129,13 @@  discard block
 block discarded – undo
129 129
 	 * @return mixed|string
130 130
 	 */
131 131
 	public function getCommentsLink(Node $node) {
132
-		$href =  $this->server->getBaseUri();
132
+		$href = $this->server->getBaseUri();
133 133
 		$entryPoint = strpos($href, '/remote.php/');
134
-		if($entryPoint === false) {
134
+		if ($entryPoint === false) {
135 135
 			// in case we end up somewhere else, unexpectedly.
136 136
 			return null;
137 137
 		}
138
-		$commentsPart = 'dav/comments/files/' . rawurldecode($node->getId());
138
+		$commentsPart = 'dav/comments/files/'.rawurldecode($node->getId());
139 139
 		$href = substr_replace($href, $commentsPart, $entryPoint + strlen('/remote.php/'));
140 140
 		return $href;
141 141
 	}
@@ -149,13 +149,13 @@  discard block
 block discarded – undo
149 149
 	 */
150 150
 	public function getUnreadCount(Node $node) {
151 151
 		$user = $this->userSession->getUser();
152
-		if(is_null($user)) {
152
+		if (is_null($user)) {
153 153
 			return null;
154 154
 		}
155 155
 
156
-		$lastRead = $this->commentsManager->getReadMark('files', (string)$node->getId(), $user);
156
+		$lastRead = $this->commentsManager->getReadMark('files', (string) $node->getId(), $user);
157 157
 
158
-		return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId(), $lastRead);
158
+		return $this->commentsManager->getNumberOfCommentsForObject('files', (string) $node->getId(), $lastRead);
159 159
 	}
160 160
 
161 161
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/Node.php 2 patches
Indentation   +322 added lines, -322 removed lines patch added patch discarded remove patch
@@ -44,332 +44,332 @@
 block discarded – undo
44 44
 
45 45
 abstract class Node implements \Sabre\DAV\INode {
46 46
 
47
-	/**
48
-	 * @var \OC\Files\View
49
-	 */
50
-	protected $fileView;
51
-
52
-	/**
53
-	 * The path to the current node
54
-	 *
55
-	 * @var string
56
-	 */
57
-	protected $path;
58
-
59
-	/**
60
-	 * node properties cache
61
-	 *
62
-	 * @var array
63
-	 */
64
-	protected $property_cache = null;
65
-
66
-	/**
67
-	 * @var \OCP\Files\FileInfo
68
-	 */
69
-	protected $info;
70
-
71
-	/**
72
-	 * @var IManager
73
-	 */
74
-	protected $shareManager;
75
-
76
-	/**
77
-	 * Sets up the node, expects a full path name
78
-	 *
79
-	 * @param \OC\Files\View $view
80
-	 * @param \OCP\Files\FileInfo $info
81
-	 * @param IManager $shareManager
82
-	 */
83
-	public function __construct(View $view, FileInfo $info, IManager $shareManager = null) {
84
-		$this->fileView = $view;
85
-		$this->path = $this->fileView->getRelativePath($info->getPath());
86
-		$this->info = $info;
87
-		if ($shareManager) {
88
-			$this->shareManager = $shareManager;
89
-		} else {
90
-			$this->shareManager = \OC::$server->getShareManager();
91
-		}
92
-	}
93
-
94
-	protected function refreshInfo() {
95
-		$this->info = $this->fileView->getFileInfo($this->path);
96
-	}
97
-
98
-	/**
99
-	 *  Returns the name of the node
100
-	 *
101
-	 * @return string
102
-	 */
103
-	public function getName() {
104
-		return $this->info->getName();
105
-	}
106
-
107
-	/**
108
-	 * Returns the full path
109
-	 *
110
-	 * @return string
111
-	 */
112
-	public function getPath() {
113
-		return $this->path;
114
-	}
115
-
116
-	/**
117
-	 * Renames the node
118
-	 *
119
-	 * @param string $name The new name
120
-	 * @throws \Sabre\DAV\Exception\BadRequest
121
-	 * @throws \Sabre\DAV\Exception\Forbidden
122
-	 */
123
-	public function setName($name) {
124
-
125
-		// rename is only allowed if the update privilege is granted
126
-		if (!$this->info->isUpdateable()) {
127
-			throw new \Sabre\DAV\Exception\Forbidden();
128
-		}
129
-
130
-		list($parentPath,) = \Sabre\Uri\split($this->path);
131
-		list(, $newName) = \Sabre\Uri\split($name);
132
-
133
-		// verify path of the target
134
-		$this->verifyPath();
135
-
136
-		$newPath = $parentPath . '/' . $newName;
137
-
138
-		$this->fileView->rename($this->path, $newPath);
139
-
140
-		$this->path = $newPath;
141
-
142
-		$this->refreshInfo();
143
-	}
144
-
145
-	public function setPropertyCache($property_cache) {
146
-		$this->property_cache = $property_cache;
147
-	}
148
-
149
-	/**
150
-	 * Returns the last modification time, as a unix timestamp
151
-	 *
152
-	 * @return int timestamp as integer
153
-	 */
154
-	public function getLastModified() {
155
-		$timestamp = $this->info->getMtime();
156
-		if (!empty($timestamp)) {
157
-			return (int)$timestamp;
158
-		}
159
-		return $timestamp;
160
-	}
161
-
162
-	/**
163
-	 *  sets the last modification time of the file (mtime) to the value given
164
-	 *  in the second parameter or to now if the second param is empty.
165
-	 *  Even if the modification time is set to a custom value the access time is set to now.
166
-	 */
167
-	public function touch($mtime) {
168
-		$mtime = $this->sanitizeMtime($mtime);
169
-		$this->fileView->touch($this->path, $mtime);
170
-		$this->refreshInfo();
171
-	}
172
-
173
-	/**
174
-	 * Returns the ETag for a file
175
-	 *
176
-	 * An ETag is a unique identifier representing the current version of the
177
-	 * file. If the file changes, the ETag MUST change.  The ETag is an
178
-	 * arbitrary string, but MUST be surrounded by double-quotes.
179
-	 *
180
-	 * Return null if the ETag can not effectively be determined
181
-	 *
182
-	 * @return string
183
-	 */
184
-	public function getETag() {
185
-		return '"' . $this->info->getEtag() . '"';
186
-	}
187
-
188
-	/**
189
-	 * Sets the ETag
190
-	 *
191
-	 * @param string $etag
192
-	 *
193
-	 * @return int file id of updated file or -1 on failure
194
-	 */
195
-	public function setETag($etag) {
196
-		return $this->fileView->putFileInfo($this->path, array('etag' => $etag));
197
-	}
198
-
199
-	/**
200
-	 * Returns the size of the node, in bytes
201
-	 *
202
-	 * @return integer
203
-	 */
204
-	public function getSize() {
205
-		return $this->info->getSize();
206
-	}
207
-
208
-	/**
209
-	 * Returns the cache's file id
210
-	 *
211
-	 * @return int
212
-	 */
213
-	public function getId() {
214
-		return $this->info->getId();
215
-	}
216
-
217
-	/**
218
-	 * @return string|null
219
-	 */
220
-	public function getFileId() {
221
-		if ($this->info->getId()) {
222
-			$instanceId = \OC_Util::getInstanceId();
223
-			$id = sprintf('%08d', $this->info->getId());
224
-			return $id . $instanceId;
225
-		}
226
-
227
-		return null;
228
-	}
229
-
230
-	/**
231
-	 * @return integer
232
-	 */
233
-	public function getInternalFileId() {
234
-		return $this->info->getId();
235
-	}
236
-
237
-	/**
238
-	 * @param string $user
239
-	 * @return int
240
-	 */
241
-	public function getSharePermissions($user) {
242
-
243
-		// check of we access a federated share
244
-		if ($user !== null) {
245
-			try {
246
-				$share = $this->shareManager->getShareByToken($user);
247
-				return $share->getPermissions();
248
-			} catch (ShareNotFound $e) {
249
-				// ignore
250
-			}
251
-		}
252
-
253
-		$storage = $this->info->getStorage();
254
-
255
-		$path = $this->info->getInternalPath();
256
-
257
-		if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
258
-			/** @var \OCA\Files_Sharing\SharedStorage $storage */
259
-			$permissions = (int)$storage->getShare()->getPermissions();
260
-		} else {
261
-			$permissions = $storage->getPermissions($path);
262
-		}
263
-
264
-		/*
47
+    /**
48
+     * @var \OC\Files\View
49
+     */
50
+    protected $fileView;
51
+
52
+    /**
53
+     * The path to the current node
54
+     *
55
+     * @var string
56
+     */
57
+    protected $path;
58
+
59
+    /**
60
+     * node properties cache
61
+     *
62
+     * @var array
63
+     */
64
+    protected $property_cache = null;
65
+
66
+    /**
67
+     * @var \OCP\Files\FileInfo
68
+     */
69
+    protected $info;
70
+
71
+    /**
72
+     * @var IManager
73
+     */
74
+    protected $shareManager;
75
+
76
+    /**
77
+     * Sets up the node, expects a full path name
78
+     *
79
+     * @param \OC\Files\View $view
80
+     * @param \OCP\Files\FileInfo $info
81
+     * @param IManager $shareManager
82
+     */
83
+    public function __construct(View $view, FileInfo $info, IManager $shareManager = null) {
84
+        $this->fileView = $view;
85
+        $this->path = $this->fileView->getRelativePath($info->getPath());
86
+        $this->info = $info;
87
+        if ($shareManager) {
88
+            $this->shareManager = $shareManager;
89
+        } else {
90
+            $this->shareManager = \OC::$server->getShareManager();
91
+        }
92
+    }
93
+
94
+    protected function refreshInfo() {
95
+        $this->info = $this->fileView->getFileInfo($this->path);
96
+    }
97
+
98
+    /**
99
+     *  Returns the name of the node
100
+     *
101
+     * @return string
102
+     */
103
+    public function getName() {
104
+        return $this->info->getName();
105
+    }
106
+
107
+    /**
108
+     * Returns the full path
109
+     *
110
+     * @return string
111
+     */
112
+    public function getPath() {
113
+        return $this->path;
114
+    }
115
+
116
+    /**
117
+     * Renames the node
118
+     *
119
+     * @param string $name The new name
120
+     * @throws \Sabre\DAV\Exception\BadRequest
121
+     * @throws \Sabre\DAV\Exception\Forbidden
122
+     */
123
+    public function setName($name) {
124
+
125
+        // rename is only allowed if the update privilege is granted
126
+        if (!$this->info->isUpdateable()) {
127
+            throw new \Sabre\DAV\Exception\Forbidden();
128
+        }
129
+
130
+        list($parentPath,) = \Sabre\Uri\split($this->path);
131
+        list(, $newName) = \Sabre\Uri\split($name);
132
+
133
+        // verify path of the target
134
+        $this->verifyPath();
135
+
136
+        $newPath = $parentPath . '/' . $newName;
137
+
138
+        $this->fileView->rename($this->path, $newPath);
139
+
140
+        $this->path = $newPath;
141
+
142
+        $this->refreshInfo();
143
+    }
144
+
145
+    public function setPropertyCache($property_cache) {
146
+        $this->property_cache = $property_cache;
147
+    }
148
+
149
+    /**
150
+     * Returns the last modification time, as a unix timestamp
151
+     *
152
+     * @return int timestamp as integer
153
+     */
154
+    public function getLastModified() {
155
+        $timestamp = $this->info->getMtime();
156
+        if (!empty($timestamp)) {
157
+            return (int)$timestamp;
158
+        }
159
+        return $timestamp;
160
+    }
161
+
162
+    /**
163
+     *  sets the last modification time of the file (mtime) to the value given
164
+     *  in the second parameter or to now if the second param is empty.
165
+     *  Even if the modification time is set to a custom value the access time is set to now.
166
+     */
167
+    public function touch($mtime) {
168
+        $mtime = $this->sanitizeMtime($mtime);
169
+        $this->fileView->touch($this->path, $mtime);
170
+        $this->refreshInfo();
171
+    }
172
+
173
+    /**
174
+     * Returns the ETag for a file
175
+     *
176
+     * An ETag is a unique identifier representing the current version of the
177
+     * file. If the file changes, the ETag MUST change.  The ETag is an
178
+     * arbitrary string, but MUST be surrounded by double-quotes.
179
+     *
180
+     * Return null if the ETag can not effectively be determined
181
+     *
182
+     * @return string
183
+     */
184
+    public function getETag() {
185
+        return '"' . $this->info->getEtag() . '"';
186
+    }
187
+
188
+    /**
189
+     * Sets the ETag
190
+     *
191
+     * @param string $etag
192
+     *
193
+     * @return int file id of updated file or -1 on failure
194
+     */
195
+    public function setETag($etag) {
196
+        return $this->fileView->putFileInfo($this->path, array('etag' => $etag));
197
+    }
198
+
199
+    /**
200
+     * Returns the size of the node, in bytes
201
+     *
202
+     * @return integer
203
+     */
204
+    public function getSize() {
205
+        return $this->info->getSize();
206
+    }
207
+
208
+    /**
209
+     * Returns the cache's file id
210
+     *
211
+     * @return int
212
+     */
213
+    public function getId() {
214
+        return $this->info->getId();
215
+    }
216
+
217
+    /**
218
+     * @return string|null
219
+     */
220
+    public function getFileId() {
221
+        if ($this->info->getId()) {
222
+            $instanceId = \OC_Util::getInstanceId();
223
+            $id = sprintf('%08d', $this->info->getId());
224
+            return $id . $instanceId;
225
+        }
226
+
227
+        return null;
228
+    }
229
+
230
+    /**
231
+     * @return integer
232
+     */
233
+    public function getInternalFileId() {
234
+        return $this->info->getId();
235
+    }
236
+
237
+    /**
238
+     * @param string $user
239
+     * @return int
240
+     */
241
+    public function getSharePermissions($user) {
242
+
243
+        // check of we access a federated share
244
+        if ($user !== null) {
245
+            try {
246
+                $share = $this->shareManager->getShareByToken($user);
247
+                return $share->getPermissions();
248
+            } catch (ShareNotFound $e) {
249
+                // ignore
250
+            }
251
+        }
252
+
253
+        $storage = $this->info->getStorage();
254
+
255
+        $path = $this->info->getInternalPath();
256
+
257
+        if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
258
+            /** @var \OCA\Files_Sharing\SharedStorage $storage */
259
+            $permissions = (int)$storage->getShare()->getPermissions();
260
+        } else {
261
+            $permissions = $storage->getPermissions($path);
262
+        }
263
+
264
+        /*
265 265
 		 * We can always share non moveable mount points with DELETE and UPDATE
266 266
 		 * Eventually we need to do this properly
267 267
 		 */
268
-		$mountpoint = $this->info->getMountPoint();
269
-		if (!($mountpoint instanceof MoveableMount)) {
270
-			$mountpointpath = $mountpoint->getMountPoint();
271
-			if (substr($mountpointpath, -1) === '/') {
272
-				$mountpointpath = substr($mountpointpath, 0, -1);
273
-			}
274
-
275
-			if ($mountpointpath === $this->info->getPath()) {
276
-				$permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
277
-			}
278
-		}
279
-
280
-		/*
268
+        $mountpoint = $this->info->getMountPoint();
269
+        if (!($mountpoint instanceof MoveableMount)) {
270
+            $mountpointpath = $mountpoint->getMountPoint();
271
+            if (substr($mountpointpath, -1) === '/') {
272
+                $mountpointpath = substr($mountpointpath, 0, -1);
273
+            }
274
+
275
+            if ($mountpointpath === $this->info->getPath()) {
276
+                $permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
277
+            }
278
+        }
279
+
280
+        /*
281 281
 		 * Files can't have create or delete permissions
282 282
 		 */
283
-		if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) {
284
-			$permissions &= ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE);
285
-		}
286
-
287
-		return $permissions;
288
-	}
289
-
290
-	/**
291
-	 * @return string
292
-	 */
293
-	public function getDavPermissions() {
294
-		$p = '';
295
-		if ($this->info->isShared()) {
296
-			$p .= 'S';
297
-		}
298
-		if ($this->info->isShareable()) {
299
-			$p .= 'R';
300
-		}
301
-		if ($this->info->isMounted()) {
302
-			$p .= 'M';
303
-		}
304
-		if ($this->info->isReadable()) {
305
-			$p .= 'G';
306
-		}
307
-		if ($this->info->isDeletable()) {
308
-			$p .= 'D';
309
-		}
310
-		if ($this->info->isUpdateable()) {
311
-			$p .= 'NV'; // Renameable, Moveable
312
-		}
313
-		if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) {
314
-			if ($this->info->isUpdateable()) {
315
-				$p .= 'W';
316
-			}
317
-		} else {
318
-			if ($this->info->isCreatable()) {
319
-				$p .= 'CK';
320
-			}
321
-		}
322
-		return $p;
323
-	}
324
-
325
-	public function getOwner() {
326
-		return $this->info->getOwner();
327
-	}
328
-
329
-	protected function verifyPath() {
330
-		try {
331
-			$fileName = basename($this->info->getPath());
332
-			$this->fileView->verifyPath($this->path, $fileName);
333
-		} catch (\OCP\Files\InvalidPathException $ex) {
334
-			throw new InvalidPath($ex->getMessage());
335
-		}
336
-	}
337
-
338
-	/**
339
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
340
-	 */
341
-	public function acquireLock($type) {
342
-		$this->fileView->lockFile($this->path, $type);
343
-	}
344
-
345
-	/**
346
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
347
-	 */
348
-	public function releaseLock($type) {
349
-		$this->fileView->unlockFile($this->path, $type);
350
-	}
351
-
352
-	/**
353
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
354
-	 */
355
-	public function changeLock($type) {
356
-		$this->fileView->changeLock($this->path, $type);
357
-	}
358
-
359
-	public function getFileInfo() {
360
-		return $this->info;
361
-	}
362
-
363
-	protected function sanitizeMtime($mtimeFromRequest) {
364
-		// In PHP 5.X "is_numeric" returns true for strings in hexadecimal
365
-		// notation. This is no longer the case in PHP 7.X, so this check
366
-		// ensures that strings with hexadecimal notations fail too in PHP 5.X.
367
-		$isHexadecimal = is_string($mtimeFromRequest) && preg_match('/^\s*0[xX]/', $mtimeFromRequest);
368
-		if ($isHexadecimal || !is_numeric($mtimeFromRequest)) {
369
-			throw new \InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).');
370
-		}
371
-
372
-		return (int)$mtimeFromRequest;
373
-	}
283
+        if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) {
284
+            $permissions &= ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE);
285
+        }
286
+
287
+        return $permissions;
288
+    }
289
+
290
+    /**
291
+     * @return string
292
+     */
293
+    public function getDavPermissions() {
294
+        $p = '';
295
+        if ($this->info->isShared()) {
296
+            $p .= 'S';
297
+        }
298
+        if ($this->info->isShareable()) {
299
+            $p .= 'R';
300
+        }
301
+        if ($this->info->isMounted()) {
302
+            $p .= 'M';
303
+        }
304
+        if ($this->info->isReadable()) {
305
+            $p .= 'G';
306
+        }
307
+        if ($this->info->isDeletable()) {
308
+            $p .= 'D';
309
+        }
310
+        if ($this->info->isUpdateable()) {
311
+            $p .= 'NV'; // Renameable, Moveable
312
+        }
313
+        if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) {
314
+            if ($this->info->isUpdateable()) {
315
+                $p .= 'W';
316
+            }
317
+        } else {
318
+            if ($this->info->isCreatable()) {
319
+                $p .= 'CK';
320
+            }
321
+        }
322
+        return $p;
323
+    }
324
+
325
+    public function getOwner() {
326
+        return $this->info->getOwner();
327
+    }
328
+
329
+    protected function verifyPath() {
330
+        try {
331
+            $fileName = basename($this->info->getPath());
332
+            $this->fileView->verifyPath($this->path, $fileName);
333
+        } catch (\OCP\Files\InvalidPathException $ex) {
334
+            throw new InvalidPath($ex->getMessage());
335
+        }
336
+    }
337
+
338
+    /**
339
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
340
+     */
341
+    public function acquireLock($type) {
342
+        $this->fileView->lockFile($this->path, $type);
343
+    }
344
+
345
+    /**
346
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
347
+     */
348
+    public function releaseLock($type) {
349
+        $this->fileView->unlockFile($this->path, $type);
350
+    }
351
+
352
+    /**
353
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
354
+     */
355
+    public function changeLock($type) {
356
+        $this->fileView->changeLock($this->path, $type);
357
+    }
358
+
359
+    public function getFileInfo() {
360
+        return $this->info;
361
+    }
362
+
363
+    protected function sanitizeMtime($mtimeFromRequest) {
364
+        // In PHP 5.X "is_numeric" returns true for strings in hexadecimal
365
+        // notation. This is no longer the case in PHP 7.X, so this check
366
+        // ensures that strings with hexadecimal notations fail too in PHP 5.X.
367
+        $isHexadecimal = is_string($mtimeFromRequest) && preg_match('/^\s*0[xX]/', $mtimeFromRequest);
368
+        if ($isHexadecimal || !is_numeric($mtimeFromRequest)) {
369
+            throw new \InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).');
370
+        }
371
+
372
+        return (int)$mtimeFromRequest;
373
+    }
374 374
 
375 375
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -133,7 +133,7 @@  discard block
 block discarded – undo
133 133
 		// verify path of the target
134 134
 		$this->verifyPath();
135 135
 
136
-		$newPath = $parentPath . '/' . $newName;
136
+		$newPath = $parentPath.'/'.$newName;
137 137
 
138 138
 		$this->fileView->rename($this->path, $newPath);
139 139
 
@@ -154,7 +154,7 @@  discard block
 block discarded – undo
154 154
 	public function getLastModified() {
155 155
 		$timestamp = $this->info->getMtime();
156 156
 		if (!empty($timestamp)) {
157
-			return (int)$timestamp;
157
+			return (int) $timestamp;
158 158
 		}
159 159
 		return $timestamp;
160 160
 	}
@@ -182,7 +182,7 @@  discard block
 block discarded – undo
182 182
 	 * @return string
183 183
 	 */
184 184
 	public function getETag() {
185
-		return '"' . $this->info->getEtag() . '"';
185
+		return '"'.$this->info->getEtag().'"';
186 186
 	}
187 187
 
188 188
 	/**
@@ -221,7 +221,7 @@  discard block
 block discarded – undo
221 221
 		if ($this->info->getId()) {
222 222
 			$instanceId = \OC_Util::getInstanceId();
223 223
 			$id = sprintf('%08d', $this->info->getId());
224
-			return $id . $instanceId;
224
+			return $id.$instanceId;
225 225
 		}
226 226
 
227 227
 		return null;
@@ -256,7 +256,7 @@  discard block
 block discarded – undo
256 256
 
257 257
 		if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
258 258
 			/** @var \OCA\Files_Sharing\SharedStorage $storage */
259
-			$permissions = (int)$storage->getShare()->getPermissions();
259
+			$permissions = (int) $storage->getShare()->getPermissions();
260 260
 		} else {
261 261
 			$permissions = $storage->getPermissions($path);
262 262
 		}
@@ -369,7 +369,7 @@  discard block
 block discarded – undo
369 369
 			throw new \InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).');
370 370
 		}
371 371
 
372
-		return (int)$mtimeFromRequest;
372
+		return (int) $mtimeFromRequest;
373 373
 	}
374 374
 
375 375
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Search/Xml/Filter/OffsetFilter.php 2 patches
Indentation   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -29,17 +29,17 @@
 block discarded – undo
29 29
 
30 30
 class OffsetFilter implements XmlDeserializable {
31 31
 
32
-	/**
33
-	 * @param Reader $reader
34
-	 * @throws BadRequest
35
-	 * @return int
36
-	 */
37
-	static function xmlDeserialize(Reader $reader) {
38
-		$value = $reader->parseInnerTree();
39
-		if (!is_int($value) && !is_string($value)) {
40
-			throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}offset has illegal value');
41
-		}
32
+    /**
33
+     * @param Reader $reader
34
+     * @throws BadRequest
35
+     * @return int
36
+     */
37
+    static function xmlDeserialize(Reader $reader) {
38
+        $value = $reader->parseInnerTree();
39
+        if (!is_int($value) && !is_string($value)) {
40
+            throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}offset has illegal value');
41
+        }
42 42
 
43
-		return (int)$value;
44
-	}
43
+        return (int)$value;
44
+    }
45 45
 }
46 46
\ No newline at end of file
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -37,9 +37,9 @@
 block discarded – undo
37 37
 	static function xmlDeserialize(Reader $reader) {
38 38
 		$value = $reader->parseInnerTree();
39 39
 		if (!is_int($value) && !is_string($value)) {
40
-			throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}offset has illegal value');
40
+			throw new BadRequest('The {'.SearchPlugin::NS_Nextcloud.'}offset has illegal value');
41 41
 		}
42 42
 
43
-		return (int)$value;
43
+		return (int) $value;
44 44
 	}
45 45
 }
46 46
\ No newline at end of file
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Search/Xml/Filter/LimitFilter.php 2 patches
Indentation   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -29,17 +29,17 @@
 block discarded – undo
29 29
 
30 30
 class LimitFilter implements XmlDeserializable {
31 31
 
32
-	/**
33
-	 * @param Reader $reader
34
-	 * @throws BadRequest
35
-	 * @return int
36
-	 */
37
-	static function xmlDeserialize(Reader $reader) {
38
-		$value = $reader->parseInnerTree();
39
-		if (!is_int($value) && !is_string($value)) {
40
-			throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}limit has illegal value');
41
-		}
32
+    /**
33
+     * @param Reader $reader
34
+     * @throws BadRequest
35
+     * @return int
36
+     */
37
+    static function xmlDeserialize(Reader $reader) {
38
+        $value = $reader->parseInnerTree();
39
+        if (!is_int($value) && !is_string($value)) {
40
+            throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}limit has illegal value');
41
+        }
42 42
 
43
-		return (int)$value;
44
-	}
43
+        return (int)$value;
44
+    }
45 45
 }
46 46
\ No newline at end of file
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -37,9 +37,9 @@
 block discarded – undo
37 37
 	static function xmlDeserialize(Reader $reader) {
38 38
 		$value = $reader->parseInnerTree();
39 39
 		if (!is_int($value) && !is_string($value)) {
40
-			throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}limit has illegal value');
40
+			throw new BadRequest('The {'.SearchPlugin::NS_Nextcloud.'}limit has illegal value');
41 41
 		}
42 42
 
43
-		return (int)$value;
43
+		return (int) $value;
44 44
 	}
45 45
 }
46 46
\ No newline at end of file
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Expiration.php 2 patches
Indentation   +135 added lines, -135 removed lines patch added patch discarded remove patch
@@ -28,139 +28,139 @@
 block discarded – undo
28 28
 
29 29
 class Expiration {
30 30
 
31
-	// how long do we keep files in the trash bin if no other value is defined in the config file (unit: days)
32
-	const DEFAULT_RETENTION_OBLIGATION = 30;
33
-	const NO_OBLIGATION = -1;
34
-
35
-	/** @var ITimeFactory */
36
-	private $timeFactory;
37
-
38
-	/** @var string */
39
-	private $retentionObligation;
40
-
41
-	/** @var int */
42
-	private $minAge;
43
-
44
-	/** @var int */
45
-	private $maxAge;
46
-
47
-	/** @var bool */
48
-	private $canPurgeToSaveSpace;
49
-
50
-	public function __construct(IConfig $config,ITimeFactory $timeFactory){
51
-		$this->timeFactory = $timeFactory;
52
-		$this->retentionObligation = $config->getSystemValue('trashbin_retention_obligation', 'auto');
53
-
54
-		if ($this->retentionObligation !== 'disabled') {
55
-			$this->parseRetentionObligation();
56
-		}
57
-	}
58
-
59
-	/**
60
-	 * Is trashbin expiration enabled
61
-	 * @return bool
62
-	 */
63
-	public function isEnabled(){
64
-		return $this->retentionObligation !== 'disabled';
65
-	}
66
-
67
-	/**
68
-	 * Check if given timestamp in expiration range
69
-	 * @param int $timestamp
70
-	 * @param bool $quotaExceeded
71
-	 * @return bool
72
-	 */
73
-	public function isExpired($timestamp, $quotaExceeded = false){
74
-		// No expiration if disabled
75
-		if (!$this->isEnabled()) {
76
-			return false;
77
-		}
78
-
79
-		// Purge to save space (if allowed)
80
-		if ($quotaExceeded && $this->canPurgeToSaveSpace) {
81
-			return true;
82
-		}
83
-
84
-		$time = $this->timeFactory->getTime();
85
-		// Never expire dates in future e.g. misconfiguration or negative time
86
-		// adjustment
87
-		if ($time<$timestamp) {
88
-			return false;
89
-		}
90
-
91
-		// Purge as too old
92
-		if ($this->maxAge !== self::NO_OBLIGATION) {
93
-			$maxTimestamp = $time - ($this->maxAge * 86400);
94
-			$isOlderThanMax = $timestamp < $maxTimestamp;
95
-		} else {
96
-			$isOlderThanMax = false;
97
-		}
98
-
99
-		if ($this->minAge !== self::NO_OBLIGATION) {
100
-			// older than Min obligation and we are running out of quota?
101
-			$minTimestamp = $time - ($this->minAge * 86400);
102
-			$isMinReached = ($timestamp < $minTimestamp) && $quotaExceeded;
103
-		} else {
104
-			$isMinReached = false;
105
-		}
106
-
107
-		return $isOlderThanMax || $isMinReached;
108
-	}
109
-
110
-	/**
111
-	 * @return bool|int
112
-	 */
113
-	public function getMaxAgeAsTimestamp() {
114
-		$maxAge = false;
115
-		if ($this->isEnabled() && $this->maxAge !== self::NO_OBLIGATION) {
116
-			$time = $this->timeFactory->getTime();
117
-			$maxAge = $time - ($this->maxAge * 86400);
118
-		}
119
-		return $maxAge;
120
-	}
121
-
122
-	private function parseRetentionObligation(){
123
-		$splitValues = explode(',', $this->retentionObligation);
124
-		if (!isset($splitValues[0])) {
125
-			$minValue = self::DEFAULT_RETENTION_OBLIGATION;
126
-		} else {
127
-			$minValue = trim($splitValues[0]);
128
-		}
129
-
130
-		if (!isset($splitValues[1]) && $minValue === 'auto') {
131
-			$maxValue = 'auto';
132
-		} elseif (!isset($splitValues[1])) {
133
-			$maxValue = self::DEFAULT_RETENTION_OBLIGATION;
134
-		} else {
135
-			$maxValue = trim($splitValues[1]);
136
-		}
137
-
138
-		if ($minValue === 'auto' && $maxValue === 'auto') {
139
-			// Default: Keep for 30 days but delete anytime if space needed
140
-			$this->minAge = self::DEFAULT_RETENTION_OBLIGATION;
141
-			$this->maxAge = self::NO_OBLIGATION;
142
-			$this->canPurgeToSaveSpace = true;
143
-		} elseif ($minValue !== 'auto' && $maxValue === 'auto') {
144
-			// Keep for X days but delete anytime if space needed
145
-			$this->minAge = (int)$minValue;
146
-			$this->maxAge = self::NO_OBLIGATION;
147
-			$this->canPurgeToSaveSpace = true;
148
-		} elseif ($minValue === 'auto' && $maxValue !== 'auto') {
149
-			// Delete anytime if space needed, Delete all older than max automatically
150
-			$this->minAge = self::NO_OBLIGATION;
151
-			$this->maxAge = (int)$maxValue;
152
-			$this->canPurgeToSaveSpace = true;
153
-		} elseif ($minValue !== 'auto' && $maxValue !== 'auto') {
154
-			// Delete all older than max OR older than min if space needed
155
-
156
-			// Max < Min as per https://github.com/owncloud/core/issues/16300
157
-			if ($maxValue < $minValue) {
158
-				$maxValue = $minValue;
159
-			}
160
-
161
-			$this->minAge = (int)$minValue;
162
-			$this->maxAge = (int)$maxValue;
163
-			$this->canPurgeToSaveSpace = false;
164
-		}
165
-	}
31
+    // how long do we keep files in the trash bin if no other value is defined in the config file (unit: days)
32
+    const DEFAULT_RETENTION_OBLIGATION = 30;
33
+    const NO_OBLIGATION = -1;
34
+
35
+    /** @var ITimeFactory */
36
+    private $timeFactory;
37
+
38
+    /** @var string */
39
+    private $retentionObligation;
40
+
41
+    /** @var int */
42
+    private $minAge;
43
+
44
+    /** @var int */
45
+    private $maxAge;
46
+
47
+    /** @var bool */
48
+    private $canPurgeToSaveSpace;
49
+
50
+    public function __construct(IConfig $config,ITimeFactory $timeFactory){
51
+        $this->timeFactory = $timeFactory;
52
+        $this->retentionObligation = $config->getSystemValue('trashbin_retention_obligation', 'auto');
53
+
54
+        if ($this->retentionObligation !== 'disabled') {
55
+            $this->parseRetentionObligation();
56
+        }
57
+    }
58
+
59
+    /**
60
+     * Is trashbin expiration enabled
61
+     * @return bool
62
+     */
63
+    public function isEnabled(){
64
+        return $this->retentionObligation !== 'disabled';
65
+    }
66
+
67
+    /**
68
+     * Check if given timestamp in expiration range
69
+     * @param int $timestamp
70
+     * @param bool $quotaExceeded
71
+     * @return bool
72
+     */
73
+    public function isExpired($timestamp, $quotaExceeded = false){
74
+        // No expiration if disabled
75
+        if (!$this->isEnabled()) {
76
+            return false;
77
+        }
78
+
79
+        // Purge to save space (if allowed)
80
+        if ($quotaExceeded && $this->canPurgeToSaveSpace) {
81
+            return true;
82
+        }
83
+
84
+        $time = $this->timeFactory->getTime();
85
+        // Never expire dates in future e.g. misconfiguration or negative time
86
+        // adjustment
87
+        if ($time<$timestamp) {
88
+            return false;
89
+        }
90
+
91
+        // Purge as too old
92
+        if ($this->maxAge !== self::NO_OBLIGATION) {
93
+            $maxTimestamp = $time - ($this->maxAge * 86400);
94
+            $isOlderThanMax = $timestamp < $maxTimestamp;
95
+        } else {
96
+            $isOlderThanMax = false;
97
+        }
98
+
99
+        if ($this->minAge !== self::NO_OBLIGATION) {
100
+            // older than Min obligation and we are running out of quota?
101
+            $minTimestamp = $time - ($this->minAge * 86400);
102
+            $isMinReached = ($timestamp < $minTimestamp) && $quotaExceeded;
103
+        } else {
104
+            $isMinReached = false;
105
+        }
106
+
107
+        return $isOlderThanMax || $isMinReached;
108
+    }
109
+
110
+    /**
111
+     * @return bool|int
112
+     */
113
+    public function getMaxAgeAsTimestamp() {
114
+        $maxAge = false;
115
+        if ($this->isEnabled() && $this->maxAge !== self::NO_OBLIGATION) {
116
+            $time = $this->timeFactory->getTime();
117
+            $maxAge = $time - ($this->maxAge * 86400);
118
+        }
119
+        return $maxAge;
120
+    }
121
+
122
+    private function parseRetentionObligation(){
123
+        $splitValues = explode(',', $this->retentionObligation);
124
+        if (!isset($splitValues[0])) {
125
+            $minValue = self::DEFAULT_RETENTION_OBLIGATION;
126
+        } else {
127
+            $minValue = trim($splitValues[0]);
128
+        }
129
+
130
+        if (!isset($splitValues[1]) && $minValue === 'auto') {
131
+            $maxValue = 'auto';
132
+        } elseif (!isset($splitValues[1])) {
133
+            $maxValue = self::DEFAULT_RETENTION_OBLIGATION;
134
+        } else {
135
+            $maxValue = trim($splitValues[1]);
136
+        }
137
+
138
+        if ($minValue === 'auto' && $maxValue === 'auto') {
139
+            // Default: Keep for 30 days but delete anytime if space needed
140
+            $this->minAge = self::DEFAULT_RETENTION_OBLIGATION;
141
+            $this->maxAge = self::NO_OBLIGATION;
142
+            $this->canPurgeToSaveSpace = true;
143
+        } elseif ($minValue !== 'auto' && $maxValue === 'auto') {
144
+            // Keep for X days but delete anytime if space needed
145
+            $this->minAge = (int)$minValue;
146
+            $this->maxAge = self::NO_OBLIGATION;
147
+            $this->canPurgeToSaveSpace = true;
148
+        } elseif ($minValue === 'auto' && $maxValue !== 'auto') {
149
+            // Delete anytime if space needed, Delete all older than max automatically
150
+            $this->minAge = self::NO_OBLIGATION;
151
+            $this->maxAge = (int)$maxValue;
152
+            $this->canPurgeToSaveSpace = true;
153
+        } elseif ($minValue !== 'auto' && $maxValue !== 'auto') {
154
+            // Delete all older than max OR older than min if space needed
155
+
156
+            // Max < Min as per https://github.com/owncloud/core/issues/16300
157
+            if ($maxValue < $minValue) {
158
+                $maxValue = $minValue;
159
+            }
160
+
161
+            $this->minAge = (int)$minValue;
162
+            $this->maxAge = (int)$maxValue;
163
+            $this->canPurgeToSaveSpace = false;
164
+        }
165
+    }
166 166
 }
Please login to merge, or discard this patch.
Spacing   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -47,7 +47,7 @@  discard block
 block discarded – undo
47 47
 	/** @var bool */
48 48
 	private $canPurgeToSaveSpace;
49 49
 
50
-	public function __construct(IConfig $config,ITimeFactory $timeFactory){
50
+	public function __construct(IConfig $config, ITimeFactory $timeFactory) {
51 51
 		$this->timeFactory = $timeFactory;
52 52
 		$this->retentionObligation = $config->getSystemValue('trashbin_retention_obligation', 'auto');
53 53
 
@@ -60,7 +60,7 @@  discard block
 block discarded – undo
60 60
 	 * Is trashbin expiration enabled
61 61
 	 * @return bool
62 62
 	 */
63
-	public function isEnabled(){
63
+	public function isEnabled() {
64 64
 		return $this->retentionObligation !== 'disabled';
65 65
 	}
66 66
 
@@ -70,7 +70,7 @@  discard block
 block discarded – undo
70 70
 	 * @param bool $quotaExceeded
71 71
 	 * @return bool
72 72
 	 */
73
-	public function isExpired($timestamp, $quotaExceeded = false){
73
+	public function isExpired($timestamp, $quotaExceeded = false) {
74 74
 		// No expiration if disabled
75 75
 		if (!$this->isEnabled()) {
76 76
 			return false;
@@ -84,7 +84,7 @@  discard block
 block discarded – undo
84 84
 		$time = $this->timeFactory->getTime();
85 85
 		// Never expire dates in future e.g. misconfiguration or negative time
86 86
 		// adjustment
87
-		if ($time<$timestamp) {
87
+		if ($time < $timestamp) {
88 88
 			return false;
89 89
 		}
90 90
 
@@ -119,7 +119,7 @@  discard block
 block discarded – undo
119 119
 		return $maxAge;
120 120
 	}
121 121
 
122
-	private function parseRetentionObligation(){
122
+	private function parseRetentionObligation() {
123 123
 		$splitValues = explode(',', $this->retentionObligation);
124 124
 		if (!isset($splitValues[0])) {
125 125
 			$minValue = self::DEFAULT_RETENTION_OBLIGATION;
@@ -142,13 +142,13 @@  discard block
 block discarded – undo
142 142
 			$this->canPurgeToSaveSpace = true;
143 143
 		} elseif ($minValue !== 'auto' && $maxValue === 'auto') {
144 144
 			// Keep for X days but delete anytime if space needed
145
-			$this->minAge = (int)$minValue;
145
+			$this->minAge = (int) $minValue;
146 146
 			$this->maxAge = self::NO_OBLIGATION;
147 147
 			$this->canPurgeToSaveSpace = true;
148 148
 		} elseif ($minValue === 'auto' && $maxValue !== 'auto') {
149 149
 			// Delete anytime if space needed, Delete all older than max automatically
150 150
 			$this->minAge = self::NO_OBLIGATION;
151
-			$this->maxAge = (int)$maxValue;
151
+			$this->maxAge = (int) $maxValue;
152 152
 			$this->canPurgeToSaveSpace = true;
153 153
 		} elseif ($minValue !== 'auto' && $maxValue !== 'auto') {
154 154
 			// Delete all older than max OR older than min if space needed
@@ -158,8 +158,8 @@  discard block
 block discarded – undo
158 158
 				$maxValue = $minValue;
159 159
 			}
160 160
 
161
-			$this->minAge = (int)$minValue;
162
-			$this->maxAge = (int)$maxValue;
161
+			$this->minAge = (int) $minValue;
162
+			$this->maxAge = (int) $maxValue;
163 163
 			$this->canPurgeToSaveSpace = false;
164 164
 		}
165 165
 	}
Please login to merge, or discard this patch.
apps/files_versions/lib/Expiration.php 2 patches
Indentation   +169 added lines, -169 removed lines patch added patch discarded remove patch
@@ -27,173 +27,173 @@
 block discarded – undo
27 27
 
28 28
 class Expiration {
29 29
 
30
-	// how long do we keep files a version if no other value is defined in the config file (unit: days)
31
-	const NO_OBLIGATION = -1;
32
-
33
-	/** @var ITimeFactory */
34
-	private $timeFactory;
35
-
36
-	/** @var string */
37
-	private $retentionObligation;
38
-
39
-	/** @var int */
40
-	private $minAge;
41
-
42
-	/** @var int */
43
-	private $maxAge;
44
-
45
-	/** @var bool */
46
-	private $canPurgeToSaveSpace;
47
-
48
-	public function __construct(IConfig $config,ITimeFactory $timeFactory){
49
-		$this->timeFactory = $timeFactory;
50
-		$this->retentionObligation = $config->getSystemValue('versions_retention_obligation', 'auto');
51
-
52
-		if ($this->retentionObligation !== 'disabled') {
53
-			$this->parseRetentionObligation();
54
-		}
55
-	}
56
-
57
-	/**
58
-	 * Is versions expiration enabled
59
-	 * @return bool
60
-	 */
61
-	public function isEnabled(){
62
-		return $this->retentionObligation !== 'disabled';
63
-	}
64
-
65
-	/**
66
-	 * Is default expiration active
67
-	 */
68
-	public function shouldAutoExpire(){
69
-		return $this->minAge === self::NO_OBLIGATION
70
-				|| $this->maxAge === self::NO_OBLIGATION;
71
-	}
72
-
73
-	/**
74
-	 * Check if given timestamp in expiration range
75
-	 * @param int $timestamp
76
-	 * @param bool $quotaExceeded
77
-	 * @return bool
78
-	 */
79
-	public function isExpired($timestamp, $quotaExceeded = false){
80
-		// No expiration if disabled
81
-		if (!$this->isEnabled()) {
82
-			return false;
83
-		}
84
-
85
-		// Purge to save space (if allowed)
86
-		if ($quotaExceeded && $this->canPurgeToSaveSpace) {
87
-			return true;
88
-		}
89
-
90
-		$time = $this->timeFactory->getTime();
91
-		// Never expire dates in future e.g. misconfiguration or negative time
92
-		// adjustment
93
-		if ($time<$timestamp) {
94
-			return false;
95
-		}
96
-
97
-		// Purge as too old
98
-		if ($this->maxAge !== self::NO_OBLIGATION) {
99
-			$maxTimestamp = $time - ($this->maxAge * 86400);
100
-			$isOlderThanMax = $timestamp < $maxTimestamp;
101
-		} else {
102
-			$isOlderThanMax = false;
103
-		}
104
-
105
-		if ($this->minAge !== self::NO_OBLIGATION) {
106
-			// older than Min obligation and we are running out of quota?
107
-			$minTimestamp = $time - ($this->minAge * 86400);
108
-			$isMinReached = ($timestamp < $minTimestamp) && $quotaExceeded;
109
-		} else {
110
-			$isMinReached = false;
111
-		}
112
-
113
-		return $isOlderThanMax || $isMinReached;
114
-	}
115
-
116
-	/**
117
-	 * Get maximal retention obligation as a timestamp
118
-	 * @return int
119
-	 */
120
-	public function getMaxAgeAsTimestamp(){
121
-		$maxAge = false;
122
-		if ($this->isEnabled() && $this->maxAge !== self::NO_OBLIGATION) {
123
-			$time = $this->timeFactory->getTime();
124
-			$maxAge = $time - ($this->maxAge * 86400);
125
-		}
126
-		return $maxAge;
127
-	}
128
-
129
-	/**
130
-	* Read versions_retention_obligation, validate it 
131
-	* and set private members accordingly
132
-	*/
133
-	private function parseRetentionObligation(){
134
-		$splitValues = explode(',', $this->retentionObligation);
135
-		if (!isset($splitValues[0])) {
136
-			$minValue = 'auto';
137
-		} else {
138
-			$minValue = trim($splitValues[0]);
139
-		}
140
-
141
-		if (!isset($splitValues[1])) {
142
-			$maxValue = 'auto';
143
-		} else {
144
-			$maxValue = trim($splitValues[1]);
145
-		}
146
-
147
-		$isValid = true;
148
-		// Validate
149
-		if (!ctype_digit($minValue) && $minValue !== 'auto') {
150
-			$isValid = false;
151
-			\OC::$server->getLogger()->warning(
152
-					$minValue . ' is not a valid value for minimal versions retention obligation. Check versions_retention_obligation in your config.php. Falling back to auto.',
153
-					['app'=>'files_versions']
154
-			);
155
-		}
156
-
157
-		if (!ctype_digit($maxValue) && $maxValue !== 'auto') {
158
-			$isValid = false;
159
-			\OC::$server->getLogger()->warning(
160
-					$maxValue . ' is not a valid value for maximal versions retention obligation. Check versions_retention_obligation in your config.php. Falling back to auto.',
161
-					['app'=>'files_versions']
162
-			);
163
-		}
164
-
165
-		if (!$isValid){
166
-			$minValue = 'auto';
167
-			$maxValue = 'auto';
168
-		}
169
-
170
-
171
-		if ($minValue === 'auto' && $maxValue === 'auto') {
172
-			// Default: Delete anytime if space needed
173
-			$this->minAge = self::NO_OBLIGATION;
174
-			$this->maxAge = self::NO_OBLIGATION;
175
-			$this->canPurgeToSaveSpace = true;
176
-		} elseif ($minValue !== 'auto' && $maxValue === 'auto') {
177
-			// Keep for X days but delete anytime if space needed
178
-			$this->minAge = (int)$minValue;
179
-			$this->maxAge = self::NO_OBLIGATION;
180
-			$this->canPurgeToSaveSpace = true;
181
-		} elseif ($minValue === 'auto' && $maxValue !== 'auto') {
182
-			// Delete anytime if space needed, Delete all older than max automatically
183
-			$this->minAge = self::NO_OBLIGATION;
184
-			$this->maxAge = (int)$maxValue;
185
-			$this->canPurgeToSaveSpace = true;
186
-		} elseif ($minValue !== 'auto' && $maxValue !== 'auto') {
187
-			// Delete all older than max OR older than min if space needed
188
-
189
-			// Max < Min as per https://github.com/owncloud/core/issues/16301
190
-			if ($maxValue < $minValue) {
191
-				$maxValue = $minValue;
192
-			}
193
-
194
-			$this->minAge = (int)$minValue;
195
-			$this->maxAge = (int)$maxValue;
196
-			$this->canPurgeToSaveSpace = false;
197
-		}
198
-	}
30
+    // how long do we keep files a version if no other value is defined in the config file (unit: days)
31
+    const NO_OBLIGATION = -1;
32
+
33
+    /** @var ITimeFactory */
34
+    private $timeFactory;
35
+
36
+    /** @var string */
37
+    private $retentionObligation;
38
+
39
+    /** @var int */
40
+    private $minAge;
41
+
42
+    /** @var int */
43
+    private $maxAge;
44
+
45
+    /** @var bool */
46
+    private $canPurgeToSaveSpace;
47
+
48
+    public function __construct(IConfig $config,ITimeFactory $timeFactory){
49
+        $this->timeFactory = $timeFactory;
50
+        $this->retentionObligation = $config->getSystemValue('versions_retention_obligation', 'auto');
51
+
52
+        if ($this->retentionObligation !== 'disabled') {
53
+            $this->parseRetentionObligation();
54
+        }
55
+    }
56
+
57
+    /**
58
+     * Is versions expiration enabled
59
+     * @return bool
60
+     */
61
+    public function isEnabled(){
62
+        return $this->retentionObligation !== 'disabled';
63
+    }
64
+
65
+    /**
66
+     * Is default expiration active
67
+     */
68
+    public function shouldAutoExpire(){
69
+        return $this->minAge === self::NO_OBLIGATION
70
+                || $this->maxAge === self::NO_OBLIGATION;
71
+    }
72
+
73
+    /**
74
+     * Check if given timestamp in expiration range
75
+     * @param int $timestamp
76
+     * @param bool $quotaExceeded
77
+     * @return bool
78
+     */
79
+    public function isExpired($timestamp, $quotaExceeded = false){
80
+        // No expiration if disabled
81
+        if (!$this->isEnabled()) {
82
+            return false;
83
+        }
84
+
85
+        // Purge to save space (if allowed)
86
+        if ($quotaExceeded && $this->canPurgeToSaveSpace) {
87
+            return true;
88
+        }
89
+
90
+        $time = $this->timeFactory->getTime();
91
+        // Never expire dates in future e.g. misconfiguration or negative time
92
+        // adjustment
93
+        if ($time<$timestamp) {
94
+            return false;
95
+        }
96
+
97
+        // Purge as too old
98
+        if ($this->maxAge !== self::NO_OBLIGATION) {
99
+            $maxTimestamp = $time - ($this->maxAge * 86400);
100
+            $isOlderThanMax = $timestamp < $maxTimestamp;
101
+        } else {
102
+            $isOlderThanMax = false;
103
+        }
104
+
105
+        if ($this->minAge !== self::NO_OBLIGATION) {
106
+            // older than Min obligation and we are running out of quota?
107
+            $minTimestamp = $time - ($this->minAge * 86400);
108
+            $isMinReached = ($timestamp < $minTimestamp) && $quotaExceeded;
109
+        } else {
110
+            $isMinReached = false;
111
+        }
112
+
113
+        return $isOlderThanMax || $isMinReached;
114
+    }
115
+
116
+    /**
117
+     * Get maximal retention obligation as a timestamp
118
+     * @return int
119
+     */
120
+    public function getMaxAgeAsTimestamp(){
121
+        $maxAge = false;
122
+        if ($this->isEnabled() && $this->maxAge !== self::NO_OBLIGATION) {
123
+            $time = $this->timeFactory->getTime();
124
+            $maxAge = $time - ($this->maxAge * 86400);
125
+        }
126
+        return $maxAge;
127
+    }
128
+
129
+    /**
130
+     * Read versions_retention_obligation, validate it 
131
+     * and set private members accordingly
132
+     */
133
+    private function parseRetentionObligation(){
134
+        $splitValues = explode(',', $this->retentionObligation);
135
+        if (!isset($splitValues[0])) {
136
+            $minValue = 'auto';
137
+        } else {
138
+            $minValue = trim($splitValues[0]);
139
+        }
140
+
141
+        if (!isset($splitValues[1])) {
142
+            $maxValue = 'auto';
143
+        } else {
144
+            $maxValue = trim($splitValues[1]);
145
+        }
146
+
147
+        $isValid = true;
148
+        // Validate
149
+        if (!ctype_digit($minValue) && $minValue !== 'auto') {
150
+            $isValid = false;
151
+            \OC::$server->getLogger()->warning(
152
+                    $minValue . ' is not a valid value for minimal versions retention obligation. Check versions_retention_obligation in your config.php. Falling back to auto.',
153
+                    ['app'=>'files_versions']
154
+            );
155
+        }
156
+
157
+        if (!ctype_digit($maxValue) && $maxValue !== 'auto') {
158
+            $isValid = false;
159
+            \OC::$server->getLogger()->warning(
160
+                    $maxValue . ' is not a valid value for maximal versions retention obligation. Check versions_retention_obligation in your config.php. Falling back to auto.',
161
+                    ['app'=>'files_versions']
162
+            );
163
+        }
164
+
165
+        if (!$isValid){
166
+            $minValue = 'auto';
167
+            $maxValue = 'auto';
168
+        }
169
+
170
+
171
+        if ($minValue === 'auto' && $maxValue === 'auto') {
172
+            // Default: Delete anytime if space needed
173
+            $this->minAge = self::NO_OBLIGATION;
174
+            $this->maxAge = self::NO_OBLIGATION;
175
+            $this->canPurgeToSaveSpace = true;
176
+        } elseif ($minValue !== 'auto' && $maxValue === 'auto') {
177
+            // Keep for X days but delete anytime if space needed
178
+            $this->minAge = (int)$minValue;
179
+            $this->maxAge = self::NO_OBLIGATION;
180
+            $this->canPurgeToSaveSpace = true;
181
+        } elseif ($minValue === 'auto' && $maxValue !== 'auto') {
182
+            // Delete anytime if space needed, Delete all older than max automatically
183
+            $this->minAge = self::NO_OBLIGATION;
184
+            $this->maxAge = (int)$maxValue;
185
+            $this->canPurgeToSaveSpace = true;
186
+        } elseif ($minValue !== 'auto' && $maxValue !== 'auto') {
187
+            // Delete all older than max OR older than min if space needed
188
+
189
+            // Max < Min as per https://github.com/owncloud/core/issues/16301
190
+            if ($maxValue < $minValue) {
191
+                $maxValue = $minValue;
192
+            }
193
+
194
+            $this->minAge = (int)$minValue;
195
+            $this->maxAge = (int)$maxValue;
196
+            $this->canPurgeToSaveSpace = false;
197
+        }
198
+    }
199 199
 }
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -45,7 +45,7 @@  discard block
 block discarded – undo
45 45
 	/** @var bool */
46 46
 	private $canPurgeToSaveSpace;
47 47
 
48
-	public function __construct(IConfig $config,ITimeFactory $timeFactory){
48
+	public function __construct(IConfig $config, ITimeFactory $timeFactory) {
49 49
 		$this->timeFactory = $timeFactory;
50 50
 		$this->retentionObligation = $config->getSystemValue('versions_retention_obligation', 'auto');
51 51
 
@@ -58,14 +58,14 @@  discard block
 block discarded – undo
58 58
 	 * Is versions expiration enabled
59 59
 	 * @return bool
60 60
 	 */
61
-	public function isEnabled(){
61
+	public function isEnabled() {
62 62
 		return $this->retentionObligation !== 'disabled';
63 63
 	}
64 64
 
65 65
 	/**
66 66
 	 * Is default expiration active
67 67
 	 */
68
-	public function shouldAutoExpire(){
68
+	public function shouldAutoExpire() {
69 69
 		return $this->minAge === self::NO_OBLIGATION
70 70
 				|| $this->maxAge === self::NO_OBLIGATION;
71 71
 	}
@@ -76,7 +76,7 @@  discard block
 block discarded – undo
76 76
 	 * @param bool $quotaExceeded
77 77
 	 * @return bool
78 78
 	 */
79
-	public function isExpired($timestamp, $quotaExceeded = false){
79
+	public function isExpired($timestamp, $quotaExceeded = false) {
80 80
 		// No expiration if disabled
81 81
 		if (!$this->isEnabled()) {
82 82
 			return false;
@@ -90,7 +90,7 @@  discard block
 block discarded – undo
90 90
 		$time = $this->timeFactory->getTime();
91 91
 		// Never expire dates in future e.g. misconfiguration or negative time
92 92
 		// adjustment
93
-		if ($time<$timestamp) {
93
+		if ($time < $timestamp) {
94 94
 			return false;
95 95
 		}
96 96
 
@@ -117,7 +117,7 @@  discard block
 block discarded – undo
117 117
 	 * Get maximal retention obligation as a timestamp
118 118
 	 * @return int
119 119
 	 */
120
-	public function getMaxAgeAsTimestamp(){
120
+	public function getMaxAgeAsTimestamp() {
121 121
 		$maxAge = false;
122 122
 		if ($this->isEnabled() && $this->maxAge !== self::NO_OBLIGATION) {
123 123
 			$time = $this->timeFactory->getTime();
@@ -130,7 +130,7 @@  discard block
 block discarded – undo
130 130
 	* Read versions_retention_obligation, validate it 
131 131
 	* and set private members accordingly
132 132
 	*/
133
-	private function parseRetentionObligation(){
133
+	private function parseRetentionObligation() {
134 134
 		$splitValues = explode(',', $this->retentionObligation);
135 135
 		if (!isset($splitValues[0])) {
136 136
 			$minValue = 'auto';
@@ -149,7 +149,7 @@  discard block
 block discarded – undo
149 149
 		if (!ctype_digit($minValue) && $minValue !== 'auto') {
150 150
 			$isValid = false;
151 151
 			\OC::$server->getLogger()->warning(
152
-					$minValue . ' is not a valid value for minimal versions retention obligation. Check versions_retention_obligation in your config.php. Falling back to auto.',
152
+					$minValue.' is not a valid value for minimal versions retention obligation. Check versions_retention_obligation in your config.php. Falling back to auto.',
153 153
 					['app'=>'files_versions']
154 154
 			);
155 155
 		}
@@ -157,12 +157,12 @@  discard block
 block discarded – undo
157 157
 		if (!ctype_digit($maxValue) && $maxValue !== 'auto') {
158 158
 			$isValid = false;
159 159
 			\OC::$server->getLogger()->warning(
160
-					$maxValue . ' is not a valid value for maximal versions retention obligation. Check versions_retention_obligation in your config.php. Falling back to auto.',
160
+					$maxValue.' is not a valid value for maximal versions retention obligation. Check versions_retention_obligation in your config.php. Falling back to auto.',
161 161
 					['app'=>'files_versions']
162 162
 			);
163 163
 		}
164 164
 
165
-		if (!$isValid){
165
+		if (!$isValid) {
166 166
 			$minValue = 'auto';
167 167
 			$maxValue = 'auto';
168 168
 		}
@@ -175,13 +175,13 @@  discard block
 block discarded – undo
175 175
 			$this->canPurgeToSaveSpace = true;
176 176
 		} elseif ($minValue !== 'auto' && $maxValue === 'auto') {
177 177
 			// Keep for X days but delete anytime if space needed
178
-			$this->minAge = (int)$minValue;
178
+			$this->minAge = (int) $minValue;
179 179
 			$this->maxAge = self::NO_OBLIGATION;
180 180
 			$this->canPurgeToSaveSpace = true;
181 181
 		} elseif ($minValue === 'auto' && $maxValue !== 'auto') {
182 182
 			// Delete anytime if space needed, Delete all older than max automatically
183 183
 			$this->minAge = self::NO_OBLIGATION;
184
-			$this->maxAge = (int)$maxValue;
184
+			$this->maxAge = (int) $maxValue;
185 185
 			$this->canPurgeToSaveSpace = true;
186 186
 		} elseif ($minValue !== 'auto' && $maxValue !== 'auto') {
187 187
 			// Delete all older than max OR older than min if space needed
@@ -191,8 +191,8 @@  discard block
 block discarded – undo
191 191
 				$maxValue = $minValue;
192 192
 			}
193 193
 
194
-			$this->minAge = (int)$minValue;
195
-			$this->maxAge = (int)$maxValue;
194
+			$this->minAge = (int) $minValue;
195
+			$this->maxAge = (int) $maxValue;
196 196
 			$this->canPurgeToSaveSpace = false;
197 197
 		}
198 198
 	}
Please login to merge, or discard this patch.
apps/files_versions/lib/Storage.php 2 patches
Indentation   +792 added lines, -792 removed lines patch added patch discarded remove patch
@@ -54,797 +54,797 @@
 block discarded – undo
54 54
 
55 55
 class Storage {
56 56
 
57
-	const DEFAULTENABLED=true;
58
-	const DEFAULTMAXSIZE=50; // unit: percentage; 50% of available disk space/quota
59
-	const VERSIONS_ROOT = 'files_versions/';
60
-
61
-	const DELETE_TRIGGER_MASTER_REMOVED = 0;
62
-	const DELETE_TRIGGER_RETENTION_CONSTRAINT = 1;
63
-	const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;
64
-
65
-	// files for which we can remove the versions after the delete operation was successful
66
-	private static $deletedFiles = array();
67
-
68
-	private static $sourcePathAndUser = array();
69
-
70
-	private static $max_versions_per_interval = array(
71
-		//first 10sec, one version every 2sec
72
-		1 => array('intervalEndsAfter' => 10,      'step' => 2),
73
-		//next minute, one version every 10sec
74
-		2 => array('intervalEndsAfter' => 60,      'step' => 10),
75
-		//next hour, one version every minute
76
-		3 => array('intervalEndsAfter' => 3600,    'step' => 60),
77
-		//next 24h, one version every hour
78
-		4 => array('intervalEndsAfter' => 86400,   'step' => 3600),
79
-		//next 30days, one version per day
80
-		5 => array('intervalEndsAfter' => 2592000, 'step' => 86400),
81
-		//until the end one version per week
82
-		6 => array('intervalEndsAfter' => -1,      'step' => 604800),
83
-	);
84
-
85
-	/** @var \OCA\Files_Versions\AppInfo\Application */
86
-	private static $application;
87
-
88
-	/**
89
-	 * get the UID of the owner of the file and the path to the file relative to
90
-	 * owners files folder
91
-	 *
92
-	 * @param string $filename
93
-	 * @return array
94
-	 * @throws \OC\User\NoUserException
95
-	 */
96
-	public static function getUidAndFilename($filename) {
97
-		$uid = Filesystem::getOwner($filename);
98
-		$userManager = \OC::$server->getUserManager();
99
-		// if the user with the UID doesn't exists, e.g. because the UID points
100
-		// to a remote user with a federated cloud ID we use the current logged-in
101
-		// user. We need a valid local user to create the versions
102
-		if (!$userManager->userExists($uid)) {
103
-			$uid = User::getUser();
104
-		}
105
-		Filesystem::initMountPoints($uid);
106
-		if ( $uid !== User::getUser() ) {
107
-			$info = Filesystem::getFileInfo($filename);
108
-			$ownerView = new View('/'.$uid.'/files');
109
-			try {
110
-				$filename = $ownerView->getPath($info['fileid']);
111
-				// make sure that the file name doesn't end with a trailing slash
112
-				// can for example happen single files shared across servers
113
-				$filename = rtrim($filename, '/');
114
-			} catch (NotFoundException $e) {
115
-				$filename = null;
116
-			}
117
-		}
118
-		return [$uid, $filename];
119
-	}
120
-
121
-	/**
122
-	 * Remember the owner and the owner path of the source file
123
-	 *
124
-	 * @param string $source source path
125
-	 */
126
-	public static function setSourcePathAndUser($source) {
127
-		list($uid, $path) = self::getUidAndFilename($source);
128
-		self::$sourcePathAndUser[$source] = array('uid' => $uid, 'path' => $path);
129
-	}
130
-
131
-	/**
132
-	 * Gets the owner and the owner path from the source path
133
-	 *
134
-	 * @param string $source source path
135
-	 * @return array with user id and path
136
-	 */
137
-	public static function getSourcePathAndUser($source) {
138
-
139
-		if (isset(self::$sourcePathAndUser[$source])) {
140
-			$uid = self::$sourcePathAndUser[$source]['uid'];
141
-			$path = self::$sourcePathAndUser[$source]['path'];
142
-			unset(self::$sourcePathAndUser[$source]);
143
-		} else {
144
-			$uid = $path = false;
145
-		}
146
-		return array($uid, $path);
147
-	}
148
-
149
-	/**
150
-	 * get current size of all versions from a given user
151
-	 *
152
-	 * @param string $user user who owns the versions
153
-	 * @return int versions size
154
-	 */
155
-	private static function getVersionsSize($user) {
156
-		$view = new View('/' . $user);
157
-		$fileInfo = $view->getFileInfo('/files_versions');
158
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
159
-	}
160
-
161
-	/**
162
-	 * store a new version of a file.
163
-	 */
164
-	public static function store($filename) {
165
-
166
-		// if the file gets streamed we need to remove the .part extension
167
-		// to get the right target
168
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
169
-		if ($ext === 'part') {
170
-			$filename = substr($filename, 0, strlen($filename) - 5);
171
-		}
172
-
173
-		// we only handle existing files
174
-		if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
175
-			return false;
176
-		}
177
-
178
-		list($uid, $filename) = self::getUidAndFilename($filename);
179
-
180
-		$files_view = new View('/'.$uid .'/files');
181
-		$users_view = new View('/'.$uid);
182
-
183
-		$eventDispatcher = \OC::$server->getEventDispatcher();
184
-		$id = $files_view->getFileInfo($filename)->getId();
185
-		$nodes = \OC::$server->getRootFolder()->getById($id);
186
-		foreach ($nodes as $node) {
187
-			$event = new CreateVersionEvent($node);
188
-			$eventDispatcher->dispatch('OCA\Files_Versions::createVersion', $event);
189
-			if ($event->shouldCreateVersion() === false) {
190
-				return false;
191
-			}
192
-		}
193
-
194
-		// no use making versions for empty files
195
-		if ($files_view->filesize($filename) === 0) {
196
-			return false;
197
-		}
198
-
199
-		// create all parent folders
200
-		self::createMissingDirectories($filename, $users_view);
201
-
202
-		self::scheduleExpire($uid, $filename);
203
-
204
-		// store a new version of a file
205
-		$mtime = $users_view->filemtime('files/' . $filename);
206
-		$users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
207
-		// call getFileInfo to enforce a file cache entry for the new version
208
-		$users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
209
-	}
210
-
211
-
212
-	/**
213
-	 * mark file as deleted so that we can remove the versions if the file is gone
214
-	 * @param string $path
215
-	 */
216
-	public static function markDeletedFile($path) {
217
-		list($uid, $filename) = self::getUidAndFilename($path);
218
-		self::$deletedFiles[$path] = array(
219
-			'uid' => $uid,
220
-			'filename' => $filename);
221
-	}
222
-
223
-	/**
224
-	 * delete the version from the storage and cache
225
-	 *
226
-	 * @param View $view
227
-	 * @param string $path
228
-	 */
229
-	protected static function deleteVersion($view, $path) {
230
-		$view->unlink($path);
231
-		/**
232
-		 * @var \OC\Files\Storage\Storage $storage
233
-		 * @var string $internalPath
234
-		 */
235
-		list($storage, $internalPath) = $view->resolvePath($path);
236
-		$cache = $storage->getCache($internalPath);
237
-		$cache->remove($internalPath);
238
-	}
239
-
240
-	/**
241
-	 * Delete versions of a file
242
-	 */
243
-	public static function delete($path) {
244
-
245
-		$deletedFile = self::$deletedFiles[$path];
246
-		$uid = $deletedFile['uid'];
247
-		$filename = $deletedFile['filename'];
248
-
249
-		if (!Filesystem::file_exists($path)) {
250
-
251
-			$view = new View('/' . $uid . '/files_versions');
252
-
253
-			$versions = self::getVersions($uid, $filename);
254
-			if (!empty($versions)) {
255
-				foreach ($versions as $v) {
256
-					\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
257
-					self::deleteVersion($view, $filename . '.v' . $v['version']);
258
-					\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
259
-				}
260
-			}
261
-		}
262
-		unset(self::$deletedFiles[$path]);
263
-	}
264
-
265
-	/**
266
-	 * Rename or copy versions of a file of the given paths
267
-	 *
268
-	 * @param string $sourcePath source path of the file to move, relative to
269
-	 * the currently logged in user's "files" folder
270
-	 * @param string $targetPath target path of the file to move, relative to
271
-	 * the currently logged in user's "files" folder
272
-	 * @param string $operation can be 'copy' or 'rename'
273
-	 */
274
-	public static function renameOrCopy($sourcePath, $targetPath, $operation) {
275
-		list($sourceOwner, $sourcePath) = self::getSourcePathAndUser($sourcePath);
276
-
277
-		// it was a upload of a existing file if no old path exists
278
-		// in this case the pre-hook already called the store method and we can
279
-		// stop here
280
-		if ($sourcePath === false) {
281
-			return true;
282
-		}
283
-
284
-		list($targetOwner, $targetPath) = self::getUidAndFilename($targetPath);
285
-
286
-		$sourcePath = ltrim($sourcePath, '/');
287
-		$targetPath = ltrim($targetPath, '/');
288
-
289
-		$rootView = new View('');
290
-
291
-		// did we move a directory ?
292
-		if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
293
-			// does the directory exists for versions too ?
294
-			if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
295
-				// create missing dirs if necessary
296
-				self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
297
-
298
-				// move the directory containing the versions
299
-				$rootView->$operation(
300
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath,
301
-					'/' . $targetOwner . '/files_versions/' . $targetPath
302
-				);
303
-			}
304
-		} else if ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
305
-			// create missing dirs if necessary
306
-			self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
307
-
308
-			foreach ($versions as $v) {
309
-				// move each version one by one to the target directory
310
-				$rootView->$operation(
311
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
312
-					'/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
313
-				);
314
-			}
315
-		}
316
-
317
-		// if we moved versions directly for a file, schedule expiration check for that file
318
-		if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
319
-			self::scheduleExpire($targetOwner, $targetPath);
320
-		}
321
-
322
-	}
323
-
324
-	/**
325
-	 * Rollback to an old version of a file.
326
-	 *
327
-	 * @param string $file file name
328
-	 * @param int $revision revision timestamp
329
-	 * @return bool
330
-	 */
331
-	public static function rollback($file, $revision) {
332
-
333
-		// add expected leading slash
334
-		$file = '/' . ltrim($file, '/');
335
-		list($uid, $filename) = self::getUidAndFilename($file);
336
-		if ($uid === null || trim($filename, '/') === '') {
337
-			return false;
338
-		}
339
-
340
-		$users_view = new View('/'.$uid);
341
-		$files_view = new View('/'. User::getUser().'/files');
342
-
343
-		$versionCreated = false;
344
-
345
-		$fileInfo = $files_view->getFileInfo($file);
346
-
347
-		// check if user has the permissions to revert a version
348
-		if (!$fileInfo->isUpdateable()) {
349
-			return false;
350
-		}
351
-
352
-		//first create a new version
353
-		$version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename);
354
-		if (!$users_view->file_exists($version)) {
355
-			$users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename));
356
-			$versionCreated = true;
357
-		}
358
-
359
-		$fileToRestore =  'files_versions' . $filename . '.v' . $revision;
360
-
361
-		// Restore encrypted version of the old file for the newly restored file
362
-		// This has to happen manually here since the file is manually copied below
363
-		$oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion();
364
-		$oldFileInfo = $users_view->getFileInfo($fileToRestore);
365
-		$cache = $fileInfo->getStorage()->getCache();
366
-		$cache->update(
367
-			$fileInfo->getId(), [
368
-				'encrypted' => $oldVersion,
369
-				'encryptedVersion' => $oldVersion,
370
-				'size' => $oldFileInfo->getSize()
371
-			]
372
-		);
373
-
374
-		// rollback
375
-		if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
376
-			$files_view->touch($file, $revision);
377
-			Storage::scheduleExpire($uid, $file);
378
-			\OC_Hook::emit('\OCP\Versions', 'rollback', array(
379
-				'path' => $filename,
380
-				'revision' => $revision,
381
-			));
382
-			return true;
383
-		} else if ($versionCreated) {
384
-			self::deleteVersion($users_view, $version);
385
-		}
386
-
387
-		return false;
388
-
389
-	}
390
-
391
-	/**
392
-	 * Stream copy file contents from $path1 to $path2
393
-	 *
394
-	 * @param View $view view to use for copying
395
-	 * @param string $path1 source file to copy
396
-	 * @param string $path2 target file
397
-	 *
398
-	 * @return bool true for success, false otherwise
399
-	 */
400
-	private static function copyFileContents($view, $path1, $path2) {
401
-		/** @var \OC\Files\Storage\Storage $storage1 */
402
-		list($storage1, $internalPath1) = $view->resolvePath($path1);
403
-		/** @var \OC\Files\Storage\Storage $storage2 */
404
-		list($storage2, $internalPath2) = $view->resolvePath($path2);
405
-
406
-		$view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
407
-		$view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
408
-
409
-		// TODO add a proper way of overwriting a file while maintaining file ids
410
-		if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
411
-			$source = $storage1->fopen($internalPath1, 'r');
412
-			$target = $storage2->fopen($internalPath2, 'w');
413
-			list(, $result) = \OC_Helper::streamCopy($source, $target);
414
-			fclose($source);
415
-			fclose($target);
416
-
417
-			if ($result !== false) {
418
-				$storage1->unlink($internalPath1);
419
-			}
420
-		} else {
421
-			$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
422
-		}
423
-
424
-		$view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
425
-		$view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
426
-
427
-		return ($result !== false);
428
-	}
429
-
430
-	/**
431
-	 * get a list of all available versions of a file in descending chronological order
432
-	 * @param string $uid user id from the owner of the file
433
-	 * @param string $filename file to find versions of, relative to the user files dir
434
-	 * @param string $userFullPath
435
-	 * @return array versions newest version first
436
-	 */
437
-	public static function getVersions($uid, $filename, $userFullPath = '') {
438
-		$versions = array();
439
-		if (empty($filename)) {
440
-			return $versions;
441
-		}
442
-		// fetch for old versions
443
-		$view = new View('/' . $uid . '/');
444
-
445
-		$pathinfo = pathinfo($filename);
446
-		$versionedFile = $pathinfo['basename'];
447
-
448
-		$dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
449
-
450
-		$dirContent = false;
451
-		if ($view->is_dir($dir)) {
452
-			$dirContent = $view->opendir($dir);
453
-		}
454
-
455
-		if ($dirContent === false) {
456
-			return $versions;
457
-		}
458
-
459
-		if (is_resource($dirContent)) {
460
-			while (($entryName = readdir($dirContent)) !== false) {
461
-				if (!Filesystem::isIgnoredDir($entryName)) {
462
-					$pathparts = pathinfo($entryName);
463
-					$filename = $pathparts['filename'];
464
-					if ($filename === $versionedFile) {
465
-						$pathparts = pathinfo($entryName);
466
-						$timestamp = substr($pathparts['extension'], 1);
467
-						$filename = $pathparts['filename'];
468
-						$key = $timestamp . '#' . $filename;
469
-						$versions[$key]['version'] = $timestamp;
470
-						$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471
-						if (empty($userFullPath)) {
472
-							$versions[$key]['preview'] = '';
473
-						} else {
474
-							$versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475
-						}
476
-						$versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
477
-						$versions[$key]['name'] = $versionedFile;
478
-						$versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
479
-						$versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480
-					}
481
-				}
482
-			}
483
-			closedir($dirContent);
484
-		}
485
-
486
-		// sort with newest version first
487
-		krsort($versions);
488
-
489
-		return $versions;
490
-	}
491
-
492
-	/**
493
-	 * Expire versions that older than max version retention time
494
-	 * @param string $uid
495
-	 */
496
-	public static function expireOlderThanMaxForUser($uid){
497
-		$expiration = self::getExpiration();
498
-		$threshold = $expiration->getMaxAgeAsTimestamp();
499
-		$versions = self::getAllVersions($uid);
500
-		if (!$threshold || !array_key_exists('all', $versions)) {
501
-			return;
502
-		}
503
-
504
-		$toDelete = [];
505
-		foreach (array_reverse($versions['all']) as $key => $version) {
506
-			if ((int)$version['version'] <$threshold) {
507
-				$toDelete[$key] = $version;
508
-			} else {
509
-				//Versions are sorted by time - nothing mo to iterate.
510
-				break;
511
-			}
512
-		}
513
-
514
-		$view = new View('/' . $uid . '/files_versions');
515
-		if (!empty($toDelete)) {
516
-			foreach ($toDelete as $version) {
517
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
518
-				self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
519
-				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
520
-			}
521
-		}
522
-	}
523
-
524
-	/**
525
-	 * translate a timestamp into a string like "5 days ago"
526
-	 * @param int $timestamp
527
-	 * @return string for example "5 days ago"
528
-	 */
529
-	private static function getHumanReadableTimestamp($timestamp) {
530
-
531
-		$diff = time() - $timestamp;
532
-
533
-		if ($diff < 60) { // first minute
534
-			return  $diff . " seconds ago";
535
-		} elseif ($diff < 3600) { //first hour
536
-			return round($diff / 60) . " minutes ago";
537
-		} elseif ($diff < 86400) { // first day
538
-			return round($diff / 3600) . " hours ago";
539
-		} elseif ($diff < 604800) { //first week
540
-			return round($diff / 86400) . " days ago";
541
-		} elseif ($diff < 2419200) { //first month
542
-			return round($diff / 604800) . " weeks ago";
543
-		} elseif ($diff < 29030400) { // first year
544
-			return round($diff / 2419200) . " months ago";
545
-		} else {
546
-			return round($diff / 29030400) . " years ago";
547
-		}
548
-
549
-	}
550
-
551
-	/**
552
-	 * returns all stored file versions from a given user
553
-	 * @param string $uid id of the user
554
-	 * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
555
-	 */
556
-	private static function getAllVersions($uid) {
557
-		$view = new View('/' . $uid . '/');
558
-		$dirs = array(self::VERSIONS_ROOT);
559
-		$versions = array();
560
-
561
-		while (!empty($dirs)) {
562
-			$dir = array_pop($dirs);
563
-			$files = $view->getDirectoryContent($dir);
564
-
565
-			foreach ($files as $file) {
566
-				$fileData = $file->getData();
567
-				$filePath = $dir . '/' . $fileData['name'];
568
-				if ($file['type'] === 'dir') {
569
-					array_push($dirs, $filePath);
570
-				} else {
571
-					$versionsBegin = strrpos($filePath, '.v');
572
-					$relPathStart = strlen(self::VERSIONS_ROOT);
573
-					$version = substr($filePath, $versionsBegin + 2);
574
-					$relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
575
-					$key = $version . '#' . $relpath;
576
-					$versions[$key] = array('path' => $relpath, 'timestamp' => $version);
577
-				}
578
-			}
579
-		}
580
-
581
-		// newest version first
582
-		krsort($versions);
583
-
584
-		$result = array();
585
-
586
-		foreach ($versions as $key => $value) {
587
-			$size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']);
588
-			$filename = $value['path'];
589
-
590
-			$result['all'][$key]['version'] = $value['timestamp'];
591
-			$result['all'][$key]['path'] = $filename;
592
-			$result['all'][$key]['size'] = $size;
593
-
594
-			$result['by_file'][$filename][$key]['version'] = $value['timestamp'];
595
-			$result['by_file'][$filename][$key]['path'] = $filename;
596
-			$result['by_file'][$filename][$key]['size'] = $size;
597
-		}
598
-
599
-		return $result;
600
-	}
601
-
602
-	/**
603
-	 * get list of files we want to expire
604
-	 * @param array $versions list of versions
605
-	 * @param integer $time
606
-	 * @param bool $quotaExceeded is versions storage limit reached
607
-	 * @return array containing the list of to deleted versions and the size of them
608
-	 */
609
-	protected static function getExpireList($time, $versions, $quotaExceeded = false) {
610
-		$expiration = self::getExpiration();
611
-
612
-		if ($expiration->shouldAutoExpire()) {
613
-			list($toDelete, $size) = self::getAutoExpireList($time, $versions);
614
-		} else {
615
-			$size = 0;
616
-			$toDelete = [];  // versions we want to delete
617
-		}
618
-
619
-		foreach ($versions as $key => $version) {
620
-			if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
621
-				$size += $version['size'];
622
-				$toDelete[$key] = $version['path'] . '.v' . $version['version'];
623
-			}
624
-		}
625
-
626
-		return [$toDelete, $size];
627
-	}
628
-
629
-	/**
630
-	 * get list of files we want to expire
631
-	 * @param array $versions list of versions
632
-	 * @param integer $time
633
-	 * @return array containing the list of to deleted versions and the size of them
634
-	 */
635
-	protected static function getAutoExpireList($time, $versions) {
636
-		$size = 0;
637
-		$toDelete = array();  // versions we want to delete
638
-
639
-		$interval = 1;
640
-		$step = Storage::$max_versions_per_interval[$interval]['step'];
641
-		if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
642
-			$nextInterval = -1;
643
-		} else {
644
-			$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
645
-		}
646
-
647
-		$firstVersion = reset($versions);
648
-		$firstKey = key($versions);
649
-		$prevTimestamp = $firstVersion['version'];
650
-		$nextVersion = $firstVersion['version'] - $step;
651
-		unset($versions[$firstKey]);
652
-
653
-		foreach ($versions as $key => $version) {
654
-			$newInterval = true;
655
-			while ($newInterval) {
656
-				if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
657
-					if ($version['version'] > $nextVersion) {
658
-						//distance between two version too small, mark to delete
659
-						$toDelete[$key] = $version['path'] . '.v' . $version['version'];
660
-						$size += $version['size'];
661
-						\OCP\Util::writeLog('files_versions', 'Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, \OCP\Util::INFO);
662
-					} else {
663
-						$nextVersion = $version['version'] - $step;
664
-						$prevTimestamp = $version['version'];
665
-					}
666
-					$newInterval = false; // version checked so we can move to the next one
667
-				} else { // time to move on to the next interval
668
-					$interval++;
669
-					$step = Storage::$max_versions_per_interval[$interval]['step'];
670
-					$nextVersion = $prevTimestamp - $step;
671
-					if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
672
-						$nextInterval = -1;
673
-					} else {
674
-						$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
675
-					}
676
-					$newInterval = true; // we changed the interval -> check same version with new interval
677
-				}
678
-			}
679
-		}
680
-
681
-		return array($toDelete, $size);
682
-	}
683
-
684
-	/**
685
-	 * Schedule versions expiration for the given file
686
-	 *
687
-	 * @param string $uid owner of the file
688
-	 * @param string $fileName file/folder for which to schedule expiration
689
-	 */
690
-	private static function scheduleExpire($uid, $fileName) {
691
-		// let the admin disable auto expire
692
-		$expiration = self::getExpiration();
693
-		if ($expiration->isEnabled()) {
694
-			$command = new Expire($uid, $fileName);
695
-			\OC::$server->getCommandBus()->push($command);
696
-		}
697
-	}
698
-
699
-	/**
700
-	 * Expire versions which exceed the quota.
701
-	 *
702
-	 * This will setup the filesystem for the given user but will not
703
-	 * tear it down afterwards.
704
-	 *
705
-	 * @param string $filename path to file to expire
706
-	 * @param string $uid user for which to expire the version
707
-	 * @return bool|int|null
708
-	 */
709
-	public static function expire($filename, $uid) {
710
-		$expiration = self::getExpiration();
711
-
712
-		if ($expiration->isEnabled()) {
713
-			// get available disk space for user
714
-			$user = \OC::$server->getUserManager()->get($uid);
715
-			if (is_null($user)) {
716
-				\OCP\Util::writeLog('files_versions', 'Backends provided no user object for ' . $uid, \OCP\Util::ERROR);
717
-				throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
718
-			}
719
-
720
-			\OC_Util::setupFS($uid);
721
-
722
-			if (!Filesystem::file_exists($filename)) {
723
-				return false;
724
-			}
725
-
726
-			if (empty($filename)) {
727
-				// file maybe renamed or deleted
728
-				return false;
729
-			}
730
-			$versionsFileview = new View('/'.$uid.'/files_versions');
731
-
732
-			$softQuota = true;
733
-			$quota = $user->getQuota();
734
-			if ( $quota === null || $quota === 'none' ) {
735
-				$quota = Filesystem::free_space('/');
736
-				$softQuota = false;
737
-			} else {
738
-				$quota = \OCP\Util::computerFileSize($quota);
739
-			}
740
-
741
-			// make sure that we have the current size of the version history
742
-			$versionsSize = self::getVersionsSize($uid);
743
-
744
-			// calculate available space for version history
745
-			// subtract size of files and current versions size from quota
746
-			if ($quota >= 0) {
747
-				if ($softQuota) {
748
-					$files_view = new View('/' . $uid . '/files');
749
-					$rootInfo = $files_view->getFileInfo('/', false);
750
-					$free = $quota - $rootInfo['size']; // remaining free space for user
751
-					if ($free > 0) {
752
-						$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $versionsSize; // how much space can be used for versions
753
-					} else {
754
-						$availableSpace = $free - $versionsSize;
755
-					}
756
-				} else {
757
-					$availableSpace = $quota;
758
-				}
759
-			} else {
760
-				$availableSpace = PHP_INT_MAX;
761
-			}
762
-
763
-			$allVersions = Storage::getVersions($uid, $filename);
764
-
765
-			$time = time();
766
-			list($toDelete, $sizeOfDeletedVersions) = self::getExpireList($time, $allVersions, $availableSpace <= 0);
767
-
768
-			$availableSpace = $availableSpace + $sizeOfDeletedVersions;
769
-			$versionsSize = $versionsSize - $sizeOfDeletedVersions;
770
-
771
-			// if still not enough free space we rearrange the versions from all files
772
-			if ($availableSpace <= 0) {
773
-				$result = Storage::getAllVersions($uid);
774
-				$allVersions = $result['all'];
775
-
776
-				foreach ($result['by_file'] as $versions) {
777
-					list($toDeleteNew, $size) = self::getExpireList($time, $versions, $availableSpace <= 0);
778
-					$toDelete = array_merge($toDelete, $toDeleteNew);
779
-					$sizeOfDeletedVersions += $size;
780
-				}
781
-				$availableSpace = $availableSpace + $sizeOfDeletedVersions;
782
-				$versionsSize = $versionsSize - $sizeOfDeletedVersions;
783
-			}
784
-
785
-			foreach($toDelete as $key => $path) {
786
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
787
-				self::deleteVersion($versionsFileview, $path);
788
-				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
789
-				unset($allVersions[$key]); // update array with the versions we keep
790
-				\OCP\Util::writeLog('files_versions', "Expire: " . $path, \OCP\Util::INFO);
791
-			}
792
-
793
-			// Check if enough space is available after versions are rearranged.
794
-			// If not we delete the oldest versions until we meet the size limit for versions,
795
-			// but always keep the two latest versions
796
-			$numOfVersions = count($allVersions) -2 ;
797
-			$i = 0;
798
-			// sort oldest first and make sure that we start at the first element
799
-			ksort($allVersions);
800
-			reset($allVersions);
801
-			while ($availableSpace < 0 && $i < $numOfVersions) {
802
-				$version = current($allVersions);
803
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
804
-				self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
805
-				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
806
-				\OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'] , \OCP\Util::INFO);
807
-				$versionsSize -= $version['size'];
808
-				$availableSpace += $version['size'];
809
-				next($allVersions);
810
-				$i++;
811
-			}
812
-
813
-			return $versionsSize; // finally return the new size of the version history
814
-		}
815
-
816
-		return false;
817
-	}
818
-
819
-	/**
820
-	 * Create recursively missing directories inside of files_versions
821
-	 * that match the given path to a file.
822
-	 *
823
-	 * @param string $filename $path to a file, relative to the user's
824
-	 * "files" folder
825
-	 * @param View $view view on data/user/
826
-	 */
827
-	private static function createMissingDirectories($filename, $view) {
828
-		$dirname = Filesystem::normalizePath(dirname($filename));
829
-		$dirParts = explode('/', $dirname);
830
-		$dir = "/files_versions";
831
-		foreach ($dirParts as $part) {
832
-			$dir = $dir . '/' . $part;
833
-			if (!$view->file_exists($dir)) {
834
-				$view->mkdir($dir);
835
-			}
836
-		}
837
-	}
838
-
839
-	/**
840
-	 * Static workaround
841
-	 * @return Expiration
842
-	 */
843
-	protected static function getExpiration(){
844
-		if (is_null(self::$application)) {
845
-			self::$application = new Application();
846
-		}
847
-		return self::$application->getContainer()->query('Expiration');
848
-	}
57
+    const DEFAULTENABLED=true;
58
+    const DEFAULTMAXSIZE=50; // unit: percentage; 50% of available disk space/quota
59
+    const VERSIONS_ROOT = 'files_versions/';
60
+
61
+    const DELETE_TRIGGER_MASTER_REMOVED = 0;
62
+    const DELETE_TRIGGER_RETENTION_CONSTRAINT = 1;
63
+    const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;
64
+
65
+    // files for which we can remove the versions after the delete operation was successful
66
+    private static $deletedFiles = array();
67
+
68
+    private static $sourcePathAndUser = array();
69
+
70
+    private static $max_versions_per_interval = array(
71
+        //first 10sec, one version every 2sec
72
+        1 => array('intervalEndsAfter' => 10,      'step' => 2),
73
+        //next minute, one version every 10sec
74
+        2 => array('intervalEndsAfter' => 60,      'step' => 10),
75
+        //next hour, one version every minute
76
+        3 => array('intervalEndsAfter' => 3600,    'step' => 60),
77
+        //next 24h, one version every hour
78
+        4 => array('intervalEndsAfter' => 86400,   'step' => 3600),
79
+        //next 30days, one version per day
80
+        5 => array('intervalEndsAfter' => 2592000, 'step' => 86400),
81
+        //until the end one version per week
82
+        6 => array('intervalEndsAfter' => -1,      'step' => 604800),
83
+    );
84
+
85
+    /** @var \OCA\Files_Versions\AppInfo\Application */
86
+    private static $application;
87
+
88
+    /**
89
+     * get the UID of the owner of the file and the path to the file relative to
90
+     * owners files folder
91
+     *
92
+     * @param string $filename
93
+     * @return array
94
+     * @throws \OC\User\NoUserException
95
+     */
96
+    public static function getUidAndFilename($filename) {
97
+        $uid = Filesystem::getOwner($filename);
98
+        $userManager = \OC::$server->getUserManager();
99
+        // if the user with the UID doesn't exists, e.g. because the UID points
100
+        // to a remote user with a federated cloud ID we use the current logged-in
101
+        // user. We need a valid local user to create the versions
102
+        if (!$userManager->userExists($uid)) {
103
+            $uid = User::getUser();
104
+        }
105
+        Filesystem::initMountPoints($uid);
106
+        if ( $uid !== User::getUser() ) {
107
+            $info = Filesystem::getFileInfo($filename);
108
+            $ownerView = new View('/'.$uid.'/files');
109
+            try {
110
+                $filename = $ownerView->getPath($info['fileid']);
111
+                // make sure that the file name doesn't end with a trailing slash
112
+                // can for example happen single files shared across servers
113
+                $filename = rtrim($filename, '/');
114
+            } catch (NotFoundException $e) {
115
+                $filename = null;
116
+            }
117
+        }
118
+        return [$uid, $filename];
119
+    }
120
+
121
+    /**
122
+     * Remember the owner and the owner path of the source file
123
+     *
124
+     * @param string $source source path
125
+     */
126
+    public static function setSourcePathAndUser($source) {
127
+        list($uid, $path) = self::getUidAndFilename($source);
128
+        self::$sourcePathAndUser[$source] = array('uid' => $uid, 'path' => $path);
129
+    }
130
+
131
+    /**
132
+     * Gets the owner and the owner path from the source path
133
+     *
134
+     * @param string $source source path
135
+     * @return array with user id and path
136
+     */
137
+    public static function getSourcePathAndUser($source) {
138
+
139
+        if (isset(self::$sourcePathAndUser[$source])) {
140
+            $uid = self::$sourcePathAndUser[$source]['uid'];
141
+            $path = self::$sourcePathAndUser[$source]['path'];
142
+            unset(self::$sourcePathAndUser[$source]);
143
+        } else {
144
+            $uid = $path = false;
145
+        }
146
+        return array($uid, $path);
147
+    }
148
+
149
+    /**
150
+     * get current size of all versions from a given user
151
+     *
152
+     * @param string $user user who owns the versions
153
+     * @return int versions size
154
+     */
155
+    private static function getVersionsSize($user) {
156
+        $view = new View('/' . $user);
157
+        $fileInfo = $view->getFileInfo('/files_versions');
158
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
159
+    }
160
+
161
+    /**
162
+     * store a new version of a file.
163
+     */
164
+    public static function store($filename) {
165
+
166
+        // if the file gets streamed we need to remove the .part extension
167
+        // to get the right target
168
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
169
+        if ($ext === 'part') {
170
+            $filename = substr($filename, 0, strlen($filename) - 5);
171
+        }
172
+
173
+        // we only handle existing files
174
+        if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
175
+            return false;
176
+        }
177
+
178
+        list($uid, $filename) = self::getUidAndFilename($filename);
179
+
180
+        $files_view = new View('/'.$uid .'/files');
181
+        $users_view = new View('/'.$uid);
182
+
183
+        $eventDispatcher = \OC::$server->getEventDispatcher();
184
+        $id = $files_view->getFileInfo($filename)->getId();
185
+        $nodes = \OC::$server->getRootFolder()->getById($id);
186
+        foreach ($nodes as $node) {
187
+            $event = new CreateVersionEvent($node);
188
+            $eventDispatcher->dispatch('OCA\Files_Versions::createVersion', $event);
189
+            if ($event->shouldCreateVersion() === false) {
190
+                return false;
191
+            }
192
+        }
193
+
194
+        // no use making versions for empty files
195
+        if ($files_view->filesize($filename) === 0) {
196
+            return false;
197
+        }
198
+
199
+        // create all parent folders
200
+        self::createMissingDirectories($filename, $users_view);
201
+
202
+        self::scheduleExpire($uid, $filename);
203
+
204
+        // store a new version of a file
205
+        $mtime = $users_view->filemtime('files/' . $filename);
206
+        $users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
207
+        // call getFileInfo to enforce a file cache entry for the new version
208
+        $users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
209
+    }
210
+
211
+
212
+    /**
213
+     * mark file as deleted so that we can remove the versions if the file is gone
214
+     * @param string $path
215
+     */
216
+    public static function markDeletedFile($path) {
217
+        list($uid, $filename) = self::getUidAndFilename($path);
218
+        self::$deletedFiles[$path] = array(
219
+            'uid' => $uid,
220
+            'filename' => $filename);
221
+    }
222
+
223
+    /**
224
+     * delete the version from the storage and cache
225
+     *
226
+     * @param View $view
227
+     * @param string $path
228
+     */
229
+    protected static function deleteVersion($view, $path) {
230
+        $view->unlink($path);
231
+        /**
232
+         * @var \OC\Files\Storage\Storage $storage
233
+         * @var string $internalPath
234
+         */
235
+        list($storage, $internalPath) = $view->resolvePath($path);
236
+        $cache = $storage->getCache($internalPath);
237
+        $cache->remove($internalPath);
238
+    }
239
+
240
+    /**
241
+     * Delete versions of a file
242
+     */
243
+    public static function delete($path) {
244
+
245
+        $deletedFile = self::$deletedFiles[$path];
246
+        $uid = $deletedFile['uid'];
247
+        $filename = $deletedFile['filename'];
248
+
249
+        if (!Filesystem::file_exists($path)) {
250
+
251
+            $view = new View('/' . $uid . '/files_versions');
252
+
253
+            $versions = self::getVersions($uid, $filename);
254
+            if (!empty($versions)) {
255
+                foreach ($versions as $v) {
256
+                    \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
257
+                    self::deleteVersion($view, $filename . '.v' . $v['version']);
258
+                    \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
259
+                }
260
+            }
261
+        }
262
+        unset(self::$deletedFiles[$path]);
263
+    }
264
+
265
+    /**
266
+     * Rename or copy versions of a file of the given paths
267
+     *
268
+     * @param string $sourcePath source path of the file to move, relative to
269
+     * the currently logged in user's "files" folder
270
+     * @param string $targetPath target path of the file to move, relative to
271
+     * the currently logged in user's "files" folder
272
+     * @param string $operation can be 'copy' or 'rename'
273
+     */
274
+    public static function renameOrCopy($sourcePath, $targetPath, $operation) {
275
+        list($sourceOwner, $sourcePath) = self::getSourcePathAndUser($sourcePath);
276
+
277
+        // it was a upload of a existing file if no old path exists
278
+        // in this case the pre-hook already called the store method and we can
279
+        // stop here
280
+        if ($sourcePath === false) {
281
+            return true;
282
+        }
283
+
284
+        list($targetOwner, $targetPath) = self::getUidAndFilename($targetPath);
285
+
286
+        $sourcePath = ltrim($sourcePath, '/');
287
+        $targetPath = ltrim($targetPath, '/');
288
+
289
+        $rootView = new View('');
290
+
291
+        // did we move a directory ?
292
+        if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
293
+            // does the directory exists for versions too ?
294
+            if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
295
+                // create missing dirs if necessary
296
+                self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
297
+
298
+                // move the directory containing the versions
299
+                $rootView->$operation(
300
+                    '/' . $sourceOwner . '/files_versions/' . $sourcePath,
301
+                    '/' . $targetOwner . '/files_versions/' . $targetPath
302
+                );
303
+            }
304
+        } else if ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
305
+            // create missing dirs if necessary
306
+            self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
307
+
308
+            foreach ($versions as $v) {
309
+                // move each version one by one to the target directory
310
+                $rootView->$operation(
311
+                    '/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
312
+                    '/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
313
+                );
314
+            }
315
+        }
316
+
317
+        // if we moved versions directly for a file, schedule expiration check for that file
318
+        if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
319
+            self::scheduleExpire($targetOwner, $targetPath);
320
+        }
321
+
322
+    }
323
+
324
+    /**
325
+     * Rollback to an old version of a file.
326
+     *
327
+     * @param string $file file name
328
+     * @param int $revision revision timestamp
329
+     * @return bool
330
+     */
331
+    public static function rollback($file, $revision) {
332
+
333
+        // add expected leading slash
334
+        $file = '/' . ltrim($file, '/');
335
+        list($uid, $filename) = self::getUidAndFilename($file);
336
+        if ($uid === null || trim($filename, '/') === '') {
337
+            return false;
338
+        }
339
+
340
+        $users_view = new View('/'.$uid);
341
+        $files_view = new View('/'. User::getUser().'/files');
342
+
343
+        $versionCreated = false;
344
+
345
+        $fileInfo = $files_view->getFileInfo($file);
346
+
347
+        // check if user has the permissions to revert a version
348
+        if (!$fileInfo->isUpdateable()) {
349
+            return false;
350
+        }
351
+
352
+        //first create a new version
353
+        $version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename);
354
+        if (!$users_view->file_exists($version)) {
355
+            $users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename));
356
+            $versionCreated = true;
357
+        }
358
+
359
+        $fileToRestore =  'files_versions' . $filename . '.v' . $revision;
360
+
361
+        // Restore encrypted version of the old file for the newly restored file
362
+        // This has to happen manually here since the file is manually copied below
363
+        $oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion();
364
+        $oldFileInfo = $users_view->getFileInfo($fileToRestore);
365
+        $cache = $fileInfo->getStorage()->getCache();
366
+        $cache->update(
367
+            $fileInfo->getId(), [
368
+                'encrypted' => $oldVersion,
369
+                'encryptedVersion' => $oldVersion,
370
+                'size' => $oldFileInfo->getSize()
371
+            ]
372
+        );
373
+
374
+        // rollback
375
+        if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
376
+            $files_view->touch($file, $revision);
377
+            Storage::scheduleExpire($uid, $file);
378
+            \OC_Hook::emit('\OCP\Versions', 'rollback', array(
379
+                'path' => $filename,
380
+                'revision' => $revision,
381
+            ));
382
+            return true;
383
+        } else if ($versionCreated) {
384
+            self::deleteVersion($users_view, $version);
385
+        }
386
+
387
+        return false;
388
+
389
+    }
390
+
391
+    /**
392
+     * Stream copy file contents from $path1 to $path2
393
+     *
394
+     * @param View $view view to use for copying
395
+     * @param string $path1 source file to copy
396
+     * @param string $path2 target file
397
+     *
398
+     * @return bool true for success, false otherwise
399
+     */
400
+    private static function copyFileContents($view, $path1, $path2) {
401
+        /** @var \OC\Files\Storage\Storage $storage1 */
402
+        list($storage1, $internalPath1) = $view->resolvePath($path1);
403
+        /** @var \OC\Files\Storage\Storage $storage2 */
404
+        list($storage2, $internalPath2) = $view->resolvePath($path2);
405
+
406
+        $view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
407
+        $view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
408
+
409
+        // TODO add a proper way of overwriting a file while maintaining file ids
410
+        if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
411
+            $source = $storage1->fopen($internalPath1, 'r');
412
+            $target = $storage2->fopen($internalPath2, 'w');
413
+            list(, $result) = \OC_Helper::streamCopy($source, $target);
414
+            fclose($source);
415
+            fclose($target);
416
+
417
+            if ($result !== false) {
418
+                $storage1->unlink($internalPath1);
419
+            }
420
+        } else {
421
+            $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
422
+        }
423
+
424
+        $view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
425
+        $view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
426
+
427
+        return ($result !== false);
428
+    }
429
+
430
+    /**
431
+     * get a list of all available versions of a file in descending chronological order
432
+     * @param string $uid user id from the owner of the file
433
+     * @param string $filename file to find versions of, relative to the user files dir
434
+     * @param string $userFullPath
435
+     * @return array versions newest version first
436
+     */
437
+    public static function getVersions($uid, $filename, $userFullPath = '') {
438
+        $versions = array();
439
+        if (empty($filename)) {
440
+            return $versions;
441
+        }
442
+        // fetch for old versions
443
+        $view = new View('/' . $uid . '/');
444
+
445
+        $pathinfo = pathinfo($filename);
446
+        $versionedFile = $pathinfo['basename'];
447
+
448
+        $dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
449
+
450
+        $dirContent = false;
451
+        if ($view->is_dir($dir)) {
452
+            $dirContent = $view->opendir($dir);
453
+        }
454
+
455
+        if ($dirContent === false) {
456
+            return $versions;
457
+        }
458
+
459
+        if (is_resource($dirContent)) {
460
+            while (($entryName = readdir($dirContent)) !== false) {
461
+                if (!Filesystem::isIgnoredDir($entryName)) {
462
+                    $pathparts = pathinfo($entryName);
463
+                    $filename = $pathparts['filename'];
464
+                    if ($filename === $versionedFile) {
465
+                        $pathparts = pathinfo($entryName);
466
+                        $timestamp = substr($pathparts['extension'], 1);
467
+                        $filename = $pathparts['filename'];
468
+                        $key = $timestamp . '#' . $filename;
469
+                        $versions[$key]['version'] = $timestamp;
470
+                        $versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471
+                        if (empty($userFullPath)) {
472
+                            $versions[$key]['preview'] = '';
473
+                        } else {
474
+                            $versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475
+                        }
476
+                        $versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
477
+                        $versions[$key]['name'] = $versionedFile;
478
+                        $versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
479
+                        $versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480
+                    }
481
+                }
482
+            }
483
+            closedir($dirContent);
484
+        }
485
+
486
+        // sort with newest version first
487
+        krsort($versions);
488
+
489
+        return $versions;
490
+    }
491
+
492
+    /**
493
+     * Expire versions that older than max version retention time
494
+     * @param string $uid
495
+     */
496
+    public static function expireOlderThanMaxForUser($uid){
497
+        $expiration = self::getExpiration();
498
+        $threshold = $expiration->getMaxAgeAsTimestamp();
499
+        $versions = self::getAllVersions($uid);
500
+        if (!$threshold || !array_key_exists('all', $versions)) {
501
+            return;
502
+        }
503
+
504
+        $toDelete = [];
505
+        foreach (array_reverse($versions['all']) as $key => $version) {
506
+            if ((int)$version['version'] <$threshold) {
507
+                $toDelete[$key] = $version;
508
+            } else {
509
+                //Versions are sorted by time - nothing mo to iterate.
510
+                break;
511
+            }
512
+        }
513
+
514
+        $view = new View('/' . $uid . '/files_versions');
515
+        if (!empty($toDelete)) {
516
+            foreach ($toDelete as $version) {
517
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
518
+                self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
519
+                \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
520
+            }
521
+        }
522
+    }
523
+
524
+    /**
525
+     * translate a timestamp into a string like "5 days ago"
526
+     * @param int $timestamp
527
+     * @return string for example "5 days ago"
528
+     */
529
+    private static function getHumanReadableTimestamp($timestamp) {
530
+
531
+        $diff = time() - $timestamp;
532
+
533
+        if ($diff < 60) { // first minute
534
+            return  $diff . " seconds ago";
535
+        } elseif ($diff < 3600) { //first hour
536
+            return round($diff / 60) . " minutes ago";
537
+        } elseif ($diff < 86400) { // first day
538
+            return round($diff / 3600) . " hours ago";
539
+        } elseif ($diff < 604800) { //first week
540
+            return round($diff / 86400) . " days ago";
541
+        } elseif ($diff < 2419200) { //first month
542
+            return round($diff / 604800) . " weeks ago";
543
+        } elseif ($diff < 29030400) { // first year
544
+            return round($diff / 2419200) . " months ago";
545
+        } else {
546
+            return round($diff / 29030400) . " years ago";
547
+        }
548
+
549
+    }
550
+
551
+    /**
552
+     * returns all stored file versions from a given user
553
+     * @param string $uid id of the user
554
+     * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
555
+     */
556
+    private static function getAllVersions($uid) {
557
+        $view = new View('/' . $uid . '/');
558
+        $dirs = array(self::VERSIONS_ROOT);
559
+        $versions = array();
560
+
561
+        while (!empty($dirs)) {
562
+            $dir = array_pop($dirs);
563
+            $files = $view->getDirectoryContent($dir);
564
+
565
+            foreach ($files as $file) {
566
+                $fileData = $file->getData();
567
+                $filePath = $dir . '/' . $fileData['name'];
568
+                if ($file['type'] === 'dir') {
569
+                    array_push($dirs, $filePath);
570
+                } else {
571
+                    $versionsBegin = strrpos($filePath, '.v');
572
+                    $relPathStart = strlen(self::VERSIONS_ROOT);
573
+                    $version = substr($filePath, $versionsBegin + 2);
574
+                    $relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
575
+                    $key = $version . '#' . $relpath;
576
+                    $versions[$key] = array('path' => $relpath, 'timestamp' => $version);
577
+                }
578
+            }
579
+        }
580
+
581
+        // newest version first
582
+        krsort($versions);
583
+
584
+        $result = array();
585
+
586
+        foreach ($versions as $key => $value) {
587
+            $size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']);
588
+            $filename = $value['path'];
589
+
590
+            $result['all'][$key]['version'] = $value['timestamp'];
591
+            $result['all'][$key]['path'] = $filename;
592
+            $result['all'][$key]['size'] = $size;
593
+
594
+            $result['by_file'][$filename][$key]['version'] = $value['timestamp'];
595
+            $result['by_file'][$filename][$key]['path'] = $filename;
596
+            $result['by_file'][$filename][$key]['size'] = $size;
597
+        }
598
+
599
+        return $result;
600
+    }
601
+
602
+    /**
603
+     * get list of files we want to expire
604
+     * @param array $versions list of versions
605
+     * @param integer $time
606
+     * @param bool $quotaExceeded is versions storage limit reached
607
+     * @return array containing the list of to deleted versions and the size of them
608
+     */
609
+    protected static function getExpireList($time, $versions, $quotaExceeded = false) {
610
+        $expiration = self::getExpiration();
611
+
612
+        if ($expiration->shouldAutoExpire()) {
613
+            list($toDelete, $size) = self::getAutoExpireList($time, $versions);
614
+        } else {
615
+            $size = 0;
616
+            $toDelete = [];  // versions we want to delete
617
+        }
618
+
619
+        foreach ($versions as $key => $version) {
620
+            if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
621
+                $size += $version['size'];
622
+                $toDelete[$key] = $version['path'] . '.v' . $version['version'];
623
+            }
624
+        }
625
+
626
+        return [$toDelete, $size];
627
+    }
628
+
629
+    /**
630
+     * get list of files we want to expire
631
+     * @param array $versions list of versions
632
+     * @param integer $time
633
+     * @return array containing the list of to deleted versions and the size of them
634
+     */
635
+    protected static function getAutoExpireList($time, $versions) {
636
+        $size = 0;
637
+        $toDelete = array();  // versions we want to delete
638
+
639
+        $interval = 1;
640
+        $step = Storage::$max_versions_per_interval[$interval]['step'];
641
+        if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
642
+            $nextInterval = -1;
643
+        } else {
644
+            $nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
645
+        }
646
+
647
+        $firstVersion = reset($versions);
648
+        $firstKey = key($versions);
649
+        $prevTimestamp = $firstVersion['version'];
650
+        $nextVersion = $firstVersion['version'] - $step;
651
+        unset($versions[$firstKey]);
652
+
653
+        foreach ($versions as $key => $version) {
654
+            $newInterval = true;
655
+            while ($newInterval) {
656
+                if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
657
+                    if ($version['version'] > $nextVersion) {
658
+                        //distance between two version too small, mark to delete
659
+                        $toDelete[$key] = $version['path'] . '.v' . $version['version'];
660
+                        $size += $version['size'];
661
+                        \OCP\Util::writeLog('files_versions', 'Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, \OCP\Util::INFO);
662
+                    } else {
663
+                        $nextVersion = $version['version'] - $step;
664
+                        $prevTimestamp = $version['version'];
665
+                    }
666
+                    $newInterval = false; // version checked so we can move to the next one
667
+                } else { // time to move on to the next interval
668
+                    $interval++;
669
+                    $step = Storage::$max_versions_per_interval[$interval]['step'];
670
+                    $nextVersion = $prevTimestamp - $step;
671
+                    if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
672
+                        $nextInterval = -1;
673
+                    } else {
674
+                        $nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
675
+                    }
676
+                    $newInterval = true; // we changed the interval -> check same version with new interval
677
+                }
678
+            }
679
+        }
680
+
681
+        return array($toDelete, $size);
682
+    }
683
+
684
+    /**
685
+     * Schedule versions expiration for the given file
686
+     *
687
+     * @param string $uid owner of the file
688
+     * @param string $fileName file/folder for which to schedule expiration
689
+     */
690
+    private static function scheduleExpire($uid, $fileName) {
691
+        // let the admin disable auto expire
692
+        $expiration = self::getExpiration();
693
+        if ($expiration->isEnabled()) {
694
+            $command = new Expire($uid, $fileName);
695
+            \OC::$server->getCommandBus()->push($command);
696
+        }
697
+    }
698
+
699
+    /**
700
+     * Expire versions which exceed the quota.
701
+     *
702
+     * This will setup the filesystem for the given user but will not
703
+     * tear it down afterwards.
704
+     *
705
+     * @param string $filename path to file to expire
706
+     * @param string $uid user for which to expire the version
707
+     * @return bool|int|null
708
+     */
709
+    public static function expire($filename, $uid) {
710
+        $expiration = self::getExpiration();
711
+
712
+        if ($expiration->isEnabled()) {
713
+            // get available disk space for user
714
+            $user = \OC::$server->getUserManager()->get($uid);
715
+            if (is_null($user)) {
716
+                \OCP\Util::writeLog('files_versions', 'Backends provided no user object for ' . $uid, \OCP\Util::ERROR);
717
+                throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
718
+            }
719
+
720
+            \OC_Util::setupFS($uid);
721
+
722
+            if (!Filesystem::file_exists($filename)) {
723
+                return false;
724
+            }
725
+
726
+            if (empty($filename)) {
727
+                // file maybe renamed or deleted
728
+                return false;
729
+            }
730
+            $versionsFileview = new View('/'.$uid.'/files_versions');
731
+
732
+            $softQuota = true;
733
+            $quota = $user->getQuota();
734
+            if ( $quota === null || $quota === 'none' ) {
735
+                $quota = Filesystem::free_space('/');
736
+                $softQuota = false;
737
+            } else {
738
+                $quota = \OCP\Util::computerFileSize($quota);
739
+            }
740
+
741
+            // make sure that we have the current size of the version history
742
+            $versionsSize = self::getVersionsSize($uid);
743
+
744
+            // calculate available space for version history
745
+            // subtract size of files and current versions size from quota
746
+            if ($quota >= 0) {
747
+                if ($softQuota) {
748
+                    $files_view = new View('/' . $uid . '/files');
749
+                    $rootInfo = $files_view->getFileInfo('/', false);
750
+                    $free = $quota - $rootInfo['size']; // remaining free space for user
751
+                    if ($free > 0) {
752
+                        $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $versionsSize; // how much space can be used for versions
753
+                    } else {
754
+                        $availableSpace = $free - $versionsSize;
755
+                    }
756
+                } else {
757
+                    $availableSpace = $quota;
758
+                }
759
+            } else {
760
+                $availableSpace = PHP_INT_MAX;
761
+            }
762
+
763
+            $allVersions = Storage::getVersions($uid, $filename);
764
+
765
+            $time = time();
766
+            list($toDelete, $sizeOfDeletedVersions) = self::getExpireList($time, $allVersions, $availableSpace <= 0);
767
+
768
+            $availableSpace = $availableSpace + $sizeOfDeletedVersions;
769
+            $versionsSize = $versionsSize - $sizeOfDeletedVersions;
770
+
771
+            // if still not enough free space we rearrange the versions from all files
772
+            if ($availableSpace <= 0) {
773
+                $result = Storage::getAllVersions($uid);
774
+                $allVersions = $result['all'];
775
+
776
+                foreach ($result['by_file'] as $versions) {
777
+                    list($toDeleteNew, $size) = self::getExpireList($time, $versions, $availableSpace <= 0);
778
+                    $toDelete = array_merge($toDelete, $toDeleteNew);
779
+                    $sizeOfDeletedVersions += $size;
780
+                }
781
+                $availableSpace = $availableSpace + $sizeOfDeletedVersions;
782
+                $versionsSize = $versionsSize - $sizeOfDeletedVersions;
783
+            }
784
+
785
+            foreach($toDelete as $key => $path) {
786
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
787
+                self::deleteVersion($versionsFileview, $path);
788
+                \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
789
+                unset($allVersions[$key]); // update array with the versions we keep
790
+                \OCP\Util::writeLog('files_versions', "Expire: " . $path, \OCP\Util::INFO);
791
+            }
792
+
793
+            // Check if enough space is available after versions are rearranged.
794
+            // If not we delete the oldest versions until we meet the size limit for versions,
795
+            // but always keep the two latest versions
796
+            $numOfVersions = count($allVersions) -2 ;
797
+            $i = 0;
798
+            // sort oldest first and make sure that we start at the first element
799
+            ksort($allVersions);
800
+            reset($allVersions);
801
+            while ($availableSpace < 0 && $i < $numOfVersions) {
802
+                $version = current($allVersions);
803
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
804
+                self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
805
+                \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
806
+                \OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'] , \OCP\Util::INFO);
807
+                $versionsSize -= $version['size'];
808
+                $availableSpace += $version['size'];
809
+                next($allVersions);
810
+                $i++;
811
+            }
812
+
813
+            return $versionsSize; // finally return the new size of the version history
814
+        }
815
+
816
+        return false;
817
+    }
818
+
819
+    /**
820
+     * Create recursively missing directories inside of files_versions
821
+     * that match the given path to a file.
822
+     *
823
+     * @param string $filename $path to a file, relative to the user's
824
+     * "files" folder
825
+     * @param View $view view on data/user/
826
+     */
827
+    private static function createMissingDirectories($filename, $view) {
828
+        $dirname = Filesystem::normalizePath(dirname($filename));
829
+        $dirParts = explode('/', $dirname);
830
+        $dir = "/files_versions";
831
+        foreach ($dirParts as $part) {
832
+            $dir = $dir . '/' . $part;
833
+            if (!$view->file_exists($dir)) {
834
+                $view->mkdir($dir);
835
+            }
836
+        }
837
+    }
838
+
839
+    /**
840
+     * Static workaround
841
+     * @return Expiration
842
+     */
843
+    protected static function getExpiration(){
844
+        if (is_null(self::$application)) {
845
+            self::$application = new Application();
846
+        }
847
+        return self::$application->getContainer()->query('Expiration');
848
+    }
849 849
 
850 850
 }
Please login to merge, or discard this patch.
Spacing   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -54,8 +54,8 @@  discard block
 block discarded – undo
54 54
 
55 55
 class Storage {
56 56
 
57
-	const DEFAULTENABLED=true;
58
-	const DEFAULTMAXSIZE=50; // unit: percentage; 50% of available disk space/quota
57
+	const DEFAULTENABLED = true;
58
+	const DEFAULTMAXSIZE = 50; // unit: percentage; 50% of available disk space/quota
59 59
 	const VERSIONS_ROOT = 'files_versions/';
60 60
 
61 61
 	const DELETE_TRIGGER_MASTER_REMOVED = 0;
@@ -69,17 +69,17 @@  discard block
 block discarded – undo
69 69
 
70 70
 	private static $max_versions_per_interval = array(
71 71
 		//first 10sec, one version every 2sec
72
-		1 => array('intervalEndsAfter' => 10,      'step' => 2),
72
+		1 => array('intervalEndsAfter' => 10, 'step' => 2),
73 73
 		//next minute, one version every 10sec
74
-		2 => array('intervalEndsAfter' => 60,      'step' => 10),
74
+		2 => array('intervalEndsAfter' => 60, 'step' => 10),
75 75
 		//next hour, one version every minute
76
-		3 => array('intervalEndsAfter' => 3600,    'step' => 60),
76
+		3 => array('intervalEndsAfter' => 3600, 'step' => 60),
77 77
 		//next 24h, one version every hour
78
-		4 => array('intervalEndsAfter' => 86400,   'step' => 3600),
78
+		4 => array('intervalEndsAfter' => 86400, 'step' => 3600),
79 79
 		//next 30days, one version per day
80 80
 		5 => array('intervalEndsAfter' => 2592000, 'step' => 86400),
81 81
 		//until the end one version per week
82
-		6 => array('intervalEndsAfter' => -1,      'step' => 604800),
82
+		6 => array('intervalEndsAfter' => -1, 'step' => 604800),
83 83
 	);
84 84
 
85 85
 	/** @var \OCA\Files_Versions\AppInfo\Application */
@@ -103,7 +103,7 @@  discard block
 block discarded – undo
103 103
 			$uid = User::getUser();
104 104
 		}
105 105
 		Filesystem::initMountPoints($uid);
106
-		if ( $uid !== User::getUser() ) {
106
+		if ($uid !== User::getUser()) {
107 107
 			$info = Filesystem::getFileInfo($filename);
108 108
 			$ownerView = new View('/'.$uid.'/files');
109 109
 			try {
@@ -153,7 +153,7 @@  discard block
 block discarded – undo
153 153
 	 * @return int versions size
154 154
 	 */
155 155
 	private static function getVersionsSize($user) {
156
-		$view = new View('/' . $user);
156
+		$view = new View('/'.$user);
157 157
 		$fileInfo = $view->getFileInfo('/files_versions');
158 158
 		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
159 159
 	}
@@ -171,13 +171,13 @@  discard block
 block discarded – undo
171 171
 		}
172 172
 
173 173
 		// we only handle existing files
174
-		if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
174
+		if (!Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
175 175
 			return false;
176 176
 		}
177 177
 
178 178
 		list($uid, $filename) = self::getUidAndFilename($filename);
179 179
 
180
-		$files_view = new View('/'.$uid .'/files');
180
+		$files_view = new View('/'.$uid.'/files');
181 181
 		$users_view = new View('/'.$uid);
182 182
 
183 183
 		$eventDispatcher = \OC::$server->getEventDispatcher();
@@ -202,10 +202,10 @@  discard block
 block discarded – undo
202 202
 		self::scheduleExpire($uid, $filename);
203 203
 
204 204
 		// store a new version of a file
205
-		$mtime = $users_view->filemtime('files/' . $filename);
206
-		$users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
205
+		$mtime = $users_view->filemtime('files/'.$filename);
206
+		$users_view->copy('files/'.$filename, 'files_versions/'.$filename.'.v'.$mtime);
207 207
 		// call getFileInfo to enforce a file cache entry for the new version
208
-		$users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
208
+		$users_view->getFileInfo('files_versions/'.$filename.'.v'.$mtime);
209 209
 	}
210 210
 
211 211
 
@@ -248,14 +248,14 @@  discard block
 block discarded – undo
248 248
 
249 249
 		if (!Filesystem::file_exists($path)) {
250 250
 
251
-			$view = new View('/' . $uid . '/files_versions');
251
+			$view = new View('/'.$uid.'/files_versions');
252 252
 
253 253
 			$versions = self::getVersions($uid, $filename);
254 254
 			if (!empty($versions)) {
255 255
 				foreach ($versions as $v) {
256
-					\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
257
-					self::deleteVersion($view, $filename . '.v' . $v['version']);
258
-					\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
256
+					\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path.$v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
257
+					self::deleteVersion($view, $filename.'.v'.$v['version']);
258
+					\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path.$v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
259 259
 				}
260 260
 			}
261 261
 		}
@@ -289,33 +289,33 @@  discard block
 block discarded – undo
289 289
 		$rootView = new View('');
290 290
 
291 291
 		// did we move a directory ?
292
-		if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
292
+		if ($rootView->is_dir('/'.$targetOwner.'/files/'.$targetPath)) {
293 293
 			// does the directory exists for versions too ?
294
-			if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
294
+			if ($rootView->is_dir('/'.$sourceOwner.'/files_versions/'.$sourcePath)) {
295 295
 				// create missing dirs if necessary
296
-				self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
296
+				self::createMissingDirectories($targetPath, new View('/'.$targetOwner));
297 297
 
298 298
 				// move the directory containing the versions
299 299
 				$rootView->$operation(
300
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath,
301
-					'/' . $targetOwner . '/files_versions/' . $targetPath
300
+					'/'.$sourceOwner.'/files_versions/'.$sourcePath,
301
+					'/'.$targetOwner.'/files_versions/'.$targetPath
302 302
 				);
303 303
 			}
304
-		} else if ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
304
+		} else if ($versions = Storage::getVersions($sourceOwner, '/'.$sourcePath)) {
305 305
 			// create missing dirs if necessary
306
-			self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
306
+			self::createMissingDirectories($targetPath, new View('/'.$targetOwner));
307 307
 
308 308
 			foreach ($versions as $v) {
309 309
 				// move each version one by one to the target directory
310 310
 				$rootView->$operation(
311
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
312
-					'/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
311
+					'/'.$sourceOwner.'/files_versions/'.$sourcePath.'.v'.$v['version'],
312
+					'/'.$targetOwner.'/files_versions/'.$targetPath.'.v'.$v['version']
313 313
 				);
314 314
 			}
315 315
 		}
316 316
 
317 317
 		// if we moved versions directly for a file, schedule expiration check for that file
318
-		if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
318
+		if (!$rootView->is_dir('/'.$targetOwner.'/files/'.$targetPath)) {
319 319
 			self::scheduleExpire($targetOwner, $targetPath);
320 320
 		}
321 321
 
@@ -331,14 +331,14 @@  discard block
 block discarded – undo
331 331
 	public static function rollback($file, $revision) {
332 332
 
333 333
 		// add expected leading slash
334
-		$file = '/' . ltrim($file, '/');
334
+		$file = '/'.ltrim($file, '/');
335 335
 		list($uid, $filename) = self::getUidAndFilename($file);
336 336
 		if ($uid === null || trim($filename, '/') === '') {
337 337
 			return false;
338 338
 		}
339 339
 
340 340
 		$users_view = new View('/'.$uid);
341
-		$files_view = new View('/'. User::getUser().'/files');
341
+		$files_view = new View('/'.User::getUser().'/files');
342 342
 
343 343
 		$versionCreated = false;
344 344
 
@@ -356,7 +356,7 @@  discard block
 block discarded – undo
356 356
 			$versionCreated = true;
357 357
 		}
358 358
 
359
-		$fileToRestore =  'files_versions' . $filename . '.v' . $revision;
359
+		$fileToRestore = 'files_versions'.$filename.'.v'.$revision;
360 360
 
361 361
 		// Restore encrypted version of the old file for the newly restored file
362 362
 		// This has to happen manually here since the file is manually copied below
@@ -372,7 +372,7 @@  discard block
 block discarded – undo
372 372
 		);
373 373
 
374 374
 		// rollback
375
-		if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
375
+		if (self::copyFileContents($users_view, $fileToRestore, 'files'.$filename)) {
376 376
 			$files_view->touch($file, $revision);
377 377
 			Storage::scheduleExpire($uid, $file);
378 378
 			\OC_Hook::emit('\OCP\Versions', 'rollback', array(
@@ -440,12 +440,12 @@  discard block
 block discarded – undo
440 440
 			return $versions;
441 441
 		}
442 442
 		// fetch for old versions
443
-		$view = new View('/' . $uid . '/');
443
+		$view = new View('/'.$uid.'/');
444 444
 
445 445
 		$pathinfo = pathinfo($filename);
446 446
 		$versionedFile = $pathinfo['basename'];
447 447
 
448
-		$dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
448
+		$dir = Filesystem::normalizePath(self::VERSIONS_ROOT.'/'.$pathinfo['dirname']);
449 449
 
450 450
 		$dirContent = false;
451 451
 		if ($view->is_dir($dir)) {
@@ -465,7 +465,7 @@  discard block
 block discarded – undo
465 465
 						$pathparts = pathinfo($entryName);
466 466
 						$timestamp = substr($pathparts['extension'], 1);
467 467
 						$filename = $pathparts['filename'];
468
-						$key = $timestamp . '#' . $filename;
468
+						$key = $timestamp.'#'.$filename;
469 469
 						$versions[$key]['version'] = $timestamp;
470 470
 						$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471 471
 						if (empty($userFullPath)) {
@@ -473,9 +473,9 @@  discard block
 block discarded – undo
473 473
 						} else {
474 474
 							$versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475 475
 						}
476
-						$versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
476
+						$versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'].'/'.$filename);
477 477
 						$versions[$key]['name'] = $versionedFile;
478
-						$versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
478
+						$versions[$key]['size'] = $view->filesize($dir.'/'.$entryName);
479 479
 						$versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480 480
 					}
481 481
 				}
@@ -493,7 +493,7 @@  discard block
 block discarded – undo
493 493
 	 * Expire versions that older than max version retention time
494 494
 	 * @param string $uid
495 495
 	 */
496
-	public static function expireOlderThanMaxForUser($uid){
496
+	public static function expireOlderThanMaxForUser($uid) {
497 497
 		$expiration = self::getExpiration();
498 498
 		$threshold = $expiration->getMaxAgeAsTimestamp();
499 499
 		$versions = self::getAllVersions($uid);
@@ -503,7 +503,7 @@  discard block
 block discarded – undo
503 503
 
504 504
 		$toDelete = [];
505 505
 		foreach (array_reverse($versions['all']) as $key => $version) {
506
-			if ((int)$version['version'] <$threshold) {
506
+			if ((int) $version['version'] < $threshold) {
507 507
 				$toDelete[$key] = $version;
508 508
 			} else {
509 509
 				//Versions are sorted by time - nothing mo to iterate.
@@ -511,11 +511,11 @@  discard block
 block discarded – undo
511 511
 			}
512 512
 		}
513 513
 
514
-		$view = new View('/' . $uid . '/files_versions');
514
+		$view = new View('/'.$uid.'/files_versions');
515 515
 		if (!empty($toDelete)) {
516 516
 			foreach ($toDelete as $version) {
517 517
 				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
518
-				self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
518
+				self::deleteVersion($view, $version['path'].'.v'.$version['version']);
519 519
 				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
520 520
 			}
521 521
 		}
@@ -531,19 +531,19 @@  discard block
 block discarded – undo
531 531
 		$diff = time() - $timestamp;
532 532
 
533 533
 		if ($diff < 60) { // first minute
534
-			return  $diff . " seconds ago";
534
+			return  $diff." seconds ago";
535 535
 		} elseif ($diff < 3600) { //first hour
536
-			return round($diff / 60) . " minutes ago";
536
+			return round($diff / 60)." minutes ago";
537 537
 		} elseif ($diff < 86400) { // first day
538
-			return round($diff / 3600) . " hours ago";
538
+			return round($diff / 3600)." hours ago";
539 539
 		} elseif ($diff < 604800) { //first week
540
-			return round($diff / 86400) . " days ago";
540
+			return round($diff / 86400)." days ago";
541 541
 		} elseif ($diff < 2419200) { //first month
542
-			return round($diff / 604800) . " weeks ago";
542
+			return round($diff / 604800)." weeks ago";
543 543
 		} elseif ($diff < 29030400) { // first year
544
-			return round($diff / 2419200) . " months ago";
544
+			return round($diff / 2419200)." months ago";
545 545
 		} else {
546
-			return round($diff / 29030400) . " years ago";
546
+			return round($diff / 29030400)." years ago";
547 547
 		}
548 548
 
549 549
 	}
@@ -554,7 +554,7 @@  discard block
 block discarded – undo
554 554
 	 * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
555 555
 	 */
556 556
 	private static function getAllVersions($uid) {
557
-		$view = new View('/' . $uid . '/');
557
+		$view = new View('/'.$uid.'/');
558 558
 		$dirs = array(self::VERSIONS_ROOT);
559 559
 		$versions = array();
560 560
 
@@ -564,7 +564,7 @@  discard block
 block discarded – undo
564 564
 
565 565
 			foreach ($files as $file) {
566 566
 				$fileData = $file->getData();
567
-				$filePath = $dir . '/' . $fileData['name'];
567
+				$filePath = $dir.'/'.$fileData['name'];
568 568
 				if ($file['type'] === 'dir') {
569 569
 					array_push($dirs, $filePath);
570 570
 				} else {
@@ -572,7 +572,7 @@  discard block
 block discarded – undo
572 572
 					$relPathStart = strlen(self::VERSIONS_ROOT);
573 573
 					$version = substr($filePath, $versionsBegin + 2);
574 574
 					$relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
575
-					$key = $version . '#' . $relpath;
575
+					$key = $version.'#'.$relpath;
576 576
 					$versions[$key] = array('path' => $relpath, 'timestamp' => $version);
577 577
 				}
578 578
 			}
@@ -613,13 +613,13 @@  discard block
 block discarded – undo
613 613
 			list($toDelete, $size) = self::getAutoExpireList($time, $versions);
614 614
 		} else {
615 615
 			$size = 0;
616
-			$toDelete = [];  // versions we want to delete
616
+			$toDelete = []; // versions we want to delete
617 617
 		}
618 618
 
619 619
 		foreach ($versions as $key => $version) {
620 620
 			if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
621 621
 				$size += $version['size'];
622
-				$toDelete[$key] = $version['path'] . '.v' . $version['version'];
622
+				$toDelete[$key] = $version['path'].'.v'.$version['version'];
623 623
 			}
624 624
 		}
625 625
 
@@ -634,7 +634,7 @@  discard block
 block discarded – undo
634 634
 	 */
635 635
 	protected static function getAutoExpireList($time, $versions) {
636 636
 		$size = 0;
637
-		$toDelete = array();  // versions we want to delete
637
+		$toDelete = array(); // versions we want to delete
638 638
 
639 639
 		$interval = 1;
640 640
 		$step = Storage::$max_versions_per_interval[$interval]['step'];
@@ -656,9 +656,9 @@  discard block
 block discarded – undo
656 656
 				if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
657 657
 					if ($version['version'] > $nextVersion) {
658 658
 						//distance between two version too small, mark to delete
659
-						$toDelete[$key] = $version['path'] . '.v' . $version['version'];
659
+						$toDelete[$key] = $version['path'].'.v'.$version['version'];
660 660
 						$size += $version['size'];
661
-						\OCP\Util::writeLog('files_versions', 'Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, \OCP\Util::INFO);
661
+						\OCP\Util::writeLog('files_versions', 'Mark to expire '.$version['path'].' next version should be '.$nextVersion." or smaller. (prevTimestamp: ".$prevTimestamp."; step: ".$step, \OCP\Util::INFO);
662 662
 					} else {
663 663
 						$nextVersion = $version['version'] - $step;
664 664
 						$prevTimestamp = $version['version'];
@@ -713,8 +713,8 @@  discard block
 block discarded – undo
713 713
 			// get available disk space for user
714 714
 			$user = \OC::$server->getUserManager()->get($uid);
715 715
 			if (is_null($user)) {
716
-				\OCP\Util::writeLog('files_versions', 'Backends provided no user object for ' . $uid, \OCP\Util::ERROR);
717
-				throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
716
+				\OCP\Util::writeLog('files_versions', 'Backends provided no user object for '.$uid, \OCP\Util::ERROR);
717
+				throw new \OC\User\NoUserException('Backends provided no user object for '.$uid);
718 718
 			}
719 719
 
720 720
 			\OC_Util::setupFS($uid);
@@ -731,7 +731,7 @@  discard block
 block discarded – undo
731 731
 
732 732
 			$softQuota = true;
733 733
 			$quota = $user->getQuota();
734
-			if ( $quota === null || $quota === 'none' ) {
734
+			if ($quota === null || $quota === 'none') {
735 735
 				$quota = Filesystem::free_space('/');
736 736
 				$softQuota = false;
737 737
 			} else {
@@ -745,7 +745,7 @@  discard block
 block discarded – undo
745 745
 			// subtract size of files and current versions size from quota
746 746
 			if ($quota >= 0) {
747 747
 				if ($softQuota) {
748
-					$files_view = new View('/' . $uid . '/files');
748
+					$files_view = new View('/'.$uid.'/files');
749 749
 					$rootInfo = $files_view->getFileInfo('/', false);
750 750
 					$free = $quota - $rootInfo['size']; // remaining free space for user
751 751
 					if ($free > 0) {
@@ -782,18 +782,18 @@  discard block
 block discarded – undo
782 782
 				$versionsSize = $versionsSize - $sizeOfDeletedVersions;
783 783
 			}
784 784
 
785
-			foreach($toDelete as $key => $path) {
785
+			foreach ($toDelete as $key => $path) {
786 786
 				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
787 787
 				self::deleteVersion($versionsFileview, $path);
788 788
 				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
789 789
 				unset($allVersions[$key]); // update array with the versions we keep
790
-				\OCP\Util::writeLog('files_versions', "Expire: " . $path, \OCP\Util::INFO);
790
+				\OCP\Util::writeLog('files_versions', "Expire: ".$path, \OCP\Util::INFO);
791 791
 			}
792 792
 
793 793
 			// Check if enough space is available after versions are rearranged.
794 794
 			// If not we delete the oldest versions until we meet the size limit for versions,
795 795
 			// but always keep the two latest versions
796
-			$numOfVersions = count($allVersions) -2 ;
796
+			$numOfVersions = count($allVersions) - 2;
797 797
 			$i = 0;
798 798
 			// sort oldest first and make sure that we start at the first element
799 799
 			ksort($allVersions);
@@ -801,9 +801,9 @@  discard block
 block discarded – undo
801 801
 			while ($availableSpace < 0 && $i < $numOfVersions) {
802 802
 				$version = current($allVersions);
803 803
 				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
804
-				self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
804
+				self::deleteVersion($versionsFileview, $version['path'].'.v'.$version['version']);
805 805
 				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
806
-				\OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'] , \OCP\Util::INFO);
806
+				\OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: '.$version['path'].'.v'.$version['version'], \OCP\Util::INFO);
807 807
 				$versionsSize -= $version['size'];
808 808
 				$availableSpace += $version['size'];
809 809
 				next($allVersions);
@@ -829,7 +829,7 @@  discard block
 block discarded – undo
829 829
 		$dirParts = explode('/', $dirname);
830 830
 		$dir = "/files_versions";
831 831
 		foreach ($dirParts as $part) {
832
-			$dir = $dir . '/' . $part;
832
+			$dir = $dir.'/'.$part;
833 833
 			if (!$view->file_exists($dir)) {
834 834
 				$view->mkdir($dir);
835 835
 			}
@@ -840,7 +840,7 @@  discard block
 block discarded – undo
840 840
 	 * Static workaround
841 841
 	 * @return Expiration
842 842
 	 */
843
-	protected static function getExpiration(){
843
+	protected static function getExpiration() {
844 844
 		if (is_null(self::$application)) {
845 845
 			self::$application = new Application();
846 846
 		}
Please login to merge, or discard this patch.
apps/user_ldap/lib/Wizard.php 2 patches
Indentation   +1305 added lines, -1305 removed lines patch added patch discarded remove patch
@@ -41,1311 +41,1311 @@
 block discarded – undo
41 41
 use OC\ServerNotAvailableException;
42 42
 
43 43
 class Wizard extends LDAPUtility {
44
-	/** @var \OCP\IL10N */
45
-	static protected $l;
46
-	protected $access;
47
-	protected $cr;
48
-	protected $configuration;
49
-	protected $result;
50
-	protected $resultCache = array();
51
-
52
-	const LRESULT_PROCESSED_OK = 2;
53
-	const LRESULT_PROCESSED_INVALID = 3;
54
-	const LRESULT_PROCESSED_SKIP = 4;
55
-
56
-	const LFILTER_LOGIN      = 2;
57
-	const LFILTER_USER_LIST  = 3;
58
-	const LFILTER_GROUP_LIST = 4;
59
-
60
-	const LFILTER_MODE_ASSISTED = 2;
61
-	const LFILTER_MODE_RAW = 1;
62
-
63
-	const LDAP_NW_TIMEOUT = 4;
64
-
65
-	/**
66
-	 * Constructor
67
-	 * @param Configuration $configuration an instance of Configuration
68
-	 * @param ILDAPWrapper $ldap an instance of ILDAPWrapper
69
-	 * @param Access $access
70
-	 */
71
-	public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) {
72
-		parent::__construct($ldap);
73
-		$this->configuration = $configuration;
74
-		if(is_null(Wizard::$l)) {
75
-			Wizard::$l = \OC::$server->getL10N('user_ldap');
76
-		}
77
-		$this->access = $access;
78
-		$this->result = new WizardResult();
79
-	}
80
-
81
-	public function  __destruct() {
82
-		if($this->result->hasChanges()) {
83
-			$this->configuration->saveConfiguration();
84
-		}
85
-	}
86
-
87
-	/**
88
-	 * counts entries in the LDAP directory
89
-	 *
90
-	 * @param string $filter the LDAP search filter
91
-	 * @param string $type a string being either 'users' or 'groups';
92
-	 * @return bool|int
93
-	 * @throws \Exception
94
-	 */
95
-	public function countEntries($filter, $type) {
96
-		$reqs = array('ldapHost', 'ldapPort', 'ldapBase');
97
-		if($type === 'users') {
98
-			$reqs[] = 'ldapUserFilter';
99
-		}
100
-		if(!$this->checkRequirements($reqs)) {
101
-			throw new \Exception('Requirements not met', 400);
102
-		}
103
-
104
-		$attr = array('dn'); // default
105
-		$limit = 1001;
106
-		if($type === 'groups') {
107
-			$result =  $this->access->countGroups($filter, $attr, $limit);
108
-		} else if($type === 'users') {
109
-			$result = $this->access->countUsers($filter, $attr, $limit);
110
-		} else if ($type === 'objects') {
111
-			$result = $this->access->countObjects($limit);
112
-		} else {
113
-			throw new \Exception('Internal error: Invalid object type', 500);
114
-		}
115
-
116
-		return $result;
117
-	}
118
-
119
-	/**
120
-	 * formats the return value of a count operation to the string to be
121
-	 * inserted.
122
-	 *
123
-	 * @param bool|int $count
124
-	 * @return int|string
125
-	 */
126
-	private function formatCountResult($count) {
127
-		$formatted = ($count !== false) ? $count : 0;
128
-		if($formatted > 1000) {
129
-			$formatted = '> 1000';
130
-		}
131
-		return $formatted;
132
-	}
133
-
134
-	public function countGroups() {
135
-		$filter = $this->configuration->ldapGroupFilter;
136
-
137
-		if(empty($filter)) {
138
-			$output = self::$l->n('%s group found', '%s groups found', 0, array(0));
139
-			$this->result->addChange('ldap_group_count', $output);
140
-			return $this->result;
141
-		}
142
-
143
-		try {
144
-			$groupsTotal = $this->formatCountResult($this->countEntries($filter, 'groups'));
145
-		} catch (\Exception $e) {
146
-			//400 can be ignored, 500 is forwarded
147
-			if($e->getCode() === 500) {
148
-				throw $e;
149
-			}
150
-			return false;
151
-		}
152
-		$output = self::$l->n('%s group found', '%s groups found', $groupsTotal, array($groupsTotal));
153
-		$this->result->addChange('ldap_group_count', $output);
154
-		return $this->result;
155
-	}
156
-
157
-	/**
158
-	 * @return WizardResult
159
-	 * @throws \Exception
160
-	 */
161
-	public function countUsers() {
162
-		$filter = $this->access->getFilterForUserCount();
163
-
164
-		$usersTotal = $this->formatCountResult($this->countEntries($filter, 'users'));
165
-		$output = self::$l->n('%s user found', '%s users found', $usersTotal, array($usersTotal));
166
-		$this->result->addChange('ldap_user_count', $output);
167
-		return $this->result;
168
-	}
169
-
170
-	/**
171
-	 * counts any objects in the currently set base dn
172
-	 *
173
-	 * @return WizardResult
174
-	 * @throws \Exception
175
-	 */
176
-	public function countInBaseDN() {
177
-		// we don't need to provide a filter in this case
178
-		$total = $this->countEntries(null, 'objects');
179
-		if($total === false) {
180
-			throw new \Exception('invalid results received');
181
-		}
182
-		$this->result->addChange('ldap_test_base', $total);
183
-		return $this->result;
184
-	}
185
-
186
-	/**
187
-	 * counts users with a specified attribute
188
-	 * @param string $attr
189
-	 * @param bool $existsCheck
190
-	 * @return int|bool
191
-	 */
192
-	public function countUsersWithAttribute($attr, $existsCheck = false) {
193
-		if(!$this->checkRequirements(array('ldapHost',
194
-										   'ldapPort',
195
-										   'ldapBase',
196
-										   'ldapUserFilter',
197
-										   ))) {
198
-			return  false;
199
-		}
200
-
201
-		$filter = $this->access->combineFilterWithAnd(array(
202
-			$this->configuration->ldapUserFilter,
203
-			$attr . '=*'
204
-		));
205
-
206
-		$limit = ($existsCheck === false) ? null : 1;
207
-
208
-		return $this->access->countUsers($filter, array('dn'), $limit);
209
-	}
210
-
211
-	/**
212
-	 * detects the display name attribute. If a setting is already present that
213
-	 * returns at least one hit, the detection will be canceled.
214
-	 * @return WizardResult|bool
215
-	 * @throws \Exception
216
-	 */
217
-	public function detectUserDisplayNameAttribute() {
218
-		if(!$this->checkRequirements(array('ldapHost',
219
-										'ldapPort',
220
-										'ldapBase',
221
-										'ldapUserFilter',
222
-										))) {
223
-			return  false;
224
-		}
225
-
226
-		$attr = $this->configuration->ldapUserDisplayName;
227
-		if ($attr !== '' && $attr !== 'displayName') {
228
-			// most likely not the default value with upper case N,
229
-			// verify it still produces a result
230
-			$count = (int)$this->countUsersWithAttribute($attr, true);
231
-			if($count > 0) {
232
-				//no change, but we sent it back to make sure the user interface
233
-				//is still correct, even if the ajax call was cancelled meanwhile
234
-				$this->result->addChange('ldap_display_name', $attr);
235
-				return $this->result;
236
-			}
237
-		}
238
-
239
-		// first attribute that has at least one result wins
240
-		$displayNameAttrs = array('displayname', 'cn');
241
-		foreach ($displayNameAttrs as $attr) {
242
-			$count = (int)$this->countUsersWithAttribute($attr, true);
243
-
244
-			if($count > 0) {
245
-				$this->applyFind('ldap_display_name', $attr);
246
-				return $this->result;
247
-			}
248
-		};
249
-
250
-		throw new \Exception(self::$l->t('Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings.'));
251
-	}
252
-
253
-	/**
254
-	 * detects the most often used email attribute for users applying to the
255
-	 * user list filter. If a setting is already present that returns at least
256
-	 * one hit, the detection will be canceled.
257
-	 * @return WizardResult|bool
258
-	 */
259
-	public function detectEmailAttribute() {
260
-		if(!$this->checkRequirements(array('ldapHost',
261
-										   'ldapPort',
262
-										   'ldapBase',
263
-										   'ldapUserFilter',
264
-										   ))) {
265
-			return  false;
266
-		}
267
-
268
-		$attr = $this->configuration->ldapEmailAttribute;
269
-		if ($attr !== '') {
270
-			$count = (int)$this->countUsersWithAttribute($attr, true);
271
-			if($count > 0) {
272
-				return false;
273
-			}
274
-			$writeLog = true;
275
-		} else {
276
-			$writeLog = false;
277
-		}
278
-
279
-		$emailAttributes = array('mail', 'mailPrimaryAddress');
280
-		$winner = '';
281
-		$maxUsers = 0;
282
-		foreach($emailAttributes as $attr) {
283
-			$count = $this->countUsersWithAttribute($attr);
284
-			if($count > $maxUsers) {
285
-				$maxUsers = $count;
286
-				$winner = $attr;
287
-			}
288
-		}
289
-
290
-		if($winner !== '') {
291
-			$this->applyFind('ldap_email_attr', $winner);
292
-			if($writeLog) {
293
-				\OCP\Util::writeLog('user_ldap', 'The mail attribute has ' .
294
-					'automatically been reset, because the original value ' .
295
-					'did not return any results.', \OCP\Util::INFO);
296
-			}
297
-		}
298
-
299
-		return $this->result;
300
-	}
301
-
302
-	/**
303
-	 * @return WizardResult
304
-	 * @throws \Exception
305
-	 */
306
-	public function determineAttributes() {
307
-		if(!$this->checkRequirements(array('ldapHost',
308
-										   'ldapPort',
309
-										   'ldapBase',
310
-										   'ldapUserFilter',
311
-										   ))) {
312
-			return  false;
313
-		}
314
-
315
-		$attributes = $this->getUserAttributes();
316
-
317
-		natcasesort($attributes);
318
-		$attributes = array_values($attributes);
319
-
320
-		$this->result->addOptions('ldap_loginfilter_attributes', $attributes);
321
-
322
-		$selected = $this->configuration->ldapLoginFilterAttributes;
323
-		if(is_array($selected) && !empty($selected)) {
324
-			$this->result->addChange('ldap_loginfilter_attributes', $selected);
325
-		}
326
-
327
-		return $this->result;
328
-	}
329
-
330
-	/**
331
-	 * detects the available LDAP attributes
332
-	 * @return array|false The instance's WizardResult instance
333
-	 * @throws \Exception
334
-	 */
335
-	private function getUserAttributes() {
336
-		if(!$this->checkRequirements(array('ldapHost',
337
-										   'ldapPort',
338
-										   'ldapBase',
339
-										   'ldapUserFilter',
340
-										   ))) {
341
-			return  false;
342
-		}
343
-		$cr = $this->getConnection();
344
-		if(!$cr) {
345
-			throw new \Exception('Could not connect to LDAP');
346
-		}
347
-
348
-		$base = $this->configuration->ldapBase[0];
349
-		$filter = $this->configuration->ldapUserFilter;
350
-		$rr = $this->ldap->search($cr, $base, $filter, array(), 1, 1);
351
-		if(!$this->ldap->isResource($rr)) {
352
-			return false;
353
-		}
354
-		$er = $this->ldap->firstEntry($cr, $rr);
355
-		$attributes = $this->ldap->getAttributes($cr, $er);
356
-		$pureAttributes = array();
357
-		for($i = 0; $i < $attributes['count']; $i++) {
358
-			$pureAttributes[] = $attributes[$i];
359
-		}
360
-
361
-		return $pureAttributes;
362
-	}
363
-
364
-	/**
365
-	 * detects the available LDAP groups
366
-	 * @return WizardResult|false the instance's WizardResult instance
367
-	 */
368
-	public function determineGroupsForGroups() {
369
-		return $this->determineGroups('ldap_groupfilter_groups',
370
-									  'ldapGroupFilterGroups',
371
-									  false);
372
-	}
373
-
374
-	/**
375
-	 * detects the available LDAP groups
376
-	 * @return WizardResult|false the instance's WizardResult instance
377
-	 */
378
-	public function determineGroupsForUsers() {
379
-		return $this->determineGroups('ldap_userfilter_groups',
380
-									  'ldapUserFilterGroups');
381
-	}
382
-
383
-	/**
384
-	 * detects the available LDAP groups
385
-	 * @param string $dbKey
386
-	 * @param string $confKey
387
-	 * @param bool $testMemberOf
388
-	 * @return WizardResult|false the instance's WizardResult instance
389
-	 * @throws \Exception
390
-	 */
391
-	private function determineGroups($dbKey, $confKey, $testMemberOf = true) {
392
-		if(!$this->checkRequirements(array('ldapHost',
393
-										   'ldapPort',
394
-										   'ldapBase',
395
-										   ))) {
396
-			return  false;
397
-		}
398
-		$cr = $this->getConnection();
399
-		if(!$cr) {
400
-			throw new \Exception('Could not connect to LDAP');
401
-		}
402
-
403
-		$this->fetchGroups($dbKey, $confKey);
404
-
405
-		if($testMemberOf) {
406
-			$this->configuration->hasMemberOfFilterSupport = $this->testMemberOf();
407
-			$this->result->markChange();
408
-			if(!$this->configuration->hasMemberOfFilterSupport) {
409
-				throw new \Exception('memberOf is not supported by the server');
410
-			}
411
-		}
412
-
413
-		return $this->result;
414
-	}
415
-
416
-	/**
417
-	 * fetches all groups from LDAP and adds them to the result object
418
-	 *
419
-	 * @param string $dbKey
420
-	 * @param string $confKey
421
-	 * @return array $groupEntries
422
-	 * @throws \Exception
423
-	 */
424
-	public function fetchGroups($dbKey, $confKey) {
425
-		$obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames', 'groupOfUniqueNames');
426
-
427
-		$filterParts = array();
428
-		foreach($obclasses as $obclass) {
429
-			$filterParts[] = 'objectclass='.$obclass;
430
-		}
431
-		//we filter for everything
432
-		//- that looks like a group and
433
-		//- has the group display name set
434
-		$filter = $this->access->combineFilterWithOr($filterParts);
435
-		$filter = $this->access->combineFilterWithAnd(array($filter, 'cn=*'));
436
-
437
-		$groupNames = array();
438
-		$groupEntries = array();
439
-		$limit = 400;
440
-		$offset = 0;
441
-		do {
442
-			// we need to request dn additionally here, otherwise memberOf
443
-			// detection will fail later
444
-			$result = $this->access->searchGroups($filter, array('cn', 'dn'), $limit, $offset);
445
-			foreach($result as $item) {
446
-				if(!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
447
-					// just in case - no issue known
448
-					continue;
449
-				}
450
-				$groupNames[] = $item['cn'][0];
451
-				$groupEntries[] = $item;
452
-			}
453
-			$offset += $limit;
454
-		} while ($this->access->hasMoreResults());
455
-
456
-		if(count($groupNames) > 0) {
457
-			natsort($groupNames);
458
-			$this->result->addOptions($dbKey, array_values($groupNames));
459
-		} else {
460
-			throw new \Exception(self::$l->t('Could not find the desired feature'));
461
-		}
462
-
463
-		$setFeatures = $this->configuration->$confKey;
464
-		if(is_array($setFeatures) && !empty($setFeatures)) {
465
-			//something is already configured? pre-select it.
466
-			$this->result->addChange($dbKey, $setFeatures);
467
-		}
468
-		return $groupEntries;
469
-	}
470
-
471
-	public function determineGroupMemberAssoc() {
472
-		if(!$this->checkRequirements(array('ldapHost',
473
-										   'ldapPort',
474
-										   'ldapGroupFilter',
475
-										   ))) {
476
-			return  false;
477
-		}
478
-		$attribute = $this->detectGroupMemberAssoc();
479
-		if($attribute === false) {
480
-			return false;
481
-		}
482
-		$this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute));
483
-		$this->result->addChange('ldap_group_member_assoc_attribute', $attribute);
484
-
485
-		return $this->result;
486
-	}
487
-
488
-	/**
489
-	 * Detects the available object classes
490
-	 * @return WizardResult|false the instance's WizardResult instance
491
-	 * @throws \Exception
492
-	 */
493
-	public function determineGroupObjectClasses() {
494
-		if(!$this->checkRequirements(array('ldapHost',
495
-										   'ldapPort',
496
-										   'ldapBase',
497
-										   ))) {
498
-			return  false;
499
-		}
500
-		$cr = $this->getConnection();
501
-		if(!$cr) {
502
-			throw new \Exception('Could not connect to LDAP');
503
-		}
504
-
505
-		$obclasses = array('groupOfNames', 'groupOfUniqueNames', 'group', 'posixGroup', '*');
506
-		$this->determineFeature($obclasses,
507
-								'objectclass',
508
-								'ldap_groupfilter_objectclass',
509
-								'ldapGroupFilterObjectclass',
510
-								false);
511
-
512
-		return $this->result;
513
-	}
514
-
515
-	/**
516
-	 * detects the available object classes
517
-	 * @return WizardResult
518
-	 * @throws \Exception
519
-	 */
520
-	public function determineUserObjectClasses() {
521
-		if(!$this->checkRequirements(array('ldapHost',
522
-										   'ldapPort',
523
-										   'ldapBase',
524
-										   ))) {
525
-			return  false;
526
-		}
527
-		$cr = $this->getConnection();
528
-		if(!$cr) {
529
-			throw new \Exception('Could not connect to LDAP');
530
-		}
531
-
532
-		$obclasses = array('inetOrgPerson', 'person', 'organizationalPerson',
533
-						   'user', 'posixAccount', '*');
534
-		$filter = $this->configuration->ldapUserFilter;
535
-		//if filter is empty, it is probably the first time the wizard is called
536
-		//then, apply suggestions.
537
-		$this->determineFeature($obclasses,
538
-								'objectclass',
539
-								'ldap_userfilter_objectclass',
540
-								'ldapUserFilterObjectclass',
541
-								empty($filter));
542
-
543
-		return $this->result;
544
-	}
545
-
546
-	/**
547
-	 * @return WizardResult|false
548
-	 * @throws \Exception
549
-	 */
550
-	public function getGroupFilter() {
551
-		if(!$this->checkRequirements(array('ldapHost',
552
-										   'ldapPort',
553
-										   'ldapBase',
554
-										   ))) {
555
-			return false;
556
-		}
557
-		//make sure the use display name is set
558
-		$displayName = $this->configuration->ldapGroupDisplayName;
559
-		if ($displayName === '') {
560
-			$d = $this->configuration->getDefaults();
561
-			$this->applyFind('ldap_group_display_name',
562
-							 $d['ldap_group_display_name']);
563
-		}
564
-		$filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST);
565
-
566
-		$this->applyFind('ldap_group_filter', $filter);
567
-		return $this->result;
568
-	}
569
-
570
-	/**
571
-	 * @return WizardResult|false
572
-	 * @throws \Exception
573
-	 */
574
-	public function getUserListFilter() {
575
-		if(!$this->checkRequirements(array('ldapHost',
576
-										   'ldapPort',
577
-										   'ldapBase',
578
-										   ))) {
579
-			return false;
580
-		}
581
-		//make sure the use display name is set
582
-		$displayName = $this->configuration->ldapUserDisplayName;
583
-		if ($displayName === '') {
584
-			$d = $this->configuration->getDefaults();
585
-			$this->applyFind('ldap_display_name', $d['ldap_display_name']);
586
-		}
587
-		$filter = $this->composeLdapFilter(self::LFILTER_USER_LIST);
588
-		if(!$filter) {
589
-			throw new \Exception('Cannot create filter');
590
-		}
591
-
592
-		$this->applyFind('ldap_userlist_filter', $filter);
593
-		return $this->result;
594
-	}
595
-
596
-	/**
597
-	 * @return bool|WizardResult
598
-	 * @throws \Exception
599
-	 */
600
-	public function getUserLoginFilter() {
601
-		if(!$this->checkRequirements(array('ldapHost',
602
-										   'ldapPort',
603
-										   'ldapBase',
604
-										   'ldapUserFilter',
605
-										   ))) {
606
-			return false;
607
-		}
608
-
609
-		$filter = $this->composeLdapFilter(self::LFILTER_LOGIN);
610
-		if(!$filter) {
611
-			throw new \Exception('Cannot create filter');
612
-		}
613
-
614
-		$this->applyFind('ldap_login_filter', $filter);
615
-		return $this->result;
616
-	}
617
-
618
-	/**
619
-	 * @return bool|WizardResult
620
-	 * @param string $loginName
621
-	 * @throws \Exception
622
-	 */
623
-	public function testLoginName($loginName) {
624
-		if(!$this->checkRequirements(array('ldapHost',
625
-			'ldapPort',
626
-			'ldapBase',
627
-			'ldapLoginFilter',
628
-		))) {
629
-			return false;
630
-		}
631
-
632
-		$cr = $this->access->connection->getConnectionResource();
633
-		if(!$this->ldap->isResource($cr)) {
634
-			throw new \Exception('connection error');
635
-		}
636
-
637
-		if(mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
638
-			=== false) {
639
-			throw new \Exception('missing placeholder');
640
-		}
641
-
642
-		$users = $this->access->countUsersByLoginName($loginName);
643
-		if($this->ldap->errno($cr) !== 0) {
644
-			throw new \Exception($this->ldap->error($cr));
645
-		}
646
-		$filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter);
647
-		$this->result->addChange('ldap_test_loginname', $users);
648
-		$this->result->addChange('ldap_test_effective_filter', $filter);
649
-		return $this->result;
650
-	}
651
-
652
-	/**
653
-	 * Tries to determine the port, requires given Host, User DN and Password
654
-	 * @return WizardResult|false WizardResult on success, false otherwise
655
-	 * @throws \Exception
656
-	 */
657
-	public function guessPortAndTLS() {
658
-		if(!$this->checkRequirements(array('ldapHost',
659
-										   ))) {
660
-			return false;
661
-		}
662
-		$this->checkHost();
663
-		$portSettings = $this->getPortSettingsToTry();
664
-
665
-		if(!is_array($portSettings)) {
666
-			throw new \Exception(print_r($portSettings, true));
667
-		}
668
-
669
-		//proceed from the best configuration and return on first success
670
-		foreach($portSettings as $setting) {
671
-			$p = $setting['port'];
672
-			$t = $setting['tls'];
673
-			\OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, \OCP\Util::DEBUG);
674
-			//connectAndBind may throw Exception, it needs to be catched by the
675
-			//callee of this method
676
-
677
-			try {
678
-				$settingsFound = $this->connectAndBind($p, $t);
679
-			} catch (\Exception $e) {
680
-				// any reply other than -1 (= cannot connect) is already okay,
681
-				// because then we found the server
682
-				// unavailable startTLS returns -11
683
-				if($e->getCode() > 0) {
684
-					$settingsFound = true;
685
-				} else {
686
-					throw $e;
687
-				}
688
-			}
689
-
690
-			if ($settingsFound === true) {
691
-				$config = array(
692
-					'ldapPort' => $p,
693
-					'ldapTLS' => intval($t)
694
-				);
695
-				$this->configuration->setConfiguration($config);
696
-				\OCP\Util::writeLog('user_ldap', 'Wiz: detected Port ' . $p, \OCP\Util::DEBUG);
697
-				$this->result->addChange('ldap_port', $p);
698
-				return $this->result;
699
-			}
700
-		}
701
-
702
-		//custom port, undetected (we do not brute force)
703
-		return false;
704
-	}
705
-
706
-	/**
707
-	 * tries to determine a base dn from User DN or LDAP Host
708
-	 * @return WizardResult|false WizardResult on success, false otherwise
709
-	 */
710
-	public function guessBaseDN() {
711
-		if(!$this->checkRequirements(array('ldapHost',
712
-										   'ldapPort',
713
-										   ))) {
714
-			return false;
715
-		}
716
-
717
-		//check whether a DN is given in the agent name (99.9% of all cases)
718
-		$base = null;
719
-		$i = stripos($this->configuration->ldapAgentName, 'dc=');
720
-		if($i !== false) {
721
-			$base = substr($this->configuration->ldapAgentName, $i);
722
-			if($this->testBaseDN($base)) {
723
-				$this->applyFind('ldap_base', $base);
724
-				return $this->result;
725
-			}
726
-		}
727
-
728
-		//this did not help :(
729
-		//Let's see whether we can parse the Host URL and convert the domain to
730
-		//a base DN
731
-		$helper = new Helper(\OC::$server->getConfig());
732
-		$domain = $helper->getDomainFromURL($this->configuration->ldapHost);
733
-		if(!$domain) {
734
-			return false;
735
-		}
736
-
737
-		$dparts = explode('.', $domain);
738
-		while(count($dparts) > 0) {
739
-			$base2 = 'dc=' . implode(',dc=', $dparts);
740
-			if ($base !== $base2 && $this->testBaseDN($base2)) {
741
-				$this->applyFind('ldap_base', $base2);
742
-				return $this->result;
743
-			}
744
-			array_shift($dparts);
745
-		}
746
-
747
-		return false;
748
-	}
749
-
750
-	/**
751
-	 * sets the found value for the configuration key in the WizardResult
752
-	 * as well as in the Configuration instance
753
-	 * @param string $key the configuration key
754
-	 * @param string $value the (detected) value
755
-	 *
756
-	 */
757
-	private function applyFind($key, $value) {
758
-		$this->result->addChange($key, $value);
759
-		$this->configuration->setConfiguration(array($key => $value));
760
-	}
761
-
762
-	/**
763
-	 * Checks, whether a port was entered in the Host configuration
764
-	 * field. In this case the port will be stripped off, but also stored as
765
-	 * setting.
766
-	 */
767
-	private function checkHost() {
768
-		$host = $this->configuration->ldapHost;
769
-		$hostInfo = parse_url($host);
770
-
771
-		//removes Port from Host
772
-		if(is_array($hostInfo) && isset($hostInfo['port'])) {
773
-			$port = $hostInfo['port'];
774
-			$host = str_replace(':'.$port, '', $host);
775
-			$this->applyFind('ldap_host', $host);
776
-			$this->applyFind('ldap_port', $port);
777
-		}
778
-	}
779
-
780
-	/**
781
-	 * tries to detect the group member association attribute which is
782
-	 * one of 'uniqueMember', 'memberUid', 'member', 'gidNumber'
783
-	 * @return string|false, string with the attribute name, false on error
784
-	 * @throws \Exception
785
-	 */
786
-	private function detectGroupMemberAssoc() {
787
-		$possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber');
788
-		$filter = $this->configuration->ldapGroupFilter;
789
-		if(empty($filter)) {
790
-			return false;
791
-		}
792
-		$cr = $this->getConnection();
793
-		if(!$cr) {
794
-			throw new \Exception('Could not connect to LDAP');
795
-		}
796
-		$base = $this->configuration->ldapBase[0];
797
-		$rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000);
798
-		if(!$this->ldap->isResource($rr)) {
799
-			return false;
800
-		}
801
-		$er = $this->ldap->firstEntry($cr, $rr);
802
-		while(is_resource($er)) {
803
-			$this->ldap->getDN($cr, $er);
804
-			$attrs = $this->ldap->getAttributes($cr, $er);
805
-			$result = array();
806
-			$possibleAttrsCount = count($possibleAttrs);
807
-			for($i = 0; $i < $possibleAttrsCount; $i++) {
808
-				if(isset($attrs[$possibleAttrs[$i]])) {
809
-					$result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count'];
810
-				}
811
-			}
812
-			if(!empty($result)) {
813
-				natsort($result);
814
-				return key($result);
815
-			}
816
-
817
-			$er = $this->ldap->nextEntry($cr, $er);
818
-		}
819
-
820
-		return false;
821
-	}
822
-
823
-	/**
824
-	 * Checks whether for a given BaseDN results will be returned
825
-	 * @param string $base the BaseDN to test
826
-	 * @return bool true on success, false otherwise
827
-	 * @throws \Exception
828
-	 */
829
-	private function testBaseDN($base) {
830
-		$cr = $this->getConnection();
831
-		if(!$cr) {
832
-			throw new \Exception('Could not connect to LDAP');
833
-		}
834
-
835
-		//base is there, let's validate it. If we search for anything, we should
836
-		//get a result set > 0 on a proper base
837
-		$rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1);
838
-		if(!$this->ldap->isResource($rr)) {
839
-			$errorNo  = $this->ldap->errno($cr);
840
-			$errorMsg = $this->ldap->error($cr);
841
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Could not search base '.$base.
842
-							' Error '.$errorNo.': '.$errorMsg, \OCP\Util::INFO);
843
-			return false;
844
-		}
845
-		$entries = $this->ldap->countEntries($cr, $rr);
846
-		return ($entries !== false) && ($entries > 0);
847
-	}
848
-
849
-	/**
850
-	 * Checks whether the server supports memberOf in LDAP Filter.
851
-	 * Note: at least in OpenLDAP, availability of memberOf is dependent on
852
-	 * a configured objectClass. I.e. not necessarily for all available groups
853
-	 * memberOf does work.
854
-	 *
855
-	 * @return bool true if it does, false otherwise
856
-	 * @throws \Exception
857
-	 */
858
-	private function testMemberOf() {
859
-		$cr = $this->getConnection();
860
-		if(!$cr) {
861
-			throw new \Exception('Could not connect to LDAP');
862
-		}
863
-		$result = $this->access->countUsers('memberOf=*', array('memberOf'), 1);
864
-		if(is_int($result) &&  $result > 0) {
865
-			return true;
866
-		}
867
-		return false;
868
-	}
869
-
870
-	/**
871
-	 * creates an LDAP Filter from given configuration
872
-	 * @param integer $filterType int, for which use case the filter shall be created
873
-	 * can be any of self::LFILTER_USER_LIST, self::LFILTER_LOGIN or
874
-	 * self::LFILTER_GROUP_LIST
875
-	 * @return string|false string with the filter on success, false otherwise
876
-	 * @throws \Exception
877
-	 */
878
-	private function composeLdapFilter($filterType) {
879
-		$filter = '';
880
-		$parts = 0;
881
-		switch ($filterType) {
882
-			case self::LFILTER_USER_LIST:
883
-				$objcs = $this->configuration->ldapUserFilterObjectclass;
884
-				//glue objectclasses
885
-				if(is_array($objcs) && count($objcs) > 0) {
886
-					$filter .= '(|';
887
-					foreach($objcs as $objc) {
888
-						$filter .= '(objectclass=' . $objc . ')';
889
-					}
890
-					$filter .= ')';
891
-					$parts++;
892
-				}
893
-				//glue group memberships
894
-				if($this->configuration->hasMemberOfFilterSupport) {
895
-					$cns = $this->configuration->ldapUserFilterGroups;
896
-					if(is_array($cns) && count($cns) > 0) {
897
-						$filter .= '(|';
898
-						$cr = $this->getConnection();
899
-						if(!$cr) {
900
-							throw new \Exception('Could not connect to LDAP');
901
-						}
902
-						$base = $this->configuration->ldapBase[0];
903
-						foreach($cns as $cn) {
904
-							$rr = $this->ldap->search($cr, $base, 'cn=' . $cn, array('dn', 'primaryGroupToken'));
905
-							if(!$this->ldap->isResource($rr)) {
906
-								continue;
907
-							}
908
-							$er = $this->ldap->firstEntry($cr, $rr);
909
-							$attrs = $this->ldap->getAttributes($cr, $er);
910
-							$dn = $this->ldap->getDN($cr, $er);
911
-							if ($dn === false || $dn === '') {
912
-								continue;
913
-							}
914
-							$filterPart = '(memberof=' . $dn . ')';
915
-							if(isset($attrs['primaryGroupToken'])) {
916
-								$pgt = $attrs['primaryGroupToken'][0];
917
-								$primaryFilterPart = '(primaryGroupID=' . $pgt .')';
918
-								$filterPart = '(|' . $filterPart . $primaryFilterPart . ')';
919
-							}
920
-							$filter .= $filterPart;
921
-						}
922
-						$filter .= ')';
923
-					}
924
-					$parts++;
925
-				}
926
-				//wrap parts in AND condition
927
-				if($parts > 1) {
928
-					$filter = '(&' . $filter . ')';
929
-				}
930
-				if ($filter === '') {
931
-					$filter = '(objectclass=*)';
932
-				}
933
-				break;
934
-
935
-			case self::LFILTER_GROUP_LIST:
936
-				$objcs = $this->configuration->ldapGroupFilterObjectclass;
937
-				//glue objectclasses
938
-				if(is_array($objcs) && count($objcs) > 0) {
939
-					$filter .= '(|';
940
-					foreach($objcs as $objc) {
941
-						$filter .= '(objectclass=' . $objc . ')';
942
-					}
943
-					$filter .= ')';
944
-					$parts++;
945
-				}
946
-				//glue group memberships
947
-				$cns = $this->configuration->ldapGroupFilterGroups;
948
-				if(is_array($cns) && count($cns) > 0) {
949
-					$filter .= '(|';
950
-					foreach($cns as $cn) {
951
-						$filter .= '(cn=' . $cn . ')';
952
-					}
953
-					$filter .= ')';
954
-				}
955
-				$parts++;
956
-				//wrap parts in AND condition
957
-				if($parts > 1) {
958
-					$filter = '(&' . $filter . ')';
959
-				}
960
-				break;
961
-
962
-			case self::LFILTER_LOGIN:
963
-				$ulf = $this->configuration->ldapUserFilter;
964
-				$loginpart = '=%uid';
965
-				$filterUsername = '';
966
-				$userAttributes = $this->getUserAttributes();
967
-				$userAttributes = array_change_key_case(array_flip($userAttributes));
968
-				$parts = 0;
969
-
970
-				if($this->configuration->ldapLoginFilterUsername === '1') {
971
-					$attr = '';
972
-					if(isset($userAttributes['uid'])) {
973
-						$attr = 'uid';
974
-					} else if(isset($userAttributes['samaccountname'])) {
975
-						$attr = 'samaccountname';
976
-					} else if(isset($userAttributes['cn'])) {
977
-						//fallback
978
-						$attr = 'cn';
979
-					}
980
-					if ($attr !== '') {
981
-						$filterUsername = '(' . $attr . $loginpart . ')';
982
-						$parts++;
983
-					}
984
-				}
985
-
986
-				$filterEmail = '';
987
-				if($this->configuration->ldapLoginFilterEmail === '1') {
988
-					$filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))';
989
-					$parts++;
990
-				}
991
-
992
-				$filterAttributes = '';
993
-				$attrsToFilter = $this->configuration->ldapLoginFilterAttributes;
994
-				if(is_array($attrsToFilter) && count($attrsToFilter) > 0) {
995
-					$filterAttributes = '(|';
996
-					foreach($attrsToFilter as $attribute) {
997
-						$filterAttributes .= '(' . $attribute . $loginpart . ')';
998
-					}
999
-					$filterAttributes .= ')';
1000
-					$parts++;
1001
-				}
1002
-
1003
-				$filterLogin = '';
1004
-				if($parts > 1) {
1005
-					$filterLogin = '(|';
1006
-				}
1007
-				$filterLogin .= $filterUsername;
1008
-				$filterLogin .= $filterEmail;
1009
-				$filterLogin .= $filterAttributes;
1010
-				if($parts > 1) {
1011
-					$filterLogin .= ')';
1012
-				}
1013
-
1014
-				$filter = '(&'.$ulf.$filterLogin.')';
1015
-				break;
1016
-		}
1017
-
1018
-		\OCP\Util::writeLog('user_ldap', 'Wiz: Final filter '.$filter, \OCP\Util::DEBUG);
1019
-
1020
-		return $filter;
1021
-	}
1022
-
1023
-	/**
1024
-	 * Connects and Binds to an LDAP Server
1025
-	 *
1026
-	 * @param int $port the port to connect with
1027
-	 * @param bool $tls whether startTLS is to be used
1028
-	 * @return bool
1029
-	 * @throws \Exception
1030
-	 */
1031
-	private function connectAndBind($port, $tls) {
1032
-		//connect, does not really trigger any server communication
1033
-		$host = $this->configuration->ldapHost;
1034
-		$hostInfo = parse_url($host);
1035
-		if(!$hostInfo) {
1036
-			throw new \Exception(self::$l->t('Invalid Host'));
1037
-		}
1038
-		\OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', \OCP\Util::DEBUG);
1039
-		$cr = $this->ldap->connect($host, $port);
1040
-		if(!is_resource($cr)) {
1041
-			throw new \Exception(self::$l->t('Invalid Host'));
1042
-		}
1043
-
1044
-		//set LDAP options
1045
-		$this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1046
-		$this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1047
-		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1048
-
1049
-		try {
1050
-			if($tls) {
1051
-				$isTlsWorking = @$this->ldap->startTls($cr);
1052
-				if(!$isTlsWorking) {
1053
-					return false;
1054
-				}
1055
-			}
1056
-
1057
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', \OCP\Util::DEBUG);
1058
-			//interesting part: do the bind!
1059
-			$login = $this->ldap->bind($cr,
1060
-				$this->configuration->ldapAgentName,
1061
-				$this->configuration->ldapAgentPassword
1062
-			);
1063
-			$errNo = $this->ldap->errno($cr);
1064
-			$error = ldap_error($cr);
1065
-			$this->ldap->unbind($cr);
1066
-		} catch(ServerNotAvailableException $e) {
1067
-			return false;
1068
-		}
1069
-
1070
-		if($login === true) {
1071
-			$this->ldap->unbind($cr);
1072
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '. $port . ' TLS ' . intval($tls), \OCP\Util::DEBUG);
1073
-			return true;
1074
-		}
1075
-
1076
-		if($errNo === -1) {
1077
-			//host, port or TLS wrong
1078
-			return false;
1079
-		}
1080
-		throw new \Exception($error, $errNo);
1081
-	}
1082
-
1083
-	/**
1084
-	 * checks whether a valid combination of agent and password has been
1085
-	 * provided (either two values or nothing for anonymous connect)
1086
-	 * @return bool, true if everything is fine, false otherwise
1087
-	 */
1088
-	private function checkAgentRequirements() {
1089
-		$agent = $this->configuration->ldapAgentName;
1090
-		$pwd = $this->configuration->ldapAgentPassword;
1091
-
1092
-		return
1093
-			($agent !== '' && $pwd !== '')
1094
-			||  ($agent === '' && $pwd === '')
1095
-		;
1096
-	}
1097
-
1098
-	/**
1099
-	 * @param array $reqs
1100
-	 * @return bool
1101
-	 */
1102
-	private function checkRequirements($reqs) {
1103
-		$this->checkAgentRequirements();
1104
-		foreach($reqs as $option) {
1105
-			$value = $this->configuration->$option;
1106
-			if(empty($value)) {
1107
-				return false;
1108
-			}
1109
-		}
1110
-		return true;
1111
-	}
1112
-
1113
-	/**
1114
-	 * does a cumulativeSearch on LDAP to get different values of a
1115
-	 * specified attribute
1116
-	 * @param string[] $filters array, the filters that shall be used in the search
1117
-	 * @param string $attr the attribute of which a list of values shall be returned
1118
-	 * @param int $dnReadLimit the amount of how many DNs should be analyzed.
1119
-	 * The lower, the faster
1120
-	 * @param string $maxF string. if not null, this variable will have the filter that
1121
-	 * yields most result entries
1122
-	 * @return array|false an array with the values on success, false otherwise
1123
-	 */
1124
-	public function cumulativeSearchOnAttribute($filters, $attr, $dnReadLimit = 3, &$maxF = null) {
1125
-		$dnRead = array();
1126
-		$foundItems = array();
1127
-		$maxEntries = 0;
1128
-		if(!is_array($this->configuration->ldapBase)
1129
-		   || !isset($this->configuration->ldapBase[0])) {
1130
-			return false;
1131
-		}
1132
-		$base = $this->configuration->ldapBase[0];
1133
-		$cr = $this->getConnection();
1134
-		if(!$this->ldap->isResource($cr)) {
1135
-			return false;
1136
-		}
1137
-		$lastFilter = null;
1138
-		if(isset($filters[count($filters)-1])) {
1139
-			$lastFilter = $filters[count($filters)-1];
1140
-		}
1141
-		foreach($filters as $filter) {
1142
-			if($lastFilter === $filter && count($foundItems) > 0) {
1143
-				//skip when the filter is a wildcard and results were found
1144
-				continue;
1145
-			}
1146
-			// 20k limit for performance and reason
1147
-			$rr = $this->ldap->search($cr, $base, $filter, array($attr), 0, 20000);
1148
-			if(!$this->ldap->isResource($rr)) {
1149
-				continue;
1150
-			}
1151
-			$entries = $this->ldap->countEntries($cr, $rr);
1152
-			$getEntryFunc = 'firstEntry';
1153
-			if(($entries !== false) && ($entries > 0)) {
1154
-				if(!is_null($maxF) && $entries > $maxEntries) {
1155
-					$maxEntries = $entries;
1156
-					$maxF = $filter;
1157
-				}
1158
-				$dnReadCount = 0;
1159
-				do {
1160
-					$entry = $this->ldap->$getEntryFunc($cr, $rr);
1161
-					$getEntryFunc = 'nextEntry';
1162
-					if(!$this->ldap->isResource($entry)) {
1163
-						continue 2;
1164
-					}
1165
-					$rr = $entry; //will be expected by nextEntry next round
1166
-					$attributes = $this->ldap->getAttributes($cr, $entry);
1167
-					$dn = $this->ldap->getDN($cr, $entry);
1168
-					if($dn === false || in_array($dn, $dnRead)) {
1169
-						continue;
1170
-					}
1171
-					$newItems = array();
1172
-					$state = $this->getAttributeValuesFromEntry($attributes,
1173
-																$attr,
1174
-																$newItems);
1175
-					$dnReadCount++;
1176
-					$foundItems = array_merge($foundItems, $newItems);
1177
-					$this->resultCache[$dn][$attr] = $newItems;
1178
-					$dnRead[] = $dn;
1179
-				} while(($state === self::LRESULT_PROCESSED_SKIP
1180
-						|| $this->ldap->isResource($entry))
1181
-						&& ($dnReadLimit === 0 || $dnReadCount < $dnReadLimit));
1182
-			}
1183
-		}
1184
-
1185
-		return array_unique($foundItems);
1186
-	}
1187
-
1188
-	/**
1189
-	 * determines if and which $attr are available on the LDAP server
1190
-	 * @param string[] $objectclasses the objectclasses to use as search filter
1191
-	 * @param string $attr the attribute to look for
1192
-	 * @param string $dbkey the dbkey of the setting the feature is connected to
1193
-	 * @param string $confkey the confkey counterpart for the $dbkey as used in the
1194
-	 * Configuration class
1195
-	 * @param bool $po whether the objectClass with most result entries
1196
-	 * shall be pre-selected via the result
1197
-	 * @return array|false list of found items.
1198
-	 * @throws \Exception
1199
-	 */
1200
-	private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) {
1201
-		$cr = $this->getConnection();
1202
-		if(!$cr) {
1203
-			throw new \Exception('Could not connect to LDAP');
1204
-		}
1205
-		$p = 'objectclass=';
1206
-		foreach($objectclasses as $key => $value) {
1207
-			$objectclasses[$key] = $p.$value;
1208
-		}
1209
-		$maxEntryObjC = '';
1210
-
1211
-		//how deep to dig?
1212
-		//When looking for objectclasses, testing few entries is sufficient,
1213
-		$dig = 3;
1214
-
1215
-		$availableFeatures =
1216
-			$this->cumulativeSearchOnAttribute($objectclasses, $attr,
1217
-											   $dig, $maxEntryObjC);
1218
-		if(is_array($availableFeatures)
1219
-		   && count($availableFeatures) > 0) {
1220
-			natcasesort($availableFeatures);
1221
-			//natcasesort keeps indices, but we must get rid of them for proper
1222
-			//sorting in the web UI. Therefore: array_values
1223
-			$this->result->addOptions($dbkey, array_values($availableFeatures));
1224
-		} else {
1225
-			throw new \Exception(self::$l->t('Could not find the desired feature'));
1226
-		}
1227
-
1228
-		$setFeatures = $this->configuration->$confkey;
1229
-		if(is_array($setFeatures) && !empty($setFeatures)) {
1230
-			//something is already configured? pre-select it.
1231
-			$this->result->addChange($dbkey, $setFeatures);
1232
-		} else if ($po && $maxEntryObjC !== '') {
1233
-			//pre-select objectclass with most result entries
1234
-			$maxEntryObjC = str_replace($p, '', $maxEntryObjC);
1235
-			$this->applyFind($dbkey, $maxEntryObjC);
1236
-			$this->result->addChange($dbkey, $maxEntryObjC);
1237
-		}
1238
-
1239
-		return $availableFeatures;
1240
-	}
1241
-
1242
-	/**
1243
-	 * appends a list of values fr
1244
-	 * @param resource $result the return value from ldap_get_attributes
1245
-	 * @param string $attribute the attribute values to look for
1246
-	 * @param array &$known new values will be appended here
1247
-	 * @return int, state on of the class constants LRESULT_PROCESSED_OK,
1248
-	 * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP
1249
-	 */
1250
-	private function getAttributeValuesFromEntry($result, $attribute, &$known) {
1251
-		if(!is_array($result)
1252
-		   || !isset($result['count'])
1253
-		   || !$result['count'] > 0) {
1254
-			return self::LRESULT_PROCESSED_INVALID;
1255
-		}
1256
-
1257
-		// strtolower on all keys for proper comparison
1258
-		$result = \OCP\Util::mb_array_change_key_case($result);
1259
-		$attribute = strtolower($attribute);
1260
-		if(isset($result[$attribute])) {
1261
-			foreach($result[$attribute] as $key => $val) {
1262
-				if($key === 'count') {
1263
-					continue;
1264
-				}
1265
-				if(!in_array($val, $known)) {
1266
-					$known[] = $val;
1267
-				}
1268
-			}
1269
-			return self::LRESULT_PROCESSED_OK;
1270
-		} else {
1271
-			return self::LRESULT_PROCESSED_SKIP;
1272
-		}
1273
-	}
1274
-
1275
-	/**
1276
-	 * @return bool|mixed
1277
-	 */
1278
-	private function getConnection() {
1279
-		if(!is_null($this->cr)) {
1280
-			return $this->cr;
1281
-		}
1282
-
1283
-		$cr = $this->ldap->connect(
1284
-			$this->configuration->ldapHost,
1285
-			$this->configuration->ldapPort
1286
-		);
1287
-
1288
-		$this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1289
-		$this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1290
-		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1291
-		if($this->configuration->ldapTLS === 1) {
1292
-			$this->ldap->startTls($cr);
1293
-		}
1294
-
1295
-		$lo = @$this->ldap->bind($cr,
1296
-								 $this->configuration->ldapAgentName,
1297
-								 $this->configuration->ldapAgentPassword);
1298
-		if($lo === true) {
1299
-			$this->$cr = $cr;
1300
-			return $cr;
1301
-		}
1302
-
1303
-		return false;
1304
-	}
1305
-
1306
-	/**
1307
-	 * @return array
1308
-	 */
1309
-	private function getDefaultLdapPortSettings() {
1310
-		static $settings = array(
1311
-								array('port' => 7636, 'tls' => false),
1312
-								array('port' =>  636, 'tls' => false),
1313
-								array('port' => 7389, 'tls' => true),
1314
-								array('port' =>  389, 'tls' => true),
1315
-								array('port' => 7389, 'tls' => false),
1316
-								array('port' =>  389, 'tls' => false),
1317
-						  );
1318
-		return $settings;
1319
-	}
1320
-
1321
-	/**
1322
-	 * @return array
1323
-	 */
1324
-	private function getPortSettingsToTry() {
1325
-		//389 ← LDAP / Unencrypted or StartTLS
1326
-		//636 ← LDAPS / SSL
1327
-		//7xxx ← UCS. need to be checked first, because both ports may be open
1328
-		$host = $this->configuration->ldapHost;
1329
-		$port = intval($this->configuration->ldapPort);
1330
-		$portSettings = array();
1331
-
1332
-		//In case the port is already provided, we will check this first
1333
-		if($port > 0) {
1334
-			$hostInfo = parse_url($host);
1335
-			if(!(is_array($hostInfo)
1336
-				&& isset($hostInfo['scheme'])
1337
-				&& stripos($hostInfo['scheme'], 'ldaps') !== false)) {
1338
-				$portSettings[] = array('port' => $port, 'tls' => true);
1339
-			}
1340
-			$portSettings[] =array('port' => $port, 'tls' => false);
1341
-		}
1342
-
1343
-		//default ports
1344
-		$portSettings = array_merge($portSettings,
1345
-		                            $this->getDefaultLdapPortSettings());
1346
-
1347
-		return $portSettings;
1348
-	}
44
+    /** @var \OCP\IL10N */
45
+    static protected $l;
46
+    protected $access;
47
+    protected $cr;
48
+    protected $configuration;
49
+    protected $result;
50
+    protected $resultCache = array();
51
+
52
+    const LRESULT_PROCESSED_OK = 2;
53
+    const LRESULT_PROCESSED_INVALID = 3;
54
+    const LRESULT_PROCESSED_SKIP = 4;
55
+
56
+    const LFILTER_LOGIN      = 2;
57
+    const LFILTER_USER_LIST  = 3;
58
+    const LFILTER_GROUP_LIST = 4;
59
+
60
+    const LFILTER_MODE_ASSISTED = 2;
61
+    const LFILTER_MODE_RAW = 1;
62
+
63
+    const LDAP_NW_TIMEOUT = 4;
64
+
65
+    /**
66
+     * Constructor
67
+     * @param Configuration $configuration an instance of Configuration
68
+     * @param ILDAPWrapper $ldap an instance of ILDAPWrapper
69
+     * @param Access $access
70
+     */
71
+    public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) {
72
+        parent::__construct($ldap);
73
+        $this->configuration = $configuration;
74
+        if(is_null(Wizard::$l)) {
75
+            Wizard::$l = \OC::$server->getL10N('user_ldap');
76
+        }
77
+        $this->access = $access;
78
+        $this->result = new WizardResult();
79
+    }
80
+
81
+    public function  __destruct() {
82
+        if($this->result->hasChanges()) {
83
+            $this->configuration->saveConfiguration();
84
+        }
85
+    }
86
+
87
+    /**
88
+     * counts entries in the LDAP directory
89
+     *
90
+     * @param string $filter the LDAP search filter
91
+     * @param string $type a string being either 'users' or 'groups';
92
+     * @return bool|int
93
+     * @throws \Exception
94
+     */
95
+    public function countEntries($filter, $type) {
96
+        $reqs = array('ldapHost', 'ldapPort', 'ldapBase');
97
+        if($type === 'users') {
98
+            $reqs[] = 'ldapUserFilter';
99
+        }
100
+        if(!$this->checkRequirements($reqs)) {
101
+            throw new \Exception('Requirements not met', 400);
102
+        }
103
+
104
+        $attr = array('dn'); // default
105
+        $limit = 1001;
106
+        if($type === 'groups') {
107
+            $result =  $this->access->countGroups($filter, $attr, $limit);
108
+        } else if($type === 'users') {
109
+            $result = $this->access->countUsers($filter, $attr, $limit);
110
+        } else if ($type === 'objects') {
111
+            $result = $this->access->countObjects($limit);
112
+        } else {
113
+            throw new \Exception('Internal error: Invalid object type', 500);
114
+        }
115
+
116
+        return $result;
117
+    }
118
+
119
+    /**
120
+     * formats the return value of a count operation to the string to be
121
+     * inserted.
122
+     *
123
+     * @param bool|int $count
124
+     * @return int|string
125
+     */
126
+    private function formatCountResult($count) {
127
+        $formatted = ($count !== false) ? $count : 0;
128
+        if($formatted > 1000) {
129
+            $formatted = '> 1000';
130
+        }
131
+        return $formatted;
132
+    }
133
+
134
+    public function countGroups() {
135
+        $filter = $this->configuration->ldapGroupFilter;
136
+
137
+        if(empty($filter)) {
138
+            $output = self::$l->n('%s group found', '%s groups found', 0, array(0));
139
+            $this->result->addChange('ldap_group_count', $output);
140
+            return $this->result;
141
+        }
142
+
143
+        try {
144
+            $groupsTotal = $this->formatCountResult($this->countEntries($filter, 'groups'));
145
+        } catch (\Exception $e) {
146
+            //400 can be ignored, 500 is forwarded
147
+            if($e->getCode() === 500) {
148
+                throw $e;
149
+            }
150
+            return false;
151
+        }
152
+        $output = self::$l->n('%s group found', '%s groups found', $groupsTotal, array($groupsTotal));
153
+        $this->result->addChange('ldap_group_count', $output);
154
+        return $this->result;
155
+    }
156
+
157
+    /**
158
+     * @return WizardResult
159
+     * @throws \Exception
160
+     */
161
+    public function countUsers() {
162
+        $filter = $this->access->getFilterForUserCount();
163
+
164
+        $usersTotal = $this->formatCountResult($this->countEntries($filter, 'users'));
165
+        $output = self::$l->n('%s user found', '%s users found', $usersTotal, array($usersTotal));
166
+        $this->result->addChange('ldap_user_count', $output);
167
+        return $this->result;
168
+    }
169
+
170
+    /**
171
+     * counts any objects in the currently set base dn
172
+     *
173
+     * @return WizardResult
174
+     * @throws \Exception
175
+     */
176
+    public function countInBaseDN() {
177
+        // we don't need to provide a filter in this case
178
+        $total = $this->countEntries(null, 'objects');
179
+        if($total === false) {
180
+            throw new \Exception('invalid results received');
181
+        }
182
+        $this->result->addChange('ldap_test_base', $total);
183
+        return $this->result;
184
+    }
185
+
186
+    /**
187
+     * counts users with a specified attribute
188
+     * @param string $attr
189
+     * @param bool $existsCheck
190
+     * @return int|bool
191
+     */
192
+    public function countUsersWithAttribute($attr, $existsCheck = false) {
193
+        if(!$this->checkRequirements(array('ldapHost',
194
+                                            'ldapPort',
195
+                                            'ldapBase',
196
+                                            'ldapUserFilter',
197
+                                            ))) {
198
+            return  false;
199
+        }
200
+
201
+        $filter = $this->access->combineFilterWithAnd(array(
202
+            $this->configuration->ldapUserFilter,
203
+            $attr . '=*'
204
+        ));
205
+
206
+        $limit = ($existsCheck === false) ? null : 1;
207
+
208
+        return $this->access->countUsers($filter, array('dn'), $limit);
209
+    }
210
+
211
+    /**
212
+     * detects the display name attribute. If a setting is already present that
213
+     * returns at least one hit, the detection will be canceled.
214
+     * @return WizardResult|bool
215
+     * @throws \Exception
216
+     */
217
+    public function detectUserDisplayNameAttribute() {
218
+        if(!$this->checkRequirements(array('ldapHost',
219
+                                        'ldapPort',
220
+                                        'ldapBase',
221
+                                        'ldapUserFilter',
222
+                                        ))) {
223
+            return  false;
224
+        }
225
+
226
+        $attr = $this->configuration->ldapUserDisplayName;
227
+        if ($attr !== '' && $attr !== 'displayName') {
228
+            // most likely not the default value with upper case N,
229
+            // verify it still produces a result
230
+            $count = (int)$this->countUsersWithAttribute($attr, true);
231
+            if($count > 0) {
232
+                //no change, but we sent it back to make sure the user interface
233
+                //is still correct, even if the ajax call was cancelled meanwhile
234
+                $this->result->addChange('ldap_display_name', $attr);
235
+                return $this->result;
236
+            }
237
+        }
238
+
239
+        // first attribute that has at least one result wins
240
+        $displayNameAttrs = array('displayname', 'cn');
241
+        foreach ($displayNameAttrs as $attr) {
242
+            $count = (int)$this->countUsersWithAttribute($attr, true);
243
+
244
+            if($count > 0) {
245
+                $this->applyFind('ldap_display_name', $attr);
246
+                return $this->result;
247
+            }
248
+        };
249
+
250
+        throw new \Exception(self::$l->t('Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings.'));
251
+    }
252
+
253
+    /**
254
+     * detects the most often used email attribute for users applying to the
255
+     * user list filter. If a setting is already present that returns at least
256
+     * one hit, the detection will be canceled.
257
+     * @return WizardResult|bool
258
+     */
259
+    public function detectEmailAttribute() {
260
+        if(!$this->checkRequirements(array('ldapHost',
261
+                                            'ldapPort',
262
+                                            'ldapBase',
263
+                                            'ldapUserFilter',
264
+                                            ))) {
265
+            return  false;
266
+        }
267
+
268
+        $attr = $this->configuration->ldapEmailAttribute;
269
+        if ($attr !== '') {
270
+            $count = (int)$this->countUsersWithAttribute($attr, true);
271
+            if($count > 0) {
272
+                return false;
273
+            }
274
+            $writeLog = true;
275
+        } else {
276
+            $writeLog = false;
277
+        }
278
+
279
+        $emailAttributes = array('mail', 'mailPrimaryAddress');
280
+        $winner = '';
281
+        $maxUsers = 0;
282
+        foreach($emailAttributes as $attr) {
283
+            $count = $this->countUsersWithAttribute($attr);
284
+            if($count > $maxUsers) {
285
+                $maxUsers = $count;
286
+                $winner = $attr;
287
+            }
288
+        }
289
+
290
+        if($winner !== '') {
291
+            $this->applyFind('ldap_email_attr', $winner);
292
+            if($writeLog) {
293
+                \OCP\Util::writeLog('user_ldap', 'The mail attribute has ' .
294
+                    'automatically been reset, because the original value ' .
295
+                    'did not return any results.', \OCP\Util::INFO);
296
+            }
297
+        }
298
+
299
+        return $this->result;
300
+    }
301
+
302
+    /**
303
+     * @return WizardResult
304
+     * @throws \Exception
305
+     */
306
+    public function determineAttributes() {
307
+        if(!$this->checkRequirements(array('ldapHost',
308
+                                            'ldapPort',
309
+                                            'ldapBase',
310
+                                            'ldapUserFilter',
311
+                                            ))) {
312
+            return  false;
313
+        }
314
+
315
+        $attributes = $this->getUserAttributes();
316
+
317
+        natcasesort($attributes);
318
+        $attributes = array_values($attributes);
319
+
320
+        $this->result->addOptions('ldap_loginfilter_attributes', $attributes);
321
+
322
+        $selected = $this->configuration->ldapLoginFilterAttributes;
323
+        if(is_array($selected) && !empty($selected)) {
324
+            $this->result->addChange('ldap_loginfilter_attributes', $selected);
325
+        }
326
+
327
+        return $this->result;
328
+    }
329
+
330
+    /**
331
+     * detects the available LDAP attributes
332
+     * @return array|false The instance's WizardResult instance
333
+     * @throws \Exception
334
+     */
335
+    private function getUserAttributes() {
336
+        if(!$this->checkRequirements(array('ldapHost',
337
+                                            'ldapPort',
338
+                                            'ldapBase',
339
+                                            'ldapUserFilter',
340
+                                            ))) {
341
+            return  false;
342
+        }
343
+        $cr = $this->getConnection();
344
+        if(!$cr) {
345
+            throw new \Exception('Could not connect to LDAP');
346
+        }
347
+
348
+        $base = $this->configuration->ldapBase[0];
349
+        $filter = $this->configuration->ldapUserFilter;
350
+        $rr = $this->ldap->search($cr, $base, $filter, array(), 1, 1);
351
+        if(!$this->ldap->isResource($rr)) {
352
+            return false;
353
+        }
354
+        $er = $this->ldap->firstEntry($cr, $rr);
355
+        $attributes = $this->ldap->getAttributes($cr, $er);
356
+        $pureAttributes = array();
357
+        for($i = 0; $i < $attributes['count']; $i++) {
358
+            $pureAttributes[] = $attributes[$i];
359
+        }
360
+
361
+        return $pureAttributes;
362
+    }
363
+
364
+    /**
365
+     * detects the available LDAP groups
366
+     * @return WizardResult|false the instance's WizardResult instance
367
+     */
368
+    public function determineGroupsForGroups() {
369
+        return $this->determineGroups('ldap_groupfilter_groups',
370
+                                        'ldapGroupFilterGroups',
371
+                                        false);
372
+    }
373
+
374
+    /**
375
+     * detects the available LDAP groups
376
+     * @return WizardResult|false the instance's WizardResult instance
377
+     */
378
+    public function determineGroupsForUsers() {
379
+        return $this->determineGroups('ldap_userfilter_groups',
380
+                                        'ldapUserFilterGroups');
381
+    }
382
+
383
+    /**
384
+     * detects the available LDAP groups
385
+     * @param string $dbKey
386
+     * @param string $confKey
387
+     * @param bool $testMemberOf
388
+     * @return WizardResult|false the instance's WizardResult instance
389
+     * @throws \Exception
390
+     */
391
+    private function determineGroups($dbKey, $confKey, $testMemberOf = true) {
392
+        if(!$this->checkRequirements(array('ldapHost',
393
+                                            'ldapPort',
394
+                                            'ldapBase',
395
+                                            ))) {
396
+            return  false;
397
+        }
398
+        $cr = $this->getConnection();
399
+        if(!$cr) {
400
+            throw new \Exception('Could not connect to LDAP');
401
+        }
402
+
403
+        $this->fetchGroups($dbKey, $confKey);
404
+
405
+        if($testMemberOf) {
406
+            $this->configuration->hasMemberOfFilterSupport = $this->testMemberOf();
407
+            $this->result->markChange();
408
+            if(!$this->configuration->hasMemberOfFilterSupport) {
409
+                throw new \Exception('memberOf is not supported by the server');
410
+            }
411
+        }
412
+
413
+        return $this->result;
414
+    }
415
+
416
+    /**
417
+     * fetches all groups from LDAP and adds them to the result object
418
+     *
419
+     * @param string $dbKey
420
+     * @param string $confKey
421
+     * @return array $groupEntries
422
+     * @throws \Exception
423
+     */
424
+    public function fetchGroups($dbKey, $confKey) {
425
+        $obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames', 'groupOfUniqueNames');
426
+
427
+        $filterParts = array();
428
+        foreach($obclasses as $obclass) {
429
+            $filterParts[] = 'objectclass='.$obclass;
430
+        }
431
+        //we filter for everything
432
+        //- that looks like a group and
433
+        //- has the group display name set
434
+        $filter = $this->access->combineFilterWithOr($filterParts);
435
+        $filter = $this->access->combineFilterWithAnd(array($filter, 'cn=*'));
436
+
437
+        $groupNames = array();
438
+        $groupEntries = array();
439
+        $limit = 400;
440
+        $offset = 0;
441
+        do {
442
+            // we need to request dn additionally here, otherwise memberOf
443
+            // detection will fail later
444
+            $result = $this->access->searchGroups($filter, array('cn', 'dn'), $limit, $offset);
445
+            foreach($result as $item) {
446
+                if(!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
447
+                    // just in case - no issue known
448
+                    continue;
449
+                }
450
+                $groupNames[] = $item['cn'][0];
451
+                $groupEntries[] = $item;
452
+            }
453
+            $offset += $limit;
454
+        } while ($this->access->hasMoreResults());
455
+
456
+        if(count($groupNames) > 0) {
457
+            natsort($groupNames);
458
+            $this->result->addOptions($dbKey, array_values($groupNames));
459
+        } else {
460
+            throw new \Exception(self::$l->t('Could not find the desired feature'));
461
+        }
462
+
463
+        $setFeatures = $this->configuration->$confKey;
464
+        if(is_array($setFeatures) && !empty($setFeatures)) {
465
+            //something is already configured? pre-select it.
466
+            $this->result->addChange($dbKey, $setFeatures);
467
+        }
468
+        return $groupEntries;
469
+    }
470
+
471
+    public function determineGroupMemberAssoc() {
472
+        if(!$this->checkRequirements(array('ldapHost',
473
+                                            'ldapPort',
474
+                                            'ldapGroupFilter',
475
+                                            ))) {
476
+            return  false;
477
+        }
478
+        $attribute = $this->detectGroupMemberAssoc();
479
+        if($attribute === false) {
480
+            return false;
481
+        }
482
+        $this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute));
483
+        $this->result->addChange('ldap_group_member_assoc_attribute', $attribute);
484
+
485
+        return $this->result;
486
+    }
487
+
488
+    /**
489
+     * Detects the available object classes
490
+     * @return WizardResult|false the instance's WizardResult instance
491
+     * @throws \Exception
492
+     */
493
+    public function determineGroupObjectClasses() {
494
+        if(!$this->checkRequirements(array('ldapHost',
495
+                                            'ldapPort',
496
+                                            'ldapBase',
497
+                                            ))) {
498
+            return  false;
499
+        }
500
+        $cr = $this->getConnection();
501
+        if(!$cr) {
502
+            throw new \Exception('Could not connect to LDAP');
503
+        }
504
+
505
+        $obclasses = array('groupOfNames', 'groupOfUniqueNames', 'group', 'posixGroup', '*');
506
+        $this->determineFeature($obclasses,
507
+                                'objectclass',
508
+                                'ldap_groupfilter_objectclass',
509
+                                'ldapGroupFilterObjectclass',
510
+                                false);
511
+
512
+        return $this->result;
513
+    }
514
+
515
+    /**
516
+     * detects the available object classes
517
+     * @return WizardResult
518
+     * @throws \Exception
519
+     */
520
+    public function determineUserObjectClasses() {
521
+        if(!$this->checkRequirements(array('ldapHost',
522
+                                            'ldapPort',
523
+                                            'ldapBase',
524
+                                            ))) {
525
+            return  false;
526
+        }
527
+        $cr = $this->getConnection();
528
+        if(!$cr) {
529
+            throw new \Exception('Could not connect to LDAP');
530
+        }
531
+
532
+        $obclasses = array('inetOrgPerson', 'person', 'organizationalPerson',
533
+                            'user', 'posixAccount', '*');
534
+        $filter = $this->configuration->ldapUserFilter;
535
+        //if filter is empty, it is probably the first time the wizard is called
536
+        //then, apply suggestions.
537
+        $this->determineFeature($obclasses,
538
+                                'objectclass',
539
+                                'ldap_userfilter_objectclass',
540
+                                'ldapUserFilterObjectclass',
541
+                                empty($filter));
542
+
543
+        return $this->result;
544
+    }
545
+
546
+    /**
547
+     * @return WizardResult|false
548
+     * @throws \Exception
549
+     */
550
+    public function getGroupFilter() {
551
+        if(!$this->checkRequirements(array('ldapHost',
552
+                                            'ldapPort',
553
+                                            'ldapBase',
554
+                                            ))) {
555
+            return false;
556
+        }
557
+        //make sure the use display name is set
558
+        $displayName = $this->configuration->ldapGroupDisplayName;
559
+        if ($displayName === '') {
560
+            $d = $this->configuration->getDefaults();
561
+            $this->applyFind('ldap_group_display_name',
562
+                                $d['ldap_group_display_name']);
563
+        }
564
+        $filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST);
565
+
566
+        $this->applyFind('ldap_group_filter', $filter);
567
+        return $this->result;
568
+    }
569
+
570
+    /**
571
+     * @return WizardResult|false
572
+     * @throws \Exception
573
+     */
574
+    public function getUserListFilter() {
575
+        if(!$this->checkRequirements(array('ldapHost',
576
+                                            'ldapPort',
577
+                                            'ldapBase',
578
+                                            ))) {
579
+            return false;
580
+        }
581
+        //make sure the use display name is set
582
+        $displayName = $this->configuration->ldapUserDisplayName;
583
+        if ($displayName === '') {
584
+            $d = $this->configuration->getDefaults();
585
+            $this->applyFind('ldap_display_name', $d['ldap_display_name']);
586
+        }
587
+        $filter = $this->composeLdapFilter(self::LFILTER_USER_LIST);
588
+        if(!$filter) {
589
+            throw new \Exception('Cannot create filter');
590
+        }
591
+
592
+        $this->applyFind('ldap_userlist_filter', $filter);
593
+        return $this->result;
594
+    }
595
+
596
+    /**
597
+     * @return bool|WizardResult
598
+     * @throws \Exception
599
+     */
600
+    public function getUserLoginFilter() {
601
+        if(!$this->checkRequirements(array('ldapHost',
602
+                                            'ldapPort',
603
+                                            'ldapBase',
604
+                                            'ldapUserFilter',
605
+                                            ))) {
606
+            return false;
607
+        }
608
+
609
+        $filter = $this->composeLdapFilter(self::LFILTER_LOGIN);
610
+        if(!$filter) {
611
+            throw new \Exception('Cannot create filter');
612
+        }
613
+
614
+        $this->applyFind('ldap_login_filter', $filter);
615
+        return $this->result;
616
+    }
617
+
618
+    /**
619
+     * @return bool|WizardResult
620
+     * @param string $loginName
621
+     * @throws \Exception
622
+     */
623
+    public function testLoginName($loginName) {
624
+        if(!$this->checkRequirements(array('ldapHost',
625
+            'ldapPort',
626
+            'ldapBase',
627
+            'ldapLoginFilter',
628
+        ))) {
629
+            return false;
630
+        }
631
+
632
+        $cr = $this->access->connection->getConnectionResource();
633
+        if(!$this->ldap->isResource($cr)) {
634
+            throw new \Exception('connection error');
635
+        }
636
+
637
+        if(mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
638
+            === false) {
639
+            throw new \Exception('missing placeholder');
640
+        }
641
+
642
+        $users = $this->access->countUsersByLoginName($loginName);
643
+        if($this->ldap->errno($cr) !== 0) {
644
+            throw new \Exception($this->ldap->error($cr));
645
+        }
646
+        $filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter);
647
+        $this->result->addChange('ldap_test_loginname', $users);
648
+        $this->result->addChange('ldap_test_effective_filter', $filter);
649
+        return $this->result;
650
+    }
651
+
652
+    /**
653
+     * Tries to determine the port, requires given Host, User DN and Password
654
+     * @return WizardResult|false WizardResult on success, false otherwise
655
+     * @throws \Exception
656
+     */
657
+    public function guessPortAndTLS() {
658
+        if(!$this->checkRequirements(array('ldapHost',
659
+                                            ))) {
660
+            return false;
661
+        }
662
+        $this->checkHost();
663
+        $portSettings = $this->getPortSettingsToTry();
664
+
665
+        if(!is_array($portSettings)) {
666
+            throw new \Exception(print_r($portSettings, true));
667
+        }
668
+
669
+        //proceed from the best configuration and return on first success
670
+        foreach($portSettings as $setting) {
671
+            $p = $setting['port'];
672
+            $t = $setting['tls'];
673
+            \OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, \OCP\Util::DEBUG);
674
+            //connectAndBind may throw Exception, it needs to be catched by the
675
+            //callee of this method
676
+
677
+            try {
678
+                $settingsFound = $this->connectAndBind($p, $t);
679
+            } catch (\Exception $e) {
680
+                // any reply other than -1 (= cannot connect) is already okay,
681
+                // because then we found the server
682
+                // unavailable startTLS returns -11
683
+                if($e->getCode() > 0) {
684
+                    $settingsFound = true;
685
+                } else {
686
+                    throw $e;
687
+                }
688
+            }
689
+
690
+            if ($settingsFound === true) {
691
+                $config = array(
692
+                    'ldapPort' => $p,
693
+                    'ldapTLS' => intval($t)
694
+                );
695
+                $this->configuration->setConfiguration($config);
696
+                \OCP\Util::writeLog('user_ldap', 'Wiz: detected Port ' . $p, \OCP\Util::DEBUG);
697
+                $this->result->addChange('ldap_port', $p);
698
+                return $this->result;
699
+            }
700
+        }
701
+
702
+        //custom port, undetected (we do not brute force)
703
+        return false;
704
+    }
705
+
706
+    /**
707
+     * tries to determine a base dn from User DN or LDAP Host
708
+     * @return WizardResult|false WizardResult on success, false otherwise
709
+     */
710
+    public function guessBaseDN() {
711
+        if(!$this->checkRequirements(array('ldapHost',
712
+                                            'ldapPort',
713
+                                            ))) {
714
+            return false;
715
+        }
716
+
717
+        //check whether a DN is given in the agent name (99.9% of all cases)
718
+        $base = null;
719
+        $i = stripos($this->configuration->ldapAgentName, 'dc=');
720
+        if($i !== false) {
721
+            $base = substr($this->configuration->ldapAgentName, $i);
722
+            if($this->testBaseDN($base)) {
723
+                $this->applyFind('ldap_base', $base);
724
+                return $this->result;
725
+            }
726
+        }
727
+
728
+        //this did not help :(
729
+        //Let's see whether we can parse the Host URL and convert the domain to
730
+        //a base DN
731
+        $helper = new Helper(\OC::$server->getConfig());
732
+        $domain = $helper->getDomainFromURL($this->configuration->ldapHost);
733
+        if(!$domain) {
734
+            return false;
735
+        }
736
+
737
+        $dparts = explode('.', $domain);
738
+        while(count($dparts) > 0) {
739
+            $base2 = 'dc=' . implode(',dc=', $dparts);
740
+            if ($base !== $base2 && $this->testBaseDN($base2)) {
741
+                $this->applyFind('ldap_base', $base2);
742
+                return $this->result;
743
+            }
744
+            array_shift($dparts);
745
+        }
746
+
747
+        return false;
748
+    }
749
+
750
+    /**
751
+     * sets the found value for the configuration key in the WizardResult
752
+     * as well as in the Configuration instance
753
+     * @param string $key the configuration key
754
+     * @param string $value the (detected) value
755
+     *
756
+     */
757
+    private function applyFind($key, $value) {
758
+        $this->result->addChange($key, $value);
759
+        $this->configuration->setConfiguration(array($key => $value));
760
+    }
761
+
762
+    /**
763
+     * Checks, whether a port was entered in the Host configuration
764
+     * field. In this case the port will be stripped off, but also stored as
765
+     * setting.
766
+     */
767
+    private function checkHost() {
768
+        $host = $this->configuration->ldapHost;
769
+        $hostInfo = parse_url($host);
770
+
771
+        //removes Port from Host
772
+        if(is_array($hostInfo) && isset($hostInfo['port'])) {
773
+            $port = $hostInfo['port'];
774
+            $host = str_replace(':'.$port, '', $host);
775
+            $this->applyFind('ldap_host', $host);
776
+            $this->applyFind('ldap_port', $port);
777
+        }
778
+    }
779
+
780
+    /**
781
+     * tries to detect the group member association attribute which is
782
+     * one of 'uniqueMember', 'memberUid', 'member', 'gidNumber'
783
+     * @return string|false, string with the attribute name, false on error
784
+     * @throws \Exception
785
+     */
786
+    private function detectGroupMemberAssoc() {
787
+        $possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber');
788
+        $filter = $this->configuration->ldapGroupFilter;
789
+        if(empty($filter)) {
790
+            return false;
791
+        }
792
+        $cr = $this->getConnection();
793
+        if(!$cr) {
794
+            throw new \Exception('Could not connect to LDAP');
795
+        }
796
+        $base = $this->configuration->ldapBase[0];
797
+        $rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000);
798
+        if(!$this->ldap->isResource($rr)) {
799
+            return false;
800
+        }
801
+        $er = $this->ldap->firstEntry($cr, $rr);
802
+        while(is_resource($er)) {
803
+            $this->ldap->getDN($cr, $er);
804
+            $attrs = $this->ldap->getAttributes($cr, $er);
805
+            $result = array();
806
+            $possibleAttrsCount = count($possibleAttrs);
807
+            for($i = 0; $i < $possibleAttrsCount; $i++) {
808
+                if(isset($attrs[$possibleAttrs[$i]])) {
809
+                    $result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count'];
810
+                }
811
+            }
812
+            if(!empty($result)) {
813
+                natsort($result);
814
+                return key($result);
815
+            }
816
+
817
+            $er = $this->ldap->nextEntry($cr, $er);
818
+        }
819
+
820
+        return false;
821
+    }
822
+
823
+    /**
824
+     * Checks whether for a given BaseDN results will be returned
825
+     * @param string $base the BaseDN to test
826
+     * @return bool true on success, false otherwise
827
+     * @throws \Exception
828
+     */
829
+    private function testBaseDN($base) {
830
+        $cr = $this->getConnection();
831
+        if(!$cr) {
832
+            throw new \Exception('Could not connect to LDAP');
833
+        }
834
+
835
+        //base is there, let's validate it. If we search for anything, we should
836
+        //get a result set > 0 on a proper base
837
+        $rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1);
838
+        if(!$this->ldap->isResource($rr)) {
839
+            $errorNo  = $this->ldap->errno($cr);
840
+            $errorMsg = $this->ldap->error($cr);
841
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Could not search base '.$base.
842
+                            ' Error '.$errorNo.': '.$errorMsg, \OCP\Util::INFO);
843
+            return false;
844
+        }
845
+        $entries = $this->ldap->countEntries($cr, $rr);
846
+        return ($entries !== false) && ($entries > 0);
847
+    }
848
+
849
+    /**
850
+     * Checks whether the server supports memberOf in LDAP Filter.
851
+     * Note: at least in OpenLDAP, availability of memberOf is dependent on
852
+     * a configured objectClass. I.e. not necessarily for all available groups
853
+     * memberOf does work.
854
+     *
855
+     * @return bool true if it does, false otherwise
856
+     * @throws \Exception
857
+     */
858
+    private function testMemberOf() {
859
+        $cr = $this->getConnection();
860
+        if(!$cr) {
861
+            throw new \Exception('Could not connect to LDAP');
862
+        }
863
+        $result = $this->access->countUsers('memberOf=*', array('memberOf'), 1);
864
+        if(is_int($result) &&  $result > 0) {
865
+            return true;
866
+        }
867
+        return false;
868
+    }
869
+
870
+    /**
871
+     * creates an LDAP Filter from given configuration
872
+     * @param integer $filterType int, for which use case the filter shall be created
873
+     * can be any of self::LFILTER_USER_LIST, self::LFILTER_LOGIN or
874
+     * self::LFILTER_GROUP_LIST
875
+     * @return string|false string with the filter on success, false otherwise
876
+     * @throws \Exception
877
+     */
878
+    private function composeLdapFilter($filterType) {
879
+        $filter = '';
880
+        $parts = 0;
881
+        switch ($filterType) {
882
+            case self::LFILTER_USER_LIST:
883
+                $objcs = $this->configuration->ldapUserFilterObjectclass;
884
+                //glue objectclasses
885
+                if(is_array($objcs) && count($objcs) > 0) {
886
+                    $filter .= '(|';
887
+                    foreach($objcs as $objc) {
888
+                        $filter .= '(objectclass=' . $objc . ')';
889
+                    }
890
+                    $filter .= ')';
891
+                    $parts++;
892
+                }
893
+                //glue group memberships
894
+                if($this->configuration->hasMemberOfFilterSupport) {
895
+                    $cns = $this->configuration->ldapUserFilterGroups;
896
+                    if(is_array($cns) && count($cns) > 0) {
897
+                        $filter .= '(|';
898
+                        $cr = $this->getConnection();
899
+                        if(!$cr) {
900
+                            throw new \Exception('Could not connect to LDAP');
901
+                        }
902
+                        $base = $this->configuration->ldapBase[0];
903
+                        foreach($cns as $cn) {
904
+                            $rr = $this->ldap->search($cr, $base, 'cn=' . $cn, array('dn', 'primaryGroupToken'));
905
+                            if(!$this->ldap->isResource($rr)) {
906
+                                continue;
907
+                            }
908
+                            $er = $this->ldap->firstEntry($cr, $rr);
909
+                            $attrs = $this->ldap->getAttributes($cr, $er);
910
+                            $dn = $this->ldap->getDN($cr, $er);
911
+                            if ($dn === false || $dn === '') {
912
+                                continue;
913
+                            }
914
+                            $filterPart = '(memberof=' . $dn . ')';
915
+                            if(isset($attrs['primaryGroupToken'])) {
916
+                                $pgt = $attrs['primaryGroupToken'][0];
917
+                                $primaryFilterPart = '(primaryGroupID=' . $pgt .')';
918
+                                $filterPart = '(|' . $filterPart . $primaryFilterPart . ')';
919
+                            }
920
+                            $filter .= $filterPart;
921
+                        }
922
+                        $filter .= ')';
923
+                    }
924
+                    $parts++;
925
+                }
926
+                //wrap parts in AND condition
927
+                if($parts > 1) {
928
+                    $filter = '(&' . $filter . ')';
929
+                }
930
+                if ($filter === '') {
931
+                    $filter = '(objectclass=*)';
932
+                }
933
+                break;
934
+
935
+            case self::LFILTER_GROUP_LIST:
936
+                $objcs = $this->configuration->ldapGroupFilterObjectclass;
937
+                //glue objectclasses
938
+                if(is_array($objcs) && count($objcs) > 0) {
939
+                    $filter .= '(|';
940
+                    foreach($objcs as $objc) {
941
+                        $filter .= '(objectclass=' . $objc . ')';
942
+                    }
943
+                    $filter .= ')';
944
+                    $parts++;
945
+                }
946
+                //glue group memberships
947
+                $cns = $this->configuration->ldapGroupFilterGroups;
948
+                if(is_array($cns) && count($cns) > 0) {
949
+                    $filter .= '(|';
950
+                    foreach($cns as $cn) {
951
+                        $filter .= '(cn=' . $cn . ')';
952
+                    }
953
+                    $filter .= ')';
954
+                }
955
+                $parts++;
956
+                //wrap parts in AND condition
957
+                if($parts > 1) {
958
+                    $filter = '(&' . $filter . ')';
959
+                }
960
+                break;
961
+
962
+            case self::LFILTER_LOGIN:
963
+                $ulf = $this->configuration->ldapUserFilter;
964
+                $loginpart = '=%uid';
965
+                $filterUsername = '';
966
+                $userAttributes = $this->getUserAttributes();
967
+                $userAttributes = array_change_key_case(array_flip($userAttributes));
968
+                $parts = 0;
969
+
970
+                if($this->configuration->ldapLoginFilterUsername === '1') {
971
+                    $attr = '';
972
+                    if(isset($userAttributes['uid'])) {
973
+                        $attr = 'uid';
974
+                    } else if(isset($userAttributes['samaccountname'])) {
975
+                        $attr = 'samaccountname';
976
+                    } else if(isset($userAttributes['cn'])) {
977
+                        //fallback
978
+                        $attr = 'cn';
979
+                    }
980
+                    if ($attr !== '') {
981
+                        $filterUsername = '(' . $attr . $loginpart . ')';
982
+                        $parts++;
983
+                    }
984
+                }
985
+
986
+                $filterEmail = '';
987
+                if($this->configuration->ldapLoginFilterEmail === '1') {
988
+                    $filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))';
989
+                    $parts++;
990
+                }
991
+
992
+                $filterAttributes = '';
993
+                $attrsToFilter = $this->configuration->ldapLoginFilterAttributes;
994
+                if(is_array($attrsToFilter) && count($attrsToFilter) > 0) {
995
+                    $filterAttributes = '(|';
996
+                    foreach($attrsToFilter as $attribute) {
997
+                        $filterAttributes .= '(' . $attribute . $loginpart . ')';
998
+                    }
999
+                    $filterAttributes .= ')';
1000
+                    $parts++;
1001
+                }
1002
+
1003
+                $filterLogin = '';
1004
+                if($parts > 1) {
1005
+                    $filterLogin = '(|';
1006
+                }
1007
+                $filterLogin .= $filterUsername;
1008
+                $filterLogin .= $filterEmail;
1009
+                $filterLogin .= $filterAttributes;
1010
+                if($parts > 1) {
1011
+                    $filterLogin .= ')';
1012
+                }
1013
+
1014
+                $filter = '(&'.$ulf.$filterLogin.')';
1015
+                break;
1016
+        }
1017
+
1018
+        \OCP\Util::writeLog('user_ldap', 'Wiz: Final filter '.$filter, \OCP\Util::DEBUG);
1019
+
1020
+        return $filter;
1021
+    }
1022
+
1023
+    /**
1024
+     * Connects and Binds to an LDAP Server
1025
+     *
1026
+     * @param int $port the port to connect with
1027
+     * @param bool $tls whether startTLS is to be used
1028
+     * @return bool
1029
+     * @throws \Exception
1030
+     */
1031
+    private function connectAndBind($port, $tls) {
1032
+        //connect, does not really trigger any server communication
1033
+        $host = $this->configuration->ldapHost;
1034
+        $hostInfo = parse_url($host);
1035
+        if(!$hostInfo) {
1036
+            throw new \Exception(self::$l->t('Invalid Host'));
1037
+        }
1038
+        \OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', \OCP\Util::DEBUG);
1039
+        $cr = $this->ldap->connect($host, $port);
1040
+        if(!is_resource($cr)) {
1041
+            throw new \Exception(self::$l->t('Invalid Host'));
1042
+        }
1043
+
1044
+        //set LDAP options
1045
+        $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1046
+        $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1047
+        $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1048
+
1049
+        try {
1050
+            if($tls) {
1051
+                $isTlsWorking = @$this->ldap->startTls($cr);
1052
+                if(!$isTlsWorking) {
1053
+                    return false;
1054
+                }
1055
+            }
1056
+
1057
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', \OCP\Util::DEBUG);
1058
+            //interesting part: do the bind!
1059
+            $login = $this->ldap->bind($cr,
1060
+                $this->configuration->ldapAgentName,
1061
+                $this->configuration->ldapAgentPassword
1062
+            );
1063
+            $errNo = $this->ldap->errno($cr);
1064
+            $error = ldap_error($cr);
1065
+            $this->ldap->unbind($cr);
1066
+        } catch(ServerNotAvailableException $e) {
1067
+            return false;
1068
+        }
1069
+
1070
+        if($login === true) {
1071
+            $this->ldap->unbind($cr);
1072
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '. $port . ' TLS ' . intval($tls), \OCP\Util::DEBUG);
1073
+            return true;
1074
+        }
1075
+
1076
+        if($errNo === -1) {
1077
+            //host, port or TLS wrong
1078
+            return false;
1079
+        }
1080
+        throw new \Exception($error, $errNo);
1081
+    }
1082
+
1083
+    /**
1084
+     * checks whether a valid combination of agent and password has been
1085
+     * provided (either two values or nothing for anonymous connect)
1086
+     * @return bool, true if everything is fine, false otherwise
1087
+     */
1088
+    private function checkAgentRequirements() {
1089
+        $agent = $this->configuration->ldapAgentName;
1090
+        $pwd = $this->configuration->ldapAgentPassword;
1091
+
1092
+        return
1093
+            ($agent !== '' && $pwd !== '')
1094
+            ||  ($agent === '' && $pwd === '')
1095
+        ;
1096
+    }
1097
+
1098
+    /**
1099
+     * @param array $reqs
1100
+     * @return bool
1101
+     */
1102
+    private function checkRequirements($reqs) {
1103
+        $this->checkAgentRequirements();
1104
+        foreach($reqs as $option) {
1105
+            $value = $this->configuration->$option;
1106
+            if(empty($value)) {
1107
+                return false;
1108
+            }
1109
+        }
1110
+        return true;
1111
+    }
1112
+
1113
+    /**
1114
+     * does a cumulativeSearch on LDAP to get different values of a
1115
+     * specified attribute
1116
+     * @param string[] $filters array, the filters that shall be used in the search
1117
+     * @param string $attr the attribute of which a list of values shall be returned
1118
+     * @param int $dnReadLimit the amount of how many DNs should be analyzed.
1119
+     * The lower, the faster
1120
+     * @param string $maxF string. if not null, this variable will have the filter that
1121
+     * yields most result entries
1122
+     * @return array|false an array with the values on success, false otherwise
1123
+     */
1124
+    public function cumulativeSearchOnAttribute($filters, $attr, $dnReadLimit = 3, &$maxF = null) {
1125
+        $dnRead = array();
1126
+        $foundItems = array();
1127
+        $maxEntries = 0;
1128
+        if(!is_array($this->configuration->ldapBase)
1129
+           || !isset($this->configuration->ldapBase[0])) {
1130
+            return false;
1131
+        }
1132
+        $base = $this->configuration->ldapBase[0];
1133
+        $cr = $this->getConnection();
1134
+        if(!$this->ldap->isResource($cr)) {
1135
+            return false;
1136
+        }
1137
+        $lastFilter = null;
1138
+        if(isset($filters[count($filters)-1])) {
1139
+            $lastFilter = $filters[count($filters)-1];
1140
+        }
1141
+        foreach($filters as $filter) {
1142
+            if($lastFilter === $filter && count($foundItems) > 0) {
1143
+                //skip when the filter is a wildcard and results were found
1144
+                continue;
1145
+            }
1146
+            // 20k limit for performance and reason
1147
+            $rr = $this->ldap->search($cr, $base, $filter, array($attr), 0, 20000);
1148
+            if(!$this->ldap->isResource($rr)) {
1149
+                continue;
1150
+            }
1151
+            $entries = $this->ldap->countEntries($cr, $rr);
1152
+            $getEntryFunc = 'firstEntry';
1153
+            if(($entries !== false) && ($entries > 0)) {
1154
+                if(!is_null($maxF) && $entries > $maxEntries) {
1155
+                    $maxEntries = $entries;
1156
+                    $maxF = $filter;
1157
+                }
1158
+                $dnReadCount = 0;
1159
+                do {
1160
+                    $entry = $this->ldap->$getEntryFunc($cr, $rr);
1161
+                    $getEntryFunc = 'nextEntry';
1162
+                    if(!$this->ldap->isResource($entry)) {
1163
+                        continue 2;
1164
+                    }
1165
+                    $rr = $entry; //will be expected by nextEntry next round
1166
+                    $attributes = $this->ldap->getAttributes($cr, $entry);
1167
+                    $dn = $this->ldap->getDN($cr, $entry);
1168
+                    if($dn === false || in_array($dn, $dnRead)) {
1169
+                        continue;
1170
+                    }
1171
+                    $newItems = array();
1172
+                    $state = $this->getAttributeValuesFromEntry($attributes,
1173
+                                                                $attr,
1174
+                                                                $newItems);
1175
+                    $dnReadCount++;
1176
+                    $foundItems = array_merge($foundItems, $newItems);
1177
+                    $this->resultCache[$dn][$attr] = $newItems;
1178
+                    $dnRead[] = $dn;
1179
+                } while(($state === self::LRESULT_PROCESSED_SKIP
1180
+                        || $this->ldap->isResource($entry))
1181
+                        && ($dnReadLimit === 0 || $dnReadCount < $dnReadLimit));
1182
+            }
1183
+        }
1184
+
1185
+        return array_unique($foundItems);
1186
+    }
1187
+
1188
+    /**
1189
+     * determines if and which $attr are available on the LDAP server
1190
+     * @param string[] $objectclasses the objectclasses to use as search filter
1191
+     * @param string $attr the attribute to look for
1192
+     * @param string $dbkey the dbkey of the setting the feature is connected to
1193
+     * @param string $confkey the confkey counterpart for the $dbkey as used in the
1194
+     * Configuration class
1195
+     * @param bool $po whether the objectClass with most result entries
1196
+     * shall be pre-selected via the result
1197
+     * @return array|false list of found items.
1198
+     * @throws \Exception
1199
+     */
1200
+    private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) {
1201
+        $cr = $this->getConnection();
1202
+        if(!$cr) {
1203
+            throw new \Exception('Could not connect to LDAP');
1204
+        }
1205
+        $p = 'objectclass=';
1206
+        foreach($objectclasses as $key => $value) {
1207
+            $objectclasses[$key] = $p.$value;
1208
+        }
1209
+        $maxEntryObjC = '';
1210
+
1211
+        //how deep to dig?
1212
+        //When looking for objectclasses, testing few entries is sufficient,
1213
+        $dig = 3;
1214
+
1215
+        $availableFeatures =
1216
+            $this->cumulativeSearchOnAttribute($objectclasses, $attr,
1217
+                                                $dig, $maxEntryObjC);
1218
+        if(is_array($availableFeatures)
1219
+           && count($availableFeatures) > 0) {
1220
+            natcasesort($availableFeatures);
1221
+            //natcasesort keeps indices, but we must get rid of them for proper
1222
+            //sorting in the web UI. Therefore: array_values
1223
+            $this->result->addOptions($dbkey, array_values($availableFeatures));
1224
+        } else {
1225
+            throw new \Exception(self::$l->t('Could not find the desired feature'));
1226
+        }
1227
+
1228
+        $setFeatures = $this->configuration->$confkey;
1229
+        if(is_array($setFeatures) && !empty($setFeatures)) {
1230
+            //something is already configured? pre-select it.
1231
+            $this->result->addChange($dbkey, $setFeatures);
1232
+        } else if ($po && $maxEntryObjC !== '') {
1233
+            //pre-select objectclass with most result entries
1234
+            $maxEntryObjC = str_replace($p, '', $maxEntryObjC);
1235
+            $this->applyFind($dbkey, $maxEntryObjC);
1236
+            $this->result->addChange($dbkey, $maxEntryObjC);
1237
+        }
1238
+
1239
+        return $availableFeatures;
1240
+    }
1241
+
1242
+    /**
1243
+     * appends a list of values fr
1244
+     * @param resource $result the return value from ldap_get_attributes
1245
+     * @param string $attribute the attribute values to look for
1246
+     * @param array &$known new values will be appended here
1247
+     * @return int, state on of the class constants LRESULT_PROCESSED_OK,
1248
+     * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP
1249
+     */
1250
+    private function getAttributeValuesFromEntry($result, $attribute, &$known) {
1251
+        if(!is_array($result)
1252
+           || !isset($result['count'])
1253
+           || !$result['count'] > 0) {
1254
+            return self::LRESULT_PROCESSED_INVALID;
1255
+        }
1256
+
1257
+        // strtolower on all keys for proper comparison
1258
+        $result = \OCP\Util::mb_array_change_key_case($result);
1259
+        $attribute = strtolower($attribute);
1260
+        if(isset($result[$attribute])) {
1261
+            foreach($result[$attribute] as $key => $val) {
1262
+                if($key === 'count') {
1263
+                    continue;
1264
+                }
1265
+                if(!in_array($val, $known)) {
1266
+                    $known[] = $val;
1267
+                }
1268
+            }
1269
+            return self::LRESULT_PROCESSED_OK;
1270
+        } else {
1271
+            return self::LRESULT_PROCESSED_SKIP;
1272
+        }
1273
+    }
1274
+
1275
+    /**
1276
+     * @return bool|mixed
1277
+     */
1278
+    private function getConnection() {
1279
+        if(!is_null($this->cr)) {
1280
+            return $this->cr;
1281
+        }
1282
+
1283
+        $cr = $this->ldap->connect(
1284
+            $this->configuration->ldapHost,
1285
+            $this->configuration->ldapPort
1286
+        );
1287
+
1288
+        $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1289
+        $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1290
+        $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1291
+        if($this->configuration->ldapTLS === 1) {
1292
+            $this->ldap->startTls($cr);
1293
+        }
1294
+
1295
+        $lo = @$this->ldap->bind($cr,
1296
+                                    $this->configuration->ldapAgentName,
1297
+                                    $this->configuration->ldapAgentPassword);
1298
+        if($lo === true) {
1299
+            $this->$cr = $cr;
1300
+            return $cr;
1301
+        }
1302
+
1303
+        return false;
1304
+    }
1305
+
1306
+    /**
1307
+     * @return array
1308
+     */
1309
+    private function getDefaultLdapPortSettings() {
1310
+        static $settings = array(
1311
+                                array('port' => 7636, 'tls' => false),
1312
+                                array('port' =>  636, 'tls' => false),
1313
+                                array('port' => 7389, 'tls' => true),
1314
+                                array('port' =>  389, 'tls' => true),
1315
+                                array('port' => 7389, 'tls' => false),
1316
+                                array('port' =>  389, 'tls' => false),
1317
+                            );
1318
+        return $settings;
1319
+    }
1320
+
1321
+    /**
1322
+     * @return array
1323
+     */
1324
+    private function getPortSettingsToTry() {
1325
+        //389 ← LDAP / Unencrypted or StartTLS
1326
+        //636 ← LDAPS / SSL
1327
+        //7xxx ← UCS. need to be checked first, because both ports may be open
1328
+        $host = $this->configuration->ldapHost;
1329
+        $port = intval($this->configuration->ldapPort);
1330
+        $portSettings = array();
1331
+
1332
+        //In case the port is already provided, we will check this first
1333
+        if($port > 0) {
1334
+            $hostInfo = parse_url($host);
1335
+            if(!(is_array($hostInfo)
1336
+                && isset($hostInfo['scheme'])
1337
+                && stripos($hostInfo['scheme'], 'ldaps') !== false)) {
1338
+                $portSettings[] = array('port' => $port, 'tls' => true);
1339
+            }
1340
+            $portSettings[] =array('port' => $port, 'tls' => false);
1341
+        }
1342
+
1343
+        //default ports
1344
+        $portSettings = array_merge($portSettings,
1345
+                                    $this->getDefaultLdapPortSettings());
1346
+
1347
+        return $portSettings;
1348
+    }
1349 1349
 
1350 1350
 
1351 1351
 }
Please login to merge, or discard this patch.
Spacing   +152 added lines, -152 removed lines patch added patch discarded remove patch
@@ -71,7 +71,7 @@  discard block
 block discarded – undo
71 71
 	public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) {
72 72
 		parent::__construct($ldap);
73 73
 		$this->configuration = $configuration;
74
-		if(is_null(Wizard::$l)) {
74
+		if (is_null(Wizard::$l)) {
75 75
 			Wizard::$l = \OC::$server->getL10N('user_ldap');
76 76
 		}
77 77
 		$this->access = $access;
@@ -79,7 +79,7 @@  discard block
 block discarded – undo
79 79
 	}
80 80
 
81 81
 	public function  __destruct() {
82
-		if($this->result->hasChanges()) {
82
+		if ($this->result->hasChanges()) {
83 83
 			$this->configuration->saveConfiguration();
84 84
 		}
85 85
 	}
@@ -94,18 +94,18 @@  discard block
 block discarded – undo
94 94
 	 */
95 95
 	public function countEntries($filter, $type) {
96 96
 		$reqs = array('ldapHost', 'ldapPort', 'ldapBase');
97
-		if($type === 'users') {
97
+		if ($type === 'users') {
98 98
 			$reqs[] = 'ldapUserFilter';
99 99
 		}
100
-		if(!$this->checkRequirements($reqs)) {
100
+		if (!$this->checkRequirements($reqs)) {
101 101
 			throw new \Exception('Requirements not met', 400);
102 102
 		}
103 103
 
104 104
 		$attr = array('dn'); // default
105 105
 		$limit = 1001;
106
-		if($type === 'groups') {
107
-			$result =  $this->access->countGroups($filter, $attr, $limit);
108
-		} else if($type === 'users') {
106
+		if ($type === 'groups') {
107
+			$result = $this->access->countGroups($filter, $attr, $limit);
108
+		} else if ($type === 'users') {
109 109
 			$result = $this->access->countUsers($filter, $attr, $limit);
110 110
 		} else if ($type === 'objects') {
111 111
 			$result = $this->access->countObjects($limit);
@@ -125,7 +125,7 @@  discard block
 block discarded – undo
125 125
 	 */
126 126
 	private function formatCountResult($count) {
127 127
 		$formatted = ($count !== false) ? $count : 0;
128
-		if($formatted > 1000) {
128
+		if ($formatted > 1000) {
129 129
 			$formatted = '> 1000';
130 130
 		}
131 131
 		return $formatted;
@@ -134,7 +134,7 @@  discard block
 block discarded – undo
134 134
 	public function countGroups() {
135 135
 		$filter = $this->configuration->ldapGroupFilter;
136 136
 
137
-		if(empty($filter)) {
137
+		if (empty($filter)) {
138 138
 			$output = self::$l->n('%s group found', '%s groups found', 0, array(0));
139 139
 			$this->result->addChange('ldap_group_count', $output);
140 140
 			return $this->result;
@@ -144,7 +144,7 @@  discard block
 block discarded – undo
144 144
 			$groupsTotal = $this->formatCountResult($this->countEntries($filter, 'groups'));
145 145
 		} catch (\Exception $e) {
146 146
 			//400 can be ignored, 500 is forwarded
147
-			if($e->getCode() === 500) {
147
+			if ($e->getCode() === 500) {
148 148
 				throw $e;
149 149
 			}
150 150
 			return false;
@@ -176,7 +176,7 @@  discard block
 block discarded – undo
176 176
 	public function countInBaseDN() {
177 177
 		// we don't need to provide a filter in this case
178 178
 		$total = $this->countEntries(null, 'objects');
179
-		if($total === false) {
179
+		if ($total === false) {
180 180
 			throw new \Exception('invalid results received');
181 181
 		}
182 182
 		$this->result->addChange('ldap_test_base', $total);
@@ -190,7 +190,7 @@  discard block
 block discarded – undo
190 190
 	 * @return int|bool
191 191
 	 */
192 192
 	public function countUsersWithAttribute($attr, $existsCheck = false) {
193
-		if(!$this->checkRequirements(array('ldapHost',
193
+		if (!$this->checkRequirements(array('ldapHost',
194 194
 										   'ldapPort',
195 195
 										   'ldapBase',
196 196
 										   'ldapUserFilter',
@@ -200,7 +200,7 @@  discard block
 block discarded – undo
200 200
 
201 201
 		$filter = $this->access->combineFilterWithAnd(array(
202 202
 			$this->configuration->ldapUserFilter,
203
-			$attr . '=*'
203
+			$attr.'=*'
204 204
 		));
205 205
 
206 206
 		$limit = ($existsCheck === false) ? null : 1;
@@ -215,7 +215,7 @@  discard block
 block discarded – undo
215 215
 	 * @throws \Exception
216 216
 	 */
217 217
 	public function detectUserDisplayNameAttribute() {
218
-		if(!$this->checkRequirements(array('ldapHost',
218
+		if (!$this->checkRequirements(array('ldapHost',
219 219
 										'ldapPort',
220 220
 										'ldapBase',
221 221
 										'ldapUserFilter',
@@ -227,8 +227,8 @@  discard block
 block discarded – undo
227 227
 		if ($attr !== '' && $attr !== 'displayName') {
228 228
 			// most likely not the default value with upper case N,
229 229
 			// verify it still produces a result
230
-			$count = (int)$this->countUsersWithAttribute($attr, true);
231
-			if($count > 0) {
230
+			$count = (int) $this->countUsersWithAttribute($attr, true);
231
+			if ($count > 0) {
232 232
 				//no change, but we sent it back to make sure the user interface
233 233
 				//is still correct, even if the ajax call was cancelled meanwhile
234 234
 				$this->result->addChange('ldap_display_name', $attr);
@@ -239,9 +239,9 @@  discard block
 block discarded – undo
239 239
 		// first attribute that has at least one result wins
240 240
 		$displayNameAttrs = array('displayname', 'cn');
241 241
 		foreach ($displayNameAttrs as $attr) {
242
-			$count = (int)$this->countUsersWithAttribute($attr, true);
242
+			$count = (int) $this->countUsersWithAttribute($attr, true);
243 243
 
244
-			if($count > 0) {
244
+			if ($count > 0) {
245 245
 				$this->applyFind('ldap_display_name', $attr);
246 246
 				return $this->result;
247 247
 			}
@@ -257,7 +257,7 @@  discard block
 block discarded – undo
257 257
 	 * @return WizardResult|bool
258 258
 	 */
259 259
 	public function detectEmailAttribute() {
260
-		if(!$this->checkRequirements(array('ldapHost',
260
+		if (!$this->checkRequirements(array('ldapHost',
261 261
 										   'ldapPort',
262 262
 										   'ldapBase',
263 263
 										   'ldapUserFilter',
@@ -267,8 +267,8 @@  discard block
 block discarded – undo
267 267
 
268 268
 		$attr = $this->configuration->ldapEmailAttribute;
269 269
 		if ($attr !== '') {
270
-			$count = (int)$this->countUsersWithAttribute($attr, true);
271
-			if($count > 0) {
270
+			$count = (int) $this->countUsersWithAttribute($attr, true);
271
+			if ($count > 0) {
272 272
 				return false;
273 273
 			}
274 274
 			$writeLog = true;
@@ -279,19 +279,19 @@  discard block
 block discarded – undo
279 279
 		$emailAttributes = array('mail', 'mailPrimaryAddress');
280 280
 		$winner = '';
281 281
 		$maxUsers = 0;
282
-		foreach($emailAttributes as $attr) {
282
+		foreach ($emailAttributes as $attr) {
283 283
 			$count = $this->countUsersWithAttribute($attr);
284
-			if($count > $maxUsers) {
284
+			if ($count > $maxUsers) {
285 285
 				$maxUsers = $count;
286 286
 				$winner = $attr;
287 287
 			}
288 288
 		}
289 289
 
290
-		if($winner !== '') {
290
+		if ($winner !== '') {
291 291
 			$this->applyFind('ldap_email_attr', $winner);
292
-			if($writeLog) {
293
-				\OCP\Util::writeLog('user_ldap', 'The mail attribute has ' .
294
-					'automatically been reset, because the original value ' .
292
+			if ($writeLog) {
293
+				\OCP\Util::writeLog('user_ldap', 'The mail attribute has '.
294
+					'automatically been reset, because the original value '.
295 295
 					'did not return any results.', \OCP\Util::INFO);
296 296
 			}
297 297
 		}
@@ -304,7 +304,7 @@  discard block
 block discarded – undo
304 304
 	 * @throws \Exception
305 305
 	 */
306 306
 	public function determineAttributes() {
307
-		if(!$this->checkRequirements(array('ldapHost',
307
+		if (!$this->checkRequirements(array('ldapHost',
308 308
 										   'ldapPort',
309 309
 										   'ldapBase',
310 310
 										   'ldapUserFilter',
@@ -320,7 +320,7 @@  discard block
 block discarded – undo
320 320
 		$this->result->addOptions('ldap_loginfilter_attributes', $attributes);
321 321
 
322 322
 		$selected = $this->configuration->ldapLoginFilterAttributes;
323
-		if(is_array($selected) && !empty($selected)) {
323
+		if (is_array($selected) && !empty($selected)) {
324 324
 			$this->result->addChange('ldap_loginfilter_attributes', $selected);
325 325
 		}
326 326
 
@@ -333,7 +333,7 @@  discard block
 block discarded – undo
333 333
 	 * @throws \Exception
334 334
 	 */
335 335
 	private function getUserAttributes() {
336
-		if(!$this->checkRequirements(array('ldapHost',
336
+		if (!$this->checkRequirements(array('ldapHost',
337 337
 										   'ldapPort',
338 338
 										   'ldapBase',
339 339
 										   'ldapUserFilter',
@@ -341,20 +341,20 @@  discard block
 block discarded – undo
341 341
 			return  false;
342 342
 		}
343 343
 		$cr = $this->getConnection();
344
-		if(!$cr) {
344
+		if (!$cr) {
345 345
 			throw new \Exception('Could not connect to LDAP');
346 346
 		}
347 347
 
348 348
 		$base = $this->configuration->ldapBase[0];
349 349
 		$filter = $this->configuration->ldapUserFilter;
350 350
 		$rr = $this->ldap->search($cr, $base, $filter, array(), 1, 1);
351
-		if(!$this->ldap->isResource($rr)) {
351
+		if (!$this->ldap->isResource($rr)) {
352 352
 			return false;
353 353
 		}
354 354
 		$er = $this->ldap->firstEntry($cr, $rr);
355 355
 		$attributes = $this->ldap->getAttributes($cr, $er);
356 356
 		$pureAttributes = array();
357
-		for($i = 0; $i < $attributes['count']; $i++) {
357
+		for ($i = 0; $i < $attributes['count']; $i++) {
358 358
 			$pureAttributes[] = $attributes[$i];
359 359
 		}
360 360
 
@@ -389,23 +389,23 @@  discard block
 block discarded – undo
389 389
 	 * @throws \Exception
390 390
 	 */
391 391
 	private function determineGroups($dbKey, $confKey, $testMemberOf = true) {
392
-		if(!$this->checkRequirements(array('ldapHost',
392
+		if (!$this->checkRequirements(array('ldapHost',
393 393
 										   'ldapPort',
394 394
 										   'ldapBase',
395 395
 										   ))) {
396 396
 			return  false;
397 397
 		}
398 398
 		$cr = $this->getConnection();
399
-		if(!$cr) {
399
+		if (!$cr) {
400 400
 			throw new \Exception('Could not connect to LDAP');
401 401
 		}
402 402
 
403 403
 		$this->fetchGroups($dbKey, $confKey);
404 404
 
405
-		if($testMemberOf) {
405
+		if ($testMemberOf) {
406 406
 			$this->configuration->hasMemberOfFilterSupport = $this->testMemberOf();
407 407
 			$this->result->markChange();
408
-			if(!$this->configuration->hasMemberOfFilterSupport) {
408
+			if (!$this->configuration->hasMemberOfFilterSupport) {
409 409
 				throw new \Exception('memberOf is not supported by the server');
410 410
 			}
411 411
 		}
@@ -425,7 +425,7 @@  discard block
 block discarded – undo
425 425
 		$obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames', 'groupOfUniqueNames');
426 426
 
427 427
 		$filterParts = array();
428
-		foreach($obclasses as $obclass) {
428
+		foreach ($obclasses as $obclass) {
429 429
 			$filterParts[] = 'objectclass='.$obclass;
430 430
 		}
431 431
 		//we filter for everything
@@ -442,8 +442,8 @@  discard block
 block discarded – undo
442 442
 			// we need to request dn additionally here, otherwise memberOf
443 443
 			// detection will fail later
444 444
 			$result = $this->access->searchGroups($filter, array('cn', 'dn'), $limit, $offset);
445
-			foreach($result as $item) {
446
-				if(!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
445
+			foreach ($result as $item) {
446
+				if (!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
447 447
 					// just in case - no issue known
448 448
 					continue;
449 449
 				}
@@ -453,7 +453,7 @@  discard block
 block discarded – undo
453 453
 			$offset += $limit;
454 454
 		} while ($this->access->hasMoreResults());
455 455
 
456
-		if(count($groupNames) > 0) {
456
+		if (count($groupNames) > 0) {
457 457
 			natsort($groupNames);
458 458
 			$this->result->addOptions($dbKey, array_values($groupNames));
459 459
 		} else {
@@ -461,7 +461,7 @@  discard block
 block discarded – undo
461 461
 		}
462 462
 
463 463
 		$setFeatures = $this->configuration->$confKey;
464
-		if(is_array($setFeatures) && !empty($setFeatures)) {
464
+		if (is_array($setFeatures) && !empty($setFeatures)) {
465 465
 			//something is already configured? pre-select it.
466 466
 			$this->result->addChange($dbKey, $setFeatures);
467 467
 		}
@@ -469,14 +469,14 @@  discard block
 block discarded – undo
469 469
 	}
470 470
 
471 471
 	public function determineGroupMemberAssoc() {
472
-		if(!$this->checkRequirements(array('ldapHost',
472
+		if (!$this->checkRequirements(array('ldapHost',
473 473
 										   'ldapPort',
474 474
 										   'ldapGroupFilter',
475 475
 										   ))) {
476 476
 			return  false;
477 477
 		}
478 478
 		$attribute = $this->detectGroupMemberAssoc();
479
-		if($attribute === false) {
479
+		if ($attribute === false) {
480 480
 			return false;
481 481
 		}
482 482
 		$this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute));
@@ -491,14 +491,14 @@  discard block
 block discarded – undo
491 491
 	 * @throws \Exception
492 492
 	 */
493 493
 	public function determineGroupObjectClasses() {
494
-		if(!$this->checkRequirements(array('ldapHost',
494
+		if (!$this->checkRequirements(array('ldapHost',
495 495
 										   'ldapPort',
496 496
 										   'ldapBase',
497 497
 										   ))) {
498 498
 			return  false;
499 499
 		}
500 500
 		$cr = $this->getConnection();
501
-		if(!$cr) {
501
+		if (!$cr) {
502 502
 			throw new \Exception('Could not connect to LDAP');
503 503
 		}
504 504
 
@@ -518,14 +518,14 @@  discard block
 block discarded – undo
518 518
 	 * @throws \Exception
519 519
 	 */
520 520
 	public function determineUserObjectClasses() {
521
-		if(!$this->checkRequirements(array('ldapHost',
521
+		if (!$this->checkRequirements(array('ldapHost',
522 522
 										   'ldapPort',
523 523
 										   'ldapBase',
524 524
 										   ))) {
525 525
 			return  false;
526 526
 		}
527 527
 		$cr = $this->getConnection();
528
-		if(!$cr) {
528
+		if (!$cr) {
529 529
 			throw new \Exception('Could not connect to LDAP');
530 530
 		}
531 531
 
@@ -548,7 +548,7 @@  discard block
 block discarded – undo
548 548
 	 * @throws \Exception
549 549
 	 */
550 550
 	public function getGroupFilter() {
551
-		if(!$this->checkRequirements(array('ldapHost',
551
+		if (!$this->checkRequirements(array('ldapHost',
552 552
 										   'ldapPort',
553 553
 										   'ldapBase',
554 554
 										   ))) {
@@ -572,7 +572,7 @@  discard block
 block discarded – undo
572 572
 	 * @throws \Exception
573 573
 	 */
574 574
 	public function getUserListFilter() {
575
-		if(!$this->checkRequirements(array('ldapHost',
575
+		if (!$this->checkRequirements(array('ldapHost',
576 576
 										   'ldapPort',
577 577
 										   'ldapBase',
578 578
 										   ))) {
@@ -585,7 +585,7 @@  discard block
 block discarded – undo
585 585
 			$this->applyFind('ldap_display_name', $d['ldap_display_name']);
586 586
 		}
587 587
 		$filter = $this->composeLdapFilter(self::LFILTER_USER_LIST);
588
-		if(!$filter) {
588
+		if (!$filter) {
589 589
 			throw new \Exception('Cannot create filter');
590 590
 		}
591 591
 
@@ -598,7 +598,7 @@  discard block
 block discarded – undo
598 598
 	 * @throws \Exception
599 599
 	 */
600 600
 	public function getUserLoginFilter() {
601
-		if(!$this->checkRequirements(array('ldapHost',
601
+		if (!$this->checkRequirements(array('ldapHost',
602 602
 										   'ldapPort',
603 603
 										   'ldapBase',
604 604
 										   'ldapUserFilter',
@@ -607,7 +607,7 @@  discard block
 block discarded – undo
607 607
 		}
608 608
 
609 609
 		$filter = $this->composeLdapFilter(self::LFILTER_LOGIN);
610
-		if(!$filter) {
610
+		if (!$filter) {
611 611
 			throw new \Exception('Cannot create filter');
612 612
 		}
613 613
 
@@ -621,7 +621,7 @@  discard block
 block discarded – undo
621 621
 	 * @throws \Exception
622 622
 	 */
623 623
 	public function testLoginName($loginName) {
624
-		if(!$this->checkRequirements(array('ldapHost',
624
+		if (!$this->checkRequirements(array('ldapHost',
625 625
 			'ldapPort',
626 626
 			'ldapBase',
627 627
 			'ldapLoginFilter',
@@ -630,17 +630,17 @@  discard block
 block discarded – undo
630 630
 		}
631 631
 
632 632
 		$cr = $this->access->connection->getConnectionResource();
633
-		if(!$this->ldap->isResource($cr)) {
633
+		if (!$this->ldap->isResource($cr)) {
634 634
 			throw new \Exception('connection error');
635 635
 		}
636 636
 
637
-		if(mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
637
+		if (mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
638 638
 			=== false) {
639 639
 			throw new \Exception('missing placeholder');
640 640
 		}
641 641
 
642 642
 		$users = $this->access->countUsersByLoginName($loginName);
643
-		if($this->ldap->errno($cr) !== 0) {
643
+		if ($this->ldap->errno($cr) !== 0) {
644 644
 			throw new \Exception($this->ldap->error($cr));
645 645
 		}
646 646
 		$filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter);
@@ -655,22 +655,22 @@  discard block
 block discarded – undo
655 655
 	 * @throws \Exception
656 656
 	 */
657 657
 	public function guessPortAndTLS() {
658
-		if(!$this->checkRequirements(array('ldapHost',
658
+		if (!$this->checkRequirements(array('ldapHost',
659 659
 										   ))) {
660 660
 			return false;
661 661
 		}
662 662
 		$this->checkHost();
663 663
 		$portSettings = $this->getPortSettingsToTry();
664 664
 
665
-		if(!is_array($portSettings)) {
665
+		if (!is_array($portSettings)) {
666 666
 			throw new \Exception(print_r($portSettings, true));
667 667
 		}
668 668
 
669 669
 		//proceed from the best configuration and return on first success
670
-		foreach($portSettings as $setting) {
670
+		foreach ($portSettings as $setting) {
671 671
 			$p = $setting['port'];
672 672
 			$t = $setting['tls'];
673
-			\OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, \OCP\Util::DEBUG);
673
+			\OCP\Util::writeLog('user_ldap', 'Wiz: trying port '.$p.', TLS '.$t, \OCP\Util::DEBUG);
674 674
 			//connectAndBind may throw Exception, it needs to be catched by the
675 675
 			//callee of this method
676 676
 
@@ -680,7 +680,7 @@  discard block
 block discarded – undo
680 680
 				// any reply other than -1 (= cannot connect) is already okay,
681 681
 				// because then we found the server
682 682
 				// unavailable startTLS returns -11
683
-				if($e->getCode() > 0) {
683
+				if ($e->getCode() > 0) {
684 684
 					$settingsFound = true;
685 685
 				} else {
686 686
 					throw $e;
@@ -693,7 +693,7 @@  discard block
 block discarded – undo
693 693
 					'ldapTLS' => intval($t)
694 694
 				);
695 695
 				$this->configuration->setConfiguration($config);
696
-				\OCP\Util::writeLog('user_ldap', 'Wiz: detected Port ' . $p, \OCP\Util::DEBUG);
696
+				\OCP\Util::writeLog('user_ldap', 'Wiz: detected Port '.$p, \OCP\Util::DEBUG);
697 697
 				$this->result->addChange('ldap_port', $p);
698 698
 				return $this->result;
699 699
 			}
@@ -708,7 +708,7 @@  discard block
 block discarded – undo
708 708
 	 * @return WizardResult|false WizardResult on success, false otherwise
709 709
 	 */
710 710
 	public function guessBaseDN() {
711
-		if(!$this->checkRequirements(array('ldapHost',
711
+		if (!$this->checkRequirements(array('ldapHost',
712 712
 										   'ldapPort',
713 713
 										   ))) {
714 714
 			return false;
@@ -717,9 +717,9 @@  discard block
 block discarded – undo
717 717
 		//check whether a DN is given in the agent name (99.9% of all cases)
718 718
 		$base = null;
719 719
 		$i = stripos($this->configuration->ldapAgentName, 'dc=');
720
-		if($i !== false) {
720
+		if ($i !== false) {
721 721
 			$base = substr($this->configuration->ldapAgentName, $i);
722
-			if($this->testBaseDN($base)) {
722
+			if ($this->testBaseDN($base)) {
723 723
 				$this->applyFind('ldap_base', $base);
724 724
 				return $this->result;
725 725
 			}
@@ -730,13 +730,13 @@  discard block
 block discarded – undo
730 730
 		//a base DN
731 731
 		$helper = new Helper(\OC::$server->getConfig());
732 732
 		$domain = $helper->getDomainFromURL($this->configuration->ldapHost);
733
-		if(!$domain) {
733
+		if (!$domain) {
734 734
 			return false;
735 735
 		}
736 736
 
737 737
 		$dparts = explode('.', $domain);
738
-		while(count($dparts) > 0) {
739
-			$base2 = 'dc=' . implode(',dc=', $dparts);
738
+		while (count($dparts) > 0) {
739
+			$base2 = 'dc='.implode(',dc=', $dparts);
740 740
 			if ($base !== $base2 && $this->testBaseDN($base2)) {
741 741
 				$this->applyFind('ldap_base', $base2);
742 742
 				return $this->result;
@@ -769,7 +769,7 @@  discard block
 block discarded – undo
769 769
 		$hostInfo = parse_url($host);
770 770
 
771 771
 		//removes Port from Host
772
-		if(is_array($hostInfo) && isset($hostInfo['port'])) {
772
+		if (is_array($hostInfo) && isset($hostInfo['port'])) {
773 773
 			$port = $hostInfo['port'];
774 774
 			$host = str_replace(':'.$port, '', $host);
775 775
 			$this->applyFind('ldap_host', $host);
@@ -786,30 +786,30 @@  discard block
 block discarded – undo
786 786
 	private function detectGroupMemberAssoc() {
787 787
 		$possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber');
788 788
 		$filter = $this->configuration->ldapGroupFilter;
789
-		if(empty($filter)) {
789
+		if (empty($filter)) {
790 790
 			return false;
791 791
 		}
792 792
 		$cr = $this->getConnection();
793
-		if(!$cr) {
793
+		if (!$cr) {
794 794
 			throw new \Exception('Could not connect to LDAP');
795 795
 		}
796 796
 		$base = $this->configuration->ldapBase[0];
797 797
 		$rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000);
798
-		if(!$this->ldap->isResource($rr)) {
798
+		if (!$this->ldap->isResource($rr)) {
799 799
 			return false;
800 800
 		}
801 801
 		$er = $this->ldap->firstEntry($cr, $rr);
802
-		while(is_resource($er)) {
802
+		while (is_resource($er)) {
803 803
 			$this->ldap->getDN($cr, $er);
804 804
 			$attrs = $this->ldap->getAttributes($cr, $er);
805 805
 			$result = array();
806 806
 			$possibleAttrsCount = count($possibleAttrs);
807
-			for($i = 0; $i < $possibleAttrsCount; $i++) {
808
-				if(isset($attrs[$possibleAttrs[$i]])) {
807
+			for ($i = 0; $i < $possibleAttrsCount; $i++) {
808
+				if (isset($attrs[$possibleAttrs[$i]])) {
809 809
 					$result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count'];
810 810
 				}
811 811
 			}
812
-			if(!empty($result)) {
812
+			if (!empty($result)) {
813 813
 				natsort($result);
814 814
 				return key($result);
815 815
 			}
@@ -828,14 +828,14 @@  discard block
 block discarded – undo
828 828
 	 */
829 829
 	private function testBaseDN($base) {
830 830
 		$cr = $this->getConnection();
831
-		if(!$cr) {
831
+		if (!$cr) {
832 832
 			throw new \Exception('Could not connect to LDAP');
833 833
 		}
834 834
 
835 835
 		//base is there, let's validate it. If we search for anything, we should
836 836
 		//get a result set > 0 on a proper base
837 837
 		$rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1);
838
-		if(!$this->ldap->isResource($rr)) {
838
+		if (!$this->ldap->isResource($rr)) {
839 839
 			$errorNo  = $this->ldap->errno($cr);
840 840
 			$errorMsg = $this->ldap->error($cr);
841 841
 			\OCP\Util::writeLog('user_ldap', 'Wiz: Could not search base '.$base.
@@ -857,11 +857,11 @@  discard block
 block discarded – undo
857 857
 	 */
858 858
 	private function testMemberOf() {
859 859
 		$cr = $this->getConnection();
860
-		if(!$cr) {
860
+		if (!$cr) {
861 861
 			throw new \Exception('Could not connect to LDAP');
862 862
 		}
863 863
 		$result = $this->access->countUsers('memberOf=*', array('memberOf'), 1);
864
-		if(is_int($result) &&  $result > 0) {
864
+		if (is_int($result) && $result > 0) {
865 865
 			return true;
866 866
 		}
867 867
 		return false;
@@ -882,27 +882,27 @@  discard block
 block discarded – undo
882 882
 			case self::LFILTER_USER_LIST:
883 883
 				$objcs = $this->configuration->ldapUserFilterObjectclass;
884 884
 				//glue objectclasses
885
-				if(is_array($objcs) && count($objcs) > 0) {
885
+				if (is_array($objcs) && count($objcs) > 0) {
886 886
 					$filter .= '(|';
887
-					foreach($objcs as $objc) {
888
-						$filter .= '(objectclass=' . $objc . ')';
887
+					foreach ($objcs as $objc) {
888
+						$filter .= '(objectclass='.$objc.')';
889 889
 					}
890 890
 					$filter .= ')';
891 891
 					$parts++;
892 892
 				}
893 893
 				//glue group memberships
894
-				if($this->configuration->hasMemberOfFilterSupport) {
894
+				if ($this->configuration->hasMemberOfFilterSupport) {
895 895
 					$cns = $this->configuration->ldapUserFilterGroups;
896
-					if(is_array($cns) && count($cns) > 0) {
896
+					if (is_array($cns) && count($cns) > 0) {
897 897
 						$filter .= '(|';
898 898
 						$cr = $this->getConnection();
899
-						if(!$cr) {
899
+						if (!$cr) {
900 900
 							throw new \Exception('Could not connect to LDAP');
901 901
 						}
902 902
 						$base = $this->configuration->ldapBase[0];
903
-						foreach($cns as $cn) {
904
-							$rr = $this->ldap->search($cr, $base, 'cn=' . $cn, array('dn', 'primaryGroupToken'));
905
-							if(!$this->ldap->isResource($rr)) {
903
+						foreach ($cns as $cn) {
904
+							$rr = $this->ldap->search($cr, $base, 'cn='.$cn, array('dn', 'primaryGroupToken'));
905
+							if (!$this->ldap->isResource($rr)) {
906 906
 								continue;
907 907
 							}
908 908
 							$er = $this->ldap->firstEntry($cr, $rr);
@@ -911,11 +911,11 @@  discard block
 block discarded – undo
911 911
 							if ($dn === false || $dn === '') {
912 912
 								continue;
913 913
 							}
914
-							$filterPart = '(memberof=' . $dn . ')';
915
-							if(isset($attrs['primaryGroupToken'])) {
914
+							$filterPart = '(memberof='.$dn.')';
915
+							if (isset($attrs['primaryGroupToken'])) {
916 916
 								$pgt = $attrs['primaryGroupToken'][0];
917
-								$primaryFilterPart = '(primaryGroupID=' . $pgt .')';
918
-								$filterPart = '(|' . $filterPart . $primaryFilterPart . ')';
917
+								$primaryFilterPart = '(primaryGroupID='.$pgt.')';
918
+								$filterPart = '(|'.$filterPart.$primaryFilterPart.')';
919 919
 							}
920 920
 							$filter .= $filterPart;
921 921
 						}
@@ -924,8 +924,8 @@  discard block
 block discarded – undo
924 924
 					$parts++;
925 925
 				}
926 926
 				//wrap parts in AND condition
927
-				if($parts > 1) {
928
-					$filter = '(&' . $filter . ')';
927
+				if ($parts > 1) {
928
+					$filter = '(&'.$filter.')';
929 929
 				}
930 930
 				if ($filter === '') {
931 931
 					$filter = '(objectclass=*)';
@@ -935,27 +935,27 @@  discard block
 block discarded – undo
935 935
 			case self::LFILTER_GROUP_LIST:
936 936
 				$objcs = $this->configuration->ldapGroupFilterObjectclass;
937 937
 				//glue objectclasses
938
-				if(is_array($objcs) && count($objcs) > 0) {
938
+				if (is_array($objcs) && count($objcs) > 0) {
939 939
 					$filter .= '(|';
940
-					foreach($objcs as $objc) {
941
-						$filter .= '(objectclass=' . $objc . ')';
940
+					foreach ($objcs as $objc) {
941
+						$filter .= '(objectclass='.$objc.')';
942 942
 					}
943 943
 					$filter .= ')';
944 944
 					$parts++;
945 945
 				}
946 946
 				//glue group memberships
947 947
 				$cns = $this->configuration->ldapGroupFilterGroups;
948
-				if(is_array($cns) && count($cns) > 0) {
948
+				if (is_array($cns) && count($cns) > 0) {
949 949
 					$filter .= '(|';
950
-					foreach($cns as $cn) {
951
-						$filter .= '(cn=' . $cn . ')';
950
+					foreach ($cns as $cn) {
951
+						$filter .= '(cn='.$cn.')';
952 952
 					}
953 953
 					$filter .= ')';
954 954
 				}
955 955
 				$parts++;
956 956
 				//wrap parts in AND condition
957
-				if($parts > 1) {
958
-					$filter = '(&' . $filter . ')';
957
+				if ($parts > 1) {
958
+					$filter = '(&'.$filter.')';
959 959
 				}
960 960
 				break;
961 961
 
@@ -967,47 +967,47 @@  discard block
 block discarded – undo
967 967
 				$userAttributes = array_change_key_case(array_flip($userAttributes));
968 968
 				$parts = 0;
969 969
 
970
-				if($this->configuration->ldapLoginFilterUsername === '1') {
970
+				if ($this->configuration->ldapLoginFilterUsername === '1') {
971 971
 					$attr = '';
972
-					if(isset($userAttributes['uid'])) {
972
+					if (isset($userAttributes['uid'])) {
973 973
 						$attr = 'uid';
974
-					} else if(isset($userAttributes['samaccountname'])) {
974
+					} else if (isset($userAttributes['samaccountname'])) {
975 975
 						$attr = 'samaccountname';
976
-					} else if(isset($userAttributes['cn'])) {
976
+					} else if (isset($userAttributes['cn'])) {
977 977
 						//fallback
978 978
 						$attr = 'cn';
979 979
 					}
980 980
 					if ($attr !== '') {
981
-						$filterUsername = '(' . $attr . $loginpart . ')';
981
+						$filterUsername = '('.$attr.$loginpart.')';
982 982
 						$parts++;
983 983
 					}
984 984
 				}
985 985
 
986 986
 				$filterEmail = '';
987
-				if($this->configuration->ldapLoginFilterEmail === '1') {
987
+				if ($this->configuration->ldapLoginFilterEmail === '1') {
988 988
 					$filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))';
989 989
 					$parts++;
990 990
 				}
991 991
 
992 992
 				$filterAttributes = '';
993 993
 				$attrsToFilter = $this->configuration->ldapLoginFilterAttributes;
994
-				if(is_array($attrsToFilter) && count($attrsToFilter) > 0) {
994
+				if (is_array($attrsToFilter) && count($attrsToFilter) > 0) {
995 995
 					$filterAttributes = '(|';
996
-					foreach($attrsToFilter as $attribute) {
997
-						$filterAttributes .= '(' . $attribute . $loginpart . ')';
996
+					foreach ($attrsToFilter as $attribute) {
997
+						$filterAttributes .= '('.$attribute.$loginpart.')';
998 998
 					}
999 999
 					$filterAttributes .= ')';
1000 1000
 					$parts++;
1001 1001
 				}
1002 1002
 
1003 1003
 				$filterLogin = '';
1004
-				if($parts > 1) {
1004
+				if ($parts > 1) {
1005 1005
 					$filterLogin = '(|';
1006 1006
 				}
1007 1007
 				$filterLogin .= $filterUsername;
1008 1008
 				$filterLogin .= $filterEmail;
1009 1009
 				$filterLogin .= $filterAttributes;
1010
-				if($parts > 1) {
1010
+				if ($parts > 1) {
1011 1011
 					$filterLogin .= ')';
1012 1012
 				}
1013 1013
 
@@ -1032,12 +1032,12 @@  discard block
 block discarded – undo
1032 1032
 		//connect, does not really trigger any server communication
1033 1033
 		$host = $this->configuration->ldapHost;
1034 1034
 		$hostInfo = parse_url($host);
1035
-		if(!$hostInfo) {
1035
+		if (!$hostInfo) {
1036 1036
 			throw new \Exception(self::$l->t('Invalid Host'));
1037 1037
 		}
1038 1038
 		\OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', \OCP\Util::DEBUG);
1039 1039
 		$cr = $this->ldap->connect($host, $port);
1040
-		if(!is_resource($cr)) {
1040
+		if (!is_resource($cr)) {
1041 1041
 			throw new \Exception(self::$l->t('Invalid Host'));
1042 1042
 		}
1043 1043
 
@@ -1047,9 +1047,9 @@  discard block
 block discarded – undo
1047 1047
 		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1048 1048
 
1049 1049
 		try {
1050
-			if($tls) {
1050
+			if ($tls) {
1051 1051
 				$isTlsWorking = @$this->ldap->startTls($cr);
1052
-				if(!$isTlsWorking) {
1052
+				if (!$isTlsWorking) {
1053 1053
 					return false;
1054 1054
 				}
1055 1055
 			}
@@ -1063,17 +1063,17 @@  discard block
 block discarded – undo
1063 1063
 			$errNo = $this->ldap->errno($cr);
1064 1064
 			$error = ldap_error($cr);
1065 1065
 			$this->ldap->unbind($cr);
1066
-		} catch(ServerNotAvailableException $e) {
1066
+		} catch (ServerNotAvailableException $e) {
1067 1067
 			return false;
1068 1068
 		}
1069 1069
 
1070
-		if($login === true) {
1070
+		if ($login === true) {
1071 1071
 			$this->ldap->unbind($cr);
1072
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '. $port . ' TLS ' . intval($tls), \OCP\Util::DEBUG);
1072
+			\OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '.$port.' TLS '.intval($tls), \OCP\Util::DEBUG);
1073 1073
 			return true;
1074 1074
 		}
1075 1075
 
1076
-		if($errNo === -1) {
1076
+		if ($errNo === -1) {
1077 1077
 			//host, port or TLS wrong
1078 1078
 			return false;
1079 1079
 		}
@@ -1101,9 +1101,9 @@  discard block
 block discarded – undo
1101 1101
 	 */
1102 1102
 	private function checkRequirements($reqs) {
1103 1103
 		$this->checkAgentRequirements();
1104
-		foreach($reqs as $option) {
1104
+		foreach ($reqs as $option) {
1105 1105
 			$value = $this->configuration->$option;
1106
-			if(empty($value)) {
1106
+			if (empty($value)) {
1107 1107
 				return false;
1108 1108
 			}
1109 1109
 		}
@@ -1125,33 +1125,33 @@  discard block
 block discarded – undo
1125 1125
 		$dnRead = array();
1126 1126
 		$foundItems = array();
1127 1127
 		$maxEntries = 0;
1128
-		if(!is_array($this->configuration->ldapBase)
1128
+		if (!is_array($this->configuration->ldapBase)
1129 1129
 		   || !isset($this->configuration->ldapBase[0])) {
1130 1130
 			return false;
1131 1131
 		}
1132 1132
 		$base = $this->configuration->ldapBase[0];
1133 1133
 		$cr = $this->getConnection();
1134
-		if(!$this->ldap->isResource($cr)) {
1134
+		if (!$this->ldap->isResource($cr)) {
1135 1135
 			return false;
1136 1136
 		}
1137 1137
 		$lastFilter = null;
1138
-		if(isset($filters[count($filters)-1])) {
1139
-			$lastFilter = $filters[count($filters)-1];
1138
+		if (isset($filters[count($filters) - 1])) {
1139
+			$lastFilter = $filters[count($filters) - 1];
1140 1140
 		}
1141
-		foreach($filters as $filter) {
1142
-			if($lastFilter === $filter && count($foundItems) > 0) {
1141
+		foreach ($filters as $filter) {
1142
+			if ($lastFilter === $filter && count($foundItems) > 0) {
1143 1143
 				//skip when the filter is a wildcard and results were found
1144 1144
 				continue;
1145 1145
 			}
1146 1146
 			// 20k limit for performance and reason
1147 1147
 			$rr = $this->ldap->search($cr, $base, $filter, array($attr), 0, 20000);
1148
-			if(!$this->ldap->isResource($rr)) {
1148
+			if (!$this->ldap->isResource($rr)) {
1149 1149
 				continue;
1150 1150
 			}
1151 1151
 			$entries = $this->ldap->countEntries($cr, $rr);
1152 1152
 			$getEntryFunc = 'firstEntry';
1153
-			if(($entries !== false) && ($entries > 0)) {
1154
-				if(!is_null($maxF) && $entries > $maxEntries) {
1153
+			if (($entries !== false) && ($entries > 0)) {
1154
+				if (!is_null($maxF) && $entries > $maxEntries) {
1155 1155
 					$maxEntries = $entries;
1156 1156
 					$maxF = $filter;
1157 1157
 				}
@@ -1159,13 +1159,13 @@  discard block
 block discarded – undo
1159 1159
 				do {
1160 1160
 					$entry = $this->ldap->$getEntryFunc($cr, $rr);
1161 1161
 					$getEntryFunc = 'nextEntry';
1162
-					if(!$this->ldap->isResource($entry)) {
1162
+					if (!$this->ldap->isResource($entry)) {
1163 1163
 						continue 2;
1164 1164
 					}
1165 1165
 					$rr = $entry; //will be expected by nextEntry next round
1166 1166
 					$attributes = $this->ldap->getAttributes($cr, $entry);
1167 1167
 					$dn = $this->ldap->getDN($cr, $entry);
1168
-					if($dn === false || in_array($dn, $dnRead)) {
1168
+					if ($dn === false || in_array($dn, $dnRead)) {
1169 1169
 						continue;
1170 1170
 					}
1171 1171
 					$newItems = array();
@@ -1176,7 +1176,7 @@  discard block
 block discarded – undo
1176 1176
 					$foundItems = array_merge($foundItems, $newItems);
1177 1177
 					$this->resultCache[$dn][$attr] = $newItems;
1178 1178
 					$dnRead[] = $dn;
1179
-				} while(($state === self::LRESULT_PROCESSED_SKIP
1179
+				} while (($state === self::LRESULT_PROCESSED_SKIP
1180 1180
 						|| $this->ldap->isResource($entry))
1181 1181
 						&& ($dnReadLimit === 0 || $dnReadCount < $dnReadLimit));
1182 1182
 			}
@@ -1199,11 +1199,11 @@  discard block
 block discarded – undo
1199 1199
 	 */
1200 1200
 	private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) {
1201 1201
 		$cr = $this->getConnection();
1202
-		if(!$cr) {
1202
+		if (!$cr) {
1203 1203
 			throw new \Exception('Could not connect to LDAP');
1204 1204
 		}
1205 1205
 		$p = 'objectclass=';
1206
-		foreach($objectclasses as $key => $value) {
1206
+		foreach ($objectclasses as $key => $value) {
1207 1207
 			$objectclasses[$key] = $p.$value;
1208 1208
 		}
1209 1209
 		$maxEntryObjC = '';
@@ -1215,7 +1215,7 @@  discard block
 block discarded – undo
1215 1215
 		$availableFeatures =
1216 1216
 			$this->cumulativeSearchOnAttribute($objectclasses, $attr,
1217 1217
 											   $dig, $maxEntryObjC);
1218
-		if(is_array($availableFeatures)
1218
+		if (is_array($availableFeatures)
1219 1219
 		   && count($availableFeatures) > 0) {
1220 1220
 			natcasesort($availableFeatures);
1221 1221
 			//natcasesort keeps indices, but we must get rid of them for proper
@@ -1226,7 +1226,7 @@  discard block
 block discarded – undo
1226 1226
 		}
1227 1227
 
1228 1228
 		$setFeatures = $this->configuration->$confkey;
1229
-		if(is_array($setFeatures) && !empty($setFeatures)) {
1229
+		if (is_array($setFeatures) && !empty($setFeatures)) {
1230 1230
 			//something is already configured? pre-select it.
1231 1231
 			$this->result->addChange($dbkey, $setFeatures);
1232 1232
 		} else if ($po && $maxEntryObjC !== '') {
@@ -1248,7 +1248,7 @@  discard block
 block discarded – undo
1248 1248
 	 * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP
1249 1249
 	 */
1250 1250
 	private function getAttributeValuesFromEntry($result, $attribute, &$known) {
1251
-		if(!is_array($result)
1251
+		if (!is_array($result)
1252 1252
 		   || !isset($result['count'])
1253 1253
 		   || !$result['count'] > 0) {
1254 1254
 			return self::LRESULT_PROCESSED_INVALID;
@@ -1257,12 +1257,12 @@  discard block
 block discarded – undo
1257 1257
 		// strtolower on all keys for proper comparison
1258 1258
 		$result = \OCP\Util::mb_array_change_key_case($result);
1259 1259
 		$attribute = strtolower($attribute);
1260
-		if(isset($result[$attribute])) {
1261
-			foreach($result[$attribute] as $key => $val) {
1262
-				if($key === 'count') {
1260
+		if (isset($result[$attribute])) {
1261
+			foreach ($result[$attribute] as $key => $val) {
1262
+				if ($key === 'count') {
1263 1263
 					continue;
1264 1264
 				}
1265
-				if(!in_array($val, $known)) {
1265
+				if (!in_array($val, $known)) {
1266 1266
 					$known[] = $val;
1267 1267
 				}
1268 1268
 			}
@@ -1276,7 +1276,7 @@  discard block
 block discarded – undo
1276 1276
 	 * @return bool|mixed
1277 1277
 	 */
1278 1278
 	private function getConnection() {
1279
-		if(!is_null($this->cr)) {
1279
+		if (!is_null($this->cr)) {
1280 1280
 			return $this->cr;
1281 1281
 		}
1282 1282
 
@@ -1288,14 +1288,14 @@  discard block
 block discarded – undo
1288 1288
 		$this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1289 1289
 		$this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1290 1290
 		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1291
-		if($this->configuration->ldapTLS === 1) {
1291
+		if ($this->configuration->ldapTLS === 1) {
1292 1292
 			$this->ldap->startTls($cr);
1293 1293
 		}
1294 1294
 
1295 1295
 		$lo = @$this->ldap->bind($cr,
1296 1296
 								 $this->configuration->ldapAgentName,
1297 1297
 								 $this->configuration->ldapAgentPassword);
1298
-		if($lo === true) {
1298
+		if ($lo === true) {
1299 1299
 			$this->$cr = $cr;
1300 1300
 			return $cr;
1301 1301
 		}
@@ -1330,14 +1330,14 @@  discard block
 block discarded – undo
1330 1330
 		$portSettings = array();
1331 1331
 
1332 1332
 		//In case the port is already provided, we will check this first
1333
-		if($port > 0) {
1333
+		if ($port > 0) {
1334 1334
 			$hostInfo = parse_url($host);
1335
-			if(!(is_array($hostInfo)
1335
+			if (!(is_array($hostInfo)
1336 1336
 				&& isset($hostInfo['scheme'])
1337 1337
 				&& stripos($hostInfo['scheme'], 'ldaps') !== false)) {
1338 1338
 				$portSettings[] = array('port' => $port, 'tls' => true);
1339 1339
 			}
1340
-			$portSettings[] =array('port' => $port, 'tls' => false);
1340
+			$portSettings[] = array('port' => $port, 'tls' => false);
1341 1341
 		}
1342 1342
 
1343 1343
 		//default ports
Please login to merge, or discard this patch.
apps/user_ldap/lib/Jobs/CleanUp.php 2 patches
Indentation   +192 added lines, -192 removed lines patch added patch discarded remove patch
@@ -43,197 +43,197 @@
 block discarded – undo
43 43
  * @package OCA\User_LDAP\Jobs;
44 44
  */
45 45
 class CleanUp extends TimedJob {
46
-	/** @var int $limit amount of users that should be checked per run */
47
-	protected $limit = 50;
48
-
49
-	/** @var int $defaultIntervalMin default interval in minutes */
50
-	protected $defaultIntervalMin = 51;
51
-
52
-	/** @var User_LDAP|User_Proxy $userBackend */
53
-	protected $userBackend;
54
-
55
-	/** @var \OCP\IConfig $ocConfig */
56
-	protected $ocConfig;
57
-
58
-	/** @var \OCP\IDBConnection $db */
59
-	protected $db;
60
-
61
-	/** @var Helper $ldapHelper */
62
-	protected $ldapHelper;
63
-
64
-	/** @var \OCA\User_LDAP\Mapping\UserMapping */
65
-	protected $mapping;
66
-
67
-	/** @var \OCA\User_LDAP\User\DeletedUsersIndex */
68
-	protected $dui;
69
-
70
-	public function __construct() {
71
-		$minutes = \OC::$server->getConfig()->getSystemValue(
72
-			'ldapUserCleanupInterval', (string)$this->defaultIntervalMin);
73
-		$this->setInterval((int)$minutes * 60);
74
-	}
75
-
76
-	/**
77
-	 * assigns the instances passed to run() to the class properties
78
-	 * @param array $arguments
79
-	 */
80
-	public function setArguments($arguments) {
81
-		//Dependency Injection is not possible, because the constructor will
82
-		//only get values that are serialized to JSON. I.e. whatever we would
83
-		//pass in app.php we do add here, except something else is passed e.g.
84
-		//in tests.
85
-
86
-		if(isset($arguments['helper'])) {
87
-			$this->ldapHelper = $arguments['helper'];
88
-		} else {
89
-			$this->ldapHelper = new Helper(\OC::$server->getConfig());
90
-		}
91
-
92
-		if(isset($arguments['ocConfig'])) {
93
-			$this->ocConfig = $arguments['ocConfig'];
94
-		} else {
95
-			$this->ocConfig = \OC::$server->getConfig();
96
-		}
97
-
98
-		if(isset($arguments['userBackend'])) {
99
-			$this->userBackend = $arguments['userBackend'];
100
-		} else {
101
-			$this->userBackend =  new User_Proxy(
102
-				$this->ldapHelper->getServerConfigurationPrefixes(true),
103
-				new LDAP(),
104
-				$this->ocConfig,
105
-				\OC::$server->getNotificationManager(),
106
-				\OC::$server->getUserSession(),
107
-				\OC::$server->query('LDAPUserPluginManager')
108
-			);
109
-		}
110
-
111
-		if(isset($arguments['db'])) {
112
-			$this->db = $arguments['db'];
113
-		} else {
114
-			$this->db = \OC::$server->getDatabaseConnection();
115
-		}
116
-
117
-		if(isset($arguments['mapping'])) {
118
-			$this->mapping = $arguments['mapping'];
119
-		} else {
120
-			$this->mapping = new UserMapping($this->db);
121
-		}
122
-
123
-		if(isset($arguments['deletedUsersIndex'])) {
124
-			$this->dui = $arguments['deletedUsersIndex'];
125
-		} else {
126
-			$this->dui = new DeletedUsersIndex(
127
-				$this->ocConfig, $this->db, $this->mapping);
128
-		}
129
-	}
130
-
131
-	/**
132
-	 * makes the background job do its work
133
-	 * @param array $argument
134
-	 */
135
-	public function run($argument) {
136
-		$this->setArguments($argument);
137
-
138
-		if(!$this->isCleanUpAllowed()) {
139
-			return;
140
-		}
141
-		$users = $this->mapping->getList($this->getOffset(), $this->limit);
142
-		if(!is_array($users)) {
143
-			//something wrong? Let's start from the beginning next time and
144
-			//abort
145
-			$this->setOffset(true);
146
-			return;
147
-		}
148
-		$resetOffset = $this->isOffsetResetNecessary(count($users));
149
-		$this->checkUsers($users);
150
-		$this->setOffset($resetOffset);
151
-	}
152
-
153
-	/**
154
-	 * checks whether next run should start at 0 again
155
-	 * @param int $resultCount
156
-	 * @return bool
157
-	 */
158
-	public function isOffsetResetNecessary($resultCount) {
159
-		return ($resultCount < $this->limit) ? true : false;
160
-	}
161
-
162
-	/**
163
-	 * checks whether cleaning up LDAP users is allowed
164
-	 * @return bool
165
-	 */
166
-	public function isCleanUpAllowed() {
167
-		try {
168
-			if($this->ldapHelper->haveDisabledConfigurations()) {
169
-				return false;
170
-			}
171
-		} catch (\Exception $e) {
172
-			return false;
173
-		}
174
-
175
-		$enabled = $this->isCleanUpEnabled();
176
-
177
-		return $enabled;
178
-	}
179
-
180
-	/**
181
-	 * checks whether clean up is enabled by configuration
182
-	 * @return bool
183
-	 */
184
-	private function isCleanUpEnabled() {
185
-		return (bool)$this->ocConfig->getSystemValue(
186
-			'ldapUserCleanupInterval', (string)$this->defaultIntervalMin);
187
-	}
188
-
189
-	/**
190
-	 * checks users whether they are still existing
191
-	 * @param array $users result from getMappedUsers()
192
-	 */
193
-	private function checkUsers(array $users) {
194
-		foreach($users as $user) {
195
-			$this->checkUser($user);
196
-		}
197
-	}
198
-
199
-	/**
200
-	 * checks whether a user is still existing in LDAP
201
-	 * @param string[] $user
202
-	 */
203
-	private function checkUser(array $user) {
204
-		if($this->userBackend->userExistsOnLDAP($user['name'])) {
205
-			//still available, all good
206
-
207
-			return;
208
-		}
209
-
210
-		$this->dui->markUser($user['name']);
211
-	}
212
-
213
-	/**
214
-	 * gets the offset to fetch users from the mappings table
215
-	 * @return int
216
-	 */
217
-	private function getOffset() {
218
-		return (int)$this->ocConfig->getAppValue('user_ldap', 'cleanUpJobOffset', 0);
219
-	}
220
-
221
-	/**
222
-	 * sets the new offset for the next run
223
-	 * @param bool $reset whether the offset should be set to 0
224
-	 */
225
-	public function setOffset($reset = false) {
226
-		$newOffset = $reset ? 0 :
227
-			$this->getOffset() + $this->limit;
228
-		$this->ocConfig->setAppValue('user_ldap', 'cleanUpJobOffset', $newOffset);
229
-	}
230
-
231
-	/**
232
-	 * returns the chunk size (limit in DB speak)
233
-	 * @return int
234
-	 */
235
-	public function getChunkSize() {
236
-		return $this->limit;
237
-	}
46
+    /** @var int $limit amount of users that should be checked per run */
47
+    protected $limit = 50;
48
+
49
+    /** @var int $defaultIntervalMin default interval in minutes */
50
+    protected $defaultIntervalMin = 51;
51
+
52
+    /** @var User_LDAP|User_Proxy $userBackend */
53
+    protected $userBackend;
54
+
55
+    /** @var \OCP\IConfig $ocConfig */
56
+    protected $ocConfig;
57
+
58
+    /** @var \OCP\IDBConnection $db */
59
+    protected $db;
60
+
61
+    /** @var Helper $ldapHelper */
62
+    protected $ldapHelper;
63
+
64
+    /** @var \OCA\User_LDAP\Mapping\UserMapping */
65
+    protected $mapping;
66
+
67
+    /** @var \OCA\User_LDAP\User\DeletedUsersIndex */
68
+    protected $dui;
69
+
70
+    public function __construct() {
71
+        $minutes = \OC::$server->getConfig()->getSystemValue(
72
+            'ldapUserCleanupInterval', (string)$this->defaultIntervalMin);
73
+        $this->setInterval((int)$minutes * 60);
74
+    }
75
+
76
+    /**
77
+     * assigns the instances passed to run() to the class properties
78
+     * @param array $arguments
79
+     */
80
+    public function setArguments($arguments) {
81
+        //Dependency Injection is not possible, because the constructor will
82
+        //only get values that are serialized to JSON. I.e. whatever we would
83
+        //pass in app.php we do add here, except something else is passed e.g.
84
+        //in tests.
85
+
86
+        if(isset($arguments['helper'])) {
87
+            $this->ldapHelper = $arguments['helper'];
88
+        } else {
89
+            $this->ldapHelper = new Helper(\OC::$server->getConfig());
90
+        }
91
+
92
+        if(isset($arguments['ocConfig'])) {
93
+            $this->ocConfig = $arguments['ocConfig'];
94
+        } else {
95
+            $this->ocConfig = \OC::$server->getConfig();
96
+        }
97
+
98
+        if(isset($arguments['userBackend'])) {
99
+            $this->userBackend = $arguments['userBackend'];
100
+        } else {
101
+            $this->userBackend =  new User_Proxy(
102
+                $this->ldapHelper->getServerConfigurationPrefixes(true),
103
+                new LDAP(),
104
+                $this->ocConfig,
105
+                \OC::$server->getNotificationManager(),
106
+                \OC::$server->getUserSession(),
107
+                \OC::$server->query('LDAPUserPluginManager')
108
+            );
109
+        }
110
+
111
+        if(isset($arguments['db'])) {
112
+            $this->db = $arguments['db'];
113
+        } else {
114
+            $this->db = \OC::$server->getDatabaseConnection();
115
+        }
116
+
117
+        if(isset($arguments['mapping'])) {
118
+            $this->mapping = $arguments['mapping'];
119
+        } else {
120
+            $this->mapping = new UserMapping($this->db);
121
+        }
122
+
123
+        if(isset($arguments['deletedUsersIndex'])) {
124
+            $this->dui = $arguments['deletedUsersIndex'];
125
+        } else {
126
+            $this->dui = new DeletedUsersIndex(
127
+                $this->ocConfig, $this->db, $this->mapping);
128
+        }
129
+    }
130
+
131
+    /**
132
+     * makes the background job do its work
133
+     * @param array $argument
134
+     */
135
+    public function run($argument) {
136
+        $this->setArguments($argument);
137
+
138
+        if(!$this->isCleanUpAllowed()) {
139
+            return;
140
+        }
141
+        $users = $this->mapping->getList($this->getOffset(), $this->limit);
142
+        if(!is_array($users)) {
143
+            //something wrong? Let's start from the beginning next time and
144
+            //abort
145
+            $this->setOffset(true);
146
+            return;
147
+        }
148
+        $resetOffset = $this->isOffsetResetNecessary(count($users));
149
+        $this->checkUsers($users);
150
+        $this->setOffset($resetOffset);
151
+    }
152
+
153
+    /**
154
+     * checks whether next run should start at 0 again
155
+     * @param int $resultCount
156
+     * @return bool
157
+     */
158
+    public function isOffsetResetNecessary($resultCount) {
159
+        return ($resultCount < $this->limit) ? true : false;
160
+    }
161
+
162
+    /**
163
+     * checks whether cleaning up LDAP users is allowed
164
+     * @return bool
165
+     */
166
+    public function isCleanUpAllowed() {
167
+        try {
168
+            if($this->ldapHelper->haveDisabledConfigurations()) {
169
+                return false;
170
+            }
171
+        } catch (\Exception $e) {
172
+            return false;
173
+        }
174
+
175
+        $enabled = $this->isCleanUpEnabled();
176
+
177
+        return $enabled;
178
+    }
179
+
180
+    /**
181
+     * checks whether clean up is enabled by configuration
182
+     * @return bool
183
+     */
184
+    private function isCleanUpEnabled() {
185
+        return (bool)$this->ocConfig->getSystemValue(
186
+            'ldapUserCleanupInterval', (string)$this->defaultIntervalMin);
187
+    }
188
+
189
+    /**
190
+     * checks users whether they are still existing
191
+     * @param array $users result from getMappedUsers()
192
+     */
193
+    private function checkUsers(array $users) {
194
+        foreach($users as $user) {
195
+            $this->checkUser($user);
196
+        }
197
+    }
198
+
199
+    /**
200
+     * checks whether a user is still existing in LDAP
201
+     * @param string[] $user
202
+     */
203
+    private function checkUser(array $user) {
204
+        if($this->userBackend->userExistsOnLDAP($user['name'])) {
205
+            //still available, all good
206
+
207
+            return;
208
+        }
209
+
210
+        $this->dui->markUser($user['name']);
211
+    }
212
+
213
+    /**
214
+     * gets the offset to fetch users from the mappings table
215
+     * @return int
216
+     */
217
+    private function getOffset() {
218
+        return (int)$this->ocConfig->getAppValue('user_ldap', 'cleanUpJobOffset', 0);
219
+    }
220
+
221
+    /**
222
+     * sets the new offset for the next run
223
+     * @param bool $reset whether the offset should be set to 0
224
+     */
225
+    public function setOffset($reset = false) {
226
+        $newOffset = $reset ? 0 :
227
+            $this->getOffset() + $this->limit;
228
+        $this->ocConfig->setAppValue('user_ldap', 'cleanUpJobOffset', $newOffset);
229
+    }
230
+
231
+    /**
232
+     * returns the chunk size (limit in DB speak)
233
+     * @return int
234
+     */
235
+    public function getChunkSize() {
236
+        return $this->limit;
237
+    }
238 238
 
239 239
 }
Please login to merge, or discard this patch.
Spacing   +18 added lines, -19 removed lines patch added patch discarded remove patch
@@ -69,8 +69,8 @@  discard block
 block discarded – undo
69 69
 
70 70
 	public function __construct() {
71 71
 		$minutes = \OC::$server->getConfig()->getSystemValue(
72
-			'ldapUserCleanupInterval', (string)$this->defaultIntervalMin);
73
-		$this->setInterval((int)$minutes * 60);
72
+			'ldapUserCleanupInterval', (string) $this->defaultIntervalMin);
73
+		$this->setInterval((int) $minutes * 60);
74 74
 	}
75 75
 
76 76
 	/**
@@ -83,22 +83,22 @@  discard block
 block discarded – undo
83 83
 		//pass in app.php we do add here, except something else is passed e.g.
84 84
 		//in tests.
85 85
 
86
-		if(isset($arguments['helper'])) {
86
+		if (isset($arguments['helper'])) {
87 87
 			$this->ldapHelper = $arguments['helper'];
88 88
 		} else {
89 89
 			$this->ldapHelper = new Helper(\OC::$server->getConfig());
90 90
 		}
91 91
 
92
-		if(isset($arguments['ocConfig'])) {
92
+		if (isset($arguments['ocConfig'])) {
93 93
 			$this->ocConfig = $arguments['ocConfig'];
94 94
 		} else {
95 95
 			$this->ocConfig = \OC::$server->getConfig();
96 96
 		}
97 97
 
98
-		if(isset($arguments['userBackend'])) {
98
+		if (isset($arguments['userBackend'])) {
99 99
 			$this->userBackend = $arguments['userBackend'];
100 100
 		} else {
101
-			$this->userBackend =  new User_Proxy(
101
+			$this->userBackend = new User_Proxy(
102 102
 				$this->ldapHelper->getServerConfigurationPrefixes(true),
103 103
 				new LDAP(),
104 104
 				$this->ocConfig,
@@ -108,19 +108,19 @@  discard block
 block discarded – undo
108 108
 			);
109 109
 		}
110 110
 
111
-		if(isset($arguments['db'])) {
111
+		if (isset($arguments['db'])) {
112 112
 			$this->db = $arguments['db'];
113 113
 		} else {
114 114
 			$this->db = \OC::$server->getDatabaseConnection();
115 115
 		}
116 116
 
117
-		if(isset($arguments['mapping'])) {
117
+		if (isset($arguments['mapping'])) {
118 118
 			$this->mapping = $arguments['mapping'];
119 119
 		} else {
120 120
 			$this->mapping = new UserMapping($this->db);
121 121
 		}
122 122
 
123
-		if(isset($arguments['deletedUsersIndex'])) {
123
+		if (isset($arguments['deletedUsersIndex'])) {
124 124
 			$this->dui = $arguments['deletedUsersIndex'];
125 125
 		} else {
126 126
 			$this->dui = new DeletedUsersIndex(
@@ -135,11 +135,11 @@  discard block
 block discarded – undo
135 135
 	public function run($argument) {
136 136
 		$this->setArguments($argument);
137 137
 
138
-		if(!$this->isCleanUpAllowed()) {
138
+		if (!$this->isCleanUpAllowed()) {
139 139
 			return;
140 140
 		}
141 141
 		$users = $this->mapping->getList($this->getOffset(), $this->limit);
142
-		if(!is_array($users)) {
142
+		if (!is_array($users)) {
143 143
 			//something wrong? Let's start from the beginning next time and
144 144
 			//abort
145 145
 			$this->setOffset(true);
@@ -165,7 +165,7 @@  discard block
 block discarded – undo
165 165
 	 */
166 166
 	public function isCleanUpAllowed() {
167 167
 		try {
168
-			if($this->ldapHelper->haveDisabledConfigurations()) {
168
+			if ($this->ldapHelper->haveDisabledConfigurations()) {
169 169
 				return false;
170 170
 			}
171 171
 		} catch (\Exception $e) {
@@ -182,8 +182,8 @@  discard block
 block discarded – undo
182 182
 	 * @return bool
183 183
 	 */
184 184
 	private function isCleanUpEnabled() {
185
-		return (bool)$this->ocConfig->getSystemValue(
186
-			'ldapUserCleanupInterval', (string)$this->defaultIntervalMin);
185
+		return (bool) $this->ocConfig->getSystemValue(
186
+			'ldapUserCleanupInterval', (string) $this->defaultIntervalMin);
187 187
 	}
188 188
 
189 189
 	/**
@@ -191,7 +191,7 @@  discard block
 block discarded – undo
191 191
 	 * @param array $users result from getMappedUsers()
192 192
 	 */
193 193
 	private function checkUsers(array $users) {
194
-		foreach($users as $user) {
194
+		foreach ($users as $user) {
195 195
 			$this->checkUser($user);
196 196
 		}
197 197
 	}
@@ -201,7 +201,7 @@  discard block
 block discarded – undo
201 201
 	 * @param string[] $user
202 202
 	 */
203 203
 	private function checkUser(array $user) {
204
-		if($this->userBackend->userExistsOnLDAP($user['name'])) {
204
+		if ($this->userBackend->userExistsOnLDAP($user['name'])) {
205 205
 			//still available, all good
206 206
 
207 207
 			return;
@@ -215,7 +215,7 @@  discard block
 block discarded – undo
215 215
 	 * @return int
216 216
 	 */
217 217
 	private function getOffset() {
218
-		return (int)$this->ocConfig->getAppValue('user_ldap', 'cleanUpJobOffset', 0);
218
+		return (int) $this->ocConfig->getAppValue('user_ldap', 'cleanUpJobOffset', 0);
219 219
 	}
220 220
 
221 221
 	/**
@@ -223,8 +223,7 @@  discard block
 block discarded – undo
223 223
 	 * @param bool $reset whether the offset should be set to 0
224 224
 	 */
225 225
 	public function setOffset($reset = false) {
226
-		$newOffset = $reset ? 0 :
227
-			$this->getOffset() + $this->limit;
226
+		$newOffset = $reset ? 0 : $this->getOffset() + $this->limit;
228 227
 		$this->ocConfig->setAppValue('user_ldap', 'cleanUpJobOffset', $newOffset);
229 228
 	}
230 229
 
Please login to merge, or discard this patch.