Storage.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\Files_Trashbin;
  8. use OC\Files\Filesystem;
  9. use OC\Files\Storage\Wrapper\Wrapper;
  10. use OCA\Files_Trashbin\Events\MoveToTrashEvent;
  11. use OCA\Files_Trashbin\Trash\ITrashManager;
  12. use OCP\Encryption\Exceptions\GenericEncryptionException;
  13. use OCP\EventDispatcher\IEventDispatcher;
  14. use OCP\Files\IRootFolder;
  15. use OCP\Files\Node;
  16. use OCP\Files\Storage\IStorage;
  17. use OCP\IUserManager;
  18. use Psr\Log\LoggerInterface;
  19. class Storage extends Wrapper {
  20. private string $mountPoint;
  21. private IUserManager$userManager;
  22. private LoggerInterface $logger;
  23. private IEventDispatcher $eventDispatcher;
  24. private IRootFolder $rootFolder;
  25. private ITrashManager $trashManager;
  26. private bool $trashEnabled = true;
  27. /**
  28. * Storage constructor.
  29. *
  30. * @param array $parameters
  31. * @param ITrashManager|null $trashManager
  32. * @param IUserManager|null $userManager
  33. * @param LoggerInterface|null $logger
  34. * @param IEventDispatcher|null $eventDispatcher
  35. * @param IRootFolder|null $rootFolder
  36. */
  37. public function __construct(
  38. $parameters,
  39. ?ITrashManager $trashManager = null,
  40. ?IUserManager $userManager = null,
  41. ?LoggerInterface $logger = null,
  42. ?IEventDispatcher $eventDispatcher = null,
  43. ?IRootFolder $rootFolder = null
  44. ) {
  45. $this->mountPoint = $parameters['mountPoint'];
  46. $this->trashManager = $trashManager;
  47. $this->userManager = $userManager;
  48. $this->logger = $logger;
  49. $this->eventDispatcher = $eventDispatcher;
  50. $this->rootFolder = $rootFolder;
  51. parent::__construct($parameters);
  52. }
  53. /**
  54. * Deletes the given file by moving it into the trashbin.
  55. *
  56. * @param string $path path of file or folder to delete
  57. *
  58. * @return bool true if the operation succeeded, false otherwise
  59. */
  60. public function unlink($path) {
  61. if ($this->trashEnabled) {
  62. try {
  63. return $this->doDelete($path, 'unlink');
  64. } catch (GenericEncryptionException $e) {
  65. // in case of a encryption exception we delete the file right away
  66. $this->logger->info(
  67. "Can't move file " . $path .
  68. " to the trash bin, therefore it was deleted right away");
  69. return $this->storage->unlink($path);
  70. }
  71. } else {
  72. return $this->storage->unlink($path);
  73. }
  74. }
  75. /**
  76. * Deletes the given folder by moving it into the trashbin.
  77. *
  78. * @param string $path path of folder to delete
  79. *
  80. * @return bool true if the operation succeeded, false otherwise
  81. */
  82. public function rmdir($path) {
  83. if ($this->trashEnabled) {
  84. return $this->doDelete($path, 'rmdir');
  85. } else {
  86. return $this->storage->rmdir($path);
  87. }
  88. }
  89. /**
  90. * check if it is a file located in data/user/files only files in the
  91. * 'files' directory should be moved to the trash
  92. *
  93. * @param $path
  94. * @return bool
  95. */
  96. protected function shouldMoveToTrash($path) {
  97. $normalized = Filesystem::normalizePath($this->mountPoint . '/' . $path);
  98. $parts = explode('/', $normalized);
  99. if (count($parts) < 4 || strpos($normalized, '/appdata_') === 0) {
  100. return false;
  101. }
  102. // check if there is a app which want to disable the trash bin for this file
  103. $fileId = $this->storage->getCache()->getId($path);
  104. $owner = $this->storage->getOwner($path);
  105. if ($owner === false || $this->storage->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class)) {
  106. $nodes = $this->rootFolder->getById($fileId);
  107. } else {
  108. $nodes = $this->rootFolder->getUserFolder($owner)->getById($fileId);
  109. }
  110. foreach ($nodes as $node) {
  111. $event = $this->createMoveToTrashEvent($node);
  112. $this->eventDispatcher->dispatchTyped($event);
  113. $this->eventDispatcher->dispatch('OCA\Files_Trashbin::moveToTrash', $event);
  114. if ($event->shouldMoveToTrashBin() === false) {
  115. return false;
  116. }
  117. }
  118. if ($parts[2] === 'files' && $this->userManager->userExists($parts[1])) {
  119. return true;
  120. }
  121. return false;
  122. }
  123. /**
  124. * get move to trash event
  125. *
  126. * @param Node $node
  127. * @return MoveToTrashEvent
  128. */
  129. protected function createMoveToTrashEvent(Node $node) {
  130. return new MoveToTrashEvent($node);
  131. }
  132. /**
  133. * Run the delete operation with the given method
  134. *
  135. * @param string $path path of file or folder to delete
  136. * @param string $method either "unlink" or "rmdir"
  137. *
  138. * @return bool true if the operation succeeded, false otherwise
  139. */
  140. private function doDelete($path, $method) {
  141. if (
  142. !\OC::$server->getAppManager()->isEnabledForUser('files_trashbin')
  143. || (pathinfo($path, PATHINFO_EXTENSION) === 'part')
  144. || $this->shouldMoveToTrash($path) === false
  145. ) {
  146. return call_user_func([$this->storage, $method], $path);
  147. }
  148. // check permissions before we continue, this is especially important for
  149. // shared files
  150. if (!$this->isDeletable($path)) {
  151. return false;
  152. }
  153. $isMovedToTrash = $this->trashManager->moveToTrash($this, $path);
  154. if (!$isMovedToTrash) {
  155. return call_user_func([$this->storage, $method], $path);
  156. } else {
  157. return true;
  158. }
  159. }
  160. /**
  161. * Setup the storage wrapper callback
  162. */
  163. public static function setupStorage() {
  164. $trashManager = \OC::$server->get(ITrashManager::class);
  165. $userManager = \OC::$server->get(IUserManager::class);
  166. $logger = \OC::$server->get(LoggerInterface::class);
  167. $eventDispatcher = \OC::$server->get(IEventDispatcher::class);
  168. $rootFolder = \OC::$server->get(IRootFolder::class);
  169. Filesystem::addStorageWrapper(
  170. 'oc_trashbin',
  171. function (string $mountPoint, IStorage $storage) use ($trashManager, $userManager, $logger, $eventDispatcher, $rootFolder) {
  172. return new Storage(
  173. ['storage' => $storage, 'mountPoint' => $mountPoint],
  174. $trashManager,
  175. $userManager,
  176. $logger,
  177. $eventDispatcher,
  178. $rootFolder,
  179. );
  180. },
  181. 1);
  182. }
  183. public function getMountPoint() {
  184. return $this->mountPoint;
  185. }
  186. public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
  187. $sourceIsTrashbin = $sourceStorage->instanceOfStorage(Storage::class);
  188. try {
  189. // the fallback for moving between storage involves a copy+delete
  190. // we don't want to trigger the trashbin when doing the delete
  191. if ($sourceIsTrashbin) {
  192. /** @var Storage $sourceStorage */
  193. $sourceStorage->disableTrash();
  194. }
  195. $result = parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
  196. if ($sourceIsTrashbin) {
  197. /** @var Storage $sourceStorage */
  198. $sourceStorage->enableTrash();
  199. }
  200. return $result;
  201. } catch (\Exception $e) {
  202. if ($sourceIsTrashbin) {
  203. /** @var Storage $sourceStorage */
  204. $sourceStorage->enableTrash();
  205. }
  206. throw $e;
  207. }
  208. }
  209. protected function disableTrash() {
  210. $this->trashEnabled = false;
  211. }
  212. protected function enableTrash() {
  213. $this->trashEnabled = true;
  214. }
  215. }