1: | <?php |
2: | |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | namespace Aurora\Modules\OfficeDocumentEditor; |
9: | |
10: | use Afterlogic\DAV\FS\File; |
11: | use Afterlogic\DAV\FS\Directory; |
12: | use Afterlogic\DAV\FS\Permission; |
13: | use Aurora\Api; |
14: | use Afterlogic\DAV\Server; |
15: | use Aurora\System\Application; |
16: | use Aurora\Modules\Core\Module as CoreModule; |
17: | use Aurora\Modules\Files\Module as FilesModule; |
18: | use Aurora\Modules\Files\Classes\FileItem; |
19: | use Aurora\Modules\OfficeDocumentEditor\Exceptions\Exception; |
20: | use Aurora\System\Enums\FileStorageType; |
21: | |
22: | use function Sabre\Uri\split; |
23: | |
24: | |
25: | |
26: | |
27: | |
28: | |
29: | |
30: | |
31: | class Module extends \Aurora\System\Module\AbstractModule |
32: | { |
33: | public $ExtsSpreadsheet = [ |
34: | ".xls", |
35: | ".xlsx", |
36: | ".xlsm", |
37: | ".xlt", |
38: | ".xltx", |
39: | ".xltm", |
40: | ".ods", |
41: | ".fods", |
42: | ".ots", |
43: | ".csv" |
44: | ]; |
45: | public $ExtsPresentation = [ |
46: | ".pps", |
47: | ".ppsx", |
48: | ".ppsm", |
49: | ".ppt", |
50: | ".pptx", |
51: | ".pptm", |
52: | ".pot", |
53: | ".potx", |
54: | ".potm", |
55: | ".odp", |
56: | ".fodp", |
57: | ".otp" |
58: | ]; |
59: | public $ExtsDocument = [ |
60: | ".doc", |
61: | ".docx", |
62: | ".docm", |
63: | ".dot", |
64: | ".dotx", |
65: | ".dotm", |
66: | ".odt", |
67: | ".fodt", |
68: | ".ott", |
69: | ".rtf", |
70: | ".txt", |
71: | ".html", |
72: | ".htm", |
73: | ".mht", |
74: | ".pdf", |
75: | ".djvu", |
76: | ".fb2", |
77: | ".epub", |
78: | ".xps" |
79: | ]; |
80: | |
81: | |
82: | |
83: | |
84: | |
85: | |
86: | |
87: | public function init() |
88: | { |
89: | $this->aErrors = [ |
90: | Enums\ErrorCodes::ExtensionCannotBeConverted => $this->i18N('ERROR_EXTENSION_CANNOT_BE_CONVERTED') |
91: | ]; |
92: | |
93: | $this->AddEntries([ |
94: | 'editor' => 'EntryEditor', |
95: | 'ode-callback' => 'EntryCallback' |
96: | ]); |
97: | |
98: | $this->subscribeEvent('System::RunEntry::before', [$this, 'onBeforeFileViewEntry'], 10); |
99: | $this->subscribeEvent('Files::GetFile', [$this, 'onGetFile'], 10); |
100: | $this->subscribeEvent('Files::GetItems', [$this, 'onGetItems'], 20000); |
101: | $this->subscribeEvent('Files::GetFileInfo::after', [$this, 'onAfterGetFileInfo'], 20000); |
102: | $this->subscribeEvent('AddToContentSecurityPolicyDefault', [$this, 'onAddToContentSecurityPolicyDefault']); |
103: | } |
104: | |
105: | public function GetSettings() |
106: | { |
107: | Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser); |
108: | |
109: | return [ |
110: | 'ExtensionsToView' => $this->getOfficeExtensions() |
111: | ]; |
112: | } |
113: | |
114: | protected function getExtensionsToView() |
115: | { |
116: | return $this->getConfig('ExtensionsToView', []); |
117: | } |
118: | |
119: | protected function getExtensionsToConvert() |
120: | { |
121: | return $this->getConfig('ExtensionsToConvert', []); |
122: | } |
123: | |
124: | protected function getExtensionsToEdit() |
125: | { |
126: | return $this->getConfig('ExtensionsToEdit', []); |
127: | } |
128: | |
129: | protected function getOfficeExtensions() |
130: | { |
131: | return array_merge( |
132: | $this->getExtensionsToView(), |
133: | $this->getExtensionsToEdit(), |
134: | array_keys($this->getExtensionsToConvert()) |
135: | ); |
136: | } |
137: | |
138: | protected function getDocumentType($filename) |
139: | { |
140: | $ext = strtolower('.' . pathinfo($filename, PATHINFO_EXTENSION)); |
141: | |
142: | if (in_array($ext, $this->ExtsDocument)) { |
143: | return "word"; |
144: | } |
145: | if (in_array($ext, $this->ExtsSpreadsheet)) { |
146: | return "cell"; |
147: | } |
148: | if (in_array($ext, $this->ExtsPresentation)) { |
149: | return "slide"; |
150: | } |
151: | return ""; |
152: | } |
153: | |
154: | protected function isReadOnlyDocument($filename) |
155: | { |
156: | $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); |
157: | |
158: | return in_array($ext, $this->getExtensionsToView()); |
159: | } |
160: | |
161: | protected function documentCanBeEdited($filename) |
162: | { |
163: | $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); |
164: | |
165: | return in_array($ext, $this->getExtensionsToEdit()); |
166: | } |
167: | |
168: | protected function documentCanBeConverted($sFilename) |
169: | { |
170: | $ext = strtolower(pathinfo($sFilename, PATHINFO_EXTENSION)); |
171: | |
172: | return in_array($ext, array_keys($this->getExtensionsToConvert())); |
173: | } |
174: | |
175: | |
176: | |
177: | |
178: | |
179: | public function isOfficeDocument($sFileName = '') |
180: | { |
181: | $sExtensions = implode('|', $this->getOfficeExtensions()); |
182: | return !!preg_match('/\.(' . $sExtensions . ')$/', strtolower(trim($sFileName))); |
183: | } |
184: | |
185: | protected function isTrustedRequest() |
186: | { |
187: | return true; |
188: | |
189: | $bResult = false; |
190: | |
191: | $sTrustedServerHost = $this->getConfig('TrustedServerHost', ''); |
192: | if (empty($sTrustedServerHost)) { |
193: | $bResult = true; |
194: | } else { |
195: | if (!empty($_SERVER['HTTP_CLIENT_IP'])) { |
196: | $ip = $_SERVER['HTTP_CLIENT_IP']; |
197: | } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { |
198: | $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; |
199: | } else { |
200: | $ip = $_SERVER['REMOTE_ADDR']; |
201: | } |
202: | |
203: | $bResult = $sTrustedServerHost === $ip; |
204: | } |
205: | |
206: | return $bResult; |
207: | } |
208: | |
209: | |
210: | |
211: | |
212: | |
213: | |
214: | public function onBeforeFileViewEntry(&$aArguments, &$aResult) |
215: | { |
216: | $aEntries = [ |
217: | 'download-file', |
218: | 'file-cache', |
219: | 'mail-attachment' |
220: | ]; |
221: | if (in_array($aArguments['EntryName'], $aEntries)) { |
222: | $sEntry = (string) \Aurora\System\Router::getItemByIndex(0, ''); |
223: | $sHash = (string) \Aurora\System\Router::getItemByIndex(1, ''); |
224: | $sAction = (string) \Aurora\System\Router::getItemByIndex(2, ''); |
225: | |
226: | $aValues = Api::DecodeKeyValues($sHash); |
227: | |
228: | $sFileName = isset($aValues['FileName']) ? urldecode($aValues['FileName']) : ''; |
229: | if (empty($sFileName)) { |
230: | $sFileName = isset($aValues['Name']) ? urldecode($aValues['Name']) : ''; |
231: | } |
232: | if ($sAction === 'view' && $this->isOfficeDocument($sFileName) && !isset($aValues['AuthToken'])) { |
233: | $sViewerUrl = './?editor=' . urlencode($sEntry .'/' . $sHash . '/' . $sAction . '/' . time()); |
234: | \header('Location: ' . $sViewerUrl); |
235: | } elseif ($this->isOfficeDocument($sFileName) || $sFileName === 'diff.zip' || $sFileName === 'changes.json') { |
236: | if ($this->isTrustedRequest()) { |
237: | $sAuthToken = isset($aValues['AuthToken']) ? $aValues['AuthToken'] : null; |
238: | if (isset($sAuthToken)) { |
239: | Api::setAuthToken($sAuthToken); |
240: | Api::setUserId( |
241: | Api::getAuthenticatedUserId($sAuthToken) |
242: | ); |
243: | } |
244: | } |
245: | } |
246: | } |
247: | } |
248: | |
249: | public function EntryEditor() |
250: | { |
251: | $sResult = ''; |
252: | $sFullUrl = Application::getBaseUrl(); |
253: | $sMode = 'view'; |
254: | $fileuri = isset($_GET['editor']) ? $_GET['editor'] : null; |
255: | $filename = null; |
256: | $sHash = null; |
257: | $aHashValues = []; |
258: | $docKey = null; |
259: | $lastModified = time(); |
260: | $aHistory = []; |
261: | |
262: | $oUser = Api::getAuthenticatedUser(); |
263: | |
264: | if (isset($fileuri)) { |
265: | $fileuri = \urldecode($fileuri); |
266: | $aFileuri = \explode('/', $fileuri); |
267: | if (isset($aFileuri[1])) { |
268: | $sHash = $aFileuri[1]; |
269: | $aHashValues = Api::DecodeKeyValues($sHash); |
270: | if (!isset($aHashValues['AuthToken'])) { |
271: | $aHashValues['AuthToken'] = Api::UserSession()->Set( |
272: | [ |
273: | 'token' => 'auth', |
274: | 'id' => Api::getAuthenticatedUserId() |
275: | ], |
276: | time(), |
277: | time() + 60 * 5 |
278: | ); |
279: | } |
280: | } |
281: | $sHash = Api::EncodeKeyValues($aHashValues); |
282: | $aFileuri[1] = $sHash; |
283: | $fileuri = implode('/', $aFileuri); |
284: | $fileuri = $sFullUrl . '?' . $fileuri; |
285: | } |
286: | |
287: | if (isset($sHash)) { |
288: | $aHashValues = Api::DecodeKeyValues($sHash); |
289: | if (isset($aHashValues['FileName'])) { |
290: | $filename = $aHashValues['FileName']; |
291: | } elseif (isset($aHashValues['Name'])) { |
292: | $filename = $aHashValues['Name']; |
293: | } |
294: | if (isset($aHashValues['Edit'])) { |
295: | $sMode = 'edit'; |
296: | } |
297: | |
298: | $sHash = Api::EncodeKeyValues($aHashValues); |
299: | $oFileInfo = null; |
300: | try { |
301: | if (isset($aHashValues['UserId'], $aHashValues['Type'], $aHashValues['Path'], $aHashValues['Id'])) { |
302: | $oFileInfo = FilesModule::Decorator()->GetFileInfo( |
303: | $aHashValues['UserId'], |
304: | $aHashValues['Type'], |
305: | $aHashValues['Path'], |
306: | $aHashValues['Id'] |
307: | ); |
308: | } |
309: | } catch (\Exception $oEx) { |
310: | } |
311: | |
312: | if ($oFileInfo) { |
313: | $lastModified = $oFileInfo->LastModified; |
314: | $docKey = \md5($oFileInfo->RealPath . $lastModified); |
315: | $oFileInfo->Path = $aHashValues['Path']; |
316: | $SharedWithMeAccess = isset($oFileInfo->ExtendedProps['SharedWithMeAccess']) ? (int) $oFileInfo->ExtendedProps['SharedWithMeAccess'] : null; |
317: | |
318: | if (!isset($SharedWithMeAccess) && $oFileInfo->Owner !== $oUser->PublicId) { |
319: | list($sParentPath, $sParentId) = \Sabre\Uri\split($aHashValues['Path']); |
320: | $oParentFileInfo = null; |
321: | try { |
322: | $oParentFileInfo = FilesModule::Decorator()->GetFileInfo( |
323: | $aHashValues['UserId'], |
324: | $aHashValues['Type'], |
325: | $sParentPath, |
326: | $sParentId |
327: | ); |
328: | } catch (\Exception $oEx) { |
329: | } |
330: | |
331: | if (isset($oParentFileInfo) && $oParentFileInfo->Owner === $oUser->PublicId) { |
332: | $oFileInfo->Owner = $oParentFileInfo->Owner; |
333: | } |
334: | } |
335: | |
336: | $sMode = (isset($SharedWithMeAccess) && ($SharedWithMeAccess === Permission::Write || $SharedWithMeAccess === Permission::Reshare)) || |
337: | (!isset($SharedWithMeAccess) && $oFileInfo->Owner === $oUser->PublicId) || ($oFileInfo->TypeStr === FileStorageType::Corporate) ? $sMode : 'view'; |
338: | $aHistory = $this->getHistory($oFileInfo, $docKey, $fileuri); |
339: | } elseif (isset($aHashValues['FileName'])) { |
340: | $docKey = \md5($aHashValues['FileName'] . time()); |
341: | } elseif (isset($aHashValues['Name'])) { |
342: | $docKey = \md5($aHashValues['Name'] . time()); |
343: | } |
344: | } |
345: | |
346: | $bIsReadOnlyMode = ($sMode === 'view') ? true : false; |
347: | |
348: | $filetype = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); |
349: | $lang = 'en'; |
350: | $mode = $bIsReadOnlyMode || $this->isReadOnlyDocument($filename) ? 'view' : 'edit'; |
351: | $fileuriUser = ''; |
352: | |
353: | $serverPath = $this->getConfig('DocumentServerUrl', null); |
354: | |
355: | $callbackUrl = $sFullUrl . '?ode-callback/' . $sHash; |
356: | |
357: | if (isset($fileuri) && isset($serverPath)) { |
358: | if ($oUser) { |
359: | $uid = (string) $oUser->Id; |
360: | $uname = !empty($oUser->Name) ? $oUser->Name : $oUser->PublicId; |
361: | $lang = \Aurora\System\Utils::ConvertLanguageNameToShort($oUser->Language); |
362: | } |
363: | |
364: | $config = [ |
365: | "type" => Api::IsMobileApplication() ? "mobile" : 'desktop', |
366: | "documentType" => $this->getDocumentType($filename), |
367: | "document" => [ |
368: | "title" => $filename, |
369: | "url" => $fileuri, |
370: | "fileType" => $filetype, |
371: | "key" => $docKey, |
372: | "info" => [ |
373: | "owner" => $uname, |
374: | "uploaded" => date('d.m.y', $lastModified) |
375: | ], |
376: | "permissions" => [ |
377: | "comment" => !$bIsReadOnlyMode, |
378: | "download" => true, |
379: | "edit" => !$bIsReadOnlyMode, |
380: | "fillForms" => !$bIsReadOnlyMode, |
381: | "modifyFilter" => !$bIsReadOnlyMode, |
382: | "modifyContentControl" => !$bIsReadOnlyMode, |
383: | "review" => !$bIsReadOnlyMode, |
384: | "changeHistory" => !$bIsReadOnlyMode, |
385: | "chat" => !$bIsReadOnlyMode |
386: | ] |
387: | ], |
388: | "editorConfig" => [ |
389: | "actionLink" => empty($_GET["actionLink"]) ? null : json_decode($_GET["actionLink"]), |
390: | "mode" => $mode, |
391: | "lang" => $lang, |
392: | "callbackUrl" => $callbackUrl, |
393: | "user" => [ |
394: | "id" => $uid, |
395: | "name" => $uname |
396: | ], |
397: | "embedded" => [ |
398: | "saveUrl" => $fileuriUser, |
399: | "embedUrl" => $fileuriUser, |
400: | "shareUrl" => $fileuriUser, |
401: | "toolbarDocked" => "top", |
402: | ], |
403: | "customization" => [ |
404: | "comments" => !$bIsReadOnlyMode, |
405: | "about" => false, |
406: | "feedback" => false, |
407: | "goback" => false, |
408: | "forcesave" => true, |
409: | |
410: | |
411: | |
412: | ] |
413: | ] |
414: | ]; |
415: | |
416: | $oJwt = new Classes\JwtManager($this->getConfig('Secret', '')); |
417: | if ($oJwt->isJwtEnabled()) { |
418: | $config['token'] = $oJwt->jwtEncode($config); |
419: | } |
420: | |
421: | $sResult = \file_get_contents($this->GetPath().'/templates/Editor.html'); |
422: | |
423: | $iUserId = Api::getAuthenticatedUserId(); |
424: | if (0 < $iUserId) { |
425: | $sResult = strtr($sResult, [ |
426: | '{{DOC_SERV_API_URL}}' => $serverPath . '/web-apps/apps/api/documents/api.js', |
427: | '{{CONFIG}}' => \json_encode($config), |
428: | '{{HISTORY}}' => isset($aHistory[0]) ? \json_encode($aHistory[0]) : 'false', |
429: | '{{HISTORY_DATA}}' => isset($aHistory[1]) ? \json_encode($aHistory[1]) : 'false' |
430: | ]); |
431: | \Aurora\Modules\CoreWebclient\Module::Decorator()->SetHtmlOutputHeaders(); |
432: | @header('Cache-Control: no-cache, no-store, must-revalidate', true); |
433: | @header('Pragma: no-cache', true); |
434: | @header('Expires: 0', true); |
435: | } else { |
436: | Api::Location('./'); |
437: | } |
438: | } |
439: | |
440: | return $sResult; |
441: | } |
442: | |
443: | public function CreateBlankDocument($Type, $Path, $FileName) |
444: | { |
445: | $mResult = false; |
446: | $ext = strtolower(pathinfo($FileName, PATHINFO_EXTENSION)); |
447: | $sFilePath = $this->GetPath() . "/data/new." . $ext; |
448: | if (file_exists($sFilePath)) { |
449: | $rData = \fopen($sFilePath, "r"); |
450: | $FileName = FilesModule::Decorator()->GetNonExistentFileName( |
451: | Api::getAuthenticatedUserId(), |
452: | $Type, |
453: | $Path, |
454: | $FileName |
455: | ); |
456: | $mResult = $this->createFile( |
457: | Api::getAuthenticatedUserId(), |
458: | $Type, |
459: | $Path, |
460: | $FileName, |
461: | $rData |
462: | ); |
463: | \fclose($rData); |
464: | |
465: | if ($mResult) { |
466: | $mResult = FilesModule::Decorator()->GetFileInfo( |
467: | Api::getAuthenticatedUserId(), |
468: | $Type, |
469: | $Path, |
470: | $FileName |
471: | ); |
472: | } |
473: | } |
474: | return $mResult; |
475: | } |
476: | |
477: | public function ConvertDocument($Type, $Path, $FileName) |
478: | { |
479: | $mResult = false; |
480: | $aExtensions = $this->getExtensionsToConvert(); |
481: | $sExtension = pathinfo($FileName, PATHINFO_EXTENSION); |
482: | $sNewExtension = isset($aExtensions[$sExtension]) ? $aExtensions[$sExtension] : null; |
483: | if ($sNewExtension === null) { |
484: | throw new Exceptions\Exception(Enums\ErrorCodes::ExtensionCannotBeConverted); |
485: | } else { |
486: | $mResult = self::Decorator()->ConvertDocumentToFormat($Type, $Path, $FileName, $sNewExtension); |
487: | } |
488: | |
489: | return $mResult; |
490: | } |
491: | |
492: | public function ConvertDocumentToFormat($Type, $Path, $FileName, $ToExtension) |
493: | { |
494: | $mResult = false; |
495: | $oFileInfo = FilesModule::Decorator()->GetFileInfo( |
496: | Api::getAuthenticatedUserId(), |
497: | $Type, |
498: | $Path, |
499: | $FileName |
500: | ); |
501: | if ($oFileInfo instanceof FileItem) { |
502: | $sConvertedDocumentUri = null; |
503: | $aPathParts = pathinfo($FileName); |
504: | $sFromExtension = $aPathParts['extension']; |
505: | $sFileNameWOExt = $aPathParts['filename']; |
506: | $sDocumentUri = ''; |
507: | |
508: | if (isset($oFileInfo->Actions['download']['url'])) { |
509: | $sDownloadUrl = $oFileInfo->Actions['download']['url']; |
510: | $aUrlParts = \explode('/', $sDownloadUrl); |
511: | if (isset($aUrlParts[1])) { |
512: | $aUrlParts[1] = $this->GetFileTempHash($aUrlParts[1]); |
513: | $sDocumentUri = Application::getBaseUrl() . \implode('/', $aUrlParts); |
514: | $this->GetConvertedUri( |
515: | $sDocumentUri, |
516: | $sFromExtension, |
517: | $ToExtension, |
518: | '', |
519: | false, |
520: | $sConvertedDocumentUri |
521: | ); |
522: | if (!empty($sConvertedDocumentUri)) { |
523: | $rData = \file_get_contents($sConvertedDocumentUri); |
524: | if ($rData !== false) { |
525: | $sNewFileName = FilesModule::Decorator()->GetNonExistentFileName( |
526: | Api::getAuthenticatedUserId(), |
527: | $Type, |
528: | $Path, |
529: | $sFileNameWOExt . '.' . $ToExtension |
530: | ); |
531: | $mResult = $this->createFile( |
532: | Api::getAuthenticatedUserId(), |
533: | $Type, |
534: | $Path, |
535: | $sNewFileName, |
536: | $rData |
537: | ); |
538: | if ($mResult) { |
539: | $mResult = FilesModule::Decorator()->GetFileInfo( |
540: | Api::getAuthenticatedUserId(), |
541: | $Type, |
542: | $Path, |
543: | $sNewFileName |
544: | ); |
545: | } |
546: | } |
547: | } |
548: | } |
549: | } |
550: | } |
551: | |
552: | return $mResult; |
553: | } |
554: | |
555: | protected function GetFileTempHash($sHash) |
556: | { |
557: | $aValues = Api::DecodeKeyValues($sHash); |
558: | |
559: | $sFileName = isset($aValues['FileName']) ? urldecode($aValues['FileName']) : ''; |
560: | if (empty($sFileName)) { |
561: | $sFileName = isset($aValues['Name']) ? urldecode($aValues['Name']) : ''; |
562: | } |
563: | $aValues['AuthToken'] = Api::UserSession()->Set( |
564: | [ |
565: | 'token' => 'auth', |
566: | 'id' => Api::getAuthenticatedUserId() |
567: | ], |
568: | time(), |
569: | time() + 60 * 5 |
570: | ); |
571: | |
572: | return Api::EncodeKeyValues($aValues); |
573: | } |
574: | |
575: | protected function GetConvertedUri($document_uri, $from_extension, $to_extension, $document_revision_id, $is_async, &$converted_document_uri) |
576: | { |
577: | $converted_document_uri = ""; |
578: | $responceFromConvertService = $this->SendRequestToConvertService( |
579: | $document_uri, |
580: | $from_extension, |
581: | $to_extension, |
582: | $document_revision_id, |
583: | $is_async |
584: | ); |
585: | $json = \json_decode($responceFromConvertService, true); |
586: | |
587: | $errorElement = isset($json["error"]) ? $json["error"] : null; |
588: | if ($errorElement != null && $errorElement != "") { |
589: | $this->ProcessConvServResponceError($errorElement); |
590: | } |
591: | |
592: | $isEndConvert = $json["endConvert"]; |
593: | $percent = $json["percent"]; |
594: | |
595: | if ($isEndConvert != null && $isEndConvert == true) { |
596: | $converted_document_uri = $json["fileUrl"]; |
597: | $percent = 100; |
598: | } elseif ($percent >= 100) { |
599: | $percent = 99; |
600: | } |
601: | |
602: | return $percent; |
603: | } |
604: | |
605: | protected function SendRequestToConvertService($document_uri, $from_extension, $to_extension, $document_revision_id, $is_async) |
606: | { |
607: | $title = basename($document_uri); |
608: | if (empty($title)) { |
609: | $title = \Sabre\DAV\UUIDUtil::getUUID(); |
610: | } |
611: | |
612: | if (empty($document_revision_id)) { |
613: | $document_revision_id = $document_uri; |
614: | } |
615: | |
616: | $document_revision_id = $this->GenerateRevisionId($document_revision_id); |
617: | |
618: | $serverPath = $this->getConfig('DocumentServerUrl', null); |
619: | if ($serverPath !== null) { |
620: | $urlToConverter = $serverPath . '/ConvertService.ashx'; |
621: | } |
622: | |
623: | $arr = [ |
624: | "async" => $is_async, |
625: | "url" => $document_uri, |
626: | "outputtype" => trim($to_extension, '.'), |
627: | "filetype" => trim($from_extension, '.'), |
628: | "title" => $title, |
629: | "key" => $document_revision_id |
630: | ]; |
631: | |
632: | $headerToken = ""; |
633: | |
634: | $oJwt = new Classes\JwtManager($this->getConfig('Secret', '')); |
635: | if ($oJwt->isJwtEnabled()) { |
636: | $headerToken = $oJwt->jwtEncode([ "payload" => $arr ]); |
637: | $arr["token"] = $oJwt->jwtEncode($arr); |
638: | } |
639: | |
640: | $opts = [ |
641: | 'http' => [ |
642: | 'method' => 'POST', |
643: | 'timeout' => '120000', |
644: | 'header'=> "Content-type: application/json\r\n" . |
645: | "Accept: application/json\r\n" . |
646: | (empty($headerToken) ? "" : "Authorization: $headerToken\r\n"), |
647: | 'content' => \json_encode($arr) |
648: | ] |
649: | ]; |
650: | |
651: | if (substr($urlToConverter, 0, strlen("https")) === "https") { |
652: | $opts['ssl'] = ['verify_peer' => false]; |
653: | } |
654: | |
655: | $context = stream_context_create($opts); |
656: | $response_data = file_get_contents($urlToConverter, false, $context); |
657: | |
658: | return $response_data; |
659: | } |
660: | |
661: | protected function ProcessConvServResponceError($errorCode) |
662: | { |
663: | $errorMessageTemplate = "Error occurred in the document service: "; |
664: | $errorMessage = ''; |
665: | |
666: | switch ($errorCode) { |
667: | case -8: |
668: | $errorMessage = $errorMessageTemplate . "Error document VKey"; |
669: | break; |
670: | case -7: |
671: | $errorMessage = $errorMessageTemplate . "Error document request"; |
672: | break; |
673: | case -6: |
674: | $errorMessage = $errorMessageTemplate . "Error database"; |
675: | break; |
676: | case -5: |
677: | $errorMessage = $errorMessageTemplate . "Error unexpected guid"; |
678: | break; |
679: | case -4: |
680: | $errorMessage = $errorMessageTemplate . "Error download error"; |
681: | break; |
682: | case -3: |
683: | $errorMessage = $errorMessageTemplate . "Error convertation error"; |
684: | break; |
685: | case -2: |
686: | $errorMessage = $errorMessageTemplate . "Error convertation timeout"; |
687: | break; |
688: | case -1: |
689: | $errorMessage = $errorMessageTemplate . "Error convertation unknown"; |
690: | break; |
691: | case 0: |
692: | break; |
693: | default: |
694: | $errorMessage = $errorMessageTemplate . "ErrorCode = " . $errorCode; |
695: | break; |
696: | } |
697: | |
698: | throw new Exception($errorMessage); |
699: | } |
700: | |
701: | protected function GenerateRevisionId($expected_key) |
702: | { |
703: | if (strlen($expected_key) > 20) { |
704: | $expected_key = crc32($expected_key); |
705: | } |
706: | $key = preg_replace("[^0-9-.a-zA-Z_=]", "_", $expected_key); |
707: | $key = substr($key, 0, min(array(strlen($key), 20))); |
708: | return $key; |
709: | } |
710: | |
711: | public function EntryCallback() |
712: | { |
713: | $result = ["error" => 0]; |
714: | |
715: | if (($body_stream = file_get_contents("php://input")) === false) { |
716: | $result["error"] = "Bad Request"; |
717: | } else { |
718: | $data = json_decode($body_stream, true); |
719: | |
720: | if (isset($data['token'])) { |
721: | Api::AddSecret($data['token']); |
722: | } |
723: | Api::Log($body_stream); |
724: | |
725: | $oJwt = new Classes\JwtManager($this->getConfig('Secret', '')); |
726: | if ($oJwt->isJwtEnabled()) { |
727: | $inHeader = false; |
728: | $token = ""; |
729: | if (!empty($data["token"])) { |
730: | $token = $oJwt->jwtDecode($data["token"]); |
731: | } elseif (!empty($_SERVER['HTTP_AUTHORIZATION'])) { |
732: | $token = $oJwt->jwtDecode(substr($_SERVER['HTTP_AUTHORIZATION'], strlen("Bearer "))); |
733: | $inHeader = true; |
734: | } else { |
735: | $result["error"] = "Expected JWT"; |
736: | } |
737: | if (empty($token)) { |
738: | $result["error"] = "Invalid JWT signature"; |
739: | } else { |
740: | $data = json_decode($token, true); |
741: | if ($inHeader) { |
742: | $data = $data["payload"]; |
743: | } |
744: | } |
745: | } |
746: | |
747: | if ($data["status"] == 2 || $data["status"] == 6) { |
748: | $sHash = (string) \Aurora\System\Router::getItemByIndex(1, ''); |
749: | if (!empty($sHash)) { |
750: | $aHashValues = Api::DecodeKeyValues($sHash); |
751: | |
752: | $prevState = Api::skipCheckUserRole(true); |
753: | $oFileInfo = FilesModule::Decorator()->GetFileInfo( |
754: | $aHashValues['UserId'], |
755: | $aHashValues['Type'], |
756: | $aHashValues['Path'], |
757: | $aHashValues['Name'] |
758: | ); |
759: | if ($oFileInfo instanceof FileItem && $this->isOfficeDocument($oFileInfo->Name)) { |
760: | if ((isset($oFileInfo->ExtendedProps['SharedWithMeAccess']) && (int) $oFileInfo->ExtendedProps['SharedWithMeAccess'] === \Afterlogic\DAV\FS\Permission::Write) || !isset($oFileInfo->ExtendedProps['SharedWithMeAccess']) |
761: | && !$this->isReadOnlyDocument($oFileInfo->Name)) { |
762: | $rData = \file_get_contents($data["url"]); |
763: | if ($rData !== false) { |
764: | if ($this->getConfig('EnableHistory', false) && $data["status"] == 2) { |
765: | if ($this->isTrustedRequest()) { |
766: | $iUserId = isset($aHashValues['UserId']) ? $aHashValues['UserId'] : null; |
767: | if (isset($iUserId)) { |
768: | Server::setUser(Api::getUserPublicIdById($iUserId)); |
769: | } |
770: | } |
771: | $histDir = $this->getHistoryDir($oFileInfo, true); |
772: | if ($histDir) { |
773: | $curVer = $histDir->getFileVersion(); |
774: | $verDir = $histDir->getVersionDir($curVer + 1, true); |
775: | $ext = strtolower(pathinfo($oFileInfo->Name, PATHINFO_EXTENSION)); |
776: | |
777: | list(, $sOwnerUserPublicId) = split($verDir->getOwner()); |
778: | $iOwnerUserId = Api::getUserIdByPublicId($sOwnerUserPublicId); |
779: | |
780: | if (!isset($oFileInfo->ExtendedProps['Created']) && $curVer == 0) { |
781: | FilesModule::Decorator()->UpdateExtendedProps( |
782: | $iOwnerUserId, |
783: | $aHashValues['Type'], |
784: | $oFileInfo->Path, |
785: | $oFileInfo->Name, |
786: | [ |
787: | 'Created' => $oFileInfo->LastModified |
788: | ] |
789: | ); |
790: | } |
791: | |
792: | $fileContent = FilesModule::Decorator()->GetFileContent( |
793: | $aHashValues['UserId'], |
794: | $aHashValues['Type'], |
795: | $aHashValues['Path'], |
796: | $aHashValues['Name'] |
797: | ); |
798: | $verDir->createFile('prev.' . $ext, $fileContent); |
799: | $prevChild = $verDir->getChild('prev.' . $ext); |
800: | if ($prevChild) { |
801: | $mExtendedProps = $prevChild->getProperty('ExtendedProps'); |
802: | $aExtendedProps = is_array($mExtendedProps) ? $mExtendedProps : []; |
803: | $aExtendedProps['Created'] = $oFileInfo->LastModified; |
804: | |
805: | $prevChild->setProperty('ExtendedProps', $aExtendedProps); |
806: | } |
807: | $this->updateHistory($verDir, $data); |
808: | } |
809: | } |
810: | |
811: | $this->createFile( |
812: | $aHashValues['UserId'], |
813: | $aHashValues['Type'], |
814: | $aHashValues['Path'], |
815: | $aHashValues['Name'], |
816: | $rData |
817: | ); |
818: | } |
819: | } |
820: | } |
821: | Api::skipCheckUserRole($prevState); |
822: | } |
823: | } |
824: | } |
825: | return json_encode($result); |
826: | } |
827: | |
828: | |
829: | |
830: | |
831: | |
832: | |
833: | public function onGetFile(&$aArguments, &$aResult) |
834: | { |
835: | if ($this->isOfficeDocument($aArguments['Name'])) { |
836: | $aArguments['NoRedirect'] = true; |
837: | } |
838: | } |
839: | |
840: | |
841: | |
842: | |
843: | |
844: | |
845: | |
846: | public function onGetItems($aArgs, &$mResult) |
847: | { |
848: | if (is_array($mResult)) { |
849: | foreach ($mResult as $oItem) { |
850: | if ($oItem instanceof FileItem && $this->isOfficeDocument($oItem->Name)) { |
851: | $bEncrypted = isset($oItem->ExtendedProps['InitializationVector']); |
852: | $bAccessSet = isset($oItem->ExtendedProps['SharedWithMeAccess']); |
853: | $bHasWriteAccess = !$bAccessSet || ($bAccessSet && ((int) $oItem->ExtendedProps['SharedWithMeAccess'] === \Afterlogic\DAV\FS\Permission::Write || (int) $oItem->ExtendedProps['SharedWithMeAccess'] === \Afterlogic\DAV\FS\Permission::Reshare)); |
854: | if (!$bEncrypted && $bHasWriteAccess) { |
855: | if ($this->documentCanBeConverted($oItem->Name)) { |
856: | $oItem->UnshiftAction([ |
857: | 'convert' => [ |
858: | 'url' => '' |
859: | ] |
860: | ]); |
861: | } elseif ($this->documentCanBeEdited($oItem->Name)) { |
862: | $sHash = $oItem->getHash(); |
863: | $aHashValues = Api::DecodeKeyValues($sHash); |
864: | $aHashValues['Edit'] = true; |
865: | $sHash = Api::EncodeKeyValues($aHashValues); |
866: | $oItem->UnshiftAction([ |
867: | 'edit' => [ |
868: | 'url' => '?download-file/' . $sHash .'/view' |
869: | ] |
870: | ]); |
871: | } |
872: | } |
873: | } |
874: | } |
875: | } |
876: | } |
877: | |
878: | public function onAfterGetFileInfo($aArgs, &$mResult) |
879: | { |
880: | if ($mResult) { |
881: | if ($mResult instanceof FileItem && $this->isOfficeDocument($mResult->Name)) { |
882: | if ((isset($mResult->ExtendedProps['SharedWithMeAccess']) && ((int) $mResult->ExtendedProps['SharedWithMeAccess'] === \Afterlogic\DAV\FS\Permission::Write || (int) $mResult->ExtendedProps['SharedWithMeAccess'] === \Afterlogic\DAV\FS\Permission::Reshare)) || !isset($mResult->ExtendedProps['SharedWithMeAccess']) |
883: | && !$this->isReadOnlyDocument($mResult->Name)) { |
884: | $sHash = $mResult->getHash(); |
885: | $aHashValues = Api::DecodeKeyValues($sHash); |
886: | $aHashValues['Edit'] = true; |
887: | $sHash = Api::EncodeKeyValues($aHashValues); |
888: | $mResult->UnshiftAction([ |
889: | 'edit' => [ |
890: | 'url' => '?download-file/' . $sHash .'/view' |
891: | ] |
892: | ]); |
893: | } |
894: | } |
895: | } |
896: | } |
897: | |
898: | public function onAddToContentSecurityPolicyDefault($aArgs, &$aAddDefault) |
899: | { |
900: | $sUrl = $this->getConfig('DocumentServerUrl', null); |
901: | if (!empty($sUrl)) { |
902: | $aAddDefault[] = $sUrl; |
903: | } |
904: | } |
905: | |
906: | |
907: | |
908: | public function RestoreFromHistory($Url, $Version) |
909: | { |
910: | } |
911: | |
912: | protected function getHistoryDir($oFileInfo, $bCreateIfNotExists = false) |
913: | { |
914: | $oHistNode = false; |
915: | |
916: | $sType = $oFileInfo->TypeStr; |
917: | $sPath = $oFileInfo->Path; |
918: | $sName = $oFileInfo->Name; |
919: | $sOwner = $oFileInfo->Owner; |
920: | |
921: | |
922: | |
923: | |
924: | $oNode = Server::getNodeForPath('files/' . $sType . $sPath . '/' . $sName, $sOwner); |
925: | if ($oNode instanceof File) { |
926: | $oHistNode = $oNode->getHistoryDirectory(); |
927: | if (!$oHistNode && $bCreateIfNotExists) { |
928: | $oParentNode = Server::getNodeForPath('files/' . $sType . $sPath, $sOwner); |
929: | if ($oParentNode instanceof Directory) { |
930: | $oParentNode->createDirectory($sName . '.hist'); |
931: | } |
932: | $oHistNode = $oNode->getHistoryDirectory(); |
933: | } |
934: | } |
935: | |
936: | return $oHistNode; |
937: | } |
938: | |
939: | protected function getHistory($oFileInfo, $docKey, $sUrl) |
940: | { |
941: | $result = []; |
942: | $curVer = 0; |
943: | $histDir = $this->getHistoryDir($oFileInfo); |
944: | if ($histDir && method_exists($histDir, 'getFileVersion')) { |
945: | $curVer = $histDir->getFileVersion(); |
946: | } |
947: | |
948: | if ($curVer > 0) { |
949: | $hist = []; |
950: | $histData = []; |
951: | $oUser = CoreModule::getInstance()->GetUserByPublicId($oFileInfo->Owner); |
952: | |
953: | for ($i = 0; $i <= $curVer; $i++) { |
954: | $obj = []; |
955: | $dataObj = []; |
956: | |
957: | $verDir = $histDir->getVersionDir($i + 1); |
958: | |
959: | $key = false; |
960: | |
961: | if ($i == $curVer) { |
962: | $key = $docKey; |
963: | } else { |
964: | if ($verDir && $verDir->childExists('key.txt')) { |
965: | $oKeyFile = $verDir->getChild('key.txt'); |
966: | if ($oKeyFile instanceof \Afterlogic\DAV\FS\File) { |
967: | $mKeyData = $oKeyFile->get(false); |
968: | if (is_resource($mKeyData)) { |
969: | $key = \stream_get_contents($mKeyData); |
970: | } |
971: | } |
972: | } else { |
973: | $key = false; |
974: | } |
975: | } |
976: | |
977: | if ($key === false) { |
978: | continue; |
979: | } |
980: | $obj["key"] = $key; |
981: | $obj["version"] = $i + 1; |
982: | |
983: | if ($i === 0) { |
984: | $ext = strtolower(pathinfo($oFileInfo->Name, PATHINFO_EXTENSION)); |
985: | list(, $sUserPublicId) = split($verDir->getOwner()); |
986: | |
987: | $prevDoc = $verDir->getChild('prev.' . $ext); |
988: | $aPrevFileExtendedProps = []; |
989: | if ($prevDoc) { |
990: | $aPrevFileExtendedProps = $prevDoc->getProperty('ExtendedProps'); |
991: | } |
992: | |
993: | if (isset($aPrevFileExtendedProps['Created'])) { |
994: | $obj["created"] = $this->convetToUserTime( |
995: | $oUser, |
996: | date("Y-m-d H:i:s", $aPrevFileExtendedProps['Created']) |
997: | ); |
998: | } else { |
999: | $obj["created"] = ''; |
1000: | } |
1001: | $obj["user"] = [ |
1002: | "id" => (string) $oUser->Id, |
1003: | "name" => $oFileInfo->Owner |
1004: | ]; |
1005: | } |
1006: | |
1007: | if ($i != $curVer) { |
1008: | $ext = strtolower(pathinfo($oFileInfo->Name, PATHINFO_EXTENSION)); |
1009: | |
1010: | $sUrl = $this->getDownloadUrl( |
1011: | |
1012: | Api::getUserIdByPublicId($verDir->getUser()), |
1013: | |
1014: | $verDir->getStorage(), |
1015: | $verDir->getRelativePath() . '/' . $verDir->getName(), |
1016: | 'prev.' . $ext |
1017: | ); |
1018: | } |
1019: | |
1020: | $dataObj["key"] = $key; |
1021: | $dataObj["url"] = $sUrl; |
1022: | $dataObj["version"] = $i + 1; |
1023: | |
1024: | if ($i > 0) { |
1025: | $changes = false; |
1026: | $verDirPrev = $histDir->getVersionDir($i); |
1027: | if ($verDirPrev && $verDirPrev->childExists('changes.json')) { |
1028: | $oChangesFile = $verDirPrev->getChild('changes.json'); |
1029: | if ($oChangesFile instanceof \Afterlogic\DAV\FS\File) { |
1030: | $mChangesFileData = $oChangesFile->get(false); |
1031: | if (is_resource($mChangesFileData)) { |
1032: | $changes = \json_decode(\stream_get_contents($mChangesFileData), true); |
1033: | } |
1034: | } |
1035: | } |
1036: | |
1037: | if (!$changes) { |
1038: | continue; |
1039: | } |
1040: | $change = $changes["changes"][0]; |
1041: | |
1042: | $obj["changes"] = $changes["changes"]; |
1043: | $obj["serverVersion"] = $changes["serverVersion"]; |
1044: | $obj["created"] = $this->convetToUserTime( |
1045: | $oUser, |
1046: | $change["created"] |
1047: | ); |
1048: | $obj["user"] = $change["user"]; |
1049: | |
1050: | if (isset($histData[$i])) { |
1051: | $prev = $histData[$i]; |
1052: | $dataObj["previous"] = [ |
1053: | "key" => $prev["key"], |
1054: | "url" => $prev["url"] |
1055: | ]; |
1056: | } |
1057: | |
1058: | $dataObj["changesUrl"] = $this->getDownloadUrl( |
1059: | |
1060: | Api::getUserIdByPublicId($histDir->getUser()), |
1061: | |
1062: | $histDir->getStorage(), |
1063: | $histDir->getVersionDir($i)->getRelativePath() . '/' . $verDirPrev->getName(), |
1064: | 'diff.zip' |
1065: | ); |
1066: | } |
1067: | |
1068: | array_push($hist, $obj); |
1069: | $oJwt = new Classes\JwtManager($this->getConfig('Secret', '')); |
1070: | if ($oJwt->isJwtEnabled()) { |
1071: | $dataObj['token'] = $oJwt->jwtEncode($dataObj); |
1072: | } |
1073: | $histData[$i + 1] = $dataObj; |
1074: | } |
1075: | |
1076: | array_push( |
1077: | $result, |
1078: | [ |
1079: | "currentVersion" => $curVer + 1, |
1080: | "history" => $hist |
1081: | ], |
1082: | $histData |
1083: | ); |
1084: | } |
1085: | |
1086: | return $result; |
1087: | } |
1088: | |
1089: | protected function updateHistory($verDir, $data) |
1090: | { |
1091: | if (isset($data["changesurl"])) { |
1092: | $rChangesData = \file_get_contents($data["changesurl"]); |
1093: | if ($rChangesData !== false) { |
1094: | $verDir->createFile('diff.zip', $rChangesData); |
1095: | } |
1096: | } |
1097: | $histData = isset($data["changeshistory"]) ? $data["changeshistory"] : ''; |
1098: | if (empty($histData)) { |
1099: | $histData = json_encode($data["history"], JSON_PRETTY_PRINT); |
1100: | } |
1101: | if (!empty($histData)) { |
1102: | $verDir->createFile('changes.json', $histData); |
1103: | } |
1104: | $verDir->createFile('key.txt', $data['key']); |
1105: | } |
1106: | |
1107: | protected function createFile($iUserId, $sType, $sPath, $sFileName, $mData) |
1108: | { |
1109: | Api::Log(self::GetName() . '::writeFile'); |
1110: | |
1111: | $mResult = false; |
1112: | $aArgs = [ |
1113: | 'UserId' => $iUserId, |
1114: | 'Type' => $sType, |
1115: | 'Path' => $sPath, |
1116: | 'Name' => $sFileName, |
1117: | 'Data' => $mData, |
1118: | 'Overwrite' => true, |
1119: | 'RangeType' => 0, |
1120: | 'Offset' => 0, |
1121: | 'ExtendedProps' => [] |
1122: | ]; |
1123: | |
1124: | $this->broadcastEvent( |
1125: | 'Files::CreateFile', |
1126: | $aArgs, |
1127: | $mResult |
1128: | ); |
1129: | |
1130: | return $mResult; |
1131: | } |
1132: | |
1133: | protected function getDownloadUrl($iUserId, $sType, $sPath, $sName) |
1134: | { |
1135: | $sHash = Api::EncodeKeyValues([ |
1136: | 'UserId' => $iUserId, |
1137: | 'Id' => $sName, |
1138: | 'Type' => $sType, |
1139: | 'Path' => $sPath, |
1140: | 'Name' => $sName, |
1141: | 'FileName' => $sName, |
1142: | 'AuthToken' => Api::UserSession()->Set([ |
1143: | 'token' => 'auth', |
1144: | 'id' => $iUserId, |
1145: | 't' => time(), |
1146: | ]) |
1147: | ]); |
1148: | |
1149: | return Application::getBaseUrl() . '?download-file/' . $sHash; |
1150: | } |
1151: | |
1152: | protected function convetToUserTime($oUser, $sTime) |
1153: | { |
1154: | $dt = \DateTime::createFromFormat( |
1155: | 'Y-m-d H:i:s', |
1156: | $sTime, |
1157: | new \DateTimeZone('UTC') |
1158: | ); |
1159: | if (!empty($oUser->DefaultTimeZone)) { |
1160: | $dt->setTimezone(new \DateTimeZone($oUser->DefaultTimeZone)); |
1161: | } |
1162: | |
1163: | return $dt->format("Y-m-d H:i:s"); |
1164: | } |
1165: | } |
1166: | |