Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
be769f4
Add collectives trash backend provider to files_trashbin
mejo- Apr 12, 2023
cecd019
Trash pages instead of deleting them when files_trashbin is enabled
mejo- May 19, 2023
e8978f4
Allow to restore/delete pages from trash via Collectives API
mejo- May 19, 2023
46874d2
Refactor: move node-related functions from PageService to NodeHelper
mejo- May 19, 2023
88042dd
Add behat tests for page trash
mejo- May 19, 2023
a19e0f7
Remove versions along with pages
mejo- May 22, 2023
d8bb2f7
Delete trash and versions when deleting a collective
mejo- May 22, 2023
8b6ac69
Add behat tests for collectives trash over webdav API
mejo- May 23, 2023
cb18b01
Revert subfolders of parent page when deleting from trash
mejo- May 24, 2023
6300d97
Don't grant access to trash of read-only collectives
mejo- May 24, 2023
bcac981
Restore attachments folder along with page if it exists
mejo- May 24, 2023
c089e16
Restore+remove attachments folder from trash along with page
mejo- May 24, 2023
e310a12
Add behat tests for page trash API in public shares
mejo- May 24, 2023
ea037d5
Add fixes detected by psalm
mejo- May 24, 2023
f935a7c
Call notifyPush when trashing a page also without files_trashbin
mejo- May 25, 2023
b894a77
Delete collective leftovers even if collective folder is already gone
mejo- May 25, 2023
235b4aa
Improve exception message when trashed page not found in database
mejo- May 25, 2023
93c8d63
Also return full PageInfo objects for page trash index
mejo- May 27, 2023
14967ef
Fix handling of deleted pages with subpages
mejo- May 30, 2023
d3a46c4
PageTrashCleanup: Different return values for different failure reasons
mejo- Jun 5, 2023
710a616
Code style fixes by php-cs-fixer
mejo- Jun 5, 2023
34b080a
Make sure long filenames are truncated in trashbin
mejo- Jun 5, 2023
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
4 changes: 3 additions & 1 deletion .github/workflows/occ-cli-mysql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ jobs:
run: |
./occ app:list
./occ collectives:create --owner alice NewCollective
./occ collectives:pages:purge-obsolete
./occ collectives:index
./occ collectives:pages:expire
./occ collectives:pages:trashbin:cleanup -f
./occ collectives:pages:purge-obsolete

