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
20 changes: 17 additions & 3 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,6 +362,8 @@ 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
* @psalm-param ?list<array{label: string, icon?: string, targetType: 'view'|'table'|'url', targetUuid?: string, url?: string, technicalName?: string, parentTechnicalName?: string}> $menuItems Menu of the context, targets by uuid, parents by technical name; leave it out to keep the current menu
* @psalm-param list<array<string, mixed>> $gridViews Views without a table that the menu points at
*
* @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 @@ -371,16 +374,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): DataResponse {
public function importScheme(int $contextId, string $name, string $iconName, string $description, array $nodes, array $tables, ?array $menuItems = null, array $gridViews = []): DataResponse {
try {
$scheme = ContextScheme::createFromInputArray(['name' => $name, 'icon' => $iconName, 'description' => $description, 'nodes' => $nodes, 'tables' => $tables, 'menuItems' => $menuItems, '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);
$context = $this->contextService->importScheme($contextId, $scheme, $this->userId);
Comment thread
rjzondervan marked this conversation as resolved.
$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) {
Comment thread
rjzondervan marked this conversation as resolved.
$this->db->rollBack();
return $this->handleError($e);
} catch (PermissionError $e) {
Expand All @@ -389,6 +400,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
91 changes: 91 additions & 0 deletions lib/Model/ContextScheme.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace OCA\Tables\Model;

use DateTime;
use InvalidArgumentException;
use JsonSerializable;

class ContextScheme implements JsonSerializable {
Expand All @@ -18,9 +20,81 @@ 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', 'menuItems', 'gridViews'] as $list) {
Comment thread
rjzondervan marked this conversation as resolved.
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.');
}
}
self::assertGridViewsValid($data['gridViews'] ?? []);
return new self(
name: $data['name'],
icon: $data['icon'] ?? null,
description: $data['description'] ?? null,
nodes: array_values($data['nodes'] ?? []),
pages: [],
tables: $tables,
// A scheme without a menu leaves the application's menu alone; an empty one clears it
menuItems: isset($data['menuItems']) ? array_values($data['menuItems']) : null,
gridViews: array_values($data['gridViews'] ?? []),
);
}

/**
* Every view without a table in a scheme needs a uuid and a title; its other fields are text or absent,
* and its dates are in the format the export writes.
*
* @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 . '".');
}
}
foreach (['emoji', 'description', 'createdAt', 'lastEditAt'] as $optional) {
if (isset($gridView[$optional]) && !is_string($gridView[$optional])) {
throw new InvalidArgumentException('The grid view property "' . $optional . '" must be a string.');
}
}
foreach (['createdAt', 'lastEditAt'] as $date) {
if (isset($gridView[$date]) && DateTime::createFromFormat('Y-m-d H:i:s', $gridView[$date]) === false) {
throw new InvalidArgumentException('The grid view property "' . $date . '" must be a date like 2026-10-14 12:00:00.');
}
}
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 @@ -45,6 +119,21 @@ public function getTables(): ?array {
return $this->tables;
}

/**
* Menu items point at their target by uuid, 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 that the menu points at; they live outside the tables of the scheme.
*/
public function getGridViews(): array {
return $this->gridViews;
}

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