Skip to content
Draft
456 changes: 230 additions & 226 deletions lib/AppInfo/Application.php

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions lib/ShareReview/ShareReviewListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

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

namespace OCA\Deck\ShareReview;

use OCA\ShareReview\Sources\SourceEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;

/** @template-implements IEventListener<SourceEvent> */
class ShareReviewListener implements IEventListener {
public function __construct() {
}

public function handle(Event $event): void {
if (!$event instanceof SourceEvent) {
return;
}
$event->registerSource(ShareReviewSource::class);
}
}
128 changes: 128 additions & 0 deletions lib/ShareReview/ShareReviewSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

declare(strict_types=1);

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

namespace OCA\Deck\ShareReview;

use OCA\Deck\Db\Acl;
use OCA\ShareReview\Sources\ISource;
use OCP\Constants;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\Share\IShare;
use Psr\Log\LoggerInterface;

class ShareReviewSource implements ISource {

private const ACL_TABLE = 'deck_board_acl';
private const BOARDS_TABLE = 'deck_boards';
private const PERMISSION_MANAGE = 32;

public function __construct(
private IDBConnection $db,
private LoggerInterface $logger,
) {
}

public function getName(): string {
return 'Deck';
}

/**
* @return list<array{id: int, app: string, object: string, initiator: string, type: int, recipient: string, permissions: int, password: bool, time: string, action: string}>
*/
public function getShares(): array {
$rawShares = $this->fetchAllShares();
$appName = $this->getName();
$formatted = [];
foreach ($rawShares as $share) {
$formatted[] = [
'id' => (int)$share['id'],
'app' => $appName,
'object' => $this->resolveObjectName($share),
'initiator' => (string)$share['board_owner'],
'type' => $this->mapParticipantType((int)$share['type']),
'recipient' => (string)$share['participant'],
'permissions' => $this->computePermissions($share),
'password' => false,
'time' => '1970-01-01 01:00:00',

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems we do not have a creation/modification date of share records

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarified, no creation/modification metadata present

'action' => '',
];
}
return $formatted;
}

public function deleteShare(string $shareId): bool {
$this->logger->info('Deck ShareReview: deleting share {id}', ['id' => $shareId]);
try {
$qb = $this->db->getQueryBuilder();
$qb->delete(self::ACL_TABLE)
->where($qb->expr()->eq('id', $qb->createNamedParameter((int)$shareId, IQueryBuilder::PARAM_INT)));
return $qb->executeStatement() > 0;
} catch (Exception $e) {
$this->logger->error('Deck ShareReview: failed to delete share {id}: {message}', ['id' => $shareId, 'message' => $e->getMessage()]);
return false;
}
}

/** @return list<array<string, mixed>> */
private function fetchAllShares(): array {
try {
$qb = $this->db->getQueryBuilder();
$qb->select(
'a.id', 'a.board_id', 'a.type', 'a.participant',
'a.permission_edit', 'a.permission_share', 'a.permission_manage'
)
->selectAlias('b.title', 'board_title')
->selectAlias('b.owner', 'board_owner')
->from(self::ACL_TABLE, 'a')
->leftJoin('a', self::BOARDS_TABLE, 'b', $qb->expr()->eq('a.board_id', 'b.id'))
->orderBy('a.id', 'ASC');
$result = $qb->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
return $rows;
} catch (Exception $e) {
$this->logger->error('Deck ShareReview: failed to fetch shares: {message}', ['message' => $e->getMessage()]);
return [];
}
}

/** @param array<string, mixed> $share */
private function resolveObjectName(array $share): string {
$title = (string)($share['board_title'] ?? '');
$boardId = (int)($share['board_id'] ?? $share['id']);
return ($title !== '' ? $title : "Board $boardId") . ' (Board)';
}

private function mapParticipantType(int $type): int {
return match($type) {
Acl::PERMISSION_TYPE_USER => IShare::TYPE_USER,
Acl::PERMISSION_TYPE_GROUP => IShare::TYPE_GROUP,
Acl::PERMISSION_TYPE_REMOTE => IShare::TYPE_REMOTE,
Acl::PERMISSION_TYPE_CIRCLE => IShare::TYPE_CIRCLE,
default => IShare::TYPE_USER,
};
}

/** @param array<string, mixed> $share */
private function computePermissions(array $share): int {
$permissions = Constants::PERMISSION_READ;
if ($share['permission_edit']) {
$permissions |= Constants::PERMISSION_UPDATE | Constants::PERMISSION_CREATE | Constants::PERMISSION_DELETE;
}
if ($share['permission_share']) {
$permissions |= Constants::PERMISSION_SHARE;
}
if ($share['permission_manage']) {
$permissions |= self::PERMISSION_MANAGE;
}
return $permissions;
}
}
56 changes: 30 additions & 26 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
<?php

/**
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

require_once __DIR__ . '/../../../tests/bootstrap.php';
require_once __DIR__ . '/../appinfo/autoload.php';
<?php

/**
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

require_once __DIR__ . '/../../../tests/bootstrap.php';
require_once __DIR__ . '/../appinfo/autoload.php';

if (!interface_exists('OCA\ShareReview\Sources\ISource')) {
require_once __DIR__ . '/unit/ShareReview/Stubs.php';
}
Loading
Loading