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
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Share your tables and views with users and groups within your cloud.
Have a good time and manage whatever you want.

]]></description>
<version>2.4.0-dev.0</version>
<version>2.4.0-dev.9</version>
<licence>AGPL-3.0-or-later</licence>
<author>Nextcloud GmbH and Nextcloud contributors</author>
<namespace>Tables</namespace>
Expand Down
7 changes: 5 additions & 2 deletions lib/Db/ContextMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected function getFindContextBaseQuery(?string $userId): IQueryBuilder {
$qb->select(
'c.*',
'r.id as node_rel_id', 'r.node_id', 'r.node_type', 'r.permissions',
'p.id as page_id', 'p.page_type',
'p.id as page_id', 'p.page_type', 'p.title as page_title', 'p.emoji as page_emoji', 'p.description as page_description',
'pc.id as content_id', 'pc.order',
'n.display_mode as display_mode_default',
's.id as share_id', 's.receiver', 's.receiver_type'
Expand Down Expand Up @@ -131,7 +131,10 @@ protected function formatResultRows(array $rows, ?string $userId) {
$carry[$item['page_id']] ??= ['content' => []];
$carry[$item['page_id']]['id'] = (int)$item['page_id'];
$carry[$item['page_id']]['page_type'] = $item['page_type'];
if ($item['node_rel_id'] !== null) {
$carry[$item['page_id']]['title'] = $item['page_title'];
$carry[$item['page_id']]['emoji'] = $item['page_emoji'];
$carry[$item['page_id']]['description'] = $item['page_description'];
if ($item['node_rel_id'] !== null && $item['content_id'] !== null) {
$carry[$item['page_id']]['content'][$item['content_id']] = [
'order' => (int)$item['order'],
'node_rel_id' => (int)$item['node_rel_id']
Expand Down
13 changes: 13 additions & 0 deletions lib/Db/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@
* @method setContextId(int $value): void
* @method getPageType(): string
* @method setPageType(string $value): void
* @method getTitle(): ?string
* @method setTitle(?string $value): void
* @method getEmoji(): ?string
* @method setEmoji(?string $value): void
* @method getDescription(): ?string
* @method setDescription(?string $value): void
*/
class Page extends Entity implements \JsonSerializable {
public const TYPE_STARTPAGE = 'startpage';
public const TYPE_GRID = 'grid';

protected ?int $contextId = null;
protected ?string $pageType = null;
protected ?string $title = null;
protected ?string $emoji = null;
protected ?string $description = null;

public function __construct() {
$this->addType('id', 'integer');
Expand All @@ -31,6 +41,9 @@ public function jsonSerialize(): array {
'id' => $this->getId(),
'contextId' => $this->getContextId(),
'pageType' => $this->getPageType(),
'title' => $this->getTitle(),
'emoji' => $this->getEmoji(),
'description' => $this->getDescription(),
];
}
}
92 changes: 92 additions & 0 deletions lib/Db/Widget.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Tables\Db;

use OCP\AppFramework\Db\Entity;

/**
* A widget on a grid page: its type, optional title, its place on the grid and its
* settings, stored as one JSON object.
*
* @psalm-type TablesWidgetSettingValue = string|int|float|bool|null
*
* @method getPageId(): int
* @method setPageId(int $value): void
* @method getType(): string
* @method setType(string $value): void
* @method getTitle(): ?string
* @method setTitle(?string $value): void
* @method getGridX(): int
* @method setGridX(int $value): void
* @method getGridY(): int
* @method setGridY(int $value): void
* @method getGridWidth(): int
* @method setGridWidth(int $value): void
* @method getGridHeight(): int
* @method setGridHeight(int $value): void
* @method getSettings(): ?string
* @method setSettings(?string $value): void
*/
class Widget extends Entity implements \JsonSerializable {
protected ?int $pageId = null;
protected ?string $type = null;
protected ?string $title = null;
protected ?int $gridX = null;
protected ?int $gridY = null;
protected ?int $gridWidth = null;
protected ?int $gridHeight = null;
protected ?string $settings = null;

public function __construct() {
$this->addType('id', 'integer');
$this->addType('page_id', 'integer');
$this->addType('grid_x', 'integer');
$this->addType('grid_y', 'integer');
$this->addType('grid_width', 'integer');
$this->addType('grid_height', 'integer');
}

/**
* @return array<string, TablesWidgetSettingValue>
*/
public function getSettingsArray(): array {
$settings = $this->getSettings();
if ($settings === null || $settings === '') {
return [];
}
$decoded = json_decode($settings, true);
/** @var array<string, TablesWidgetSettingValue> */
return is_array($decoded) ? $decoded : [];
}

/**
* Stores the settings as one JSON object, `{}` when there are none.
*
* @param array<string, TablesWidgetSettingValue> $settings
* @throws \JsonException
*/
public function setSettingsArray(array $settings): void {
$this->setSettings(json_encode((object)$settings, JSON_THROW_ON_ERROR));
}

public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'pageId' => $this->getPageId(),
'type' => $this->getType(),
'title' => $this->getTitle(),
'gridX' => $this->getGridX(),
'gridY' => $this->getGridY(),
'gridWidth' => $this->getGridWidth(),
'gridHeight' => $this->getGridHeight(),
'settings' => $this->getSettingsArray(),
];
}
}
78 changes: 78 additions & 0 deletions lib/Db/WidgetMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Tables\Db;

use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;

/** @template-extends QBMapper<Widget> */
class WidgetMapper extends QBMapper {
protected string $table = 'tables_contexts_widget';

public function __construct(IDBConnection $db) {
parent::__construct($db, $this->table, Widget::class);
}

/**
* @param int[] $pageIds
* @return Widget[] ordered by page and grid position
* @throws Exception
*/
public function findByPageIds(array $pageIds): array {
if ($pageIds === []) {
return [];
}
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->table)
->where($qb->expr()->in('page_id', $qb->createNamedParameter($pageIds, IQueryBuilder::PARAM_INT_ARRAY)))
->orderBy('page_id', 'ASC')
->addOrderBy('grid_y', 'ASC')
->addOrderBy('grid_x', 'ASC')
->addOrderBy('id', 'ASC');

return $this->findEntities($qb);
}

/**
* @return int[]
* @throws Exception
*/
public function findIdsByPageId(int $pageId): array {
$qb = $this->db->getQueryBuilder();
$qb->select('id')
->from($this->table)
->where($qb->expr()->eq('page_id', $qb->createNamedParameter($pageId, IQueryBuilder::PARAM_INT)));

$result = $qb->executeQuery();
$ids = [];
while ($row = $result->fetchAssociative()) {
$ids[] = (int)$row['id'];
}
$result->closeCursor();
return $ids;
}

/**
* @param int[] $widgetIds
* @throws Exception
*/
public function deleteByIds(array $widgetIds): void {
if ($widgetIds === []) {
return;
}
$qb = $this->db->getQueryBuilder();
$qb->delete($this->table)
->where($qb->expr()->in('id', $qb->createNamedParameter($widgetIds, IQueryBuilder::PARAM_INT_ARRAY)))
->executeStatement();
}
}
31 changes: 31 additions & 0 deletions lib/Db/WidgetSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Tables\Db;

use OCP\AppFramework\Db\Entity;

/**
* The node of its application a data widget shows. The widget id is the key;
* the inherited id is not used.
*
* @method getWidgetId(): int
* @method setWidgetId(int $value): void
* @method getNodeRelId(): int
* @method setNodeRelId(int $value): void
*/
class WidgetSource extends Entity {
protected ?int $widgetId = null;
protected ?int $nodeRelId = null;

public function __construct() {
$this->addType('widget_id', 'integer');
$this->addType('node_rel_id', 'integer');
}
}
99 changes: 99 additions & 0 deletions lib/Db/WidgetSourceMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Tables\Db;

use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;

/**
* The table keys on widget_id, so rows are written with plain queries instead of
* the id based insert and update of QBMapper.
*
* @template-extends QBMapper<WidgetSource>
*/
class WidgetSourceMapper extends QBMapper {
protected string $table = 'tables_contexts_widget_source';

public function __construct(IDBConnection $db) {
parent::__construct($db, $this->table, WidgetSource::class);
}

/**
* @param int[] $widgetIds
* @return array<int, int> node relation ids by widget id
* @throws Exception
*/
public function findNodeRelIdsByWidgetIds(array $widgetIds): array {
if ($widgetIds === []) {
return [];
}
$qb = $this->db->getQueryBuilder();
$qb->select('widget_id', 'node_rel_id')
->from($this->table)
->where($qb->expr()->in('widget_id', $qb->createNamedParameter($widgetIds, IQueryBuilder::PARAM_INT_ARRAY)));

$result = $qb->executeQuery();
$sources = [];
while ($row = $result->fetchAssociative()) {
$sources[(int)$row['widget_id']] = (int)$row['node_rel_id'];
}
$result->closeCursor();
return $sources;
}

/**
* Points a widget at a node relation, or removes its source when null.
*
* @throws Exception
*/
public function setSource(int $widgetId, ?int $nodeRelId): void {
$this->deleteByWidgetIds([$widgetId]);
if ($nodeRelId === null) {
return;
}
$qb = $this->db->getQueryBuilder();
$qb->insert($this->table)
->values([
'widget_id' => $qb->createNamedParameter($widgetId, IQueryBuilder::PARAM_INT),
'node_rel_id' => $qb->createNamedParameter($nodeRelId, IQueryBuilder::PARAM_INT),
])
->executeStatement();
}

/**
* @param int[] $widgetIds
* @throws Exception
*/
public function deleteByWidgetIds(array $widgetIds): void {
if ($widgetIds === []) {
return;
}
$qb = $this->db->getQueryBuilder();
$qb->delete($this->table)
->where($qb->expr()->in('widget_id', $qb->createNamedParameter($widgetIds, IQueryBuilder::PARAM_INT_ARRAY)))
->executeStatement();
}

/**
* @param int[] $nodeRelIds
* @throws Exception
*/
public function deleteByNodeRelIds(array $nodeRelIds): void {
if ($nodeRelIds === []) {
return;
}
$qb = $this->db->getQueryBuilder();
$qb->delete($this->table)
->where($qb->expr()->in('node_rel_id', $qb->createNamedParameter($nodeRelIds, IQueryBuilder::PARAM_INT_ARRAY)))
->executeStatement();
}
}
Loading
Loading