appManager = $appManager;
$this->mapper = $mapper;
$this->tableTemplateService = $tableTemplateService;
$this->columnService = $columnService;
$this->rowService = $rowService;
$this->viewService = $viewService;
$this->shareService = $shareService;
$this->userHelper = $userHelper;
$this->favoritesService = $favoritesService;
$this->l = $l;
$this->eventDispatcher = $eventDispatcher;
$this->contextService = $contextService;
}
/**
* Find all tables for a user
*
* takes the user from actual context or the given user
* it is possible to get all tables, but only if requested by cli
*
* @param string|null $userId (null -> take from session, '' -> no user in context)
* @param bool $skipTableEnhancement
* @param bool $skipSharedTables
* @param bool $createTutorial
* @return array
* @throws InternalError
*/
public function findAll(?string $userId = null, bool $skipTableEnhancement = false, bool $skipSharedTables = false, bool $createTutorial = true): array {
/** @var string $userId */
$userId = $this->permissionsService->preCheckUserId($userId); // $userId can be set or ''
$allTables = [];
try {
$ownedTables = $this->mapper->findAll($userId); // get own tables
foreach ($ownedTables as $ownedTable) {
$allTables[$ownedTable->getId()] = $ownedTable;
}
} catch (OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError($e->getMessage());
}
// if there are no own tables found, create the tutorial table
if (count($allTables) === 0 && $createTutorial) {
try {
$tutorialTable = $this->create($this->l->t('Tutorial'), 'tutorial', '🚀');
$allTables[$tutorialTable->getId()] = $tutorialTable;
} catch (InternalError|PermissionError|DoesNotExistException|MultipleObjectsReturnedException|OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
}
if (!$skipSharedTables && $userId !== '') {
$sharedTables = $this->shareService->findTablesSharedWithMe($userId);
// clean duplicates
foreach ($sharedTables as $sharedTable) {
if (!isset($allTables[$sharedTable->getId()])) {
$allTables[$sharedTable->getId()] = $sharedTable;
}
}
}
$contexts = $this->contextService->findAll($userId);
foreach ($contexts as $context) {
$nodes = $context->getNodes();
foreach ($nodes as $node) {
if ($node['node_type'] !== Application::NODE_TYPE_TABLE
|| isset($allTables[$node['node_id']])
) {
continue;
}
$allTables[$node['node_id']] = $this->find($node['node_id'], $skipTableEnhancement, $userId);
}
}
// enhance table objects with additional data
if (!$skipTableEnhancement) {
foreach ($allTables as $table) {
/** @var string $userId */
try {
$this->enhanceTable($table, $userId);
} catch (InternalError|PermissionError $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
}
// if the table is shared with me, there are no other shares
// will avoid showing the shared icon in the FE nav
if($table->getIsShared()) {
$table->setHasShares(false);
}
}
}
return array_values($allTables);
}
/**
* @param Table[] $tables
* @return TablesTable[]
*/
public function formatTables(array $tables): array {
return array_map(fn (Table $table) => $table->jsonSerialize(), $tables);
}
/**
* add some basic values related to this table in context
*
* $userId can be set or ''
* @param Table $table
* @param string $userId
* @throws InternalError
* @throws PermissionError
*/
private function enhanceTable(Table $table, string $userId): void {
// add owner display name for UI
$this->addOwnerDisplayName($table);
// set hasShares if this table is shared by you (you share it with somebody else)
// (senseless if we have no user in context)
if ($userId !== '') {
try {
$shares = $this->shareService->findAll('table', $table->getId());
$table->setHasShares(count($shares) !== 0);
} catch (InternalError $e) {
}
}
// add the rows count
try {
$table->setRowsCount($this->rowService->getRowsCount($table->getId()));
} catch (PermissionError $e) {
$table->setRowsCount(0);
}
// add the column count
try {
$table->setColumnsCount($this->columnService->getColumnsCount($table->getId()));
} catch (PermissionError $e) {
$table->setColumnsCount(0);
}
// set if this is a shared table with you (somebody else shared it with you)
// (senseless if we have no user in context)
if ($userId !== '' && $userId !== $table->getOwnership()) {
try {
$permissions = $this->shareService->getSharedPermissionsIfSharedWithMe($table->getId(), 'table', $userId);
$table->setIsShared(true);
$table->setOnSharePermissions($permissions);
} catch (NotFoundError $e) {
try {
$table->setOnSharePermissions($this->permissionsService->getPermissionArrayForNodeFromContexts($table->getId(), 'table', $userId));
$table->setIsShared(true);
} catch (NotFoundError $e) {
}
}
}
if (!$table->getIsShared() || $table->getOnSharePermissions()->manage) {
// add the corresponding views if it is an own table, or you have table manage rights
$table->setViews($this->viewService->findAll($table));
}
if ($this->favoritesService->isFavorite(Application::NODE_TYPE_TABLE, $table->getId())) {
$table->setFavorite(true);
}
}
/**
* @param int $id
* @param string|null $userId
* @param bool $skipTableEnhancement
* @return Table
* @throws InternalError
* @throws NotFoundError
* @throws PermissionError
*/
public function find(int $id, bool $skipTableEnhancement = false, ?string $userId = null): Table {
/** @var string $userId */
$userId = $this->permissionsService->preCheckUserId($userId); // $userId can be set or ''
try {
$table = $this->mapper->find($id);
// security
if (!$this->permissionsService->canReadTable($table, $userId)) {
throw new PermissionError('PermissionError: can not read table with id '.$id);
}
if (!$skipTableEnhancement) {
$this->enhanceTable($table, $userId);
}
return $table;
} catch (DoesNotExistException $e) {
$this->logger->warning($e->getMessage());
throw new NotFoundError($e->getMessage());
} catch (MultipleObjectsReturnedException|OcpDbException $e) {
$this->logger->error($e->getMessage());
throw new InternalError($e->getMessage());
}
}
/**
* @param string $title
* @param string $template
* @param string|null $emoji
* @param string|null $userId
* @return Table
* @throws InternalError
* @noinspection DuplicatedCode
*/
public function create(string $title, string $template, ?string $emoji, ?string $description = '', ?string $userId = null): Table {
$userId = $this->permissionsService->preCheckUserId($userId, false); // we can assume that the $userId is set
$time = new DateTime();
$item = new Table();
$item->setTitle($title);
$item->setDescription($description);
if($emoji) {
$item->setEmoji($emoji);
}
$item->setOwnership($userId);
$item->setCreatedBy($userId);
$item->setLastEditBy($userId);
$item->setCreatedAt($time->format('Y-m-d H:i:s'));
$item->setLastEditAt($time->format('Y-m-d H:i:s'));
try {
$newTable = $this->mapper->insert($item);
} catch (OcpDbException $e) {
$this->logger->error($e->getMessage());
throw new InternalError($e->getMessage());
}
if ($template !== 'custom') {
try {
$table = $this->tableTemplateService->makeTemplate($newTable, $template);
} catch (InternalError|PermissionError|DoesNotExistException|MultipleObjectsReturnedException|OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
} else {
$table = $this->addOwnerDisplayName($newTable);
}
try {
$this->enhanceTable($table, $userId);
} catch (InternalError|PermissionError $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
return $table;
}
/**
* Set a new owner for a table and adjust all related ressources
*
* @param int $id
* @param string $newOwnerUserId
* @param string|null $userId
*
* @return Table
*
* @throws InternalError
* @throws NotFoundError
* @throws PermissionError
*/
public function setOwner(int $id, string $newOwnerUserId, ?string $userId = null): Table {
$userId = $this->permissionsService->preCheckUserId($userId);
try {
$table = $this->mapper->find($id);
} catch (DoesNotExistException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new NotFoundError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
} catch (MultipleObjectsReturnedException|OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
// security
if (!$this->permissionsService->canChangeElementOwner($table, $userId)) {
throw new PermissionError('PermissionError: can not change table owner with table id '.$id);
}
$table->setOwnership($newOwnerUserId);
try {
$table = $this->mapper->update($table);
} catch (OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
// change owners of related shares
try {
$this->shareService->changeSenderForNode('table', $id, $newOwnerUserId, $userId);
} catch (InternalError $e) {
$this->logger->error('Could not update related shares for a table transfer!');
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
$event = new TableOwnershipTransferredEvent(
table: $table,
toUserId: $newOwnerUserId,
fromUserId: $userId
);
$this->eventDispatcher->dispatchTyped($event);
return $table;
}
/**
* @param int $id
* @param null|string $userId
* @return Table
* @throws InternalError
* @throws NotFoundError
* @throws PermissionError
*/
public function delete(int $id, ?string $userId = null): Table {
$userId = $this->permissionsService->preCheckUserId($userId); // assume that $userId is set or ''
try {
$item = $this->mapper->find($id);
} catch (DoesNotExistException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new NotFoundError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
} catch (MultipleObjectsReturnedException|OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
// security
if (!$this->permissionsService->canManageTable($item, $userId)) {
throw new PermissionError('PermissionError: can not delete table with id '.$id);
}
// delete all rows for that table
try {
$this->rowService->deleteAllByTable($id, $userId);
} catch (PermissionError|OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
// delete all views for that table
// we must delete views before columns because we need columns
// while deleting views (in case we're deleting a table that has views)
try {
$this->viewService->deleteAllByTable($item, $userId);
} catch (InternalError|PermissionError $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
// delete all columns for that table
try {
$columns = $this->columnService->findAllByTable($id, null, $userId);
} catch (InternalError|PermissionError $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
foreach ($columns as $column) {
try {
$this->columnService->delete($column->id, true, $userId);
} catch (InternalError $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
}
// delete all shares for that table
$this->shareService->deleteAllForTable($item);
// delete node relations if view is in any context
$this->contextService->deleteNodeRel($id, Application::NODE_TYPE_TABLE);
// delete table
try {
$this->mapper->delete($item);
} catch (OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
$event = new TableDeletedEvent(table: $item);
$this->eventDispatcher->dispatchTyped($event);
return $item;
}
/**
*
* @param int $id $userId
* @param string|null $title
* @param string|null $emoji
* @param string|null $userId
* @return Table
* @throws InternalError
* @throws NotFoundError
* @throws PermissionError
*/
public function update(int $id, ?string $title, ?string $emoji, ?string $description, ?bool $archived = null, ?string $userId = null): Table {
$userId = $this->permissionsService->preCheckUserId($userId);
try {
$table = $this->mapper->find($id);
} catch (DoesNotExistException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new NotFoundError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
} catch (MultipleObjectsReturnedException|OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
// security
if (!$this->permissionsService->canUpdateTable($table, $userId)) {
throw new PermissionError('PermissionError: can not update table with id '.$id);
}
$time = new DateTime();
if ($title !== null) {
$table->setTitle($title);
}
if ($emoji !== null) {
$table->setEmoji($emoji);
}
if ($archived !== null) {
$table->setArchived($archived);
}
if ($description !== null) {
$table->setDescription($description);
}
$table->setLastEditBy($userId);
$table->setLastEditAt($time->format('Y-m-d H:i:s'));
try {
$table = $this->mapper->update($table);
} catch (OcpDbException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
try {
$this->enhanceTable($table, $userId);
} catch (InternalError|PermissionError $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
}
return $table;
}
/**
* @param string $term
* @param int $limit
* @param int $offset
* @param string|null $userId
* @return array
*/
public function search(string $term, int $limit = 100, int $offset = 0, ?string $userId = null): array {
try {
/** @var string $userId */
$userId = $this->permissionsService->preCheckUserId($userId);
$tables = $this->mapper->search($term, $userId, $limit, $offset);
foreach ($tables as &$table) {
$this->enhanceTable($table, $userId);
}
return $tables;
} catch (InternalError | PermissionError | OcpDbException $e) {
return [];
}
}
/**
* @throws PermissionError
* @throws NotFoundError
* @throws InternalError
*/
public function getScheme(int $id): TableScheme {
$columns = $this->columnService->findAllByTable($id);
$table = $this->find($id);
return new TableScheme($table->getTitle(), $table->getEmoji(), $columns, $table->getViews(), $table->getDescription(), $this->appManager->getAppVersion("tables"));
}
// PRIVATE FUNCTIONS ---------------------------------------------------------------
/**
* @param Table $table
* @return Table
*/
private function addOwnerDisplayName(Table $table): Table {
$table->setOwnerDisplayName($this->userHelper->getUserDisplayName($table->getOwnership()));
return $table;
}
}