|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* ownCloud - Richdocuments App |
|
4
|
|
|
* |
|
5
|
|
|
* @author Victor Dubiniuk |
|
6
|
|
|
* @copyright 2014 Victor Dubiniuk [email protected] |
|
7
|
|
|
* |
|
8
|
|
|
* This file is licensed under the Affero General Public License version 3 or |
|
9
|
|
|
* later. |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
namespace OCA\Richdocuments\Controller; |
|
13
|
|
|
|
|
14
|
|
|
use \OCP\AppFramework\Controller; |
|
15
|
|
|
use \OCP\IRequest; |
|
16
|
|
|
use \OCP\IConfig; |
|
17
|
|
|
use \OCP\IL10N; |
|
18
|
|
|
use \OCP\AppFramework\Http\ContentSecurityPolicy; |
|
19
|
|
|
use \OCP\AppFramework\Http\JSONResponse; |
|
20
|
|
|
use \OCP\AppFramework\Http\TemplateResponse; |
|
21
|
|
|
|
|
22
|
|
|
use \OCA\Richdocuments\AppConfig; |
|
23
|
|
|
use \OCA\Richdocuments\Db; |
|
24
|
|
|
use \OCA\Richdocuments\Helper; |
|
25
|
|
|
use \OCA\Richdocuments\Storage; |
|
26
|
|
|
use \OCA\Richdocuments\Download; |
|
27
|
|
|
use \OCA\Richdocuments\DownloadResponse; |
|
28
|
|
|
use \OCA\Richdocuments\File; |
|
29
|
|
|
use \OCA\Richdocuments\Genesis; |
|
30
|
|
|
use \OC\Files\View; |
|
31
|
|
|
use \OCP\ICacheFactory; |
|
32
|
|
|
use \OCP\ILogger; |
|
33
|
|
|
|
|
34
|
|
|
class ResponseException extends \Exception { |
|
35
|
|
|
private $hint; |
|
36
|
|
|
|
|
37
|
|
|
public function __construct($description, $hint = '') { |
|
38
|
|
|
parent::__construct($description); |
|
39
|
|
|
$this->hint = $hint; |
|
40
|
|
|
} |
|
41
|
|
|
|
|
42
|
|
|
public function getHint() { |
|
43
|
|
|
return $this->hint; |
|
44
|
|
|
} |
|
45
|
|
|
} |
|
46
|
|
|
|
|
47
|
|
|
class DocumentController extends Controller { |
|
48
|
|
|
|
|
49
|
|
|
private $uid; |
|
50
|
|
|
private $l10n; |
|
51
|
|
|
private $settings; |
|
52
|
|
|
private $appConfig; |
|
53
|
|
|
private $cache; |
|
54
|
|
|
private $logger; |
|
55
|
|
|
const ODT_TEMPLATE_PATH = '/assets/odttemplate.odt'; |
|
56
|
|
|
|
|
57
|
1 |
|
public function __construct($appName, IRequest $request, IConfig $settings, AppConfig $appConfig, IL10N $l10n, $uid, ICacheFactory $cache, ILogger $logger){ |
|
58
|
1 |
|
parent::__construct($appName, $request); |
|
59
|
1 |
|
$this->uid = $uid; |
|
60
|
1 |
|
$this->l10n = $l10n; |
|
61
|
1 |
|
$this->settings = $settings; |
|
62
|
1 |
|
$this->appConfig = $appConfig; |
|
63
|
1 |
|
$this->cache = $cache->create($appName); |
|
64
|
1 |
|
$this->logger = $logger; |
|
65
|
1 |
|
} |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* @param \SimpleXMLElement $discovery |
|
|
|
|
|
|
69
|
|
|
* @param string $mimetype |
|
70
|
|
|
*/ |
|
71
|
|
|
private function getWopiSrcUrl($discovery_parsed, $mimetype) { |
|
72
|
|
|
if(is_null($discovery_parsed) || $discovery_parsed == false) { |
|
73
|
|
|
return null; |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
$result = $discovery_parsed->xpath(sprintf('/wopi-discovery/net-zone/app[@name=\'%s\']/action', $mimetype)); |
|
77
|
|
|
if ($result && count($result) > 0) { |
|
78
|
|
|
return array( |
|
79
|
|
|
'urlsrc' => (string)$result[0]['urlsrc'], |
|
80
|
|
|
'action' => (string)$result[0]['name'] |
|
81
|
|
|
); |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
return null; |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
/** |
|
88
|
|
|
* Log the user with given $userid. |
|
89
|
|
|
* This function should only be used from public controller methods where no |
|
90
|
|
|
* existing session exists, for example, when loolwsd is directly calling a |
|
91
|
|
|
* public method with its own access token. After validating the access |
|
92
|
|
|
* token, and retrieving the correct user with help of access token, it can |
|
93
|
|
|
* be set as current user with help of this method. |
|
94
|
|
|
* |
|
95
|
|
|
* @param string $userid |
|
96
|
|
|
*/ |
|
97
|
|
|
private function loginUser($userid) { |
|
98
|
|
|
\OC_Util::tearDownFS(); |
|
99
|
|
|
|
|
100
|
|
|
$users = \OC::$server->getUserManager()->search($userid, 1, 0); |
|
101
|
|
|
if (count($users) > 0) { |
|
102
|
|
|
$user = array_shift($users); |
|
103
|
|
|
if (strcasecmp($user->getUID(), $userid) === 0) { |
|
104
|
|
|
// clear the existing sessions, if any |
|
105
|
|
|
\OC::$server->getSession()->close(); |
|
106
|
|
|
|
|
107
|
|
|
// initialize a dummy memory session |
|
108
|
|
|
$session = new \OC\Session\Memory(''); |
|
109
|
|
|
// wrap it |
|
110
|
|
|
$cryptoWrapper = \OC::$server->getSessionCryptoWrapper(); |
|
111
|
|
|
$session = $cryptoWrapper->wrapSession($session); |
|
112
|
|
|
// set our session |
|
113
|
|
|
\OC::$server->setSession($session); |
|
114
|
|
|
|
|
115
|
|
|
\OC::$server->getUserSession()->setUser($user); |
|
116
|
|
|
} |
|
117
|
|
|
} |
|
118
|
|
|
|
|
119
|
|
|
\OC_Util::setupFS(); |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
/** |
|
123
|
|
|
* Log out the current user |
|
124
|
|
|
* This is helpful when we are artifically logged in as someone |
|
125
|
|
|
*/ |
|
126
|
|
|
private function logoutUser() { |
|
127
|
|
|
\OC_Util::tearDownFS(); |
|
128
|
|
|
|
|
129
|
|
|
\OC::$server->getSession()->close(); |
|
130
|
|
|
} |
|
131
|
|
|
|
|
132
|
|
|
private function responseError($message, $hint = ''){ |
|
133
|
|
|
$errors = array('errors' => array(array('error' => $message, 'hint' => $hint))); |
|
134
|
|
|
$response = new TemplateResponse('', 'error', $errors, 'error'); |
|
135
|
|
|
return $response; |
|
136
|
|
|
} |
|
137
|
|
|
|
|
138
|
|
|
/** |
|
139
|
|
|
* Return the original wopi url or test wopi url |
|
140
|
|
|
*/ |
|
141
|
|
|
private function getWopiUrl($tester) { |
|
142
|
|
|
$wopiurl = ''; |
|
|
|
|
|
|
143
|
|
|
if ($tester) { |
|
144
|
|
|
$wopiurl = $this->appConfig->getAppValue('test_wopi_url'); |
|
145
|
|
|
} else { |
|
146
|
|
|
$wopiurl = $this->appConfig->getAppValue('wopi_url'); |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
return $wopiurl; |
|
150
|
|
|
} |
|
151
|
|
|
|
|
152
|
|
|
/** |
|
153
|
|
|
* Return true if the currently logged in user is a tester. |
|
154
|
|
|
* This depends on whether current user is the member of one of the groups |
|
155
|
|
|
* mentioned in settings (test_server_groups) |
|
156
|
|
|
*/ |
|
157
|
|
|
private function isTester() { |
|
158
|
|
|
$tester = false; |
|
159
|
|
|
|
|
160
|
|
|
$user = \OC::$server->getUserSession()->getUser()->getUID(); |
|
161
|
|
|
$testgroups = array_filter(explode('|', $this->appConfig->getAppValue('test_server_groups'))); |
|
162
|
|
|
\OC::$server->getLogger()->debug('Testgroups are {testgroups}', [ |
|
163
|
|
|
'app' => $this->appName, |
|
164
|
|
|
'testgroups' => $testgroups |
|
165
|
|
|
]); |
|
166
|
|
View Code Duplication |
foreach ($testgroups as $testgroup) { |
|
|
|
|
|
|
167
|
|
|
$test = \OC::$server->getGroupManager()->get($testgroup); |
|
168
|
|
|
if ($test !== null && sizeof($test->searchUsers($user)) > 0) { |
|
169
|
|
|
\OC::$server->getLogger()->debug('User {user} found in {group}', [ |
|
170
|
|
|
'app' => $this->appName, |
|
171
|
|
|
'user' => $user, |
|
172
|
|
|
'group' => $testgroup |
|
173
|
|
|
]); |
|
174
|
|
|
|
|
175
|
|
|
$tester = true; |
|
176
|
|
|
break; |
|
177
|
|
|
} |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
return $tester; |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
/** Return the content of discovery.xml - either from cache, or download it. |
|
184
|
|
|
*/ |
|
185
|
|
|
private function getDiscovery(){ |
|
186
|
|
|
\OC::$server->getLogger()->debug('getDiscovery(): Getting discovery.xml from the cache.'); |
|
187
|
|
|
|
|
188
|
|
|
$tester = $this->isTester(); |
|
189
|
|
|
$wopiRemote = $this->getWopiUrl($tester); |
|
190
|
|
|
$discoveryKey = 'discovery.xml'; |
|
191
|
|
|
if ($tester) { |
|
192
|
|
|
$discoveryKey = 'discovery.xml_test'; |
|
193
|
|
|
} |
|
194
|
|
|
// Provides access to information about the capabilities of a WOPI client |
|
195
|
|
|
// and the mechanisms for invoking those abilities through URIs. |
|
196
|
|
|
$wopiDiscovery = $wopiRemote . '/hosting/discovery'; |
|
197
|
|
|
|
|
198
|
|
|
// Read the memcached value (if the memcache is installed) |
|
199
|
|
|
$discovery = $this->cache->get($discoveryKey); |
|
200
|
|
|
|
|
201
|
|
|
if (is_null($discovery)) { |
|
202
|
|
|
$contact_admin = $this->l10n->t('Please contact the "%s" administrator.', array($wopiRemote)); |
|
203
|
|
|
|
|
204
|
|
|
try { |
|
205
|
|
|
$wopiClient = \OC::$server->getHTTPClientService()->newClient(); |
|
206
|
|
|
$discovery = $wopiClient->get($wopiDiscovery)->getBody(); |
|
207
|
|
|
} |
|
208
|
|
|
catch (\Exception $e) { |
|
209
|
|
|
$error_message = $e->getMessage(); |
|
210
|
|
|
if (preg_match('/^cURL error ([0-9]*):/', $error_message, $matches)) { |
|
211
|
|
|
$admin_check = $this->l10n->t('Please ask your administrator to check the Collabora Online server setting. The exact error message was: ') . $error_message; |
|
212
|
|
|
|
|
213
|
|
|
$curl_error = $matches[1]; |
|
214
|
|
|
switch ($curl_error) { |
|
215
|
|
|
case '1': |
|
216
|
|
|
throw new ResponseException($this->l10n->t('Collabora Online: The protocol specified in "%s" is not allowed.', array($wopiRemote)), $admin_check); |
|
217
|
|
|
case '3': |
|
218
|
|
|
throw new ResponseException($this->l10n->t('Collabora Online: Malformed URL "%s".', array($wopiRemote)), $admin_check); |
|
219
|
|
|
case '6': |
|
220
|
|
|
throw new ResponseException($this->l10n->t('Collabora Online: Cannot resolve the host "%s".', array($wopiRemote)), $admin_check); |
|
221
|
|
|
case '7': |
|
222
|
|
|
throw new ResponseException($this->l10n->t('Collabora Online: Cannot connect to the host "%s".', array($wopiRemote)), $admin_check); |
|
223
|
|
|
case '60': |
|
224
|
|
|
throw new ResponseException($this->l10n->t('Collabora Online: SSL certificate is not installed.'), $this->l10n->t('Please ask your administrator to add ca-chain.cert.pem to the ca-bundle.crt, for example "cat /etc/loolwsd/ca-chain.cert.pem >> <server-installation>/resources/config/ca-bundle.crt" . The exact error message was: ') . $error_message); |
|
225
|
|
|
} |
|
226
|
|
|
} |
|
227
|
|
|
throw new ResponseException($this->l10n->t('Collabora Online unknown error: ') . $error_message, $contact_admin); |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
if (!$discovery) { |
|
231
|
|
|
throw new ResponseException($this->l10n->t('Collabora Online: Unable to read discovery.xml from "%s".', array($wopiRemote)), $contact_admin); |
|
232
|
|
|
} |
|
233
|
|
|
|
|
234
|
|
|
\OC::$server->getLogger()->debug('Storing the discovery.xml under key ' . $discoveryKey . ' to the cache.'); |
|
235
|
|
|
$this->cache->set($discoveryKey, $discovery, 3600); |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
return $discovery; |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
/** Prepare document(s) structure |
|
242
|
|
|
*/ |
|
243
|
|
|
private function prepareDocuments($rawDocuments){ |
|
244
|
|
|
$discovery_parsed = null; |
|
245
|
|
|
try { |
|
246
|
|
|
$discovery = $this->getDiscovery(); |
|
247
|
|
|
|
|
248
|
|
|
$loadEntities = libxml_disable_entity_loader(true); |
|
249
|
|
|
$discovery_parsed = simplexml_load_string($discovery); |
|
250
|
|
|
libxml_disable_entity_loader($loadEntities); |
|
251
|
|
|
|
|
252
|
|
View Code Duplication |
if ($discovery_parsed === false) { |
|
|
|
|
|
|
253
|
|
|
$this->cache->remove('discovery.xml'); |
|
254
|
|
|
$wopiRemote = $this->getWopiUrl($this->isTester()); |
|
255
|
|
|
|
|
256
|
|
|
return array( |
|
257
|
|
|
'status' => 'error', |
|
258
|
|
|
'message' => $this->l10n->t('Collabora Online: discovery.xml from "%s" is not a well-formed XML string.', array($wopiRemote)), |
|
259
|
|
|
'hint' => $this->l10n->t('Please contact the "%s" administrator.', array($wopiRemote)) |
|
260
|
|
|
); |
|
261
|
|
|
} |
|
262
|
|
|
} |
|
263
|
|
|
catch (ResponseException $e) { |
|
264
|
|
|
return array( |
|
265
|
|
|
'status' => 'error', |
|
266
|
|
|
'message' => $e->getMessage(), |
|
267
|
|
|
'hint' => $e->getHint() |
|
268
|
|
|
); |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
|
|
$fileIds = array(); |
|
272
|
|
|
$documents = array(); |
|
273
|
|
|
$lolang = strtolower(str_replace('_', '-', $this->settings->getUserValue($this->uid, 'core', 'lang', 'en'))); |
|
274
|
|
|
foreach ($rawDocuments as $key=>$document) { |
|
275
|
|
|
if (is_object($document)){ |
|
276
|
|
|
$documents[] = $document->getData(); |
|
277
|
|
|
} else { |
|
278
|
|
|
$documents[$key] = $document; |
|
279
|
|
|
} |
|
280
|
|
|
$documents[$key]['icon'] = preg_replace('/\.png$/', '.svg', \OCP\Template::mimetype_icon($document['mimetype'])); |
|
281
|
|
|
$documents[$key]['hasPreview'] = \OC::$server->getPreviewManager()->isMimeSupported($document['mimetype']); |
|
282
|
|
|
$ret = $this->getWopiSrcUrl($discovery_parsed, $document['mimetype']); |
|
283
|
|
|
$documents[$key]['urlsrc'] = $ret['urlsrc']; |
|
284
|
|
|
$documents[$key]['action'] = $ret['action']; |
|
285
|
|
|
$documents[$key]['lolang'] = $lolang; |
|
286
|
|
|
$fileIds[] = $document['fileid']; |
|
287
|
|
|
} |
|
288
|
|
|
|
|
289
|
|
|
usort($documents, function($a, $b){ |
|
290
|
|
|
return @$b['mtime']-@$a['mtime']; |
|
291
|
|
|
}); |
|
292
|
|
|
|
|
293
|
|
|
$session = new Db\Session(); |
|
294
|
|
|
$sessions = $session->getCollectionBy('file_id', $fileIds); |
|
295
|
|
|
|
|
296
|
|
|
$members = array(); |
|
297
|
|
|
$member = new Db\Member(); |
|
298
|
|
|
foreach ($sessions as $session) { |
|
299
|
|
|
$members[$session['es_id']] = $member->getActiveCollection($session['es_id']); |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
return array( |
|
303
|
|
|
'status' => 'success', 'documents' => $documents,'sessions' => $sessions,'members' => $members |
|
304
|
|
|
); |
|
305
|
|
|
} |
|
306
|
|
|
|
|
307
|
|
|
/** |
|
308
|
|
|
* @NoAdminRequired |
|
309
|
|
|
* @NoCSRFRequired |
|
310
|
|
|
*/ |
|
311
|
|
|
public function index(){ |
|
312
|
|
|
$wopiRemote = $this->getWopiUrl($this->isTester()); |
|
313
|
|
|
if (($parts = parse_url($wopiRemote)) && isset($parts['scheme']) && isset($parts['host'])) { |
|
314
|
|
|
$webSocketProtocol = "ws://"; |
|
315
|
|
|
if ($parts['scheme'] == "https") { |
|
316
|
|
|
$webSocketProtocol = "wss://"; |
|
317
|
|
|
} |
|
318
|
|
|
$webSocket = sprintf( |
|
319
|
|
|
"%s%s%s", |
|
320
|
|
|
$webSocketProtocol, |
|
321
|
|
|
$parts['host'], |
|
322
|
|
|
isset($parts['port']) ? ":" . $parts['port'] : ""); |
|
323
|
|
|
} |
|
324
|
|
|
else { |
|
325
|
|
|
return $this->responseError($this->l10n->t('Collabora Online: Invalid URL "%s".', array($wopiRemote)), $this->l10n->t('Please ask your administrator to check the Collabora Online server setting.')); |
|
326
|
|
|
} |
|
327
|
|
|
|
|
328
|
|
|
$user = \OC::$server->getUserSession()->getUser(); |
|
329
|
|
|
$usergroups = array_filter(\OC::$server->getGroupManager()->getUserGroupIds($user)); |
|
330
|
|
|
$usergroups = join('|', $usergroups); |
|
331
|
|
|
\OC::$server->getLogger()->debug('User is in groups: {groups}', [ 'app' => $this->appName, 'groups' => $usergroups ]); |
|
332
|
|
|
|
|
333
|
|
|
\OC::$server->getNavigationManager()->setActiveEntry( 'richdocuments_index' ); |
|
334
|
|
|
$maxUploadFilesize = \OCP\Util::maxUploadFilesize("/"); |
|
335
|
|
|
$response = new TemplateResponse('richdocuments', 'documents', [ |
|
336
|
|
|
'enable_previews' => $this->settings->getSystemValue('enable_previews', true), |
|
337
|
|
|
'uploadMaxFilesize' => $maxUploadFilesize, |
|
338
|
|
|
'uploadMaxHumanFilesize' => \OCP\Util::humanFileSize($maxUploadFilesize), |
|
339
|
|
|
'allowShareWithLink' => $this->settings->getAppValue('core', 'shareapi_allow_links', 'yes'), |
|
340
|
|
|
'wopi_url' => $webSocket, |
|
341
|
|
|
'doc_format' => $this->appConfig->getAppValue('doc_format') |
|
342
|
|
|
]); |
|
343
|
|
|
|
|
344
|
|
|
$policy = new ContentSecurityPolicy(); |
|
345
|
|
|
$policy->addAllowedScriptDomain('\'self\' http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js http://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.12/jquery.mousewheel.min.js \'unsafe-eval\' ' . $wopiRemote); |
|
346
|
|
|
/* frame-src is deprecated on Firefox, but Safari wants it! */ |
|
347
|
|
|
$policy->addAllowedFrameDomain('\'self\' http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js http://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.12/jquery.mousewheel.min.js \'unsafe-eval\' ' . $wopiRemote . ' blob:'); |
|
348
|
|
|
$policy->addAllowedChildSrcDomain('\'self\' http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js http://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.12/jquery.mousewheel.min.js \'unsafe-eval\' ' . $wopiRemote); |
|
349
|
|
|
$policy->addAllowedConnectDomain($webSocket); |
|
350
|
|
|
$policy->addAllowedImageDomain('*'); |
|
351
|
|
|
$policy->allowInlineScript(true); |
|
352
|
|
|
$policy->addAllowedFontDomain('data:'); |
|
353
|
|
|
$response->setContentSecurityPolicy($policy); |
|
354
|
|
|
|
|
355
|
|
|
return $response; |
|
356
|
|
|
} |
|
357
|
|
|
|
|
358
|
|
|
/** |
|
359
|
|
|
* @NoAdminRequired |
|
360
|
|
|
*/ |
|
361
|
|
|
public function create(){ |
|
362
|
|
|
$mimetype = $this->request->post['mimetype']; |
|
363
|
|
|
$filename = $this->request->post['filename']; |
|
364
|
|
|
$dir = $this->request->post['dir']; |
|
365
|
|
|
|
|
366
|
|
|
$view = new View('/' . $this->uid . '/files'); |
|
367
|
|
|
if (!$dir){ |
|
368
|
|
|
$dir = '/'; |
|
369
|
|
|
} |
|
370
|
|
|
|
|
371
|
|
|
$basename = $this->l10n->t('New Document.odt'); |
|
372
|
|
|
switch ($mimetype) { |
|
373
|
|
|
case 'application/vnd.oasis.opendocument.spreadsheet': |
|
374
|
|
|
$basename = $this->l10n->t('New Spreadsheet.ods'); |
|
375
|
|
|
break; |
|
376
|
|
|
case 'application/vnd.oasis.opendocument.presentation': |
|
377
|
|
|
$basename = $this->l10n->t('New Presentation.odp'); |
|
378
|
|
|
break; |
|
379
|
|
|
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': |
|
380
|
|
|
$basename = $this->l10n->t('New Document.docx'); |
|
381
|
|
|
break; |
|
382
|
|
|
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': |
|
383
|
|
|
$basename = $this->l10n->t('New Spreadsheet.xlsx'); |
|
384
|
|
|
break; |
|
385
|
|
|
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation': |
|
386
|
|
|
$basename = $this->l10n->t('New Presentation.pptx'); |
|
387
|
|
|
break; |
|
388
|
|
|
default: |
|
389
|
|
|
// to be safe |
|
390
|
|
|
$mimetype = 'application/vnd.oasis.opendocument.text'; |
|
391
|
|
|
break; |
|
392
|
|
|
} |
|
393
|
|
|
|
|
394
|
|
|
if (!$filename){ |
|
395
|
|
|
$path = Helper::getNewFileName($view, $dir . '/' . $basename); |
|
396
|
|
|
} else { |
|
397
|
|
|
$path = $dir . '/' . $filename; |
|
398
|
|
|
} |
|
399
|
|
|
|
|
400
|
|
|
$content = ''; |
|
401
|
|
|
if (class_exists('\OC\Files\Type\TemplateManager')){ |
|
402
|
|
|
$manager = \OC_Helper::getFileTemplateManager(); |
|
403
|
|
|
$content = $manager->getTemplate($mimetype); |
|
404
|
|
|
} |
|
405
|
|
|
|
|
406
|
|
|
if (!$content){ |
|
407
|
|
|
$content = file_get_contents(dirname(__DIR__) . self::ODT_TEMPLATE_PATH); |
|
408
|
|
|
} |
|
409
|
|
|
|
|
410
|
|
|
$discovery_parsed = null; |
|
411
|
|
|
try { |
|
412
|
|
|
$discovery = $this->getDiscovery(); |
|
413
|
|
|
|
|
414
|
|
|
$loadEntities = libxml_disable_entity_loader(true); |
|
415
|
|
|
$discovery_parsed = simplexml_load_string($discovery); |
|
416
|
|
|
libxml_disable_entity_loader($loadEntities); |
|
417
|
|
|
|
|
418
|
|
View Code Duplication |
if ($discovery_parsed === false) { |
|
|
|
|
|
|
419
|
|
|
$this->cache->remove('discovery.xml'); |
|
420
|
|
|
$wopiRemote = $this->getWopiUrl($this->isTester()); |
|
421
|
|
|
|
|
422
|
|
|
return array( |
|
423
|
|
|
'status' => 'error', |
|
424
|
|
|
'message' => $this->l10n->t('Collabora Online: discovery.xml from "%s" is not a well-formed XML string.', array($wopiRemote)), |
|
425
|
|
|
'hint' => $this->l10n->t('Please contact the "%s" administrator.', array($wopiRemote)) |
|
426
|
|
|
); |
|
427
|
|
|
} |
|
428
|
|
|
} |
|
429
|
|
|
catch (ResponseException $e) { |
|
430
|
|
|
return array( |
|
431
|
|
|
'status' => 'error', |
|
432
|
|
|
'message' => $e->getMessage(), |
|
433
|
|
|
'hint' => $e->getHint() |
|
434
|
|
|
); |
|
435
|
|
|
} |
|
436
|
|
|
|
|
437
|
|
|
if ($content && $view->file_put_contents($path, $content)){ |
|
438
|
|
|
$info = $view->getFileInfo($path); |
|
439
|
|
|
$ret = $this->getWopiSrcUrl($discovery_parsed, $mimetype); |
|
440
|
|
|
$response = array( |
|
441
|
|
|
'status' => 'success', |
|
442
|
|
|
'fileid' => $info['fileid'], |
|
443
|
|
|
'urlsrc' => $ret['urlsrc'], |
|
444
|
|
|
'action' => $ret['action'], |
|
445
|
|
|
'lolang' => $this->settings->getUserValue($this->uid, 'core', 'lang', 'en'), |
|
446
|
|
|
'data' => \OCA\Files\Helper::formatFileInfo($info) |
|
447
|
|
|
); |
|
448
|
|
|
} else { |
|
449
|
|
|
$response = array( |
|
450
|
|
|
'status' => 'error', |
|
451
|
|
|
'message' => (string) $this->l10n->t('Can\'t create document') |
|
452
|
|
|
); |
|
453
|
|
|
} |
|
454
|
|
|
return $response; |
|
455
|
|
|
} |
|
456
|
|
|
|
|
457
|
|
|
/** |
|
458
|
|
|
* @NoAdminRequired |
|
459
|
|
|
* Generates and returns an access token for a given fileId. |
|
460
|
|
|
* Only for authenticated users! |
|
461
|
|
|
*/ |
|
462
|
|
|
public function wopiGetToken($fileId){ |
|
463
|
|
|
$arr = explode('_', $fileId, 2); |
|
464
|
|
|
$version = '0'; |
|
465
|
|
View Code Duplication |
if (count($arr) == 2) { |
|
|
|
|
|
|
466
|
|
|
$fileId = $arr[0]; |
|
467
|
|
|
$version = $arr[1]; |
|
468
|
|
|
} |
|
469
|
|
|
|
|
470
|
|
|
\OC::$server->getLogger()->debug('Generating WOPI Token for file {fileId}, version {version}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'version' => $version ]); |
|
471
|
|
|
|
|
472
|
|
|
$view = \OC\Files\Filesystem::getView(); |
|
473
|
|
|
$path = $view->getPath($fileId); |
|
474
|
|
|
$updatable = (bool)$view->isUpdatable($path); |
|
475
|
|
|
|
|
476
|
|
|
// Check if the editor (user who is accessing) is in editable group |
|
477
|
|
|
// UserCanWrite only if |
|
478
|
|
|
// 1. No edit groups are set or |
|
479
|
|
|
// 2. if they are set, it is in one of the edit groups |
|
480
|
|
|
$editorUid = \OC::$server->getUserSession()->getUser()->getUID(); |
|
481
|
|
|
$editGroups = array_filter(explode('|', $this->appConfig->getAppValue('edit_groups'))); |
|
482
|
|
|
if ($updatable && count($editGroups) > 0) { |
|
483
|
|
|
$updatable = false; |
|
484
|
|
View Code Duplication |
foreach($editGroups as $editGroup) { |
|
|
|
|
|
|
485
|
|
|
$editorGroup = \OC::$server->getGroupManager()->get($editGroup); |
|
486
|
|
|
if ($editorGroup !== null && sizeof($editorGroup->searchUsers($editorUid)) > 0) { |
|
487
|
|
|
\OC::$server->getLogger()->debug("Editor {editor} is in edit group {group}", [ |
|
488
|
|
|
'app' => $this->appName, |
|
489
|
|
|
'editor' => $editorUid, |
|
490
|
|
|
'group' => $editGroup |
|
491
|
|
|
]); |
|
492
|
|
|
$updatable = true; |
|
493
|
|
|
break; |
|
494
|
|
|
} |
|
495
|
|
|
} |
|
496
|
|
|
} |
|
497
|
|
|
|
|
498
|
|
|
// If token is for some versioned file |
|
499
|
|
|
if ($version !== '0') { |
|
500
|
|
|
\OC::$server->getLogger()->debug('setting updatable to false'); |
|
501
|
|
|
$updatable = false; |
|
502
|
|
|
} |
|
503
|
|
|
|
|
504
|
|
|
\OC::$server->getLogger()->debug('File with {fileid} has updatable set to {updatable}', [ 'app' => $this->appName, 'fileid' => $fileId, 'updatable' => $updatable ]); |
|
505
|
|
|
|
|
506
|
|
|
$row = new Db\Wopi(); |
|
507
|
|
|
$serverHost = $this->request->getServerProtocol() . '://' . $this->request->getServerHost(); |
|
508
|
|
|
$token = $row->generateFileToken($fileId, $version, $updatable, $serverHost); |
|
509
|
|
|
|
|
510
|
|
|
// Return the token. |
|
511
|
|
|
return array( |
|
512
|
|
|
'status' => 'success', |
|
513
|
|
|
'token' => $token |
|
514
|
|
|
); |
|
515
|
|
|
} |
|
516
|
|
|
|
|
517
|
|
|
/** |
|
518
|
|
|
* @NoAdminRequired |
|
519
|
|
|
* @NoCSRFRequired |
|
520
|
|
|
* @PublicPage |
|
521
|
|
|
* Returns general info about a file. |
|
522
|
|
|
*/ |
|
523
|
|
|
public function wopiCheckFileInfo($fileId){ |
|
524
|
|
|
$token = $this->request->getParam('access_token'); |
|
525
|
|
|
|
|
526
|
|
|
$arr = explode('_', $fileId, 2); |
|
527
|
|
|
$version = '0'; |
|
528
|
|
View Code Duplication |
if (count($arr) == 2) { |
|
|
|
|
|
|
529
|
|
|
$fileId = $arr[0]; |
|
530
|
|
|
$version = $arr[1]; |
|
531
|
|
|
} |
|
532
|
|
|
|
|
533
|
|
|
\OC::$server->getLogger()->debug('Getting info about file {fileId}, version {version} by token {token}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'version' => $version, 'token' => $token ]); |
|
534
|
|
|
|
|
535
|
|
|
$row = new Db\Wopi(); |
|
536
|
|
|
$row->loadBy('token', $token); |
|
537
|
|
|
|
|
538
|
|
|
$res = $row->getPathForToken($fileId, $version, $token); |
|
539
|
|
|
if ($res == false || http_response_code() != 200) |
|
540
|
|
|
{ |
|
541
|
|
|
return false; |
|
542
|
|
|
} |
|
543
|
|
|
|
|
544
|
|
|
// Login the user to see his mount locations |
|
545
|
|
|
$this->loginUser($res['owner']); |
|
546
|
|
|
$view = new \OC\Files\View('/' . $res['owner'] . '/files'); |
|
547
|
|
|
$info = $view->getFileInfo($res['path']); |
|
548
|
|
|
$this->logoutUser(); |
|
549
|
|
|
|
|
550
|
|
|
if (!$info) { |
|
551
|
|
|
http_response_code(404); |
|
552
|
|
|
return false; |
|
553
|
|
|
} |
|
554
|
|
|
|
|
555
|
|
|
$editorName = \OC::$server->getUserManager()->get($res['editor'])->getDisplayName(); |
|
556
|
|
|
return array( |
|
557
|
|
|
'BaseFileName' => $info['name'], |
|
558
|
|
|
'Size' => $info['size'], |
|
559
|
|
|
'Version' => $version, |
|
560
|
|
|
'UserId' => $res['editor'], |
|
561
|
|
|
'UserFriendlyName' => $editorName, |
|
562
|
|
|
'UserCanWrite' => $res['canwrite'] ? true : false, |
|
563
|
|
|
'PostMessageOrigin' => $res['server_host'] |
|
564
|
|
|
); |
|
565
|
|
|
} |
|
566
|
|
|
|
|
567
|
|
|
/** |
|
568
|
|
|
* @NoAdminRequired |
|
569
|
|
|
* @NoCSRFRequired |
|
570
|
|
|
* @PublicPage |
|
571
|
|
|
* Given an access token and a fileId, returns the contents of the file. |
|
572
|
|
|
* Expects a valid token in access_token parameter. |
|
573
|
|
|
*/ |
|
574
|
|
|
public function wopiGetFile($fileId){ |
|
575
|
|
|
$token = $this->request->getParam('access_token'); |
|
576
|
|
|
|
|
577
|
|
|
$arr = explode('_', $fileId, 2); |
|
578
|
|
|
$version = '0'; |
|
579
|
|
View Code Duplication |
if (count($arr) == 2) { |
|
|
|
|
|
|
580
|
|
|
$fileId = $arr[0]; |
|
581
|
|
|
$version = $arr[1]; |
|
582
|
|
|
} |
|
583
|
|
|
|
|
584
|
|
|
\OC::$server->getLogger()->debug('Getting contents of file {fileId}, version {version} by token {token}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'version' => $version, 'token' => $token ]); |
|
585
|
|
|
|
|
586
|
|
|
$row = new Db\Wopi(); |
|
587
|
|
|
$row->loadBy('token', $token); |
|
588
|
|
|
|
|
589
|
|
|
//TODO: Support X-WOPIMaxExpectedSize header. |
|
590
|
|
|
$res = $row->getPathForToken($fileId, $version, $token); |
|
591
|
|
|
$ownerid = $res['owner']; |
|
592
|
|
|
|
|
593
|
|
|
// Login the user to see his mount locations |
|
594
|
|
|
$this->loginUser($ownerid); |
|
595
|
|
|
$view = new \OC\Files\View('/' . $res['owner'] . '/files'); |
|
596
|
|
|
$info = $view->getFileInfo($res['path']); |
|
597
|
|
|
|
|
598
|
|
|
if (!$info) { |
|
599
|
|
|
http_response_code(404); |
|
600
|
|
|
return false; |
|
601
|
|
|
} |
|
602
|
|
|
|
|
603
|
|
|
$filename = ''; |
|
|
|
|
|
|
604
|
|
|
// If some previous version is requested, fetch it from Files_Version app |
|
605
|
|
|
if ($version !== '0') { |
|
606
|
|
|
\OCP\JSON::checkAppEnabled('files_versions'); |
|
607
|
|
|
|
|
608
|
|
|
$filename = '/files_versions/' . $info['name'] . '.v' . $version; |
|
609
|
|
|
} else { |
|
610
|
|
|
$filename = '/files' . $res['path']; |
|
611
|
|
|
} |
|
612
|
|
|
|
|
613
|
|
|
$this->logoutUser(); |
|
614
|
|
|
|
|
615
|
|
|
return new DownloadResponse($this->request, $ownerid, $filename); |
|
616
|
|
|
} |
|
617
|
|
|
|
|
618
|
|
|
/** |
|
619
|
|
|
* @NoAdminRequired |
|
620
|
|
|
* @NoCSRFRequired |
|
621
|
|
|
* @PublicPage |
|
622
|
|
|
* Given an access token and a fileId, replaces the files with the request body. |
|
623
|
|
|
* Expects a valid token in access_token parameter. |
|
624
|
|
|
*/ |
|
625
|
|
|
public function wopiPutFile($fileId){ |
|
626
|
|
|
$token = $this->request->getParam('access_token'); |
|
627
|
|
|
|
|
628
|
|
|
$arr = explode('_', $fileId, 2); |
|
629
|
|
|
$version = '0'; |
|
630
|
|
View Code Duplication |
if (count($arr) == 2) { |
|
|
|
|
|
|
631
|
|
|
$fileId = $arr[0]; |
|
632
|
|
|
$version = $arr[1]; |
|
633
|
|
|
} |
|
634
|
|
|
|
|
635
|
|
|
\OC::$server->getLogger()->debug('Putting contents of file {fileId}, version {version} by token {token}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'version' => $version, 'token' => $token ]); |
|
636
|
|
|
|
|
637
|
|
|
$row = new Db\Wopi(); |
|
638
|
|
|
$row->loadBy('token', $token); |
|
639
|
|
|
|
|
640
|
|
|
$res = $row->getPathForToken($fileId, $version, $token); |
|
641
|
|
|
if (!$res['canwrite']) { |
|
642
|
|
|
return array( |
|
643
|
|
|
'status' => 'error', |
|
644
|
|
|
'message' => 'Permission denied' |
|
645
|
|
|
); |
|
646
|
|
|
} |
|
647
|
|
|
|
|
648
|
|
|
// Log-in as the user to regiser the change under her name. |
|
649
|
|
|
$editorid = $res['editor']; |
|
650
|
|
|
// This call is made from loolwsd, so we need to initialize the |
|
651
|
|
|
// session before we can make the user who opened the document |
|
652
|
|
|
// login. This is necessary to make activity app register the |
|
653
|
|
|
// change made to this file under this user's (editorid) name. |
|
654
|
|
|
$this->loginUser($editorid); |
|
655
|
|
|
|
|
656
|
|
|
// Set up the filesystem view for the owner (where the file actually is). |
|
657
|
|
|
$userid = $res['owner']; |
|
658
|
|
|
$root = '/' . $userid . '/files'; |
|
659
|
|
|
$view = new \OC\Files\View($root); |
|
660
|
|
|
|
|
661
|
|
|
// Read the contents of the file from the POST body and store. |
|
662
|
|
|
$content = fopen('php://input', 'r'); |
|
663
|
|
|
\OC::$server->getLogger()->debug('Storing file {fileId} by {editor} owned by {owner}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'editor' => $editorid, 'owner' => $userid ]); |
|
664
|
|
|
|
|
665
|
|
|
// Setup the FS which is needed to emit hooks (versioning). |
|
666
|
|
|
\OC_Util::tearDownFS(); |
|
667
|
|
|
\OC_Util::setupFS($userid); |
|
668
|
|
|
|
|
669
|
|
|
$view->file_put_contents($res['path'], $content); |
|
670
|
|
|
|
|
671
|
|
|
$this->logoutUser(); |
|
672
|
|
|
|
|
673
|
|
|
return array( |
|
674
|
|
|
'status' => 'success' |
|
675
|
|
|
); |
|
676
|
|
|
} |
|
677
|
|
|
|
|
678
|
|
|
/** |
|
679
|
|
|
* @NoAdminRequired |
|
680
|
|
|
* @PublicPage |
|
681
|
|
|
* Process partial/complete file download |
|
682
|
|
|
*/ |
|
683
|
|
|
public function serve($esId){ |
|
684
|
|
|
$session = new Db\Session(); |
|
685
|
|
|
$session->load($esId); |
|
686
|
|
|
|
|
687
|
|
|
$filename = $session->getGenesisUrl() ? $session->getGenesisUrl() : ''; |
|
688
|
|
|
return new DownloadResponse($this->request, $session->getOwner(), $filename); |
|
689
|
|
|
} |
|
690
|
|
|
|
|
691
|
|
|
/** |
|
692
|
|
|
* @NoAdminRequired |
|
693
|
|
|
*/ |
|
694
|
|
|
public function download($path){ |
|
695
|
|
|
if (!$path){ |
|
696
|
|
|
$response = new JSONResponse(); |
|
697
|
|
|
$response->setStatus(Http::STATUS_BAD_REQUEST); |
|
698
|
|
|
return $response; |
|
699
|
|
|
} |
|
700
|
|
|
|
|
701
|
|
|
$fullPath = '/files' . $path; |
|
702
|
|
|
$fileInfo = \OC\Files\Filesystem::getFileInfo($path); |
|
703
|
|
|
if ($fileInfo){ |
|
704
|
|
|
$file = new File($fileInfo->getId()); |
|
705
|
|
|
$genesis = new Genesis($file); |
|
706
|
|
|
$fullPath = $genesis->getPath(); |
|
707
|
|
|
} |
|
708
|
|
|
return new DownloadResponse($this->request, $this->uid, $fullPath); |
|
709
|
|
|
} |
|
710
|
|
|
|
|
711
|
|
|
|
|
712
|
|
|
/** |
|
713
|
|
|
* @NoAdminRequired |
|
714
|
|
|
*/ |
|
715
|
1 |
|
public function rename($fileId){ |
|
716
|
1 |
|
$name = $this->request->post['name']; |
|
717
|
|
|
|
|
718
|
1 |
|
$view = \OC\Files\Filesystem::getView(); |
|
719
|
1 |
|
$path = $view->getPath($fileId); |
|
720
|
|
|
|
|
721
|
|
|
if ($name && $view->is_file($path) && $view->isUpdatable($path)) { |
|
722
|
|
|
$newPath = dirname($path) . '/' . $name; |
|
723
|
|
|
if ($view->rename($path, $newPath)) { |
|
724
|
|
|
return array('status' => 'success'); |
|
725
|
|
|
} |
|
726
|
|
|
} |
|
727
|
|
|
return array( |
|
728
|
|
|
'status' => 'error', |
|
729
|
|
|
'message' => (string) $this->l10n->t('You don\'t have permission to rename this document') |
|
730
|
|
|
); |
|
731
|
|
|
} |
|
732
|
|
|
|
|
733
|
|
|
/** |
|
734
|
|
|
* @NoAdminRequired |
|
735
|
|
|
* Get file information about single document with fileId |
|
736
|
|
|
*/ |
|
737
|
|
|
public function get($fileId){ |
|
738
|
|
|
$documents = array(); |
|
739
|
|
|
$documents[0] = Storage::getDocumentById($fileId); |
|
740
|
|
|
|
|
741
|
|
|
return $this->prepareDocuments($documents); |
|
742
|
|
|
} |
|
743
|
|
|
|
|
744
|
|
|
|
|
745
|
|
|
/** |
|
746
|
|
|
* @NoAdminRequired |
|
747
|
|
|
* lists the documents the user has access to (including shared files, once the code in core has been fixed) |
|
748
|
|
|
* also adds session and member info for these files |
|
749
|
|
|
*/ |
|
750
|
|
|
public function listAll(){ |
|
751
|
|
|
return $this->prepareDocuments(Storage::getDocuments()); |
|
752
|
|
|
} |
|
753
|
|
|
} |
|
754
|
|
|
|
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.
Consider the following example. The parameter
$irelandis not defined by the methodfinale(...).The most likely cause is that the parameter was changed, but the annotation was not.