diff --git a/appinfo/info.xml b/appinfo/info.xml index c132c16ab..b6edc00d1 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -71,6 +71,17 @@ Have a good time and manage whatever you want. OCA\Tables\Command\CleanLegacy OCA\Tables\Command\TransferLegacyRows + + + OCA\Tables\Activity\Setting + + + OCA\Tables\Activity\Filter + + + OCA\Tables\Activity\Provider + + Tables diff --git a/appinfo/routes.php b/appinfo/routes.php index eebd93b20..03668e695 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -100,13 +100,18 @@ // import ['name' => 'import#previewImportTable', 'url' => '/import-preview/table/{tableId}', 'verb' => 'POST'], - ['name' => 'import#importInTable', 'url' => '/import/table/{tableId}', 'verb' => 'POST'], + ['name' => 'import#scheduleImportInTable', 'url' => '/import/table/{tableId}/jobs', 'verb' => 'POST'], + ['name' => 'import#scheduleImportInView', 'url' => '/import/view/{viewId}/jobs', 'verb' => 'POST'], ['name' => 'import#previewImportView', 'url' => '/import-preview/view/{viewId}', 'verb' => 'POST'], - ['name' => 'import#importInView', 'url' => '/import/view/{viewId}', 'verb' => 'POST'], ['name' => 'import#previewUploadImportTable', 'url' => '/importupload-preview/table/{tableId}', 'verb' => 'POST'], - ['name' => 'import#importUploadInTable', 'url' => '/importupload/table/{tableId}', 'verb' => 'POST'], + ['name' => 'import#scheduleImportUploadInTable', 'url' => '/importupload/table/{tableId}/jobs', 'verb' => 'POST'], ['name' => 'import#previewUploadImportView', 'url' => '/importupload-preview/view/{viewId}', 'verb' => 'POST'], + ['name' => 'import#scheduleImportUploadInView', 'url' => '/importupload/view/{viewId}/jobs', 'verb' => 'POST'], + // deprecated endpoints + ['name' => 'import#importUploadInTable', 'url' => '/importupload/table/{tableId}', 'verb' => 'POST'], ['name' => 'import#importUploadInView', 'url' => '/importupload/view/{viewId}', 'verb' => 'POST'], + ['name' => 'import#importInTable', 'url' => '/import/table/{tableId}', 'verb' => 'POST'], + ['name' => 'import#importInView', 'url' => '/import/view/{viewId}', 'verb' => 'POST'], // search ['name' => 'search#all', 'url' => '/search/all', 'verb' => 'GET'], diff --git a/lib/Activity/ActivityConstants.php b/lib/Activity/ActivityConstants.php new file mode 100644 index 000000000..c198df424 --- /dev/null +++ b/lib/Activity/ActivityConstants.php @@ -0,0 +1,30 @@ +activityManager->generateEvent(); + $activity->setApp(Application::APP_ID) + ->setType(ActivityConstants::TYPE_IMPORT_FINISHED) + ->setAuthor($userId) + ->setObject('table', $tableId) + ->setAffectedUser($userId) + ->setSubject(ActivityConstants::SUBJECT_IMPORT_FINISHED, [ + 'actor' => $userId, + 'tableId' => $tableId, + ]) + ->setMessage(ActivityConstants::MESSAGE_IMPORT_FINISHED, [ + 'actor' => $userId, + 'tableId' => $tableId, + ] + (array)$importStats); + + $this->activityManager->publish($activity); + } +} diff --git a/lib/Activity/Filter.php b/lib/Activity/Filter.php new file mode 100644 index 000000000..4f07d3da6 --- /dev/null +++ b/lib/Activity/Filter.php @@ -0,0 +1,45 @@ +l->t('Tables'); + } + + public function getPriority(): int { + return 40; + } + + public function getIcon(): string { + return $this->url->getAbsoluteURL($this->url->imagePath(Application::APP_ID, 'app-dark.svg')); + } + + public function filterTypes(array $types): array { + return $types; + } + + public function allowedApps(): array { + return [Application::APP_ID]; + } +} diff --git a/lib/Activity/Provider.php b/lib/Activity/Provider.php new file mode 100644 index 000000000..02bcd6e88 --- /dev/null +++ b/lib/Activity/Provider.php @@ -0,0 +1,126 @@ +getApp() !== Application::APP_ID) { + throw new UnknownActivityException(); + } + + $this->l = $this->languageFactory->get(Application::APP_ID, $language); + + if ($event->getSubject() === ActivityConstants::SUBJECT_IMPORT_FINISHED) { + // $event->setParsedMessage($comment->getMessage()) + // ->setRichMessage($message, $mentions); + + $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath(Application::APP_ID, 'app-dark.svg'))); + + if ($this->activityManager->isFormattingFilteredObject()) { + try { + return $this->parseShortVersion($event); + } catch (UnknownActivityException) { + // Ignore and simply use the long version... + } + } + + return $this->parseLongVersion($event); + } + + throw new UnknownActivityException(); + } + + /** + * @throws UnknownActivityException + */ + protected function parseShortVersion(IEvent $event): IEvent { + $subjectParameters = $this->getSubjectParameters($event); + + if ($event->getSubject() === ActivityConstants::SUBJECT_IMPORT_FINISHED) { + $event->setRichSubject($this->l->t('You commented'), []); + } else { + throw new UnknownActivityException(); + } + + return $event; + } + + /** + * @throws UnknownActivityException + */ + protected function parseLongVersion(IEvent $event): IEvent { + $subjectParameters = $this->getSubjectParameters($event); + + if ($event->getSubject() === ActivityConstants::SUBJECT_IMPORT_FINISHED) { + $event->setParsedSubject($this->l->t('You commented on %1$s', [ + $subjectParameters['filePath'], + ])) + ->setRichSubject($this->l->t('You commented on {file}'), [ + 'file' => $this->generateFileParameter($subjectParameters['fileId'], $subjectParameters['filePath']), + ]); + } else { + throw new UnknownActivityException(); + } + + return $event; + } + + protected function getSubjectParameters(IEvent $event): array { + $subjectParameters = $event->getSubjectParameters(); + if (isset($subjectParameters['fileId'])) { + return $subjectParameters; + } + + return [ + 'actor' => $subjectParameters[0], + 'fileId' => $event->getObjectId(), + 'filePath' => trim($subjectParameters[1], '/'), + ]; + } + + /** + * @return array + */ + protected function generateFileParameter(int $id, string $path): array { + return [ + 'type' => 'file', + 'id' => (string)$id, + 'name' => basename($path), + 'path' => $path, + 'link' => $this->url->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $id]), + ]; + } +} diff --git a/lib/Activity/Setting.php b/lib/Activity/Setting.php new file mode 100644 index 000000000..09118bfec --- /dev/null +++ b/lib/Activity/Setting.php @@ -0,0 +1,54 @@ +l->t('Import of a file has finished'); + } + + public function getGroupIdentifier() { + return Application::APP_ID; + } + + public function getGroupName() { + return $this->l->t('Tables'); + } + + public function getPriority(): int { + return 50; + } + + public function canChangeStream(): bool { + return true; + } + + public function isDefaultEnabledStream(): bool { + return true; + } + + public function canChangeMail(): bool { + return true; + } + + public function isDefaultEnabledMail(): bool { + return false; + } +} diff --git a/lib/BackgroundJob/ImportTableJob.php b/lib/BackgroundJob/ImportTableJob.php new file mode 100644 index 000000000..542b6fabc --- /dev/null +++ b/lib/BackgroundJob/ImportTableJob.php @@ -0,0 +1,70 @@ +userSession->getUser(); + + try { + $user = $this->userManager->get($userId); + $this->userSession->setUser($user); + + $importStats = $this->importService + ->importV2( + $userId, + $tableId, + $viewId, + $argument['user_file_path'], + $argument['import_file_name'], + $argument['create_missing_columns'], + $argument['columns_config'] + ); + } finally { + $this->userSession->setUser($oldUser); + } + + if (!$tableId && $viewId) { + $tableId = $this->viewMapper->find($viewId)->getTableId(); + } + + $this->activityManager->notifyImportFinished($userId, $tableId, $importStats); + } +} diff --git a/lib/Controller/ImportController.php b/lib/Controller/ImportController.php index 17fab1a3a..00f77a20a 100644 --- a/lib/Controller/ImportController.php +++ b/lib/Controller/ImportController.php @@ -41,7 +41,6 @@ class ImportController extends Controller { use Errors; - public function __construct( IRequest $request, LoggerInterface $logger, @@ -63,6 +62,9 @@ public function previewImportTable(int $tableId, String $path): DataResponse { }); } + /** + * @deprecated Use {@link scheduleImportInTable} instead + */ #[NoAdminRequired] #[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')] public function importInTable(int $tableId, String $path, bool $createMissingColumns = true, array $columnsConfig = []): DataResponse { @@ -72,6 +74,17 @@ public function importInTable(int $tableId, String $path, bool $createMissingCol }); } + #[NoAdminRequired] + #[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')] + public function scheduleImportInTable(int $tableId, String $path, bool $createMissingColumns = true, array $columnsConfig = []): DataResponse { + return $this->handleError(function () use ($tableId, $path, $createMissingColumns, $columnsConfig) { + // minimal permission is checked, creating columns requires MANAGE permissions - currently tested on service layer + $file = $this->service->scheduleImport($tableId, null, $path, $createMissingColumns, $columnsConfig); + + return new DataResponse(['file' => $file]); + }); + } + #[NoAdminRequired] #[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')] public function previewImportView(int $viewId, String $path): DataResponse { @@ -80,6 +93,9 @@ public function previewImportView(int $viewId, String $path): DataResponse { }); } + /** + * @deprecated Use {@link scheduleImportInView} instead + */ #[NoAdminRequired] #[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')] public function importInView(int $viewId, String $path, bool $createMissingColumns = true, array $columnsConfig = []): DataResponse { @@ -89,6 +105,17 @@ public function importInView(int $viewId, String $path, bool $createMissingColum }); } + #[NoAdminRequired] + #[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')] + public function scheduleImportInView(int $viewId, String $path, bool $createMissingColumns = true, array $columnsConfig = []): DataResponse { + return $this->handleError(function () use ($viewId, $path, $createMissingColumns, $columnsConfig) { + // minimal permission is checked, creating columns requires MANAGE permissions - currently tested on service layer + $file = $this->service->scheduleImport(null, $viewId, $path, $createMissingColumns, $columnsConfig); + + return new DataResponse(['file' => $file]); + }); + } + #[NoAdminRequired] #[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')] public function previewUploadImportTable(int $tableId): DataResponse { @@ -103,6 +130,9 @@ public function previewUploadImportTable(int $tableId): DataResponse { } } + /** + * @deprecated Use {@link scheduleImportUploadInTable} instead + */ #[NoAdminRequired] #[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')] public function importUploadInTable(int $tableId, bool $createMissingColumns = true, string $columnsConfig = ''): DataResponse { @@ -119,6 +149,24 @@ public function importUploadInTable(int $tableId, bool $createMissingColumns = t } } + #[NoAdminRequired] + #[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')] + public function scheduleImportUploadInTable(int $tableId, bool $createMissingColumns = true, string $columnsConfig = ''): DataResponse { + try { + $columnsConfigArray = json_decode($columnsConfig, true); + $file = $this->getUploadedFile('uploadfile'); + return $this->handleError(function () use ($tableId, $file, $createMissingColumns, $columnsConfigArray) { + // minimal permission is checked, creating columns requires MANAGE permissions - currently tested on service layer + $file = $this->service->scheduleImport($tableId, null, $file['tmp_name'], $createMissingColumns, $columnsConfigArray); + + return new DataResponse(['file' => $file]); + }); + } catch (UploadException|NotPermittedException $e) { + $this->logger->error('Upload error', ['exception' => $e]); + return new DataResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST); + } + } + #[NoAdminRequired] #[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')] public function previewUploadImportView(int $viewId): DataResponse { @@ -133,6 +181,9 @@ public function previewUploadImportView(int $viewId): DataResponse { } } + /** + * @deprecated Use {@link scheduleImportUploadInView} instead + */ #[NoAdminRequired] #[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')] public function importUploadInView(int $viewId, bool $createMissingColumns = true, string $columnsConfig = ''): DataResponse { @@ -149,6 +200,24 @@ public function importUploadInView(int $viewId, bool $createMissingColumns = tru } } + #[NoAdminRequired] + #[RequirePermission(permission: Application::PERMISSION_CREATE, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')] + public function scheduleImportUploadInView(int $viewId, bool $createMissingColumns = true, string $columnsConfig = ''): DataResponse { + try { + $columnsConfigArray = json_decode($columnsConfig, true); + $file = $this->getUploadedFile('uploadfile'); + return $this->handleError(function () use ($viewId, $file, $createMissingColumns, $columnsConfigArray) { + // minimal permission is checked, creating columns requires MANAGE permissions - currently tested on service layer + $file = $this->service->scheduleImport(null, $viewId, $file['tmp_name'], $createMissingColumns, $columnsConfigArray); + + return new DataResponse(['file' => $file]); + }); + } catch (UploadException|NotPermittedException $e) { + $this->logger->error('Upload error', ['exception' => $e]); + return new DataResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST); + } + } + /** * @param string $key * @return array diff --git a/lib/Db/Row2Mapper.php b/lib/Db/Row2Mapper.php index 07ebe772e..e03b1898d 100644 --- a/lib/Db/Row2Mapper.php +++ b/lib/Db/Row2Mapper.php @@ -599,7 +599,10 @@ public function isRowInViewPresent(int $rowId, View $view, string $userId): bool * @throws InternalError * @throws Exception */ - public function insert(Row2 $row, array $columns): Row2 { + public function insert(Row2 $row, array $columns, ?string $userId = null): Row2 { + if ($userId) { + $this->userId = $userId; + } $this->setColumns($columns); if ($row->getId()) { diff --git a/lib/Model/ImportStats.php b/lib/Model/ImportStats.php new file mode 100644 index 000000000..aa64886a3 --- /dev/null +++ b/lib/Model/ImportStats.php @@ -0,0 +1,20 @@ +viewService->find($viewId); + $view = $this->viewService->find($viewId, true, $userId); } catch (InternalError|MultipleObjectsReturnedException $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage()); @@ -193,7 +193,7 @@ public function create( throw new InternalError('Cannot create column without table or view in context'); } - if (!$this->permissionsService->canCreateColumns($table)) { + if (!$this->permissionsService->canCreateColumns($table, $userId)) { throw new PermissionError('create column for the table id = ' . $table->getId() . ' is not allowed.'); } @@ -216,7 +216,6 @@ public function create( $i++; } - $time = new DateTime(); $item = Column::fromDto($columnDto); $item->setTitle($newTitle); $item->setTableId($table->getId()); diff --git a/lib/Service/ImportService.php b/lib/Service/ImportService.php index 2bd047064..2a573aacb 100644 --- a/lib/Service/ImportService.php +++ b/lib/Service/ImportService.php @@ -8,18 +8,26 @@ namespace OCA\Tables\Service; use OC\User\NoUserException; +use OCA\Tables\AppInfo\Application; +use OCA\Tables\BackgroundJob\ImportTableJob; use OCA\Tables\Db\Column; use OCA\Tables\Dto\Column as ColumnDto; use OCA\Tables\Errors\InternalError; use OCA\Tables\Errors\NotFoundError; use OCA\Tables\Errors\PermissionError; +use OCA\Tables\Model\ImportStats; use OCA\Tables\Service\ColumnTypes\IColumnTypeBusiness; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; +use OCP\BackgroundJob\IJobList; use OCP\DB\Exception; +use OCP\Files\AppData\IAppDataFactory; +use OCP\Files\IAppData; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\ITempManager; use OCP\IUserManager; use OCP\Server; use PhpOffice\PhpSpreadsheet\Cell\Cell; @@ -43,6 +51,7 @@ class ImportService extends SuperService { private TableService $tableService; private ViewService $viewService; private IUserManager $userManager; + private IAppData $appData; private ?int $tableId = null; private ?int $viewId = null; @@ -58,8 +67,20 @@ class ImportService extends SuperService { private array $rawColumnDataTypes = []; private array $columnsConfig = []; - public function __construct(PermissionsService $permissionsService, LoggerInterface $logger, ?string $userId, - IRootFolder $rootFolder, ColumnService $columnService, RowService $rowService, TableService $tableService, ViewService $viewService, IUserManager $userManager) { + public function __construct( + PermissionsService $permissionsService, + LoggerInterface $logger, + ?string $userId, + IRootFolder $rootFolder, + ColumnService $columnService, + RowService $rowService, + TableService $tableService, + ViewService $viewService, + IUserManager $userManager, + private IJobList $jobList, + private ITempManager $tempManager, + IAppDataFactory $appDataFactory, + ) { parent::__construct($logger, $userId, $permissionsService); $this->rootFolder = $rootFolder; $this->columnService = $columnService; @@ -67,6 +88,7 @@ public function __construct(PermissionsService $permissionsService, LoggerInterf $this->tableService = $tableService; $this->viewService = $viewService; $this->userManager = $userManager; + $this->appData = $appDataFactory->get(Application::APP_ID); } public function previewImport(?int $tableId, ?int $viewId, string $path): array { @@ -220,6 +242,7 @@ private function getPreviewData(Worksheet $worksheet): array { } /** + * @deprecated Use {@link scheduleImport} and {@link importV2} instead. * @param ?int $tableId * @param ?int $viewId * @param string $path @@ -309,6 +332,139 @@ public function import(?int $tableId, ?int $viewId, string $path, bool $createMi ]; } + /** + * @param ?int $tableId + * @param ?int $viewId + * @param string $path + * @param bool $createMissingColumns + * @return string The name of the file in the app data directory or in the user storage + * @throws DoesNotExistException + * @throws InternalError + * @throws MultipleObjectsReturnedException + * @throws NotFoundError + * @throws PermissionError + */ + public function scheduleImport(?int $tableId, ?int $viewId, string $path, bool $createMissingColumns = true, array $columnsConfig = []): string { + if ($viewId !== null) { + $view = $this->viewService->find($viewId); + if (!$this->permissionsService->canCreateRows($view)) { + throw new PermissionError('create row at the view id = ' . $viewId . ' is not allowed.'); + } + if ($createMissingColumns && !$this->permissionsService->canManageTableById($view->getTableId())) { + throw new PermissionError('create columns at the view id = ' . $viewId . ' is not allowed.'); + } + $this->viewId = $viewId; + } + if ($tableId) { + $table = $this->tableService->find($tableId); + if (!$this->permissionsService->canCreateRows($table, 'table')) { + throw new PermissionError('create row at the view id = ' . (string)$viewId . ' is not allowed.'); + } + if ($createMissingColumns && !$this->permissionsService->canManageTable($table)) { + throw new PermissionError('create columns at the view id = ' . (string)$viewId . ' is not allowed.'); + } + $this->tableId = $tableId; + } + if (!$this->tableId && !$this->viewId) { + $e = new \Exception('Neither tableId nor viewId is given.'); + $this->logger->error($e->getMessage(), ['exception' => $e]); + throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage()); + } + if ($this->tableId && $this->viewId) { + $e = new \LogicException('Both table ID and view ID are provided, but only one of them is allowed'); + $this->logger->error($e->getMessage(), ['exception' => $e]); + throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage()); + } + + if ($this->userId === null || $this->userManager->get($this->userId) === null) { + $error = 'No user in context, can not import data. Cancel.'; + $this->logger->debug($error); + throw new InternalError($error); + } + + $importFileName = null; + // uploaded file + if (\file_exists($path)) { + $importFileName = 'import_u-' . $this->userId; + if ($this->tableId) { + $importFileName .= '_t-' . $this->tableId; + } + if ($this->viewId) { + $importFileName .= '_v-' . $this->viewId; + } + $importFileName .= '_' . \date('Y-m-d_H-i-s') . '.' . pathinfo($path, PATHINFO_EXTENSION); + + $this->getImportAppDataDir() + ->newFile($importFileName) + ->putContent(\file_get_contents($path)); + } + + $this->jobList->add( + ImportTableJob::class, + [ + 'user_id' => $this->userId, + 'table_id' => $this->tableId, + 'view_id' => $this->viewId, + 'user_file_path' => $importFileName ? null : $path, + 'import_file_name' => $importFileName, + 'create_missing_columns' => $createMissingColumns, + 'columns_config' => $columnsConfig, + ] + ); + + return $importFileName ?: $path; + } + + /** + * @param string $userId + * @param ?int $tableId + * @param ?int $viewId + * @param ?string $userFilePath Path to a file in the user's storage, if the file is not uploaded + * @param ?string $importFileName Name of the file in the app data directory, if the file is uploaded + * @param bool $createMissingColumns + * @param array $columnsConfig + * @return ImportStats + * @throws DoesNotExistException + * @throws MultipleObjectsReturnedException + * @throws NotFoundError + */ + public function importV2(string $userId, ?int $tableId, ?int $viewId, ?string $userFilePath, ?string $importFileName, bool $createMissingColumns = true, array $columnsConfig = []): ImportStats { + $this->userId = $userId; + $this->tableId = $tableId; + $this->viewId = $viewId; + $this->createUnknownColumns = $createMissingColumns; + $this->columnsConfig = $columnsConfig; + + try { + if ($importFileName) { + $file = $this->getImportAppDataDir()->getFile($importFileName); + $temporaryFile = $this->tempManager->getTemporaryFile('.' . $file->getExtension()); + file_put_contents($temporaryFile, $file->getContent()); + $spreadsheet = IOFactory::load($temporaryFile); + $this->loop($spreadsheet->getActiveSheet()); + } elseif ($userFilePath) { + $file = $this->rootFolder->getUserFolder($this->userId)->get($userFilePath); + $temporaryFile = $file->getStorage()->getLocalFile($file->getInternalPath()); + $spreadsheet = IOFactory::load($temporaryFile); + $this->loop($spreadsheet->getActiveSheet()); + } else { + throw new NotFoundError('No file for import given.'); + } + } catch (NotFoundException|NotPermittedException|NoUserException|InternalError|PermissionError $e) { + $this->logger->warning('Storage for user could not be found', ['exception' => $e]); + throw new NotFoundError('Storage for user could not be found', 0, $e); + } + + return new ImportStats( + count($this->columns), + $this->countMatchingColumns, + $this->countCreatedColumns, + $this->countInsertedRows, + $this->countParsingErrors, + $this->countErrors + ); + } + /** * @param Worksheet $worksheet * @throws DoesNotExistException @@ -427,7 +583,7 @@ private function createRow(Row $row): void { } if ($hasData) { - $this->rowService->create($this->tableId, $this->viewId, $data); + $this->rowService->create($this->tableId, $this->viewId, $data, $this->userId); $this->countInsertedRows++; } else { $this->logger->debug('Skipped empty row ' . $row->getRowIndex() . ' during import'); @@ -644,4 +800,17 @@ private function parseColumnDataType(Cell $cell): array { return $dataType; } + + /** + * @return ISimpleFolder + * @throws \OCP\Files\NotPermittedException + */ + private function getImportAppDataDir(): ISimpleFolder { + try { + return $this->appData->getFolder('import'); + } catch (NotFoundException) { + return $this->appData->newFolder('import'); + } + } + } diff --git a/lib/Service/PermissionsService.php b/lib/Service/PermissionsService.php index 909f0e8ef..3ee31a8bb 100644 --- a/lib/Service/PermissionsService.php +++ b/lib/Service/PermissionsService.php @@ -83,7 +83,6 @@ public function preCheckUserId(?string $userId = null, bool $canBeEmpty = true): } if ($userId === null) { - $e = new \Exception(); $error = 'PreCheck for userId failed, requested in ' . get_class($this) . '.'; $this->logger->debug($error, ['exception' => new \Exception()]); throw new InternalError($error); @@ -666,7 +665,7 @@ private function basisCheck(Table|View|Context $element, string $nodeType, ?stri try { $userId = $this->preCheckUserId($userId); } catch (InternalError $e) { - $e = new \Exception('Cannot pre check the user id'); + $e = new \Exception('Cannot pre check the user id', 0, $e); $this->logger->error($e->getMessage(), ['exception' => $e]); return false; } diff --git a/lib/Service/RowService.php b/lib/Service/RowService.php index 8f4e2c650..10d399d92 100644 --- a/lib/Service/RowService.php +++ b/lib/Service/RowService.php @@ -152,6 +152,7 @@ public function find(int $id): Row2 { * @param int|null $tableId * @param int|null $viewId * @param RowDataInput|list $data + * @param string|null $userId * @return Row2 * * @throws NotFoundError @@ -159,7 +160,11 @@ public function find(int $id): Row2 { * @throws Exception * @throws InternalError */ - public function create(?int $tableId, ?int $viewId, RowDataInput|array $data): Row2 { + public function create(?int $tableId, ?int $viewId, RowDataInput|array $data, ?string $userId = null): Row2 { + if ($userId) { + $this->userId = $userId; + } + if ($this->userId === null || $this->userId === '') { $e = new \Exception('No user id in context, but needed.'); $this->logger->error($e->getMessage(), ['exception' => $e]); @@ -180,7 +185,7 @@ public function create(?int $tableId, ?int $viewId, RowDataInput|array $data): R } // security - if (!$this->permissionsService->canCreateRows($view)) { + if (!$this->permissionsService->canCreateRows($view, 'view', $this->userId)) { throw new PermissionError('create row at the view id = ' . $viewId . ' is not allowed.'); } @@ -198,7 +203,7 @@ public function create(?int $tableId, ?int $viewId, RowDataInput|array $data): R } // security - if (!$this->permissionsService->canCreateRows($table, 'table')) { + if (!$this->permissionsService->canCreateRows($table, 'table', $this->userId)) { throw new PermissionError('create row at the table id = ' . $tableId . ' is not allowed.'); } @@ -218,7 +223,7 @@ public function create(?int $tableId, ?int $viewId, RowDataInput|array $data): R $row2->setTableId($tableId); $row2->setData($data); try { - $insertedRow = $this->row2Mapper->insert($row2, $this->columnMapper->findAllByTable($tableId)); + $insertedRow = $this->row2Mapper->insert($row2, $this->columnMapper->findAllByTable($tableId), $this->userId); $this->eventDispatcher->dispatchTyped(new RowAddedEvent($insertedRow)); diff --git a/lib/Service/ViewService.php b/lib/Service/ViewService.php index 0960351ff..ce79149c8 100644 --- a/lib/Service/ViewService.php +++ b/lib/Service/ViewService.php @@ -272,7 +272,7 @@ public function update(int $id, array $data, ?string $userId = null, bool $skipT // we have to fetch the service here as ColumnService already depends on the ViewService, i.e. no DI $columnService = \OCP\Server::get(ColumnService::class); $columnIds = \json_decode($value, true); - $availableColumns = $columnService->findAllByTable($view->getTableId(), $view->getId(), $this->userId); + $availableColumns = $columnService->findAllByTable($view->getTableId(), $view->getId(), $userId); $availableColumns = array_map(static fn (Column $column) => $column->getId(), $availableColumns); foreach ($columnIds as $columnId) { if (!Column::isValidMetaTypeId($columnId) && !in_array($columnId, $availableColumns, true)) { diff --git a/psalm.xml b/psalm.xml index 54cf837e1..115329294 100644 --- a/psalm.xml +++ b/psalm.xml @@ -42,6 +42,7 @@ + @@ -54,6 +55,7 @@ + diff --git a/src/modules/modals/FileActionImport.vue b/src/modules/modals/FileActionImport.vue index 6ef692723..23f55462b 100644 --- a/src/modules/modals/FileActionImport.vue +++ b/src/modules/modals/FileActionImport.vue @@ -3,7 +3,7 @@ - SPDX-License-Identifier: AGPL-3.0-or-later --> - @@ -88,19 +88,6 @@ - - - - - {{ t('tables', 'Close') }} - - - - - diff --git a/src/modules/modals/Import.vue b/src/modules/modals/Import.vue index 0e166e2fe..5fd8c29bb 100644 --- a/src/modules/modals/Import.vue +++ b/src/modules/modals/Import.vue @@ -9,7 +9,7 @@ @closing="actionCancel"> - + {{ t('tables', 'Add data to the table from a file') }} @@ -67,7 +67,7 @@ - + @@ -79,19 +79,6 @@ - - - - - - - - {{ t('tables', 'Done') }} - - - - - @@ -116,7 +103,7 @@