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.2</version>
<licence>AGPL-3.0-or-later</licence>
<author>Nextcloud GmbH and Nextcloud contributors</author>
<namespace>Tables</namespace>
Expand Down
3 changes: 2 additions & 1 deletion lib/Db/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
namespace OCA\Tables\Db;

use JsonSerializable;
use OCA\Tables\Helper\PageContentHelper;

/**
* @method getName(): string
Expand Down Expand Up @@ -63,7 +64,7 @@ public function jsonSerialize(): array {
if (is_array($this->sharing) || is_array($this->nodes) || is_array($this->pages)) {
$data['sharing'] = $this->getSharing();
$data['nodes'] = $this->getNodes();
$data['pages'] = $this->getPages();
$data['pages'] = is_array($this->pages) ? PageContentHelper::readPages($this->pages) : $this->pages;
}

return $data;
Expand Down
176 changes: 90 additions & 86 deletions lib/Db/ContextMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ 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',
'pc.id as content_id', 'pc.order',
'p.id as page_id', 'p.page_type', 'p.title as page_title', 'p.technical_name as page_technical_name',
'pc.id as content_id', 'pc.order', 'pc.node_rel_id as content_node_rel_id', 'pc.type as content_type',
'pc.configuration as content_configuration', 'pc.grid_x', 'pc.grid_y', 'pc.grid_width', 'pc.grid_height',
'n.display_mode as display_mode_default',
's.id as share_id', 's.receiver', 's.receiver_type'
)
Expand All @@ -60,23 +60,80 @@ protected function getFindContextBaseQuery(?string $userId): IQueryBuilder {

$qb->leftJoin('c', 'tables_contexts_page', 'p', $qb->expr()->eq('c.id', 'p.context_id'));
$qb->leftJoin('p', 'tables_contexts_page_content', 'pc', $qb->expr()->eq('p.id', 'pc.page_id'));
$qb->leftJoin('c', 'tables_contexts_rel_context_node', 'r', $qb->expr()->eq('c.id', 'r.context_id'));
$qb->leftJoin('s', 'tables_contexts_navigation', 'n', $qb->expr()->andX(
$qb->expr()->eq('s.id', 'n.share_id'),
$qb->expr()->eq('n.user_id', $qb->createNamedParameter('')),
));

$qb->andWhere($qb->expr()->orX(
$qb->expr()->eq('pc.node_rel_id', 'r.id'),
$qb->expr()->isNull('pc.node_rel_id'),
));

$qb->orderBy('pc.order', 'ASC');
$qb->orderBy('p.id', 'ASC')
->addOrderBy('pc.order', 'ASC')
->addOrderBy('pc.id', 'ASC');

return $qb;
}

protected function formatResultRows(array $rows, ?string $userId) {
/**
* The node relations of the given contexts, by context id and relation id.
* With a node type and id, only the relations to that node are returned.
*
* @param int[] $contextIds
* @return array<int, array<int, array{id: int, node_id: int, node_type: int, permissions: int}>>
* @throws Exception
*/
protected function findNodesForContexts(array $contextIds, ?int $nodeType = null, ?int $nodeId = null): array {
$nodes = [];
if ($contextIds === []) {
return $nodes;
}
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'context_id', 'node_id', 'node_type', 'permissions')
->from('tables_contexts_rel_context_node')
->where($qb->expr()->in('context_id', $qb->createParameter('contextIds')))
->orderBy('id', 'ASC');
if ($nodeType !== null && $nodeId !== null) {
$qb->andWhere($qb->expr()->eq('node_type', $qb->createNamedParameter($nodeType, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)));
}
foreach (array_chunk($contextIds, self::DB_CHUNK_SIZE) as $chunk) {
$qb->setParameter('contextIds', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
$result = $qb->executeQuery();
while ($row = $result->fetchAssociative()) {
$nodes[(int)$row['context_id']][(int)$row['id']] = [
'id' => (int)$row['id'],
'node_id' => (int)$row['node_id'],
'node_type' => (int)$row['node_type'],
'permissions' => (int)$row['permissions'],
];
}
$result->closeCursor();
}
return $nodes;
}

