Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5617fe6
feat(Contexts): enable nav bar display logic
blizzz Jul 12, 2024
aeb83f0
fix: apply target userId when setting display mode
blizzz Jul 12, 2024
eca5cf5
fix: have visibility checks already in SQL instead of later filtering
blizzz Jul 22, 2024
4a690c7
fix(Cypress): remove check for navbar header
enjeck Nov 20, 2024
265c77e
fix: treat owners like other users
blizzz Dec 17, 2024
84e26e5
feat(Contexts): consider group shares when determining nav display
blizzz Dec 17, 2024
7f8da4a
feat: add UI elements to change nav display
enjeck Aug 16, 2024
afcbe45
fix: update context navigation display elements
enjeck Aug 22, 2024
e05745a
fix(Context): fix wrong parameters to permission check
blizzz Sep 4, 2024
6a693aa
fix(DB): override mapper::update() where tables has no id column
blizzz Sep 4, 2024
04cd53e
fix: use NcDialog
enjeck Sep 9, 2024
41b5b24
fix: properly update navigation display via checkbox
enjeck Sep 19, 2024
4be1a7c
enh: add share to oneself to control context nav display
enjeck Sep 19, 2024
bd37daf
enh: use checkbox for default nav display mode
enjeck Sep 20, 2024
1ab79e7
enh: modify display mode update for owner
enjeck Oct 3, 2024
b00f1c0
fix(Navigation): use default display mode when there is no user override
blizzz Oct 4, 2024
454844e
fix(DB): NavigationMapper has to override insert due to not having an id
blizzz Oct 4, 2024
efab19c
fix: update display mode when editing context
enjeck Dec 16, 2024
f4f3277
enh: set visibility also when context is provided by group shares
blizzz Dec 17, 2024
4ab44af
feat(Contexts): dynamic navigation bar update on config toggle
blizzz Dec 17, 2024
e1e3737
enh: remove usages of NAV_ENTRY_MODE_RECIPIENTS
enjeck Dec 30, 2024
30748c4
fix: add copyright and update openapi
enjeck Jan 4, 2025
c925f42
ci: suppress psalm warning against private NavigationController class
blizzz Jan 6, 2025
4d5a063
fix: Make labels translatable
juliusknorr Jan 9, 2025
76b51d8
Merge pull request #1295 from nextcloud/nav-display-frontend
blizzz Jan 9, 2025
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: 2 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@
['name' => 'search#all', 'url' => '/search/all', 'verb' => 'GET'],
],
'ocs' => [
['name' => 'navigation#getAppsNavigation', 'url' => '/navigation', 'verb' => 'GET'],

// API v2
['name' => 'ApiGeneral#index', 'url' => '/api/2/init', 'verb' => 'GET'],
// -> tables
Expand Down
3 changes: 0 additions & 3 deletions cypress/e2e/context.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ describe('Manage a context', () => {
cy.login(nonLocalUser)
cy.visit('apps/tables')
cy.loadContext(contextTitle)
cy.contains('header .app-menu-entry', contextTitle).should('exist')
cy.contains('h1', contextTitle).should('exist')
cy.contains('h1', tableTitle).should('exist')

Expand All @@ -99,7 +98,6 @@ describe('Manage a context', () => {
cy.login(nonLocalUser)
cy.visit('apps/tables')
cy.loadContext(contextTitle)
cy.contains('header .app-menu-entry', contextTitle).should('exist')
cy.contains('h1', contextTitle).should('exist')
})

Expand Down Expand Up @@ -190,7 +188,6 @@ describe('Manage a context', () => {
cy.login(nonLocalUser)
cy.visit('apps/tables')
cy.loadContext(contextTitle)
cy.contains('header .app-menu-entry', contextTitle).should('exist')
cy.contains('h1', contextTitle).should('exist')
cy.contains('h1', tableTitle).should('exist')
cy.get('button').contains('Create row').click()
Expand Down
43 changes: 43 additions & 0 deletions lib/Controller/NavigationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

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

namespace OCA\Tables\Controller;

use OCA\Tables\Service\ContextService;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\DataResponse;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;

/**
* This is a workaround until https://github.com/nextcloud/server/pull/49904 is
* settled in all covered NC versions; expected >= 31.
*/
class NavigationController extends \OC\Core\Controller\NavigationController {
public function __construct(
protected ContextService $contextService,
protected IUserSession $userSession,
string $appName,
IRequest $request,
INavigationManager $navigationManager,
IURLGenerator $urlGenerator
) {
parent::__construct($appName, $request, $navigationManager, $urlGenerator);
}

#[NoAdminRequired]
#[NoCSRFRequired]
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
public function getAppsNavigation(bool $absolute = false): DataResponse {
$this->contextService->addToNavigation($this->userSession->getUser()?->getUID());
return parent::getAppsNavigation($absolute);
}
}
31 changes: 24 additions & 7 deletions lib/Db/ContextMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ protected function formatResultRows(array $rows, ?string $userId) {
'display_mode_default' => (int)$item['display_mode_default'],
];
if ($userId !== null) {
if ($item['display_mode'] === null) {
$item['display_mode'] = $item['display_mode_default'];
}
$carry[$item['share_id']]['display_mode'] = (int)$item['display_mode'];
}
return $carry;
Expand Down Expand Up @@ -168,17 +171,31 @@ public function findAll(?string $userId = null): array {

return $resultEntities;
}

public function findForNavBar(string $userId): array {
$qb = $this->getFindContextBaseQuery($userId);
$qb->andWhere($qb->expr()->andX(
$groupIDs = $this->userHelper->getGroupIdsForUser($userId);
$qb->andWhere($qb->expr()->orX(
// default
$qb->expr()->gt('n.display_mode', $qb->createNamedParameter(Application::NAV_ENTRY_MODE_HIDDEN, IQueryBuilder::PARAM_INT)),
// user override
$qb->expr()->orX(
$qb->expr()->gt('n2.display_mode', $qb->createNamedParameter(Application::NAV_ENTRY_MODE_HIDDEN, IQueryBuilder::PARAM_INT)),
$qb->expr()->andX(
// requires lack of user overwrite, indicated by n2.display_mode
$qb->expr()->isNull('n2.display_mode'),
)
// requires a display mode also depending on the role…
$qb->expr()->orX(
// not an owner: requires (RECIPIENT or) ALL
$qb->expr()->andX(
// groups are not considered, yet
$qb->expr()->neq('c.owner_id', $qb->createNamedParameter($userId)),
$qb->expr()->gt('n.display_mode', $qb->createNamedParameter(Application::NAV_ENTRY_MODE_HIDDEN, IQueryBuilder::PARAM_INT)),
),
$qb->expr()->andX(
$qb->expr()->eq('s.receiver_type', $qb->createNamedParameter('group')),
$qb->expr()->in('s.receiver', $qb->createNamedParameter($groupIDs, IQueryBuilder::PARAM_STR_ARRAY)),
$qb->expr()->gt('n.display_mode', $qb->createNamedParameter(Application::NAV_ENTRY_MODE_HIDDEN, IQueryBuilder::PARAM_INT)),
)
),
),
// user override
$qb->expr()->gt('n2.display_mode', $qb->createNamedParameter(Application::NAV_ENTRY_MODE_HIDDEN, IQueryBuilder::PARAM_INT)),
));

$result = $qb->executeQuery();
Expand Down
60 changes: 60 additions & 0 deletions lib/Db/ContextNavigationMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
namespace OCA\Tables\Db;

use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
Expand Down Expand Up @@ -41,4 +42,63 @@ public function setDisplayModeByShareId(int $shareId, int $displayMode, string $

return $this->insertOrUpdate($entity);
}

// we have to overwrite QBMapper`s insert() because we do not have
// an id column in this table. Sad.
public function insert(Entity $entity): Entity {
// get updated fields to save, fields have to be set using a setter to
// be saved
$properties = $entity->getUpdatedFields();

$qb = $this->db->getQueryBuilder();
$qb->insert($this->tableName);

// build the fields
foreach ($properties as $property => $updated) {
$column = $entity->propertyToColumn($property);
$getter = 'get' . ucfirst($property);
$value = $entity->$getter();

$type = $this->getParameterTypeForProperty($entity, $property);
$qb->setValue($column, $qb->createNamedParameter($value, $type));
}

$qb->executeStatement();

return $entity;
}

// we have to overwrite QBMapper`s update() because we do not have
// an id column in this table. Sad.
public function update(Entity $entity): ContextNavigation {
if (!$entity instanceof ContextNavigation) {
throw new \LogicException('Can only update context navigation entities');
}

// if entity wasn't changed it makes no sense to run a db query
$properties = $entity->getUpdatedFields();
if (\count($properties) === 0) {
return $entity;
}

$qb = $this->db->getQueryBuilder();
$qb->update($this->tableName);

// build the fields
foreach ($properties as $property => $updated) {
$column = $entity->propertyToColumn($property);
$getter = 'get' . ucfirst($property);
$value = $entity->$getter();

$type = $this->getParameterTypeForProperty($entity, $property);
$qb->set($column, $qb->createNamedParameter($value, $type));
}

$qb->where($qb->expr()->eq('share_id', $qb->createNamedParameter($entity->getShareId(), IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($entity->getUserId(), IQueryBuilder::PARAM_STR)));

$qb->executeStatement();

return $entity;
}
}
58 changes: 2 additions & 56 deletions lib/Listener/BeforeTemplateRenderedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,18 @@

namespace OCA\Tables\Listener;

use OCA\Tables\AppInfo\Application;
use OCA\Tables\Service\ContextService;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\INavigationManager;
use OCP\IURLGenerator;
use OCP\IUserSession;

/**
* @template-implements IEventListener<Event|BeforeTemplateRenderedEvent>
*/
class BeforeTemplateRenderedListener implements IEventListener {
public function __construct(
protected INavigationManager $navigationManager,
protected IURLGenerator $urlGenerator,
protected IUserSession $userSession,
protected IUserSession $userSession,
protected ContextService $contextService,
) {
}
Expand All @@ -41,55 +36,6 @@ public function handle(Event $event): void {
return;
}

// temporarily show all
//$contexts = $this->contextService->findForNavigation($user->getUID());
$contexts = $this->contextService->findAll($user->getUID());
foreach ($contexts as $context) {
/* temporarily, show all
if ($context->getOwnerType() === Application::OWNER_TYPE_USER
&& $context->getOwnerId() === $user->getUID()) {


// filter out entries for owners unless it is set to be visible
$skipEntry = true;
foreach ($context->getSharing() as $shareInfo) {
// TODO: integrate into DB query in Mapper

if (isset($shareInfo['display_mode']) && $shareInfo['display_mode'] === Application::NAV_ENTRY_MODE_ALL) {
// a custom override makes it visible
$skipEntry = false;
break;
} elseif (!isset($shareInfo['display_mode']) && $shareInfo['display_mode_default'] === Application::NAV_ENTRY_MODE_ALL) {
// no custom override, and visible also for owner by default
$skipEntry = false;
break;
}
}
if ($skipEntry) {
continue;
}
}
*/

$this->navigationManager->add(function () use ($context) {
$iconRelPath = 'material/' . $context->getIcon() . '.svg';
if (file_exists(__DIR__ . '/../../img/' . $iconRelPath)) {
$iconUrl = $this->urlGenerator->imagePath(Application::APP_ID, $iconRelPath);
} else {
$iconUrl = $this->urlGenerator->imagePath('core', 'places/default-app-icon.svg');
}

$contextUrl = $this->urlGenerator->linkToRoute('tables.page.context', ['contextId' => $context->getId()]);

return [
'id' => Application::APP_ID . '_application_' . $context->getId(),
'name' => $context->getName(),
'href' => $contextUrl,
'icon' => $iconUrl,
'order' => 500,
'type' => 'link',
];
});
}
$this->contextService->addToNavigation($user->getUID());
}
}
75 changes: 41 additions & 34 deletions lib/Service/ContextService.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,49 +27,31 @@
use OCP\DB\Exception;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection;
use OCP\INavigationManager;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Log\Audit\CriticalActionPerformedEvent;
use Psr\Log\LoggerInterface;

class ContextService {

private ContextMapper $contextMapper;
private bool $isCLI;
private LoggerInterface $logger;
private ContextNodeRelationMapper $contextNodeRelMapper;
private PageMapper $pageMapper;
private PageContentMapper $pageContentMapper;
private PermissionsService $permissionsService;
private IUserManager $userManager;
private IEventDispatcher $eventDispatcher;
private IDBConnection $dbc;
private ShareService $shareService;

public function __construct(
ContextMapper $contextMapper,
ContextNodeRelationMapper $contextNodeRelationMapper,
PageMapper $pageMapper,
PageContentMapper $pageContentMapper,
LoggerInterface $logger,
PermissionsService $permissionsService,
IUserManager $userManager,
IEventDispatcher $eventDispatcher,
IDBConnection $dbc,
ShareService $shareService,
bool $isCLI,
private ContextMapper $contextMapper,
private ContextNodeRelationMapper $contextNodeRelMapper,
private PageMapper $pageMapper,
private PageContentMapper $pageContentMapper,
private LoggerInterface $logger,
private PermissionsService $permissionsService,
private IUserManager $userManager,
private IEventDispatcher $eventDispatcher,
private IDBConnection $dbc,
private ShareService $shareService,
private bool $isCLI,
protected INavigationManager $navigationManager,
protected IURLGenerator $urlGenerator,
) {
$this->contextMapper = $contextMapper;
$this->isCLI = $isCLI;
$this->logger = $logger;
$this->contextNodeRelMapper = $contextNodeRelationMapper;
$this->pageMapper = $pageMapper;
$this->pageContentMapper = $pageContentMapper;
$this->permissionsService = $permissionsService;
$this->userManager = $userManager;
$this->eventDispatcher = $eventDispatcher;
$this->dbc = $dbc;
$this->shareService = $shareService;
}

use TTransactional;

/**
Expand All @@ -93,6 +75,31 @@ public function findForNavigation(string $userId): array {
return $this->contextMapper->findForNavBar($userId);
}

public function addToNavigation(string $userId): void {
$contexts = $this->findForNavigation($userId);
foreach ($contexts as $context) {
$this->navigationManager->add(function () use ($context) {
$iconRelPath = 'material/' . $context->getIcon() . '.svg';
if (file_exists(__DIR__ . '/../../img/' . $iconRelPath)) {
$iconUrl = $this->urlGenerator->imagePath(Application::APP_ID, $iconRelPath);
} else {
$iconUrl = $this->urlGenerator->imagePath('core', 'places/default-app-icon.svg');
}

$contextUrl = $this->urlGenerator->linkToRoute('tables.page.context', ['contextId' => $context->getId()]);

return [
'id' => Application::APP_ID . '_application_' . $context->getId(),
'name' => $context->getName(),
'href' => $contextUrl,
'icon' => $iconUrl,
'order' => 500,
'type' => 'link',
];
});
}
}

/**
* @throws Exception
* @throws InternalError
Expand Down
4 changes: 2 additions & 2 deletions lib/Service/ShareService.php
Original file line number Diff line number Diff line change
Expand Up @@ -339,12 +339,12 @@ public function updateDisplayMode(int $shareId, int $displayMode, string $userId
}
} else {
// setting user display mode override only requires access
if (!$this->permissionsService->canAccessContextById($item->getId())) {
if (!$this->permissionsService->canAccessContextById($item->getNodeId(), $userId)) {
throw new PermissionError(sprintf('PermissionError: can not update share with id %d', $shareId));
}
}

return $this->contextNavigationMapper->setDisplayModeByShareId($shareId, $displayMode, '');
return $this->contextNavigationMapper->setDisplayModeByShareId($shareId, $displayMode, $userId);
} catch (DoesNotExistException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new NotFoundError(get_class($this) . ' - ' . __FUNCTION__ . ': '.$e->getMessage());
Expand Down
Loading
Loading