- name: Disable circles and run occ command
run: |
Expand Down
11 changes: 8 additions & 3 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ In your Nextcloud instance, simply navigate to **»Apps«**, find the
**»Circles«** and **»Collectives«** apps and enable them.

]]></description>
<version>2.5.0</version>
<version>2.6.0</version>
<licence>agpl</licence>
<author>CollectiveCloud Team</author>
<namespace>Collectives</namespace>
Expand All @@ -46,15 +46,17 @@ In your Nextcloud instance, simply navigate to **»Apps«**, find the
<nextcloud min-version="25" max-version="27" />
</dependencies>
<background-jobs>
<job>OCA\Collectives\BackgroundJob\ExpirePageTrash</job>
<job>OCA\Collectives\BackgroundJob\ExpirePageVersions</job>
<job>OCA\Collectives\BackgroundJob\PurgeObsoletePages</job>
<job>OCA\Collectives\BackgroundJob\IndexCollectives</job>
<job>OCA\Collectives\BackgroundJob\PurgeObsoletePages</job>
</background-jobs>
<commands>
<command>OCA\Collectives\Command\CreateCollective</command>
<command>OCA\Collectives\Command\ExpirePageVersions</command>
<command>OCA\Collectives\Command\PurgeObsoletePages</command>
<command>OCA\Collectives\Command\IndexCollectives</command>
<command>OCA\Collectives\Command\PageTrashCleanup</command>
<command>OCA\Collectives\Command\PurgeObsoletePages</command>
</commands>
<navigations>
<navigation>
Expand All @@ -64,6 +66,9 @@ In your Nextcloud instance, simply navigate to **»Apps«**, find the
<order>12</order>
</navigation>
</navigations>
<trash>
<backend for="OCA\Collectives\Mount\CollectiveStorage">OCA\Collectives\Trash\PageTrashBackend</backend>
</trash>
<versions>
<backend for="OCA\Collectives\Mount\CollectiveStorage">OCA\Collectives\Versions\VersionsBackend</backend>
</versions>
Expand Down
18 changes: 16 additions & 2 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,20 @@
'verb' => 'PUT', 'requirements' => ['collectiveId' => '\d+', 'parentId' => '\d+', 'id' => '\d+']],
['name' => 'page#setSubpageOrder', 'url' => '/_api/{collectiveId}/_pages/parent/{parentId}/page/{id}/subpageOrder',
'verb' => 'PUT', 'requirements' => ['collectiveId' => '\d+', 'parentId' => '\d+', 'id' => '\d+']],
['name' => 'page#delete', 'url' => '/_api/{collectiveId}/_pages/parent/{parentId}/page/{id}',
['name' => 'page#trash', 'url' => '/_api/{collectiveId}/_pages/parent/{parentId}/page/{id}',
'verb' => 'DELETE', 'requirements' => ['collectiveId' => '\d+', 'parentId' => '\d+', 'id' => '\d+']],
['name' => 'page#getBacklinks', 'url' => '/_api/{collectiveId}/_pages/parent/{parentId}/page/{id}/backlinks',
'verb' => 'GET', 'requirements' => ['collectiveId' => '\d+', 'parentId' => '\d+', 'id' => '\d+']],
['name' => 'page#getAttachments', 'url' => '/_api/{collectiveId}/_pages/parent/{parentId}/page/{id}/attachments',
'verb' => 'GET', 'requirements' => ['collectiveId' => '\d+', 'parentId' => '\d+', 'id' => '\d+']],

// pages trash API
['name' => 'pageTrash#index', 'url' => '/_api/{collectiveId}/_pages/trash', 'verb' => 'GET'],
['name' => 'pageTrash#delete', 'url' => '/_api/{collectiveId}/_pages/trash/{id}', 'verb' => 'DELETE',
'requirements' => ['id' => '\d+']],
['name' => 'pageTrash#restore', 'url' => '/_api/{collectiveId}/_pages/trash/{id}', 'verb' => 'PATCH',
'requirements' => ['id' => '\d+']],

// public collectives API
['name' => 'publicCollective#get', 'url' => '/_api/p/{token}', 'verb' => 'GET'],

Expand All @@ -78,13 +85,20 @@
'verb' => 'PUT', 'requirements' => ['parentId' => '\d+', 'id' => '\d+']],
['name' => 'publicPage#setSubpageOrder', 'url' => '/_api/p/{token}/_pages/parent/{parentId}/page/{id}/subpageOrder',
'verb' => 'PUT', 'requirements' => ['parentId' => '\d+', 'id' => '\d+']],
['name' => 'publicPage#delete', 'url' => '/_api/p/{token}/_pages/parent/{parentId}/page/{id}',
['name' => 'publicPage#trash', 'url' => '/_api/p/{token}/_pages/parent/{parentId}/page/{id}',
'verb' => 'DELETE', 'requirements' => ['parentId' => '\d+', 'id' => '\d+']],
['name' => 'publicPage#getAttachments', 'url' => '/_api/p/{token}/_pages/parent/{parentId}/page/{id}/attachments',
'verb' => 'GET', 'requirements' => ['parentId' => '\d+', 'id' => '\d+']],
['name' => 'publicPage#getBacklinks', 'url' => '/_api/p/{token}/_pages/parent/{parentId}/page/{id}/backlinks',
'verb' => 'GET', 'requirements' => ['parentId' => '\d+', 'id' => '\d+']],

// public pages trash API
['name' => 'publicPageTrash#index', 'url' => '/_api/p/{token}/_pages/trash', 'verb' => 'GET'],
['name' => 'publicPageTrash#delete', 'url' => '/_api/p/{token}/_pages/trash/{id}', 'verb' => 'DELETE',
'requirements' => ['id' => '\d+']],
['name' => 'publicPageTrash#restore', 'url' => '/_api/p/{token}/_pages/trash/{id}', 'verb' => 'PATCH',
'requirements' => ['id' => '\d+']],

// default public route (Vue.js frontend)
['name' => 'publicStart#publicIndex', 'url' => '/p/{token}/{path}', 'verb' => 'GET',
'requirements' => ['path' => '.*'], 'defaults' => ['path' => '']],
Expand Down
21 changes: 21 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Closure;
use OCA\Circles\Events\CircleDestroyedEvent;
use OCA\Collectives\CacheListener;
use OCA\Collectives\Db\CollectiveMapper;
use OCA\Collectives\Db\PageMapper;
use OCA\Collectives\Fs\UserFolderHelper;
use OCA\Collectives\Listeners\BeforeTemplateRenderedListener;
use OCA\Collectives\Listeners\CircleDestroyedListener;
Expand All @@ -20,7 +22,10 @@
use OCA\Collectives\Search\PageContentProvider;
use OCA\Collectives\Search\PageProvider;
use OCA\Collectives\Service\CollectiveHelper;
use OCA\Collectives\Trash\PageTrashBackend;
use OCA\Collectives\Trash\PageTrashManager;
use OCA\Collectives\Versions\VersionsBackend;
use OCA\Files_Versions\Versions\IVersionBackend;
use OCP\App\IAppManager;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
Expand Down Expand Up @@ -64,6 +69,22 @@ public function register(IRegistrationContext $context): void {
);
});

