Completed
Push — master ( 9adfa6...ca9d25 )
by Morris
22:40 queued 11:31
created

Migration   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 269
Duplicated Lines 27.88 %

Coupling/Cohesion

Components 2
Dependencies 5

Importance

Changes 0
Metric Value
dl 75
loc 269
rs 10
c 0
b 0
f 0
wmc 23
lcom 2
cbo 5

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B removeReShares() 0 27 4
A updateInitiatorInfo() 0 19 4
A removeSendMailOption() 0 4 1
A addPasswordColumn() 0 17 1
A findOwner() 0 13 3
B getReShares() 38 38 2
B getMissingInitiator() 37 37 2
A getShare() 0 11 1
B updateOwners() 0 30 4

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Björn Schießle <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 * @author Robin Appelman <[email protected]>
8
 * @author Roeland Jago Douma <[email protected]>
9
 *
10
 * @license AGPL-3.0
11
 *
12
 * This code is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License, version 3,
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License, version 3,
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
23
 *
24
 */
25
26
namespace OCA\Files_Sharing;
27
28
use Doctrine\DBAL\Connection;
29
use OCP\ICache;
30
use OCP\IConfig;
31
use OCP\IDBConnection;
32
use OC\Cache\CappedMemoryCache;
33
34
/**
35
 * Class Migration
36
 *
37
 * @package OCA\Files_Sharing
38
 * @group DB
39
 */
40
class Migration {
41
42
	/** @var IDBConnection */
43
	private $connection;
44
45
	/** @var  IConfig */
46
	private $config;
47
48
	/** @var  ICache with all shares we already saw */
49
	private $shareCache;
50
51
	/** @var string */
52
	private $table = 'share';
53
54
	public function __construct(IDBConnection $connection, IConfig $config) {
55
		$this->connection = $connection;
56
		$this->config = $config;
57
58
		// We cache up to 10k share items (~20MB)
59
		$this->shareCache = new CappedMemoryCache(10000);
60
	}
61
62
	/**
63
	 * move all re-shares to the owner in order to have a flat list of shares
64
	 * upgrade from oC 8.2 to 9.0 with the new sharing
65
	 */
66
	public function removeReShares() {
67
68
		$stmt = $this->getReShares();
69
70
		$owners = [];
71
		while($share = $stmt->fetch()) {
72
73
			$this->shareCache[$share['id']] = $share;
74
75
			$owners[$share['id']] = [
76
					'owner' => $this->findOwner($share),
77
					'initiator' => $share['uid_owner'],
78
					'type' => $share['share_type'],
79
			];
80
81
			if (count($owners) === 1000) {
82
				$this->updateOwners($owners);
83
				$owners = [];
84
			}
85
		}
86
87
		$stmt->closeCursor();
88
89
		if (count($owners)) {
90
			$this->updateOwners($owners);
91
		}
92
	}
93
94
	/**
95
	 * update all owner information so that all shares have an owner
96
	 * and an initiator for the upgrade from oC 8.2 to 9.0 with the new sharing
97
	 */
98
	public function updateInitiatorInfo() {
99
		while (true) {
100
			$shares = $this->getMissingInitiator(1000);
101
102
			if (empty($shares)) {
103
				break;
104
			}
105
106
			$owners = [];
107
			foreach ($shares as $share) {
108
				$owners[$share['id']] = [
109
					'owner' => $share['uid_owner'],
110
					'initiator' => $share['uid_owner'],
111
					'type' => $share['share_type'],
112
				];
113
			}
114
			$this->updateOwners($owners);
115
		}
116
	}
117
118
	/**
119
	 * this was dropped for Nextcloud 11 in favour of share by mail
120
	 */
121
	public function removeSendMailOption() {
122
		$this->config->deleteAppValue('core', 'shareapi_allow_mail_notification');
123
		$this->config->deleteAppValue('core', 'shareapi_allow_public_notification');
124
	}
125
126
	public function addPasswordColumn() {
127
		$query = $this->connection->getQueryBuilder();
128
		$query
129
			->update('share')
130
			->set('password', 'share_with')
131
			->where($query->expr()->eq('share_type', $query->createNamedParameter(\OCP\Share::SHARE_TYPE_LINK)))
132
			->andWhere($query->expr()->isNotNull('share_with'));
133
		$query->execute();
134
135
		$clearQuery = $this->connection->getQueryBuilder();
136
		$clearQuery
137
			->update('share')->set('share_with', $clearQuery->createNamedParameter(null))
138
			->where($clearQuery->expr()->eq('share_type', $clearQuery->createNamedParameter(\OCP\Share::SHARE_TYPE_LINK)));
139
140
		$clearQuery->execute();
141
142
	}
143
144
	/**
145
	 * find the owner of a re-shared file/folder
146
	 *
147
	 * @param array $share
148
	 * @return array
149
	 */
150
	private function findOwner($share) {
151
		$currentShare = $share;
152
		while(!is_null($currentShare['parent'])) {
153
			if (isset($this->shareCache[$currentShare['parent']])) {
154
				$currentShare = $this->shareCache[$currentShare['parent']];
155
			} else {
156
				$currentShare = $this->getShare((int)$currentShare['parent']);
157
				$this->shareCache[$currentShare['id']] = $currentShare;
158
			}
159
		}
160
161
		return $currentShare['uid_owner'];
162
	}
163
164
	/**
165
	 * Get $n re-shares from the database
166
	 *
167
	 * @param int $n The max number of shares to fetch
0 ignored issues
show
Bug introduced by
There is no parameter named $n. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
168
	 * @return \Doctrine\DBAL\Driver\Statement
169
	 */
170 View Code Duplication
	private function getReShares() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
		$query = $this->connection->getQueryBuilder();
172
		$query->select(['id', 'parent', 'uid_owner', 'share_type'])
173
			->from($this->table)
174
			->where($query->expr()->in(
175
				'share_type',
176
				$query->createNamedParameter(
177
					[
178
						\OCP\Share::SHARE_TYPE_USER,
179
						\OCP\Share::SHARE_TYPE_GROUP,
180
						\OCP\Share::SHARE_TYPE_LINK,
181
						\OCP\Share::SHARE_TYPE_REMOTE,
182
					],
183
					Connection::PARAM_INT_ARRAY
184
				)
185
			))
186
			->andWhere($query->expr()->in(
187
				'item_type',
188
				$query->createNamedParameter(
189
					['file', 'folder'],
190
					Connection::PARAM_STR_ARRAY
191
				)
192
			))
193
			->andWhere($query->expr()->isNotNull('parent'))
194
			->orderBy('id', 'asc');
195
		return $query->execute();
0 ignored issues
show
Bug Compatibility introduced by
The expression $query->execute(); of type Doctrine\DBAL\Driver\Statement|integer adds the type integer to the return on line 195 which is incompatible with the return type documented by OCA\Files_Sharing\Migration::getReShares of type Doctrine\DBAL\Driver\Statement.
Loading history...
196
197
198
		$shares = $result->fetchAll();
0 ignored issues
show
Unused Code introduced by
$shares = $result->fetchAll(); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
199
		$result->closeCursor();
200
201
		$ordered = [];
202
		foreach ($shares as $share) {
203
			$ordered[(int)$share['id']] = $share;
204
		}
205
206
		return $ordered;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $ordered; (array) is incompatible with the return type documented by OCA\Files_Sharing\Migration::getReShares of type Doctrine\DBAL\Driver\Statement.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
207
	}
