1: <?php
2: /**
3: * This code is licensed under AGPLv3 license or Afterlogic Software License
4: * if commercial version of the product was purchased.
5: * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
6: */
7:
8: namespace Aurora\Modules\PersonalFiles;
9:
10: use Afterlogic\DAV\Constants;
11: use Afterlogic\DAV\FS\Directory;
12: use Afterlogic\DAV\FS\File;
13: use Afterlogic\DAV\FS\Shared\File as SharedFile;
14: use Afterlogic\DAV\FS\Shared\Directory as SharedDirectory;
15: use Afterlogic\DAV\Server;
16: use Aurora\Api;
17: use Aurora\Modules\Core\Module as CoreModule;
18: use Aurora\Modules\Files\Classes\FileItem;
19: use Aurora\Modules\Files\Enums\ErrorCodes;
20: use Aurora\Modules\Files\Module as FilesModule;
21: use Aurora\System\Exceptions\ApiException;
22:
23: /**
24: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
25: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
26: * @copyright Copyright (c) 2023, Afterlogic Corp.
27:
28: * @property Settings $oModuleSettings
29: *
30: * @package Modules
31: */
32: class Module extends \Aurora\System\Module\AbstractModule
33: {
34: protected static $sStorageType = 'personal';
35: protected static $iStorageOrder = 0;
36:
37: /**
38: * Indicates if it's allowed to move files/folders to this storage.
39: * @var boolean
40: */
41: protected static $bIsDroppable = true;
42:
43: protected $oBeforeDeleteUserRootPath = '';
44: protected $oBeforeDeleteUser = null;
45: /**
46: *
47: * @var \Aurora\Modules\PersonalFiles\Manager
48: */
49: public $oManager = null;
50:
51: /**
52: *
53: * @var \Aurora\Modules\Min\Module
54: */
55: protected $oMinModuleDecorator = null;
56:
57: public function getManager()
58: {
59: if ($this->oManager === null) {
60: $this->oManager = new Manager($this);
61: }
62:
63: return $this->oManager;
64: }
65:
66: /**
67: * @return Module
68: */
69: public static function getInstance()
70: {
71: return parent::getInstance();
72: }
73:
74: /**
75: * @return Module
76: */
77: public static function Decorator()
78: {
79: return parent::Decorator();
80: }
81:
82: /**
83: * @return Settings
84: */
85: public function getModuleSettings()
86: {
87: return $this->oModuleSettings;
88: }
89:
90: /**
91: * Initializes Files Module.
92: *
93: * @ignore
94: */
95: public function init()
96: {
97: ini_set('default_charset', 'UTF-8'); //support for cyrillic characters in file names
98:
99: $this->subscribeEvent('Files::GetFile', array($this, 'onGetFile'));
100: $this->subscribeEvent('Files::CreateFile', array($this, 'onCreateFile'));
101: $this->subscribeEvent('Files::GetLinkType', array($this, 'onGetLinkType'));
102: $this->subscribeEvent('Files::CheckUrl', array($this, 'onCheckUrl'));
103:
104: $this->subscribeEvent('Files::GetStorages::after', array($this, 'onAfterGetStorages'));
105: $this->subscribeEvent('Files::GetFileInfo::after', array($this, 'onAfterGetFileInfo'), 10);
106: $this->subscribeEvent('Files::GetItems', array($this, 'onGetItems'));
107: $this->subscribeEvent('Files::CreateFolder::after', array($this, 'onAfterCreateFolder'));
108: $this->subscribeEvent('Files::Copy::after', array($this, 'onAfterCopy'));
109: $this->subscribeEvent('Files::Move::after', array($this, 'onAfterMove'));
110: $this->subscribeEvent('Files::Rename::after', array($this, 'onAfterRename'));
111: $this->subscribeEvent('Files::Delete::after', array($this, 'onAfterDelete'));
112: $this->subscribeEvent('Files::GetQuota::after', array($this, 'onAfterGetQuota'));
113: $this->subscribeEvent('Files::CreateLink::after', array($this, 'onAfterCreateLink'));
114: $this->subscribeEvent('Files::CreatePublicLink::after', array($this, 'onAfterCreatePublicLink'));
115: $this->subscribeEvent('Files::GetFileContent::after', array($this, 'onAfterGetFileContent'));
116: $this->subscribeEvent('Files::IsFileExists::after', array($this, 'onAfterIsFileExists'));
117: $this->subscribeEvent('Files::PopulateFileItem::after', array($this, 'onAfterPopulateFileItem'));
118: $this->subscribeEvent('Files::CheckQuota::after', array($this, 'onAfterCheckQuota'));
119: $this->subscribeEvent('Files::DeletePublicLink::after', array($this, 'onAfterDeletePublicLink'));
120: $this->subscribeEvent('Files::GetSubModules::after', array($this, 'onAfterGetSubModules'));
121: $this->subscribeEvent('Files::UpdateExtendedProps::after', array($this, 'onAfterUpdateExtendedProps'));
122: $this->subscribeEvent('Files::GetExtendedProps::after', array($this, 'onAfterGetExtendedProps'));
123:
124: $this->subscribeEvent('Core::DeleteUser::before', array($this, 'onBeforeDeleteUser'));
125: $this->subscribeEvent('Core::DeleteUser::after', array($this, 'onAfterDeleteUser'));
126:
127: $this->subscribeEvent('Files::GetNonExistentFileName::after', array($this, 'onAfterGetNonExistentFileName'));
128:
129: $this->subscribeEvent('Files::GetAccessInfoForPath::after', array($this, 'onAfterGetAccessInfoForPath'));
130: }
131:
132: /**
133: * Obtains list of module settings.
134: *
135: * @return array
136: */
137: public function GetSettings()
138: {
139: Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
140:
141: return array(
142: 'UserSpaceLimitMb' => $this->getUserSpaceLimitMb(),
143: );
144: }
145:
146: /**
147: * Updates module's settings - saves them to config.json file.
148: *
149: * @param int $UserSpaceLimitMb User space limit setting in Mb.
150: * @return bool
151: */
152: public function UpdateSettings($UserSpaceLimitMb)
153: {
154: Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
155:
156: FilesModule::getInstance()->setConfig('UserSpaceLimitMb', $UserSpaceLimitMb);
157: return (bool) FilesModule::getInstance()->saveModuleConfig();
158: }
159:
160: public function UpdateUsedSpace()
161: {
162: $iResult = 0;
163: Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
164: $oUser = Api::getAuthenticatedUser();
165:
166: if ($oUser) {
167: $iResult = $this->getManager()->getUserSpaceUsed($oUser->PublicId, [\Aurora\System\Enums\FileStorageType::Personal]);
168: $oUser->setExtendedProp(self::GetName() . '::UsedSpace', $iResult);
169: $oUser->save();
170: }
171:
172: return $iResult;
173: }
174:
175: /**
176: * Returns Min module decorator.
177: *
178: * @return \Aurora\System\Module\Decorator
179: */
180: private function getMinModuleDecorator()
181: {
182: return Api::GetModuleDecorator('Min');
183: }
184:
185: /**
186: * Checks if storage type is personal.
187: *
188: * @param string $Type Storage type.
189: * @return bool
190: */
191: protected function checkStorageType($Type)
192: {
193: return $Type === static::$sStorageType;
194: }
195:
196: /**
197: * Returns HTML title for specified URL.
198: * @param string $sUrl
199: * @return string
200: */
201: protected function getHtmlTitle($sUrl)
202: {
203: $oCurl = curl_init();
204: \curl_setopt_array($oCurl, array(
205: CURLOPT_URL => $sUrl,
206: CURLOPT_FOLLOWLOCATION => true,
207: CURLOPT_ENCODING => '',
208: CURLOPT_RETURNTRANSFER => true,
209: CURLOPT_AUTOREFERER => true,
210: CURLOPT_SSL_VERIFYPEER => false, //required for https urls
211: CURLOPT_CONNECTTIMEOUT => 5,
212: CURLOPT_TIMEOUT => 5,
213: CURLOPT_MAXREDIRS => 5
214: ));
215: $sContent = curl_exec($oCurl);
216: //$aInfo = curl_getinfo($oCurl);
217: curl_close($oCurl);
218:
219: preg_match('/<title>(.*?)<\/title>/s', $sContent, $aTitle);
220: return isset($aTitle['1']) ? trim($aTitle['1']) : '';
221: }
222:
223: /**
224: * Checks if file name is allowed.
225: *
226: * @param string $FileName
227: *
228: * @return bool
229: */
230: protected function isFileAllowed($FileName)
231: {
232: $forbiddenFileList = [
233: '.sabredav'
234: ];
235:
236: $FileName = \trim(\MailSo\Base\Utils::ClearFileName($FileName));
237:
238: $result = !in_array($FileName, $forbiddenFileList);
239:
240: return $result;
241: }
242:
243: /**
244: * Puts file content to $mResult.
245: * @ignore
246: * @param array $aArgs Arguments of event.
247: * @param mixed $mResult Is passed by reference.
248: */
249: public function onGetFile($aArgs, &$mResult)
250: {
251: if ($this->checkStorageType($aArgs['Type'])) {
252: $UserId = $aArgs['UserId'];
253:
254: $sUserPiblicId = Api::getUserPublicIdById($UserId);
255: $iOffset = isset($aArgs['Offset']) ? $aArgs['Offset'] : 0;
256: $iChunkSizet = isset($aArgs['ChunkSize']) ? $aArgs['ChunkSize'] : 0;
257:
258: try {
259: $mResult = $this->getManager()->getFile($sUserPiblicId, $aArgs['Type'], $aArgs['Path'], $aArgs['Id'], $iOffset, $iChunkSizet);
260: } catch (\Sabre\DAV\Exception\NotFound $oEx) {
261: $mResult = false;
262: // echo(\Aurora\System\Managers\Response::GetJsonFromObject('Json', \Aurora\System\Managers\Response::FalseResponse(__METHOD__, 404, 'Not Found')));
263: $this->oHttp->StatusHeader(404);
264: exit;
265: }
266:
267: return true;
268: }
269: }
270:
271: /**
272: * Creates file.
273: * @ignore
274: * @param array $aArgs Arguments of event.
275: * @param mixed $mResult Is passed by reference.
276: */
277: public function onCreateFile($aArgs, &$mResult)
278: {
279: if ($this->checkStorageType($aArgs['Type'])) {
280: if (!self::isFileAllowed($aArgs['Name'])) {
281: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::CanNotUploadFileErrorData);
282: }
283:
284: $UserId = $aArgs['UserId'];
285: Api::CheckAccess($UserId);
286:
287: $currentUser = Server::getUser();
288: Server::setUser(Api::getUserPublicIdById($UserId));
289: $mResult = $this->getManager()->createFile(
290: Api::getUserPublicIdById($UserId),
291: isset($aArgs['Type']) ? $aArgs['Type'] : null,
292: isset($aArgs['Path']) ? $aArgs['Path'] : null,
293: isset($aArgs['Name']) ? $aArgs['Name'] : null,
294: isset($aArgs['Data']) ? $aArgs['Data'] : null,
295: isset($aArgs['Overwrite']) ? $aArgs['Overwrite'] : null,
296: isset($aArgs['RangeType']) ? $aArgs['RangeType'] : null,
297: isset($aArgs['Offset']) ? $aArgs['Offset'] : null,
298: isset($aArgs['ExtendedProps']) ? $aArgs['ExtendedProps'] : null
299: );
300: Server::setUser($currentUser);
301:
302: self::Decorator()->UpdateUsedSpace();
303: return true;
304: }
305: }
306:
307: /**
308: * @ignore
309: * @param array $aArgs Arguments of event.
310: * @param mixed $mResult Is passed by reference.
311: */
312: public function onGetLinkType($aArgs, &$mResult)
313: {
314: $mResult = '';
315: }
316:
317: /**
318: * @ignore
319: * @param array $aArgs Arguments of event.
320: * @param mixed $mResult Is passed by reference.
321: */
322: public function onCheckUrl($aArgs, &$mResult)
323: {
324: $iUserId = Api::getAuthenticatedUserId();
325:
326: if ($iUserId) {
327: if (!empty($aArgs['Url'])) {
328: $sUrl = $aArgs['Url'];
329: if ($sUrl) {
330: $aRemoteFileInfo = \Aurora\System\Utils::GetRemoteFileInfo($sUrl);
331: if ((int)$aRemoteFileInfo['code'] > 0) {
332: $sFileName = basename($sUrl);
333: $sFileExtension = \Aurora\System\Utils::GetFileExtension($sFileName);
334:
335: if (empty($sFileExtension)) {
336: $sFileExtension = \Aurora\System\Utils::GetFileExtensionFromMimeContentType($aRemoteFileInfo['content-type']);
337: $sFileName .= '.' . $sFileExtension;
338: }
339:
340: if ($sFileExtension === 'htm' || $sFileExtension === 'html') {
341: $sTitle = $this->getHtmlTitle($sUrl);
342: }
343:
344: $mResult['Name'] = isset($sTitle) && strlen($sTitle) > 0 ? $sTitle : urldecode($sFileName);
345: $mResult['Size'] = $aRemoteFileInfo['size'];
346: }
347: }
348: }
349: }
350: }
351:
352: /**
353: * @ignore
354: * @param array $aArgs Arguments of event.
355: * @param \Aurora\Modules\Files\Classes\FileItem $oItem
356: * @return bool
357: */
358: public function onAfterPopulateFileItem($aArgs, &$oItem)
359: {
360: if ($oItem->IsLink) {
361: $sFileName = basename($oItem->LinkUrl);
362: $sFileExtension = \Aurora\System\Utils::GetFileExtension($sFileName);
363: if ($sFileExtension === 'htm' || $sFileExtension === 'html') {
364: // $oItem->Name = $this->getHtmlTitle($oItem->LinkUrl);
365: return true;
366: }
367: }
368:
369: return false;
370: }
371:
372: /**
373: * @ignore
374: * @param array $aArgs Arguments of event.
375: * @param mixed $mResult Is passed by reference.
376: */
377: public function onBeforeDeleteUser($aArgs, &$mResult)
378: {
379: if (isset($aArgs['UserId'])) {
380: $this->oBeforeDeleteUser = Api::getUserById($aArgs['UserId']);
381: if ($this->oBeforeDeleteUser) {
382: $this->oBeforeDeleteUserRootPath = $this->getManager()->oStorage->getRootPath($this->oBeforeDeleteUser->PublicId, \Aurora\System\Enums\FileStorageType::Personal, true);
383: }
384: }
385: }
386:
387: /**
388: * @ignore
389: * @param array $aArgs Arguments of event.
390: * @param mixed $mResult Is passed by reference.
391: */
392: public function onAfterDeleteUser($aArgs, $mResult)
393: {
394: if ($mResult && !empty($this->oBeforeDeleteUserRootPath)) {
395: \Aurora\System\Utils::RecRmdir($this->oBeforeDeleteUserRootPath);
396: $this->oBeforeDeleteUserRootPath = '';
397: }
398: }
399:
400: /**
401: * @ignore
402: * @param array $aArgs Arguments of event.
403: * @param mixed $mResult Is passed by reference.
404: */
405: public function onAfterGetStorages($aArgs, &$mResult)
406: {
407: array_unshift($mResult, [
408: 'Type' => static::$sStorageType,
409: 'DisplayName' => $this->i18N('LABEL_STORAGE'),
410: 'IsExternal' => false,
411: 'Order' => static::$iStorageOrder,
412: 'IsDroppable' => static::$bIsDroppable
413: ]);
414: }
415:
416: /**
417: * @ignore
418: * @param array $aArgs Arguments of event.
419: * @param mixed $mResult Is passed by reference.
420: */
421: public function onAfterGetSubModules($aArgs, &$mResult)
422: {
423: array_unshift($mResult, 'local.' . static::$sStorageType);
424: }
425:
426: /**
427: * @ignore
428: * @param array $aArgs Arguments of event.
429: * @param mixed $mResult Is passed by reference.
430: */
431: public function onGetItems($aArgs, &$mResult)
432: {
433: if ($this->checkStorageType($aArgs['Type'])) {
434: $UserId = $aArgs['UserId'];
435: Api::CheckAccess($UserId);
436:
437: $sUserPiblicId = Api::getUserPublicIdById($UserId);
438: $sHash = isset($aArgs['PublicHash']) ? $aArgs['PublicHash'] : null;
439: $bIsShared = isset($aArgs['Shared']) ? !!$aArgs['Shared'] : false;
440: $mResult = array_merge(
441: $mResult,
442: $this->getManager()->getFiles(
443: $sUserPiblicId,
444: $aArgs['Type'],
445: $aArgs['Path'],
446: $aArgs['Pattern'],
447: $sHash,
448: $bIsShared
449: )
450: );
451: }
452: }
453:
454: /**
455: * @ignore
456: * @param array $aArgs Arguments of event.
457: * @param mixed $mResult Is passed by reference.
458: */
459: public function onAfterGetFileContent($aArgs, &$mResult)
460: {
461: $UserId = $aArgs['UserId'];
462: Api::CheckAccess($UserId);
463:
464: $sUUID = Api::getUserPublicIdById($UserId);
465: $Type = $aArgs['Type'];
466: $Path = $aArgs['Path'];
467: $Name = $aArgs['Name'];
468:
469: $mFile = $this->getManager()->getFile($sUUID, $Type, $Path, $Name);
470: if (is_resource($mFile)) {
471: $mResult = stream_get_contents($mFile);
472: }
473: }
474:
475: /**
476: * @ignore
477: * @param array $aArgs Arguments of event.
478: * @param mixed $mResult Is passed by reference.
479: */
480: public function onAfterGetFileInfo($aArgs, &$mResult)
481: {
482: if ($this->checkStorageType($aArgs['Type'])) {
483: $UserId = $aArgs['UserId'];
484: Api::CheckAccess($UserId);
485:
486: $sUserPiblicId = Api::getUserPublicIdById($UserId);
487: $mResult = $this->getManager()->getFileInfo($sUserPiblicId, $aArgs['Type'], $aArgs['Path'], $aArgs['Id']);
488:
489: // return true;
490: }
491: }
492:
493: /**
494: * @ignore
495: * @param array $aArgs Arguments of event.
496: * @param mixed $mResult Is passed by reference.
497: */
498: public function onAfterCreateFolder(&$aArgs, &$mResult)
499: {
500: $UserId = $aArgs['UserId'];
501: Api::CheckAccess($UserId);
502: $sUserPiblicId = Api::getUserPublicIdById($UserId);
503: if ($this->checkStorageType($aArgs['Type'])) {
504: $mResult = $this->getManager()->createFolder($sUserPiblicId, $aArgs['Type'], $aArgs['Path'], $aArgs['FolderName']);
505: return true;
506: }
507: }
508:
509: /**
510: * @ignore
511: * @param array $aArgs Arguments of event.
512: * @param mixed $mResult Is passed by reference.
513: */
514: public function onAfterCreateLink($aArgs, &$mResult)
515: {
516: Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
517:
518: if ($this->checkStorageType($aArgs['Type'])) {
519: $Type = $aArgs['Type'];
520: $UserId = $aArgs['UserId'];
521: $Path = $aArgs['Path'];
522: $Name = $aArgs['Name'];
523: $Link = $aArgs['Link'];
524:
525: Api::CheckAccess($UserId);
526:
527: if (substr($Link, 0, 11) === 'javascript:') {
528: $Link = substr($Link, 11);
529: }
530:
531: $sUserPiblicId = Api::getUserPublicIdById($UserId);
532: if ($this->checkStorageType($Type)) {
533: $Name = \trim(\MailSo\Base\Utils::ClearFileName($Name));
534: $mResult = $this->getManager()->createLink($sUserPiblicId, $Type, $Path, $Link, $Name);
535: self::Decorator()->UpdateUsedSpace();
536: }
537: }
538: }
539:
540: /**
541: * @ignore
542: * @param array $aArgs Arguments of event.
543: * @param mixed $mResult Is passed by reference.
544: */
545: public function onAfterDelete(&$aArgs, &$mResult)
546: {
547: $UserId = $aArgs['UserId'];
548: Api::CheckAccess($UserId);
549: if ($this->checkStorageType($aArgs['Type'])) {
550: $mResult = false;
551:
552: foreach ($aArgs['Items'] as $aItem) {
553: try {
554: $oNode = Server::getNodeForPath(Constants::FILESTORAGE_PATH_ROOT . '/' . $aArgs['Type'] . '/' . $aItem['Path'] . '/' . $aItem['Name']);
555: } catch (\Exception $oEx) {
556: Api::LogException($oEx);
557: throw new ApiException(ErrorCodes::NotFound, $oEx, "Node not found");
558: }
559:
560: if (!$oNode) {
561: throw new ApiException(ErrorCodes::NotFound, null, "Node not found");
562: }
563:
564: if ($oNode instanceof \Afterlogic\DAV\FS\Shared\File || $oNode instanceof \Afterlogic\DAV\FS\Shared\Directory) {
565: if (!$oNode->isInherited()) {
566: throw new ApiException(ErrorCodes::CantDeleteSharedItem);
567: }
568: }
569:
570: \Afterlogic\DAV\Server::deleteNode('files/' . $aArgs['Type'] . '/' . $aItem['Path'] . '/' . $aItem['Name']);
571:
572: $oItem = new FileItem();
573: $oItem->Id = $aItem['Name'];
574: $oItem->Name = $aItem['Name'];
575: $oItem->TypeStr = $aArgs['Type'];
576: $oItem->Path = $aItem['Path'];
577:
578: FilesModule::Decorator()->DeletePublicLink($UserId, $aArgs['Type'], $aItem['Path'], $aItem['Name']);
579: \Aurora\System\Managers\Thumb::RemoveFromCache($UserId, $oItem->getHash(), $aItem['Name']);
580:
581: $mResult = true;
582: }
583:
584: self::Decorator()->UpdateUsedSpace();
585: }
586: }
587:
588: /**
589: * @ignore
590: * @param array $aArgs Arguments of event.
591: * @param mixed $mResult Is passed by reference.
592: */
593: public function onAfterRename(&$aArgs, &$mResult)
594: {
595: if ($this->checkStorageType($aArgs['Type'])) {
596: $UserId = $aArgs['UserId'];
597: Api::CheckAccess($UserId);
598:
599: if (!self::isFileAllowed($aArgs['Name'])) {
600: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::CanNotUploadFileErrorData);
601: }
602:
603: $sUserPiblicId = Api::getUserPublicIdById($UserId);
604: $sNewName = \trim(\MailSo\Base\Utils::ClearFileName($aArgs['NewName']));
605:
606: // $sNewName = $this->getManager()->getNonExistentFileName($sUserPiblicId, $aArgs['Type'], $aArgs['Path'], $sNewName);
607: $bIsLink = isset($aArgs['IsLink']) ? $aArgs['IsLink'] : false;
608: $mResult = $this->getManager()->rename($sUserPiblicId, $aArgs['Type'], $aArgs['Path'], $aArgs['Name'], $sNewName, $bIsLink);
609: }
610: }
611:
612: /**
613: * @ignore
614: * @param array $aArgs Arguments of event.
615: * @param mixed $mResult Is passed by reference.
616: */
617: public function onAfterCopy(&$aArgs, &$mResult)
618: {
619: $UserId = $aArgs['UserId'];
620: Api::CheckAccess($UserId);
621:
622: $sUserPiblicId = Api::getUserPublicIdById($UserId);
623:
624: if ($this->checkStorageType($aArgs['FromType'])) {
625: foreach ($aArgs['Files'] as $aItem) {
626: $bFolderIntoItself = isset($aItem['IsFolder']) && $aItem['IsFolder'] && $aArgs['ToPath'] === $aItem['FromPath'] . '/' . $aItem['Name'];
627: if (!$bFolderIntoItself) {
628: $sNewName = isset($aItem['NewName']) ? $aItem['NewName'] : $aItem['Name'];
629: $mResult = $this->getManager()->copy(
630: $sUserPiblicId,
631: $aItem['FromType'],
632: $aArgs['ToType'],
633: $aItem['FromPath'],
634: $aArgs['ToPath'],
635: $aItem['Name'],
636: $this->getManager()->getNonExistentFileName(
637: $sUserPiblicId,
638: $aArgs['ToType'],
639: $aArgs['ToPath'],
640: $sNewName
641: )
642: );
643: }
644: }
645: self::Decorator()->UpdateUsedSpace();
646: }
647: }
648:
649: /**
650: * @ignore
651: * @param array $aArgs Arguments of event.
652: * @param mixed $mResult Is passed by reference.
653: */
654: public function onAfterMove(&$aArgs, &$mResult)
655: {
656: if ($this->checkStorageType($aArgs['FromType'])) {
657: $UserId = $aArgs['UserId'];
658: Api::CheckAccess($UserId);
659:
660: $sUserPiblicId = Api::getUserPublicIdById($UserId);
661: foreach ($aArgs['Files'] as $key => $aItem) {
662: $bIntoItself = $aArgs['ToType'] . '/' . $aArgs['ToPath'] === $aItem['FromType'] . '/' . $aItem['FromPath'];
663:
664: if (!$bIntoItself) {
665: $sNewName = isset($aItem['NewName']) ? $aItem['NewName'] : $aItem['Name'];
666:
667: if (!self::IsFileAllowed(basename($sNewName))) {
668: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::CanNotUploadFileErrorData);
669: }
670:
671: $sNewName = $this->getManager()->getNonExistentFileName(
672: $sUserPiblicId,
673: $aArgs['ToType'],
674: $aArgs['ToPath'],
675: $sNewName
676: );
677: $aArgs['Files'][$key]['NewName'] = $sNewName;
678: $mResult = $this->getManager()->copy(
679: $sUserPiblicId,
680: $aItem['FromType'],
681: $aArgs['ToType'],
682: $aItem['FromPath'],
683: $aArgs['ToPath'],
684: $aItem['Name'],
685: $sNewName,
686: true
687: );
688: } else {
689: throw new ApiException(ErrorCodes::CannotCopyOrMoveItemToItself);
690: }
691: }
692:
693: self::Decorator()->UpdateUsedSpace();
694: }
695: }
696:
697: protected function getUserSpaceLimitMb()
698: {
699: $iSpaceLimitMb = FilesModule::getInstance()->oModuleSettings->UserSpaceLimitMb;
700:
701: $iUserId = Api::getAuthenticatedUserId();
702: $oUser = CoreModule::Decorator()->GetUserWithoutRoleCheck($iUserId);
703:
704: if ($oUser) {
705: $iSpaceLimitMb = $oUser->getExtendedProp('Files::UserSpaceLimitMb');
706: }
707:
708: $aArgs = [
709: 'UserId' => $iUserId
710: ];
711: $this->broadcastEvent(
712: 'GetUserSpaceLimitMb',
713: $aArgs,
714: $iSpaceLimitMb
715: );
716: return $iSpaceLimitMb;
717: }
718:
719: /**
720: * @ignore
721: * @param array $aArgs Arguments of event.
722: * @param mixed $mResult Is passed by reference.
723: */
724: public function onAfterGetQuota($aArgs, &$mResult)
725: {
726: if ($this->checkStorageType($aArgs['Type'])) {
727: $iSize = 0;
728:
729: $oUser = CoreModule::Decorator()->GetUserWithoutRoleCheck($aArgs['UserId']);
730:
731: if ($oUser) {
732: $iSize = null !== $oUser->getExtendedProp(self::GetName() . '::UsedSpace') ? $oUser->getExtendedProp(self::GetName() . '::UsedSpace') : 0;
733: }
734:
735: $mResult = array(
736: 'Used' => (int) $iSize,
737: 'Limit' => $this->getUserSpaceLimitMb() * 1024 * 1024
738: );
739: }
740: }
741:
742: /**
743: * Creates public link for file or folder.
744: * @ignore
745: * @param array $aArgs Arguments of event.
746: * @param mixed $mResult Is passed by reference.
747: */
748: public function onAfterCreatePublicLink($aArgs, &$mResult)
749: {
750: if ($this->checkStorageType($aArgs['Type'])) {
751: $UserId = (int) $aArgs['UserId'];
752: $Type = $aArgs['Type'];
753: $Path = $aArgs['Path'];
754: $Name = $aArgs['Name'];
755: $Size = $aArgs['Size'];
756: $IsFolder = $aArgs['IsFolder'];
757:
758: Api::CheckAccess($UserId);
759:
760: Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
761: $sUserPiblicId = Api::getUserPublicIdById($UserId);
762: $bFolder = (bool) $IsFolder;
763: $mResult = $this->getManager()->createPublicLink($sUserPiblicId, $Type, $Path, $Name, $Size, $bFolder);
764: self::Decorator()->UpdateUsedSpace();
765: }
766: }
767:
768: /**
769: * Deletes public link from file or folder.
770: * @ignore
771: * @param array $aArgs Arguments of event.
772: * @param mixed $mResult Is passed by reference.
773: */
774: public function onAfterDeletePublicLink($aArgs, &$mResult)
775: {
776: if ($this->checkStorageType($aArgs['Type'])) {
777: $UserId = $aArgs['UserId'];
778: $Type = $aArgs['Type'];
779: $Path = $aArgs['Path'];
780: $Name = $aArgs['Name'];
781:
782: Api::CheckAccess($UserId);
783:
784: Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
785:
786: $sUserPiblicId = Api::getUserPublicIdById($UserId);
787:
788: $mResult = $this->getManager()->deletePublicLink($sUserPiblicId, $Type, $Path, $Name);
789: self::Decorator()->UpdateUsedSpace();
790: }
791: }
792:
793: /**
794: * @ignore
795: * @param array $aArgs Arguments of event.
796: * @param mixed $mResult Is passed by reference.
797: */
798: public function onAfterIsFileExists($aArgs, &$mResult)
799: {
800: if (isset($aArgs['Type']) && $this->checkStorageType($aArgs['Type'])) {
801: $UserId = $aArgs['UserId'];
802: Api::CheckAccess($UserId);
803:
804: $mResult = $this->getManager()->isFileExists(
805: Api::getUserPublicIdById($UserId),
806: $aArgs['Type'],
807: $aArgs['Path'],
808: $aArgs['Name']
809: );
810: }
811: }
812:
813: /**
814: * @ignore
815: * @param array $aArgs Arguments of event.
816: * @param mixed $mResult Is passed by reference.
817: */
818: public function onAfterCheckQuota($aArgs, &$mResult)
819: {
820: $Type = $aArgs['Type'];
821: if ($this->checkStorageType($Type)) {
822: $sUserId = $aArgs['UserId'];
823: $iSize = (int) $aArgs['Size'];
824: $aQuota = FilesModule::Decorator()->GetQuota($sUserId, $Type);
825: $Limit = (int) $aQuota['Limit'];
826: $Used = (int) $aQuota['Used'];
827: $mResult = !($Limit > 0 && $Used + $iSize > $Limit);
828: return true;
829: }
830: }
831:
832: /**
833: * @ignore
834: * @param array $aArgs Arguments of event.
835: * @param mixed $mResult Is passed by reference.
836: */
837: public function onAfterUpdateExtendedProps(&$aArgs, &$mResult)
838: {
839: if ($this->checkStorageType($aArgs['Type'])) {
840: $UserId = $aArgs['UserId'];
841: Api::CheckAccess($UserId);
842: $sUserPiblicId = Api::getUserPublicIdById($UserId);
843: $mResult = $this->getManager()->updateExtendedProps(
844: $sUserPiblicId,
845: $aArgs['Type'],
846: $aArgs['Path'],
847: $aArgs['Name'],
848: $aArgs['ExtendedProps']
849: );
850: }
851: }
852:
853: /**
854: * @ignore
855: * @param array $aArgs Arguments of event.
856: * @param mixed $mResult Is passed by reference.
857: */
858: public function onAfterGetExtendedProps(&$aArgs, &$mResult)
859: {
860: $bCorrectArgs = isset($aArgs['Type']) && isset($aArgs['Path']) && isset($aArgs['Name']);
861: if ($bCorrectArgs && $this->checkStorageType($aArgs['Type'])) {
862: $UserId = $aArgs['UserId'];
863: Api::CheckAccess($UserId);
864: $sUserPiblicId = Api::getUserPublicIdById($UserId);
865: $mResult = $this->getManager()->getExtendedProps(
866: $sUserPiblicId,
867: $aArgs['Type'],
868: $aArgs['Path'],
869: $aArgs['Name']
870: );
871: }
872: }
873:
874: public function onAfterGetNonExistentFileName(&$aArgs, &$mResult)
875: {
876: if ($this->checkStorageType($aArgs['Type'])) {
877: $UserId = $aArgs['UserId'];
878: Api::CheckAccess($UserId);
879: $sUserPiblicId = Api::getUserPublicIdById($UserId);
880: $mResult = $this->getManager()->getNonExistentFileName(
881: $sUserPiblicId,
882: $aArgs['Type'],
883: $aArgs['Path'],
884: $aArgs['Name'],
885: $aArgs['WithoutGroup']
886: );
887: }
888: }
889:
890: public function onAfterGetAccessInfoForPath(&$aArgs, &$mResult)
891: {
892: if ($this->checkStorageType($aArgs['Type'])) {
893: $UserId = $aArgs['UserId'];
894: Api::CheckAccess($UserId);
895: $sUserPiblicId = Api::getUserPublicIdById($UserId);
896:
897: $aItems = [];
898: $sPath = $aArgs['Path'];
899: $aPaths = explode('/', $sPath);
900: do {
901: $oNode = Server::getNodeForPath('files/' . $aArgs['Type'] . '/' . $sPath, $sUserPiblicId);
902: if (!empty($sPath) && ($oNode instanceof SharedFile || $oNode instanceof SharedDirectory)) {
903: $aItems[$sPath] = $oNode->getAccess();
904: }
905: array_pop($aPaths);
906: $sPath = implode('/', $aPaths);
907: } while (count($aPaths) > 0);
908: $mResult = $aItems;
909: }
910: }
911: }
912: