diff --git a/appinfo/info.xml b/appinfo/info.xml
index e3070c31d..b80cd8257 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -57,6 +57,7 @@ Have a good time and manage whatever you want.
OCA\Tables\Command\RemoveTable
OCA\Tables\Command\RenameTable
OCA\Tables\Command\ChangeOwnershipTable
+ OCA\Tables\Command\ListContexts
OCA\Tables\Command\Clean
OCA\Tables\Command\CleanLegacy
OCA\Tables\Command\TransferLegacyRows
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 5df857fd7..75a185db0 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -123,5 +123,7 @@
['name' => 'ApiColumns#createTextColumn', 'url' => '/api/2/columns/text', 'verb' => 'POST'],
['name' => 'ApiColumns#createSelectionColumn', 'url' => '/api/2/columns/selection', 'verb' => 'POST'],
['name' => 'ApiColumns#createDatetimeColumn', 'url' => '/api/2/columns/datetime', 'verb' => 'POST'],
+
+ ['name' => 'Contexts#index', 'url' => '/api/3/contexts', 'verb' => 'GET'],
]
];
diff --git a/lib/Command/ListContexts.php b/lib/Command/ListContexts.php
new file mode 100644
index 000000000..fdc427981
--- /dev/null
+++ b/lib/Command/ListContexts.php
@@ -0,0 +1,87 @@
+contextService = $contextService;
+ $this->logger = $logger;
+ $this->config = $config;
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('tables:contexts:list')
+ ->setDescription('Get all contexts or contexts available to a specified user')
+ ->addArgument(
+ 'user-id',
+ InputArgument::OPTIONAL,
+ 'User ID of the user'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $userId = trim($input->getArgument('user-id'));
+ if ($userId === '') {
+ $userId = null;
+ }
+
+ try {
+ $contexts = $this->contextService->findAll($userId);
+ } catch (InternalError|Exception $e) {
+ $output->writeln('Error while reading contexts from DB.');
+ $this->logger->warning('Following error occurred during executing occ command "{class}"',
+ [
+ 'app' => 'tables',
+ 'class' => self::class,
+ 'exception' => $e,
+ ]
+ );
+ if ($this->config->getSystemValueBool('debug', false)) {
+ $output->writeln(sprintf('%s', $e->getMessage()));
+ $output->writeln('');
+ debug_print_backtrace();
+ $output->writeln('');
+ }
+ return 1;
+ }
+
+ foreach ($contexts as $context) {
+ $contextArray = json_decode(json_encode($context), true);
+
+ $contextArray['ownerType'] = match ($contextArray['ownerType']) {
+ 1 => 'group',
+ default => 'user',
+ };
+
+ $out = ['ID ' . $contextArray['id'] => $contextArray];
+ unset($out[$contextArray['id']]['id']);
+ $this->writeArrayInOutputFormat($input, $output, $out);
+ }
+
+ return 0;
+ }
+}
diff --git a/lib/Controller/ContextsController.php b/lib/Controller/ContextsController.php
new file mode 100644
index 000000000..724fbc143
--- /dev/null
+++ b/lib/Controller/ContextsController.php
@@ -0,0 +1,66 @@
+contextService = $contextService;
+ $this->userId = $userId;
+ }
+
+ /**
+ * [api v3] Get all contexts available to the requesting person
+ *
+ * Return an empty array if no contexts were found
+ *
+ * @return DataResponse|DataResponse
+ *
+ * 200: reporting in available contexts
+ *
+ * @NoAdminRequired
+ */
+ public function index(): DataResponse {
+ try {
+ $contexts = $this->contextService->findAll($this->userId);
+ return new DataResponse($this->contextsToArray($contexts));
+ } catch (InternalError|Exception $e) {
+ return $this->handleError($e);
+ }
+ }
+
+ /**
+ * @param Context[] $contexts
+ * @return array
+ */
+ protected function contextsToArray(array $contexts): array {
+ $result = [];
+ foreach ($contexts as $context) {
+ $result[] = $context->jsonSerialize();
+ }
+ return $result;
+ }
+}
diff --git a/lib/Db/Context.php b/lib/Db/Context.php
new file mode 100644
index 000000000..ef23e9284
--- /dev/null
+++ b/lib/Db/Context.php
@@ -0,0 +1,41 @@
+addType('id', 'integer');
+ }
+
+ public function jsonSerialize(): array {
+ return [
+ 'id' => $this->getId(),
+ 'name' => $this->getName(),
+ 'iconName' => $this->getIcon(),
+ 'description' => $this->getDescription(),
+ 'owner' => $this->getOwnerId(),
+ 'ownerType' => $this->getOwnerType()
+ ];
+ }
+}
diff --git a/lib/Db/ContextMapper.php b/lib/Db/ContextMapper.php
new file mode 100644
index 000000000..d328c5735
--- /dev/null
+++ b/lib/Db/ContextMapper.php
@@ -0,0 +1,64 @@
+ */
+class ContextMapper extends QBMapper {
+ protected string $table = 'tables_contexts_context';
+ private UserHelper $userHelper;
+
+ public function __construct(IDBConnection $db, UserHelper $userHelper) {
+ $this->userHelper = $userHelper;
+ parent::__construct($db, $this->table, Context::class);
+ }
+
+ /**
+ * @return Context[]
+ * @throws Exception
+ */
+ public function findAll(?string $userId = null): array {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('c.*')
+ ->from($this->table, 'c');
+ if ($userId !== null) {
+ $sharedToConditions = $qb->expr()->orX();
+
+ // shared to user clause
+ $userShare = $qb->expr()->andX(
+ $qb->expr()->eq('s.receiver_type', $qb->createNamedParameter('user')),
+ $qb->expr()->eq('s.receiver', $qb->createNamedParameter($userId)),
+ );
+ $sharedToConditions->add($userShare);
+
+ // shared to group clause
+ $groupIDs = $this->userHelper->getGroupIdsForUser($userId);
+ if (!empty($groupIDs)) {
+ $groupShares = $qb->expr()->andX(
+ $qb->expr()->eq('s.receiver_type', $qb->createNamedParameter('group')),
+ $qb->expr()->in('s.receiver', $qb->createNamedParameter($groupIDs, IQueryBuilder::PARAM_STR_ARRAY)),
+ );
+ $sharedToConditions->add($groupShares);
+ }
+
+ // owned contexts + apply share conditions
+ $qb->leftJoin('c', 'tables_shares', 's', $qb->expr()->andX(
+ $qb->expr()->eq('c.id', 's.node_id'),
+ $qb->expr()->eq('s.node_type', $qb->createNamedParameter('context')),
+ $sharedToConditions,
+ ));
+
+ $qb->where($qb->expr()->orX(
+ $qb->expr()->eq('owner_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)),
+ $qb->expr()->isNotNull('s.receiver'),
+ ));
+ }
+
+ return $this->findEntities($qb);
+ }
+}
diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php
index 42668bce5..d3dee211f 100644
--- a/lib/ResponseDefinitions.php
+++ b/lib/ResponseDefinitions.php
@@ -125,6 +125,15 @@
* errors_parsing_count: int,
* errors_count: int,
* }
+ *
+ * @psalm-type TablesContext = array{
+ * id: int,
+ * name: string,
+ * iconName: string,
+ * description: string,
+ * owner: string,
+ * ownerType: int,
+ * }
*/
class ResponseDefinitions {
}
diff --git a/lib/Service/ContextService.php b/lib/Service/ContextService.php
new file mode 100644
index 000000000..f1190fe2d
--- /dev/null
+++ b/lib/Service/ContextService.php
@@ -0,0 +1,43 @@
+mapper = $mapper;
+ $this->isCLI = $isCLI;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @throws InternalError
+ * @throws Exception
+ * @return Context[]
+ */
+ public function findAll(?string $userId): array {
+ if ($userId !== null && trim($userId) === '') {
+ $userId = null;
+ }
+ if ($userId === null && !$this->isCLI) {
+ $error = 'Try to set no user in context, but request is not allowed.';
+ $this->logger->warning($error);
+ throw new InternalError($error);
+ }
+ return $this->mapper->findAll($userId);
+ }
+}
diff --git a/openapi.json b/openapi.json
index cf542b1fb..82b1e5665 100644
--- a/openapi.json
+++ b/openapi.json
@@ -168,6 +168,39 @@
}
}
},
+ "Context": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "iconName",
+ "description",
+ "owner",
+ "ownerType"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ },
+ "iconName": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "owner": {
+ "type": "string"
+ },
+ "ownerType": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ },
"ImportState": {
"type": "object",
"required": [
@@ -534,9 +567,8 @@
}
},
"sort": {
- "type": "object",
- "nullable": true,
- "additionalProperties": {
+ "type": "array",
+ "items": {
"type": "object",
"required": [
"columnId",
@@ -558,48 +590,50 @@
}
},
"filter": {
- "type": "object",
- "nullable": true,
- "additionalProperties": {
- "type": "object",
- "required": [
- "columnId",
- "operator",
- "value"
- ],
- "properties": {
- "columnId": {
- "type": "integer",
- "format": "int64"
- },
- "operator": {
- "type": "string",
- "enum": [
- "begins-with",
- "ends-with",
- "contains",
- "is-equal",
- "is-greater-than",
- "is-greater-than-or-equal",
- "is-lower-than",
- "is-lower-than-or-equal",
- "is-empty"
- ]
- },
- "value": {
- "oneOf": [
- {
- "type": "string"
- },
- {
- "type": "integer",
- "format": "int64"
- },
- {
- "type": "number",
- "format": "float"
- }
- ]
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "columnId",
+ "operator",
+ "value"
+ ],
+ "properties": {
+ "columnId": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "begins-with",
+ "ends-with",
+ "contains",
+ "is-equal",
+ "is-greater-than",
+ "is-greater-than-or-equal",
+ "is-lower-than",
+ "is-lower-than-or-equal",
+ "is-empty"
+ ]
+ },
+ "value": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "integer",
+ "format": "int64"
+ },
+ {
+ "type": "number",
+ "format": "float"
+ }
+ ]
+ }
}
}
}
@@ -1865,7 +1899,11 @@
"description": "New permission value",
"required": true,
"schema": {
- "type": "integer"
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -2104,7 +2142,11 @@
"description": "Permission if receiver can read data",
"required": true,
"schema": {
- "type": "integer"
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -2113,7 +2155,11 @@
"description": "Permission if receiver can create data",
"required": true,
"schema": {
- "type": "integer"
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -2122,7 +2168,11 @@
"description": "Permission if receiver can update data",
"required": true,
"schema": {
- "type": "integer"
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -2131,7 +2181,11 @@
"description": "Permission if receiver can delete data",
"required": true,
"schema": {
- "type": "integer"
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -2140,7 +2194,11 @@
"description": "Permission if receiver can manage table",
"required": true,
"schema": {
- "type": "integer"
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -2286,7 +2344,11 @@
"description": "Permission if receiver can read data",
"schema": {
"type": "integer",
- "default": 0
+ "default": 0,
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -2295,7 +2357,11 @@
"description": "Permission if receiver can create data",
"schema": {
"type": "integer",
- "default": 0
+ "default": 0,
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -2304,7 +2370,11 @@
"description": "Permission if receiver can update data",
"schema": {
"type": "integer",
- "default": 0
+ "default": 0,
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -2313,7 +2383,11 @@
"description": "Permission if receiver can delete data",
"schema": {
"type": "integer",
- "default": 0
+ "default": 0,
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -2322,7 +2396,11 @@
"description": "Permission if receiver can manage node",
"schema": {
"type": "integer",
- "default": 0
+ "default": 0,
+ "enum": [
+ 0,
+ 1
+ ]
}
}
],
@@ -2549,7 +2627,11 @@
"description": "Is the column mandatory",
"required": true,
"schema": {
- "type": "integer"
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -2937,7 +3019,11 @@
"description": "Is the column mandatory",
"required": true,
"schema": {
- "type": "integer"
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -3185,7 +3271,11 @@
"description": "Is the column mandatory",
"required": true,
"schema": {
- "type": "integer"
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -4521,7 +4611,11 @@
"description": "Create missing columns",
"schema": {
"type": "integer",
- "default": 1
+ "default": 1,
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -4631,7 +4725,11 @@
"description": "Create missing columns",
"schema": {
"type": "integer",
- "default": 1
+ "default": 1,
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -6326,7 +6424,11 @@
"description": "Is mandatory",
"schema": {
"type": "integer",
- "default": 0
+ "default": 0,
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -6612,7 +6714,11 @@
"description": "Is mandatory",
"schema": {
"type": "integer",
- "default": 0
+ "default": 0,
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -6888,7 +6994,11 @@
"description": "Is mandatory",
"schema": {
"type": "integer",
- "default": 0
+ "default": 0,
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -7159,7 +7269,11 @@
"description": "Is mandatory",
"schema": {
"type": "integer",
- "default": 0
+ "default": 0,
+ "enum": [
+ 0,
+ 1
+ ]
}
},
{
@@ -7338,7 +7452,110 @@
}
}
}
+ },
+ "/ocs/v2.php/apps/tables/api/3/contexts": {
+ "get": {
+ "operationId": "contexts-list",
+ "summary": "[api v3] Get all contexts available to the requesting person",
+ "description": "Return an empty array if no contexts were found",
+ "tags": [
+ "contexts"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "description": "Required to be true for the API request to pass",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "reporting in available contexts",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Context"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "message"
+ ],
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
},
"tags": []
-}
+}
\ No newline at end of file
diff --git a/psalm.xml b/psalm.xml
index 745419a77..651516ea3 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -36,6 +36,7 @@
+