208
209
	/**
210
	 * Get $n re-shares from the database
211
	 *
212
	 * @param int $n The max number of shares to fetch
213
	 * @return array
214
	 */
215 View Code Duplication
	private function getMissingInitiator($n = 1000) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
216
		$query = $this->connection->getQueryBuilder();
217
		$query->select(['id', 'uid_owner', 'share_type'])
218
			->from($this->table)
219
			->where($query->expr()->in(
220
				'share_type',
221
				$query->createNamedParameter(
222
					[
223
						\OCP\Share::SHARE_TYPE_USER,
224
						\OCP\Share::SHARE_TYPE_GROUP,
225
						\OCP\Share::SHARE_TYPE_LINK,
226
						\OCP\Share::SHARE_TYPE_REMOTE,
227
					],
228
					Connection::PARAM_INT_ARRAY
229
				)
230
			))
231
			->andWhere($query->expr()->in(
232
				'item_type',
233
				$query->createNamedParameter(
234
					['file', 'folder'],
235
					Connection::PARAM_STR_ARRAY
236
				)
237
			))
238
			->andWhere($query->expr()->isNull('uid_initiator'))
239
			->orderBy('id', 'asc')
240
			->setMaxResults($n);
241
		$result = $query->execute();
242
		$shares = $result->fetchAll();
243
		$result->closeCursor();
244
245
		$ordered = [];
246
		foreach ($shares as $share) {
247
			$ordered[(int)$share['id']] = $share;
248
		}
249
250
		return $ordered;
251
	}
252
253
	/**
254
	 * get a specific share
255
	 *
256
	 * @param int $id
257
	 * @return array
258
	 */
259
	private function getShare($id) {
260
		$query = $this->connection->getQueryBuilder();
261
		$query->select(['id', 'parent', 'uid_owner'])
262
			->from($this->table)
263
			->where($query->expr()->eq('id', $query->createNamedParameter($id)));
264
		$result = $query->execute();
265
		$share = $result->fetchAll();
266
		$result->closeCursor();
267
268
		return $share[0];
269
	}
270
271
	/**
272
	 * update database with the new owners
273
	 *
274
	 * @param array $owners
275
	 * @throws \Exception
276
	 */
277
	private function updateOwners($owners) {
278
279
		$this->connection->beginTransaction();
280
281
		try {
282
283
			foreach ($owners as $id => $owner) {
284
				$query = $this->connection->getQueryBuilder();
285
				$query->update($this->table)
286
					->set('uid_owner', $query->createNamedParameter($owner['owner']))
287
					->set('uid_initiator', $query->createNamedParameter($owner['initiator']));
288
289
290
				if ((int)$owner['type'] !== \OCP\Share::SHARE_TYPE_LINK) {
291
					$query->set('parent', $query->createNamedParameter(null));
292
				}
293
294
				$query->where($query->expr()->eq('id', $query->createNamedParameter($id)));
295
296
				$query->execute();
297
			}
298
299
			$this->connection->commit();
300
301
		} catch (\Exception $e) {
302
			$this->connection->rollBack();
303
			throw $e;
304
		}
305
306
	}
307
308
}
309