/**
* Groups the rows of the base query by context and formats each context.
*
* @return Context[]
* @throws Exception
*/
protected function formatContexts(array $rows, ?string $userId, ?int $nodeType = null, ?int $nodeId = null): array {
$rowsByContext = [];
foreach ($rows as $row) {
$rowsByContext[(int)$row['id']][] = $row;
}
$nodes = $this->findNodesForContexts(array_keys($rowsByContext), $nodeType, $nodeId);

$resultEntities = [];
foreach ($rowsByContext as $contextId => $contextRows) {
$resultEntities[] = $this->formatResultRows($contextRows, $userId, $nodes[$contextId] ?? []);
}
return $resultEntities;
}

/**
* @param array<int, array{id: int, node_id: int, node_type: int, permissions: int}> $nodes
*/
protected function formatResultRows(array $rows, ?string $userId, array $nodes) {
$formatted = [
'id' => $rows[0]['id'],
'name' => $rows[0]['name'],
Expand Down Expand Up @@ -109,19 +166,7 @@ protected function formatResultRows(array $rows, ?string $userId) {
return $carry;
}, []);

$formatted['nodes'] = array_reduce($rows, function (array $carry, array $item) {
if ($item['node_rel_id'] === null) {
// empty Context
return $carry;
}
$carry[$item['node_rel_id']] = [
'id' => (int)$item['node_rel_id'],
'node_id' => (int)$item['node_id'],
'node_type' => (int)$item['node_type'],
'permissions' => (int)$item['permissions'],
];
return $carry;
}, []);
$formatted['nodes'] = $nodes;

$formatted['pages'] = array_reduce($rows, function (array $carry, array $item) {
if ($item['page_id'] === null) {
Expand All @@ -131,10 +176,19 @@ 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']]['technical_name'] = $item['page_technical_name'];
if ($item['content_id'] !== null) {
$carry[$item['page_id']]['content'][$item['content_id']] = [
'id' => (int)$item['content_id'],
'order' => (int)$item['order'],
'node_rel_id' => (int)$item['node_rel_id']
'node_rel_id' => $item['content_node_rel_id'] === null ? null : (int)$item['content_node_rel_id'],
'type' => $item['content_type'],
'configuration' => $item['content_configuration'],
'grid_x' => $item['grid_x'] === null ? null : (int)$item['grid_x'],
'grid_y' => $item['grid_y'] === null ? null : (int)$item['grid_y'],
'grid_width' => $item['grid_width'] === null ? null : (int)$item['grid_width'],
'grid_height' => $item['grid_height'] === null ? null : (int)$item['grid_height'],
];
}

Expand All @@ -154,25 +208,7 @@ public function findAll(?string $userId = null): array {
$result = $qb->executeQuery();
$r = $result->fetchAllAssociative();

$contextIds = [];
foreach ($r as $row) {
$contextIds[$row['id']] = 1;
}
$contextIds = array_keys($contextIds);
unset($row);

$resultEntities = [];
foreach ($contextIds as $contextId) {
$workArray = [];
foreach ($r as $row) {
if ((int)$row['id'] === $contextId) {
$workArray[] = $row;
}
}
$resultEntities[] = $this->formatResultRows($workArray, $userId);
}

return $resultEntities;
return $this->formatContexts($r, $userId);
}
public function findForNavBar(string $userId): array {
$qb = $this->getFindContextBaseQuery($userId);
Expand Down Expand Up @@ -204,25 +240,7 @@ public function findForNavBar(string $userId): array {
$result = $qb->executeQuery();
$r = $result->fetchAllAssociative();

$contextIds = [];
foreach ($r as $row) {
$contextIds[$row['id']] = 1;
}
$contextIds = array_keys($contextIds);
unset($row);

$resultEntities = [];
foreach ($contextIds as $contextId) {
$workArray = [];
foreach ($r as $row) {
if ((int)$row['id'] === $contextId) {
$workArray[] = $row;
}
}
$resultEntities[] = $this->formatResultRows($workArray, $userId);
}

return $resultEntities;
return $this->formatContexts($r, $userId);
}

/**
Expand All @@ -240,7 +258,7 @@ public function findById(int $contextId, ?string $userId = null): Context {
throw new NotFoundError('Context does not exist');
}

return $this->formatResultRows($r, $userId);
return $this->formatContexts($r, $userId)[0];
}

/**
Expand All @@ -250,31 +268,17 @@ public function findById(int $contextId, ?string $userId = null): Context {
public function findAllContainingNode(int $nodeType, int $nodeId, string $userId): array {
$qb = $this->getFindContextBaseQuery($userId);

$qb->andWhere($qb->expr()->eq('r.node_id', $qb->createNamedParameter($nodeId)))
->andWhere($qb->expr()->eq('r.node_type', $qb->createNamedParameter($nodeType)));
$containing = $this->db->getQueryBuilder();
$containing->select('context_id')
->from('tables_contexts_rel_context_node')
->where($containing->expr()->eq('node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)))
->andWhere($containing->expr()->eq('node_type', $qb->createNamedParameter($nodeType, IQueryBuilder::PARAM_INT)));
$qb->andWhere($qb->expr()->in('c.id', $qb->createFunction($containing->getSQL())));

$result = $qb->executeQuery();
$r = $result->fetchAllAssociative();

$contextIds = [];
foreach ($r as $row) {
$contextIds[$row['id']] = 1;
}
$contextIds = array_keys($contextIds);
unset($row);

$resultEntities = [];
foreach ($contextIds as $contextId) {
$workArray = [];
foreach ($r as $row) {
if ($row['id'] === $contextId) {
$workArray[] = $row;
}
}
$resultEntities[] = $this->formatResultRows($workArray, $userId);
}

return $resultEntities;
return $this->formatContexts($r, $userId, $nodeType, $nodeId);
}

/**
Expand Down
16 changes: 16 additions & 0 deletions lib/Db/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,45 @@

namespace OCA\Tables\Db;

use OCA\Tables\Helper\TechnicalNameHelper;
use OCP\AppFramework\Db\Entity;

/**
* @method getContextId(): int
* @method setContextId(int $value): void
* @method getPageType(): string
* @method setPageType(string $value): void
* @method getTitle(): ?string
* @method setTitle(?string $value): void
* @method getTechnicalName(): ?string
* @method setTechnicalName(?string $value): void
*/
class Page extends Entity implements \JsonSerializable {
public const TYPE_STARTPAGE = 'startpage';
public const TYPE_GRID = 'grid';
public const TECHNICAL_NAME_PATTERN = TechnicalNameHelper::PATTERN;
public const TECHNICAL_NAME_MAX_LENGTH = TechnicalNameHelper::MAX_LENGTH;

protected ?int $contextId = null;
protected ?string $pageType = null;
protected ?string $title = null;
protected ?string $technicalName = null;

public function __construct() {
$this->addType('id', 'integer');
}

public static function isValidTechnicalName(string $technicalName): bool {
return TechnicalNameHelper::isValid($technicalName);
}

public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'contextId' => $this->getContextId(),
'pageType' => $this->getPageType(),
'title' => $this->getTitle(),
'technicalName' => $this->getTechnicalName(),
];
}
}
32 changes: 30 additions & 2 deletions lib/Db/PageContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,43 @@
/**
* @method getPageId(): int
* @method setPageId(int $value): void
* @method getNodeRelId(): int
* @method setNodeRelId(int $value): void
* @method getNodeRelId(): ?int
* @method setNodeRelId(?int $value): void
* @method getOrder(): int
* @method setOrder(int $value): void
* @method getType(): string
* @method setType(string $value): void
* @method getConfiguration(): ?string
* @method setConfiguration(?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
*/
class PageContent extends Entity implements \JsonSerializable {
protected ?int $pageId = null;
protected ?int $nodeRelId = null;
protected ?int $order = null;
protected ?string $type = null;
protected ?string $configuration = null;
protected ?int $gridX = null;
protected ?int $gridY = null;
protected ?int $gridWidth = null;
protected ?int $gridHeight = null;

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

public function jsonSerialize(): array {
Expand All @@ -36,6 +58,12 @@ public function jsonSerialize(): array {
'pageId' => $this->getPageId(),
'nodeRelId' => $this->getNodeRelId(),
'order' => $this->getOrder(),
'type' => $this->getType(),
'configuration' => $this->getConfiguration(),
'gridX' => $this->getGridX(),
'gridY' => $this->getGridY(),
'gridWidth' => $this->getGridWidth(),
'gridHeight' => $this->getGridHeight(),
];
}
}
Loading
Loading