Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions lib/Controller/ContextController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use OCA\Tables\Errors\NotFoundError;
use OCA\Tables\Errors\PermissionError;
use OCA\Tables\Middleware\Attribute\RequirePermission;
use OCA\Tables\Model\ContextScheme;
use OCA\Tables\Model\MenuItemInput;
use OCA\Tables\ResponseDefinitions;
use OCA\Tables\Service\ColumnService;
Expand Down Expand Up @@ -361,7 +362,9 @@ public function previewSchemeChanges(int $contextId, array $updateScheme): DataR
* @param string $description Description of the context
* @param list<array{node_type: int, node_uuid: string, permissions: int}> $nodes Ordered meta data of the related nodes
* @psalm-param array{addTables: list<TablesTable>, modifyTables: list<TablesTable>} $tables Tables to be added or modified
* @param list<array<string, mixed>>|null $pages Pages of the scheme; each updates the page with the same technical name or is added, with its widgets
* @param list<array<string, mixed>>|null $pages Pages of the scheme; each updates the page with the same technical name or is added, with its widgets; leave it out to keep the current pages
* @psalm-param ?list<array{label: string, icon?: string, targetType: 'view'|'table'|'url'|'page', targetUuid?: string, targetPageTechnicalName?: string, url?: string, technicalName?: string, parentTechnicalName?: string}> $menuItems Menu of the context: tables and views by uuid, pages by technical name, parents by technical name; leave it out to keep the current menu
* @param list<array<string, mixed>> $gridViews Views without a table from a scheme of an older version; each becomes a page
*
* @return DataResponse<Http::STATUS_OK, array<string, mixed>, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND|Http::STATUS_FORBIDDEN|Http::STATUS_BAD_REQUEST, array{message: string}, array{}>
*
Expand All @@ -372,16 +375,24 @@ public function previewSchemeChanges(int $contextId, array $updateScheme): DataR
*/
#[NoAdminRequired]
#[RequirePermission(permission: Application::PERMISSION_MANAGE, typeParam: 'context', idParam: 'contextId')]
public function importScheme(int $contextId, string $name, string $iconName, string $description, array $nodes, array $tables, ?array $pages = null): DataResponse {
public function importScheme(int $contextId, string $name, string $iconName, string $description, array $nodes, array $tables, ?array $pages = null, ?array $menuItems = null, array $gridViews = []): DataResponse {
try {
$scheme = ContextScheme::createFromInputArray(['name' => $name, 'icon' => $iconName, 'description' => $description, 'nodes' => $nodes, 'tables' => $tables, 'menuItems' => $menuItems, 'pages' => $pages, 'gridViews' => $gridViews]);
} catch (\InvalidArgumentException $e) {
return $this->handleBadRequestError(new BadRequestError($e->getMessage(), $e->getCode(), $e));
}
try {
$this->db->beginTransaction();
$context = $this->contextService->importScheme($contextId, $name, $iconName, $description, $nodes, $tables, $this->userId, $pages === null ? null : array_values($pages));
$context = $this->contextService->importScheme($contextId, $scheme, $this->userId);
$this->db->commit();
return new DataResponse($context->jsonSerialize());
} catch (\InvalidArgumentException $e) {
$this->db->rollBack();
return $this->handleBadRequestError(new BadRequestError($e->getMessage(), $e->getCode(), $e));
} catch (Exception|MultipleObjectsReturnedException $e) {
} catch (BadRequestError $e) {
$this->db->rollBack();
return $this->handleBadRequestError($e);
} catch (Exception|MultipleObjectsReturnedException|InternalError $e) {
$this->db->rollBack();
return $this->handleError($e);
} catch (PermissionError $e) {
Expand All @@ -390,6 +401,9 @@ public function importScheme(int $contextId, string $name, string $iconName, str
} catch (DoesNotExistException $e) {
$this->db->rollBack();
return $this->handleNotFoundError(new NotFoundError($e->getMessage(), $e->getCode(), $e));
} catch (NotFoundError $e) {
$this->db->rollBack();
return $this->handleNotFoundError($e);
}
}

Expand Down
89 changes: 89 additions & 0 deletions lib/Model/ContextScheme.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace OCA\Tables\Model;

use InvalidArgumentException;
use JsonSerializable;

class ContextScheme implements JsonSerializable {
Expand All @@ -18,9 +19,76 @@ public function __construct(
protected ?array $nodes = [],
protected ?array $pages = [],
protected ?array $tables = [],
protected ?array $menuItems = [],
protected array $gridViews = [],
) {
}

/**
* Builds the scheme from an import request and refuses what the import could not handle.
*
* @param array<string, mixed> $data
* @throws InvalidArgumentException
*/
public static function createFromInputArray(array $data): self {
$tables = $data['tables'] ?? null;
if (!is_array($tables) || !isset($tables['addTables'], $tables['modifyTables']) || !is_array($tables['addTables']) || !is_array($tables['modifyTables'])) {
throw new InvalidArgumentException('Invalid tables structure provided: expected addTables and modifyTables lists.');
}
foreach (['nodes', 'pages', 'menuItems', 'gridViews'] as $list) {
if (isset($data[$list]) && !is_array($data[$list])) {
throw new InvalidArgumentException('The scheme property "' . $list . '" must be a list.');
}
}
if (!is_string($data['name'] ?? null) || trim($data['name']) === '') {
throw new InvalidArgumentException('A scheme needs a name.');
}
foreach (['icon', 'description'] as $optional) {
if (isset($data[$optional]) && !is_string($data[$optional])) {
throw new InvalidArgumentException('The scheme property "' . $optional . '" must be a string.');
}
}
foreach ($data['pages'] ?? [] as $page) {
if (!is_array($page) || (isset($page['widgets']) && !is_array($page['widgets']))) {
throw new InvalidArgumentException('Every page of a scheme must be an object, with its widgets as a list.');
}
}
self::assertGridViewsValid($data['gridViews'] ?? []);
return new self(
name: $data['name'],
icon: $data['icon'] ?? null,
description: $data['description'] ?? null,
nodes: array_values($data['nodes'] ?? []),
// A scheme without pages or without a menu leaves those of the application alone
pages: isset($data['pages']) ? array_values($data['pages']) : null,
tables: $tables,
menuItems: isset($data['menuItems']) ? array_values($data['menuItems']) : null,
gridViews: array_values($data['gridViews'] ?? []),
);
}

/**
* Grid views come from schemes of an older version, which kept pages in views without a table.
* Every one needs a uuid and a title, and its grid is an object.
*
* @throws InvalidArgumentException
*/
private static function assertGridViewsValid(array $gridViews): void {
foreach ($gridViews as $gridView) {
if (!is_array($gridView)) {
throw new InvalidArgumentException('Every entry of "gridViews" must be an object.');
}
foreach (['uuid', 'title'] as $required) {
if (!is_string($gridView[$required] ?? null) || trim($gridView[$required]) === '') {
throw new InvalidArgumentException('Every entry of "gridViews" needs a "' . $required . '".');
}
}
if (isset($gridView['grid']) && !is_array($gridView['grid'])) {
throw new InvalidArgumentException('The grid view property "grid" must be an object.');
}
}
}

public function getName(): ?string {
return $this->name ?? '';
}
Expand All @@ -37,6 +105,10 @@ public function getNodes(): ?array {
return $this->nodes;
}

/**
* Pages with their widgets; a data widget names its node by its id in the nodes of the scheme.
* Null when the scheme has no pages, which leaves the pages of the application as they are.
*/
public function getPages(): ?array {
return $this->pages;
}
Expand All @@ -45,6 +117,22 @@ public function getTables(): ?array {
return $this->tables;
}

/**
* Menu items point at tables and views by uuid and at pages by technical name, so a scheme can
* be imported on another instance. Null when the scheme has no menu, which leaves the
* application's menu as it is.
*/
public function getMenuItems(): ?array {
return $this->menuItems;
}

/**
* Views without a table from a scheme of an older version; the import turns each into a page.
*/
public function getGridViews(): array {
return $this->gridViews;
}

public function jsonSerialize(): mixed {
return [
'name' => $this->name,
Expand All @@ -53,6 +141,7 @@ public function jsonSerialize(): mixed {
'nodes' => $this->nodes,
'pages' => $this->pages,
'tables' => $this->tables,
'menuItems' => $this->menuItems,
];
}
}
Loading
Loading