$context->registerService(PageTrashBackend::class, function (ContainerInterface $c) {
$trashBackend = new PageTrashBackend(
$c->get(CollectiveFolderManager::class),
$c->get(PageTrashManager::class),
$c->get(MountProvider::class),
$c->get(CollectiveMapper::class),
$c->get(PageMapper::class),
$c->get(LoggerInterface::class)
);
$hasVersionApp = interface_exists(IVersionBackend::class);
if ($hasVersionApp) {
$trashBackend->setVersionsBackend($c->get(VersionsBackend::class));
}
return $trashBackend;
});

$context->registerService(VersionsBackend::class, function (ContainerInterface $c) {
$appManager = $c->get(IAppManager::class);
if ($appManager->isEnabledForUser('files_versions')) {
Expand Down
38 changes: 38 additions & 0 deletions lib/BackgroundJob/ExpirePageTrash.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace OCA\Collectives\BackgroundJob;

use OCA\Collectives\Service\NotFoundException;
use OCA\Collectives\Service\NotPermittedException;
use OCA\Collectives\Trash\PageTrashBackend;
use OCA\Files_Trashbin\Expiration;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;

class ExpirePageTrash extends TimedJob {
private Expiration $expiration;
private PageTrashBackend $trashBackend;

public function __construct(
ITimeFactory $time,
Expiration $expiration,
PageTrashBackend $trashBackend) {
parent::__construct($time);

// Run once per hour
$this->setInterval(60 * 60);

$this->expiration = $expiration;
$this->trashBackend = $trashBackend;
}

/**
* @param $argument
*
* @throws NotFoundException
* @throws NotPermittedException
*/
protected function run($argument): void {
$this->trashBackend->expire($this->expiration);
}
}
94 changes: 94 additions & 0 deletions lib/Command/PageTrashCleanup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

namespace OCA\Collectives\Command;

use OC\Core\Command\Base;
use OCA\Collectives\Db\CollectiveMapper;
use OCA\Collectives\Service\MissingDependencyException;
use OCA\Collectives\Service\NotFoundException;
use OCA\Collectives\Service\NotPermittedException;
use OCA\Collectives\Trash\PageTrashBackend;
use OCP\App\IAppManager;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;

class PageTrashCleanup extends Base {
private ?PageTrashBackend $trashBackend = null;
private CollectiveMapper $collectiveMapper;

public function __construct(
IAppManager $appManager,
CollectiveMapper $collectiveMapper
) {
parent::__construct();
if ($appManager->isEnabledForUser('files_trashbin')) {
$this->trashBackend = \OC::$server->get(PageTrashBackend::class);
}
$this->collectiveMapper = $collectiveMapper;
}

protected function configure(): void {
$this
->setName('collectives:pages:trashbin:cleanup')
->setDescription('Empty the collectives page trashbin.')
->addArgument('collective', InputArgument::OPTIONAL, 'name of collective')
->addOption('force', 'f', InputOption::VALUE_NONE, 'skip confirmation');
parent::configure();
}

/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
if (!$this->trashBackend) {
$output->writeln('<error>files_trashbin is disabled: collectives page trashbin is not available</error>');
return 1;
}
$helper = $this->getHelper('question');

$collectives = $this->collectiveMapper->getAll();
if ($input->getArgument('collective') !== null) {
$collectiveName = $input->getArgument('collective');

foreach ($collectives as $collective) {
$foundCollectiveName = null;
try {
$foundCollectiveName = $this->collectiveMapper->idToName($collective->getId(), null, true);
} catch (MissingDependencyException | NotFoundException | NotPermittedException $e) {
}

if ($foundCollectiveName === $collectiveName) {
$question = new ConfirmationQuestion('Are you sure you want to empty the page trashbin of collective ' . $collectiveName . '? This can not be undone. (y/N) ', false);
if (!$input->getOption('force') && !$helper->ask($input, $output, $question)) {
return -2;
}

$this->trashBackend->cleanTrashFolder($collective->getId());
return 0;
}
}

$output->writeln('<error>Collective not found: ' . $collectiveName . '</error>');
return -1;
}

$question = new ConfirmationQuestion('Are you sure you want to empty the page trashbin of all collectives? This can not be undone (y/N).', false);
if (!$input->getOption('force') && !$helper->ask($input, $output, $question)) {
return -2;
}

foreach ($collectives as $collective) {
$this->trashBackend->cleanTrashFolder($collective->getId());
}

return 0;
}
}
4 changes: 2 additions & 2 deletions lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,10 @@ public function setSubpageOrder(int $collectiveId, int $parentId, int $id, ?stri
*
* @return DataResponse
*/
public function delete(int $collectiveId, int $parentId, int $id): DataResponse {
public function trash(int $collectiveId, int $parentId, int $id): DataResponse {
return $this->handleErrorResponse(function () use ($collectiveId, $parentId, $id): array {
$userId = $this->getUserId();
$pageInfo = $this->service->delete($collectiveId, $parentId, $id, $userId);
$pageInfo = $this->service->trash($collectiveId, $parentId, $id, $userId);
return [
"data" => $pageInfo
];
Expand Down
87 changes: 87 additions & 0 deletions lib/Controller/PageTrashController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace OCA\Collectives\Controller;

use OCA\Collectives\Service\PageService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;

class PageTrashController extends Controller {
private PageService $service;
private IUserSession $userSession;
private LoggerInterface $logger;

use ErrorHelper;

public function __construct(string $appName,
IRequest $request,
PageService $service,
IUserSession $userSession,
LoggerInterface $logger) {
parent::__construct($appName, $request);
$this->service = $service;
$this->userSession = $userSession;
$this->logger = $logger;
}

/**
* @return string
*/
private function getUserId(): string {
return $this->userSession->getUser()->getUID();
}

/**
* @NoAdminRequired
*
* @param int $collectiveId
*
* @return DataResponse
*/
public function index(int $collectiveId): DataResponse {
return $this->handleErrorResponse(function () use ($collectiveId): array {
$userId = $this->getUserId();
$pageInfos = $this->service->findAllTrash($collectiveId, $userId);
return [
"data" => $pageInfos
];
}, $this->logger);
}

/**
* @NoAdminRequired
*
* @param int $collectiveId
* @param int $id
*
* @return DataResponse
*/
public function restore(int $collectiveId, int $id): DataResponse {
return $this->handleErrorResponse(function () use ($collectiveId, $id): array {
$userId = $this->getUserId();
$pageInfo = $this->service->restore($collectiveId, $id, $userId);
return [
"data" => $pageInfo
];
}, $this->logger);
}

/**
* @NoAdminRequired
*
* @param int $collectiveId
* @param int $id
*
* @return DataResponse
*/
public function delete(int $collectiveId, int $id): DataResponse {
return $this->handleErrorResponse(function () use ($collectiveId, $id): array {
$userId = $this->getUserId();
$this->service->delete($collectiveId, $id, $userId);
return [];
}, $this->logger);
}
}
4 changes: 2 additions & 2 deletions lib/Controller/PublicPageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,12 @@ public function setSubpageOrder(int $parentId, int $id, ?string $subpageOrder =
*
* @return DataResponse
*/
public function delete(int $parentId, int $id): DataResponse {
public function trash(int $parentId, int $id): DataResponse {
return $this->handleErrorResponse(function () use ($parentId, $id): array {
$this->checkEditPermissions();
$owner = $this->getShare()->getOwner();
$collectiveId = $this->getShare()->getCollectiveId();
$pageInfo = $this->service->delete($collectiveId, $parentId, $id, $owner);
$pageInfo = $this->service->trash($collectiveId, $parentId, $id, $owner);
// Shares don't have a collective path
$pageInfo->setCollectivePath('');
$pageInfo->setShareToken($this->getToken());
Expand Down
Loading