1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace OCA\Files\Service\TransferOwnership; |
4
|
|
|
|
5
|
|
|
use OC\Encryption\Manager; |
6
|
|
|
use OC\Files\Filesystem; |
7
|
|
|
use OC\Files\View; |
8
|
|
|
use OC\Share20\ProviderFactory; |
9
|
|
|
use OCP\Files\FileInfo; |
10
|
|
|
use OCP\Files\NotFoundException; |
11
|
|
|
use OCP\IConfig; |
12
|
|
|
use OCP\ILogger; |
13
|
|
|
use OCP\IUser; |
14
|
|
|
use OCP\IUserManager; |
15
|
|
|
use OCP\Share\IManager; |
16
|
|
|
use OCP\Share\IShare; |
17
|
|
|
|
18
|
|
|
class TransferOwnershipService { |
19
|
|
|
|
20
|
|
|
/** @var bool */ |
21
|
|
|
private $filesExist = false; |
22
|
|
|
/** @var bool */ |
23
|
|
|
private $foldersExist = false; |
24
|
|
|
/** @var FileInfo[] */ |
25
|
|
|
private $encryptedFiles = []; |
26
|
|
|
/** @var IShare[] */ |
27
|
|
|
private $shares = []; |
28
|
|
|
/** @var string */ |
29
|
|
|
private $sourceUser; |
30
|
|
|
/** @var string */ |
31
|
|
|
private $destinationUser; |
32
|
|
|
/** @var string */ |
33
|
|
|
private $inputPath; |
34
|
|
|
/** @var string */ |
35
|
|
|
private $finalTarget; |
36
|
|
|
|
37
|
|
|
/** @var Manager */ |
38
|
|
|
protected $encryptionManager; |
39
|
|
|
/** @var IUserManager */ |
40
|
|
|
protected $userManager; |
41
|
|
|
/** @var ILogger */ |
42
|
|
|
protected $logger; |
43
|
|
|
/** @var IConfig */ |
44
|
|
|
protected $config; |
45
|
|
|
/** @var ProviderFactory */ |
46
|
|
|
private $shareProviderFactory; |
47
|
|
|
/** @var IManager */ |
48
|
|
|
protected $shareManager; |
49
|
|
|
|
50
|
|
|
public function __construct( |
51
|
|
|
Manager $encryptionManager, |
52
|
|
|
IUserManager $userManager, |
53
|
|
|
ILogger $logger, |
54
|
|
|
IConfig $config, |
55
|
|
|
ProviderFactory $shareProviderFactory, |
56
|
|
|
IManager $shareManager |
57
|
|
|
) { |
58
|
|
|
$this->encryptionManager = $encryptionManager; |
59
|
|
|
$this->userManager = $userManager; |
60
|
|
|
$this->loggger = $logger; |
|
|
|
|
61
|
|
|
$this->config = $config; |
62
|
|
|
$this->shareProviderFactory = $shareProviderFactory; |
63
|
|
|
$this->shareManager = $shareManager; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
public function transfer(IUser $sourceUser, IUser $destinationUser, $sourcePath = '/') { |
67
|
|
|
|
68
|
|
|
$this->inputPath = $sourcePath; |
69
|
|
|
$this->destinationUser = $destinationUser->getUID(); |
70
|
|
|
$this->sourceUser = $sourceUser->getUID(); |
71
|
|
|
|
72
|
|
|
// target user has to be ready |
73
|
|
|
if (!$this->encryptionManager->isReadyForUser($this->destinationUser)) { |
74
|
|
|
throw new \Exception('The target user is not ready to accept files. The user has at least to be logged in once.'); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
// use a date format compatible across client OS |
78
|
|
|
$date = \date('Ymd_his'); |
79
|
|
|
$this->finalTarget = "$this->destinationUser/files/transferred from $this->sourceUser on $date"; |
80
|
|
|
|
81
|
|
|
// setup filesystem |
82
|
|
|
Filesystem::initMountPoints($this->sourceUser); |
83
|
|
|
Filesystem::initMountPoints($this->destinationUser); |
84
|
|
|
|
85
|
|
|
if (\strlen($this->inputPath) >= 1) { |
86
|
|
|
$view = new View(); |
87
|
|
|
$unknownDir = $this->inputPath; |
88
|
|
|
$this->inputPath = $this->sourceUser . "/files/" . $this->inputPath; |
89
|
|
|
if (!$view->is_dir($this->inputPath)) { |
90
|
|
|
throw new NotFoundException("$unknownDir not found"); |
91
|
|
|
} |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
// analyse source folder |
95
|
|
|
$this->analyse(); |
96
|
|
|
|
97
|
|
|
if (!$this->filesExist and !$this->foldersExist) { |
98
|
|
|
throw new NotFoundException("No files/folders found for transfer"); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
// collect all the shares |
102
|
|
|
$this->collectUsersShares(); |
103
|
|
|
|
104
|
|
|
// transfer the files |
105
|
|
|
$this->doTransfer(); |
106
|
|
|
|
107
|
|
|
// restore the shares |
108
|
|
|
$status = $this->restoreShares(); |
109
|
|
|
|
110
|
|
|
if ($status !== 0) { |
111
|
|
|
throw new \Exception('Failed to complete transfer'); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
return true; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
private function walkFiles(View $view, $path, \Closure $callBack) { |
118
|
|
|
foreach ($view->getDirectoryContent($path) as $fileInfo) { |
119
|
|
|
if (!$callBack($fileInfo)) { |
120
|
|
|
return; |
121
|
|
|
} |
122
|
|
|
if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) { |
123
|
|
|
$this->walkFiles($view, $fileInfo->getPath(), $callBack); |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* @throws \Exception |
130
|
|
|
*/ |
131
|
|
|
protected function analyse() { |
132
|
|
|
$view = new View(); |
133
|
|
|
$walkPath = "$this->sourceUser/files"; |
134
|
|
|
if (\strlen($this->inputPath) > 0) { |
135
|
|
|
if ($this->inputPath !== "$this->sourceUser/files") { |
136
|
|
|
$walkPath = $this->inputPath; |
137
|
|
|
$this->foldersExist = true; |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
$this->walkFiles($view, $walkPath, |
142
|
|
|
function (FileInfo $fileInfo) { |
143
|
|
|
if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) { |
144
|
|
|
// only analyze into folders from main storage, |
145
|
|
|
// sub-storages have an empty internal path |
146
|
|
|
if ($fileInfo->getInternalPath() === '' && $fileInfo->getPath() !== '') { |
147
|
|
|
return false; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
$this->foldersExist = true; |
151
|
|
|
return true; |
152
|
|
|
} |
153
|
|
|
$this->filesExist = true; |
154
|
|
|
if ($fileInfo->isEncrypted()) { |
155
|
|
|
if ($this->config->getAppValue('encryption', 'useMasterKey', 0) !== 0) { |
156
|
|
|
/** |
157
|
|
|
* We are not going to add this to encryptedFiles array. |
158
|
|
|
* Because its encrypted with masterKey and hence it doesn't |
159
|
|
|
* require user's specific password. |
160
|
|
|
*/ |
161
|
|
|
return true; |
162
|
|
|
} |
163
|
|
|
$this->encryptedFiles[] = $fileInfo; |
164
|
|
|
} |
165
|
|
|
return true; |
166
|
|
|
}); |
167
|
|
|
|
168
|
|
|
// no file is allowed to be encrypted |
169
|
|
|
if (!empty($this->encryptedFiles)) { |
170
|
|
|
throw new \Exception('Encrypted files found - decrypt them first'); |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
*/ |
176
|
|
|
private function collectUsersShares() { |
177
|
|
|
foreach ([\OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE] as $shareType) { |
178
|
|
|
$offset = 0; |
179
|
|
|
while (true) { |
180
|
|
|
$sharePage = $this->shareManager->getSharesBy($this->sourceUser, $shareType, null, true, 50, $offset); |
181
|
|
|
if (empty($sharePage)) { |
182
|
|
|
break; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
$this->shares = \array_merge($this->shares, $sharePage); |
186
|
|
|
$offset += 50; |
187
|
|
|
} |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
*/ |
194
|
|
|
protected function doTransfer() { |
195
|
|
|
$view = new View(); |
196
|
|
|
$sourcePath = (\strlen($this->inputPath) > 0) ? $this->inputPath : "$this->sourceUser/files"; |
197
|
|
|
// This change will help user to transfer the folder specified using --path option. |
198
|
|
|
// Else only the content inside folder is transferred which is not correct. |
199
|
|
|
if (\strlen($this->inputPath) > 0) { |
200
|
|
|
if ($this->inputPath !== \ltrim("$this->sourceUser/files", '/')) { |
201
|
|
|
$view->mkdir($this->finalTarget); |
202
|
|
|
$this->finalTarget = $this->finalTarget . '/' . \basename($sourcePath); |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
$view->rename($sourcePath, $this->finalTarget); |
207
|
|
|
|
208
|
|
|
if (!\is_dir("$this->sourceUser/files")) { |
209
|
|
|
// because the files folder is moved away we need to recreate it |
210
|
|
|
$view->mkdir("$this->sourceUser/files"); |
211
|
|
|
} |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
*/ |
216
|
|
|
private function restoreShares() { |
217
|
|
|
$status = 0; |
218
|
|
|
|
219
|
|
|
$childShares = []; |
220
|
|
|
foreach ($this->shares as $share) { |
221
|
|
|
try { |
222
|
|
|
/** |
223
|
|
|
* Do not process children which are already processed. |
224
|
|
|
* This piece of code populates the childShare array |
225
|
|
|
* with the child shares which will be processed. And |
226
|
|
|
* hence will avoid further processing of same share |
227
|
|
|
* again. |
228
|
|
|
*/ |
229
|
|
|
if ($share->getSharedWith() === $this->destinationUser) { |
230
|
|
|
$provider = $this->shareProviderFactory->getProviderForType($share->getShareType()); |
231
|
|
|
foreach ($provider->getChildren($share) as $child) { |
232
|
|
|
$childShares[] = $child->getId(); |
233
|
|
|
} |
234
|
|
|
} else { |
235
|
|
|
/** |
236
|
|
|
* Before doing handover to transferShare, check if the share |
237
|
|
|
* id is present in the childShares. If so then just ignore |
238
|
|
|
* this share and continue. If not ignored, the child shares |
239
|
|
|
* would be processed again, if their parent share was shared |
240
|
|
|
* with destination user. And hence we can safely avoid the |
241
|
|
|
* duplicate processing of shares here. |
242
|
|
|
*/ |
243
|
|
|
if (\in_array($share->getId(), $childShares, true)) { |
244
|
|
|
continue; |
245
|
|
|
} |
246
|
|
|
} |
247
|
|
|
$this->shareManager->transferShare($share, $this->sourceUser, $this->destinationUser, $this->finalTarget); |
248
|
|
|
} catch (\OCP\Files\NotFoundException $e) { |
249
|
|
|
$this->logger->error('Share with id ' . $share->getId() . ' points at deleted file, skipping'); |
250
|
|
|
} catch (\Exception $e) { |
251
|
|
|
$this->logger->error('Could not restore share with id ' . $share->getId() . ':' . $e->getTraceAsString() . '</error>'); |
252
|
|
|
$status = 1; |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
return $status; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
} |
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.