Skip to content
Merged
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
20 changes: 12 additions & 8 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@
use OCA\Bookmarks\Reference\BookmarkReferenceProvider;
use OCA\Bookmarks\Search\Provider;
use OCA\Bookmarks\Service\TreeCacheManager;
use OCA\ContextChat\Event\ContentProviderRegisterEvent;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\Collaboration\Reference\RenderReferenceEvent;
use OCP\ContextChat\Events\ContentProviderRegisterEvent;
use OCP\ContextChat\IContentManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Group\Events\BeforeGroupDeletedEvent;
use OCP\Group\Events\UserAddedEvent;
Expand Down Expand Up @@ -104,15 +105,18 @@ public function register(IRegistrationContext $context): void {
*/
public function boot(IBootContext $context): void {
// Register with ContextChat
if (class_exists(ContentProviderRegisterEvent::class)) {
$this->getContainer()->get(ContextChatProvider::class)->register();
$eventDispatcher = $this->getContainer()->get(IEventDispatcher::class);
$eventDispatcher->addServiceListener(InsertEvent::class, ContextChatProvider::class);
$eventDispatcher->addServiceListener(ManipulateEvent::class, ContextChatProvider::class);
$eventDispatcher->addServiceListener(BeforeDeleteEvent::class, ContextChatProvider::class);
$serverContainer = $context->getServerContainer();
if ($serverContainer->has(IContentManager::class)) {
$contentManager = $serverContainer->get(IContentManager::class);
if ($contentManager->isContextChatAvailable()) {
$eventDispatcher = $this->getContainer()->get(IEventDispatcher::class);
$eventDispatcher->addServiceListener(InsertEvent::class, ContextChatProvider::class);
$eventDispatcher->addServiceListener(ManipulateEvent::class, ContextChatProvider::class);
$eventDispatcher->addServiceListener(BeforeDeleteEvent::class, ContextChatProvider::class);
}
}
// Register with Nextcloud Flow
$container = $context->getServerContainer();
$container = $serverContainer;
CreateBookmark::register($container->get(IEventDispatcher::class));
}
}
8 changes: 4 additions & 4 deletions lib/BackgroundJobs/ContextChatIndexJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@
use OCA\Bookmarks\ContextChat\ContextChatProvider;
use OCA\Bookmarks\Service\BookmarkService;
use OCA\Bookmarks\Service\UserSettingsService;
use OCA\ContextChat\Public\ContentItem;
use OCA\ContextChat\Public\ContentManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\QueuedJob;
use OCP\ContextChat\ContentItem;
use OCP\ContextChat\IContentManager;
use OCP\IUserManager;

class ContextChatIndexJob extends QueuedJob {

public function __construct(
ITimeFactory $timeFactory,
private BookmarkService $bookmarkService,
private ?ContentManager $contentManager,
private ?IContentManager $contentManager,
private IUserManager $userManager,
private ContextChatProvider $provider,
private UserSettingsService $userSettings,
Expand All @@ -32,7 +32,7 @@ public function __construct(
}

protected function run($argument) {
if ($this->contentManager === null) {
if ($this->contentManager === null || !$this->contentManager->isContextChatAvailable()) {
return;
}
if (!isset($argument['user'])) {
Expand Down
31 changes: 22 additions & 9 deletions lib/ContextChat/ContextChatProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
use OCA\Bookmarks\Events\ManipulateEvent;
use OCA\Bookmarks\Service\BookmarkService;
use OCA\Bookmarks\Service\UserSettingsService;
use OCA\ContextChat\Event\ContentProviderRegisterEvent;
use OCA\ContextChat\Public\ContentItem;
use OCA\ContextChat\Public\ContentManager;
use OCA\ContextChat\Public\IContentProvider;
use OCP\BackgroundJob\IJobList;
use OCP\ContextChat\ContentItem;
use OCP\ContextChat\Events\ContentProviderRegisterEvent;
use OCP\ContextChat\IContentManager;
use OCP\ContextChat\IContentProvider;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IUser;
Expand All @@ -35,18 +35,18 @@ class ContextChatProvider implements IContentProvider, IEventListener {
public function __construct(
private BookmarkService $bookmarkService,
private IUserManager $userManager,
private ?ContentManager $contentManager,
private ?IContentManager $contentManager,
private IJobList $jobList,
private UserSettingsService $userSettings,
) {
}

public function handle(Event $event): void {
if ($this->contentManager === null) {
if ($event instanceof ContentProviderRegisterEvent) {
$this->register($event);
return;
}
if ($event instanceof ContentProviderRegisterEvent) {
$this->register();
if (!$this->isContextChatAvailable()) {
return;
}
if (!$event instanceof ChangeEvent) {
Expand Down Expand Up @@ -84,10 +84,23 @@ public function handle(Event $event): void {
}
}

public function register(): void {
public function register(?ContentProviderRegisterEvent $event = null): void {
if ($event !== null) {
$event->registerContentProvider($this->getAppId(), $this->getId(), self::class);
return;
}

if (!$this->isContextChatAvailable()) {
return;
}

$this->contentManager->registerContentProvider($this->getAppId(), $this->getId(), self::class);
}

private function isContextChatAvailable(): bool {
return $this->contentManager !== null && $this->contentManager->isContextChatAvailable();
}

/**
* The ID of the provider
*
Expand Down
6 changes: 3 additions & 3 deletions lib/Controller/WebViewController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
use OCA\Bookmarks\Service\SettingsService;
use OCA\Bookmarks\Service\UserSettingsService;
use OCA\Viewer\Event\LoadViewer;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
Expand All @@ -31,6 +30,7 @@
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\StreamResponse;
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
use OCP\ContextChat\IContentManager;
use OCP\DB\Exception;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
Expand All @@ -57,7 +57,7 @@ public function __construct(
private BookmarkMapper $bookmarkMapper,
private UserSettingsService $userSettingsService,
private SettingsService $settings,
private IAppManager $appManager,
private ?IContentManager $contentManager,
private IConfig $config,
private IEventDispatcher $eventDispatcher,
private LoggerInterface $logger,
Expand Down Expand Up @@ -108,7 +108,7 @@ public function index(): AugmentedTemplateResponse {
}

$this->initialState->provideInitialState($this->appName, 'tags', $this->tagsController->fullTags(true)->getData());
$this->initialState->provideInitialState($this->appName, 'contextChatInstalled', $this->appManager->isEnabledForUser('context_chat'));
$this->initialState->provideInitialState($this->appName, 'contextChatInstalled', $this->contentManager?->isContextChatAvailable() ?? false);
$this->initialState->provideInitialState($this->appName, 'appStoreEnabled', $this->config->getSystemValueBool('appstoreenabled', true));

$settings = $this->userSettingsService->toArray();
Expand Down
169 changes: 169 additions & 0 deletions tests/ContextChatIndexJobTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
<?php

/*
* Copyright (c) 2026. The Nextcloud Bookmarks contributors.
*
* This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
*/

declare(strict_types=1);

namespace OCA\Bookmarks\Tests;

use OCA\Bookmarks\AppInfo\Application;
use OCA\Bookmarks\BackgroundJobs\ContextChatIndexJob;
use OCA\Bookmarks\ContextChat\ContextChatProvider;
use OCA\Bookmarks\Db\Bookmark;
use OCA\Bookmarks\Service\BookmarkService;
use OCA\Bookmarks\Service\UserSettingsService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\ContextChat\ContentItem;
use OCP\ContextChat\IContentManager;
use OCP\IUser;
use OCP\IUserManager;
use PHPUnit\Framework\MockObject\MockObject;

class ContextChatIndexJobTest extends TestCase {
private ITimeFactory&MockObject $timeFactory;
private BookmarkService&MockObject $bookmarkService;
private IContentManager&MockObject $contentManager;
private IUserManager&MockObject $userManager;
private ContextChatProvider&MockObject $provider;
private UserSettingsService&MockObject $userSettings;
private IJobList&MockObject $jobList;
private ContextChatIndexJob $job;

protected function setUp(): void {
parent::setUp();

$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->timeFactory->method('getTime')->willReturn(1710000000);
$this->bookmarkService = $this->createMock(BookmarkService::class);
$this->contentManager = $this->createMock(IContentManager::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->provider = $this->createMock(ContextChatProvider::class);
$this->userSettings = $this->createMock(UserSettingsService::class);
$this->jobList = $this->createMock(IJobList::class);

$this->job = new ContextChatIndexJob(
$this->timeFactory,
$this->bookmarkService,
$this->contentManager,
$this->userManager,
$this->provider,
$this->userSettings,
);
$this->job->setArgument(['user' => 'alice']);
}

public function testDoesNothingWhenContextChatUnavailable(): void {
$this->contentManager->expects(self::once())
->method('isContextChatAvailable')
->willReturn(false);
$this->userManager->expects(self::never())->method('get');
$this->contentManager->expects(self::never())->method('submitContent');
$this->jobList->expects(self::once())->method('remove');
$this->jobList->expects(self::once())->method('setLastRun');
$this->jobList->expects(self::once())->method('setExecutionTime');

$this->job->start($this->jobList);
}

public function testIndexesBookmarksForEnabledUser(): void {
$user = $this->createConfiguredMock(IUser::class, ['getUID' => 'alice']);
$bookmarks = [
$this->createBookmark(1, 'alice', 'One', 'First content'),
$this->createBookmark(2, 'alice', 'Two', 'Second content'),
];

$this->contentManager->expects(self::once())
->method('isContextChatAvailable')
->willReturn(true);
$this->userManager->expects(self::once())
->method('get')
->with('alice')
->willReturn($user);
$this->userSettings->expects(self::once())
->method('setUserId')
->with('alice');
$this->userSettings->expects(self::once())
->method('get')
->with('contextchat.enabled')
->willReturn('true');
$this->bookmarkService->expects(self::once())
->method('getIterator')
->with('alice')
->willReturn((function () use ($bookmarks) {
foreach ($bookmarks as $bookmark) {
yield $bookmark;
}
})());
$this->provider->expects(self::exactly(2))
->method('getId')
->willReturn('bookmarks');
$this->contentManager->expects(self::once())
->method('submitContent')
->with(
Application::APP_ID,
self::callback(function (array $items): bool {
self::assertCount(2, $items);
self::assertContainsOnlyInstancesOf(ContentItem::class, $items);
self::assertSame('1', $items[0]->itemId);
self::assertSame('2', $items[1]->itemId);
self::assertSame(['alice'], $items[0]->users);
return true;
})
);
$this->jobList->expects(self::once())->method('remove');
$this->jobList->expects(self::once())->method('setLastRun');
$this->jobList->expects(self::once())->method('setExecutionTime');

$this->job->start($this->jobList);
}

public function testDoesNotIndexWhenUserDisabledContextChat(): void {
$user = $this->createConfiguredMock(IUser::class, ['getUID' => 'alice']);

$this->contentManager->expects(self::once())
->method('isContextChatAvailable')
->willReturn(true);
$this->userManager->expects(self::once())
->method('get')
->with('alice')
->willReturn($user);
$this->userSettings->expects(self::once())
->method('setUserId')
->with('alice');
$this->userSettings->expects(self::once())
->method('get')
->with('contextchat.enabled')
->willReturn('false');
$this->bookmarkService->expects(self::never())->method('getIterator');
$this->contentManager->expects(self::never())->method('submitContent');
$this->jobList->expects(self::once())->method('remove');
$this->jobList->expects(self::once())->method('setLastRun');
$this->jobList->expects(self::once())->method('setExecutionTime');

$this->job->start($this->jobList);
}

private function createBookmark(int $id, string $userId, string $title, string $textContent): Bookmark {
return Bookmark::fromArray([
'id' => $id,
'userId' => $userId,
'url' => 'https://example.com/' . $id,
'title' => $title,
'description' => '',
'lastmodified' => 1710000000 + $id,
'added' => 1710000000,
'clickcount' => 0,
'lastPreview' => 0,
'available' => true,
'archivedFile' => 0,
'textContent' => $textContent,
'htmlContent' => '',
'urlHash' => 'hash-' . $id,
]);
}
}
Loading
Loading