1
0
Fork 0
mirror of https://github.com/wallabag/wallabag.git synced 2025-06-27 16:36:00 +00:00

Add availability to disable an importer

This commit is contained in:
Nicolas Lœuillet 2025-05-26 15:45:55 +02:00
parent cad5a24fb6
commit 7e7674a4a6
17 changed files with 332 additions and 3 deletions

View file

@ -276,55 +276,80 @@ services:
Wallabag\Import\PocketImport:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''pocket_enabled'')' ] ]
- [ setClient, [ '@Symfony\Contracts\HttpClient\HttpClientInterface $pocketClient' ] ]
tags:
- { name: wallabag.import, alias: pocket }
Wallabag\Import\WallabagV1Import:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''wallabag_v1_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: wallabag_v1 }
Wallabag\Import\WallabagV2Import:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''wallabag_v2_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: wallabag_v2 }
Wallabag\Import\ElcuratorImport:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''elcurator_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: elcurator }
Wallabag\Import\ReadabilityImport:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''readibility_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: readability }
Wallabag\Import\InstapaperImport:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''instapaper_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: instapaper }
Wallabag\Import\PinboardImport:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''pinboard_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: pinboard }
Wallabag\Import\DeliciousImport:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''delicious_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: delicious }
Wallabag\Import\OmnivoreImport:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''omnivore_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: omnivore }
Wallabag\Import\FirefoxImport:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''firefox_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: firefox }
Wallabag\Import\ChromeImport:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''chrome_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: chrome }
Wallabag\Import\ShaarliImport:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''shaarli_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: shaarli }
Wallabag\Import\PocketHtmlImport:
calls:
- [ setEnabled, [ '@=service(''craue_config'').get(''pocket_html_enabled'')' ] ]
tags:
- { name: wallabag.import, alias: pocket_html }

View file

@ -126,6 +126,62 @@ parameters:
name: import_with_rabbitmq
value: 0
section: import
-
name: pocket_enabled
value: 1
section: import
-
name: wallabag_v1_enabled
value: 1
section: import
-
name: wallabag_v2_enabled
value: 1
section: import
-
name: elcurator_enabled
value: 1
section: import
-
name: readibility_enabled
value: 1
section: import
-
name: instapaper_enabled
value: 1
section: import
-
name: pinboard_enabled
value: 1
section: import
-
name: delicious_enabled
value: 1
section: import
-
name: omnivore_enabled
value: 1
section: import
-
name: firefox_enabled
value: 1
section: import
-
name: chrome_enabled
value: 1
section: import
-
name: shaarli_enabled
value: 1
section: import
-
name: pocket_html_enabled
value: 1
section: import
-
name: pocket_csv_enabled
value: 1
section: import
-
name: matomo_enabled
value: 0

View file

@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace Application\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Wallabag\Doctrine\WallabagMigration;
/**
* Add setting to enable or disable each importer.
*/
final class Version20250526113708 extends WallabagMigration
{
private $settings = [
[
'name' => 'pocket_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'wallabag_v1_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'wallabag_v2_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'elcura_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'readability_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'instapaper_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'pinboard_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'delicious_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'omnivore_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'firefox_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'chrome_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'shaarli_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'pocket_html_enabled',
'value' => '1',
'section' => 'import',
],
[
'name' => 'pocket_csv_enabled',
'value' => '1',
'section' => 'import',
],
];
public function up(Schema $schema): void
{
foreach ($this->settings as $setting) {
$settingEnabled = $this->connection
->fetchOne('SELECT * FROM ' . $this->getTable('internal_setting') . " WHERE name = '" . $setting['name'] . "'");
if (false !== $settingEnabled) {
continue;
}
$this->addSql('INSERT INTO ' . $this->getTable('internal_setting') . " (name, value, section) VALUES ('" . $setting['name'] . "', '" . $setting['value'] . "', '" . $setting['section'] . "');");
}
}
public function down(Schema $schema): void
{
$this->skipIf(true, 'These settings are required and should not be removed.');
}
}

View file

@ -20,10 +20,14 @@ abstract class BrowserController extends AbstractController
#[IsGranted('IMPORT_ENTRIES')]
public function indexAction(Request $request, TranslatorInterface $translator)
{
$wallabag = $this->getImportService();
if (!$this->isGranted('USE_IMPORTER', $wallabag)) {
throw $this->createAccessDeniedException('You can not access this importer.');
}
$form = $this->createForm(UploadImportType::class);
$form->handleRequest($request);
$wallabag = $this->getImportService();
$wallabag->setUser($this->getUser());
if ($form->isSubmitted() && $form->isValid()) {

View file

@ -23,6 +23,7 @@ class DeliciousController extends AbstractController
#[Route(path: '/import/delicious', name: 'import_delicious', methods: ['GET', 'POST'])]
#[IsGranted('IMPORT_ENTRIES')]
#[IsGranted('USE_IMPORTER', subject: 'delicious')]
public function indexAction(Request $request, DeliciousImport $delicious, Config $craueConfig, TranslatorInterface $translator)
{
$form = $this->createForm(UploadImportType::class);

View file

@ -20,10 +20,14 @@ abstract class HtmlController extends AbstractController
#[IsGranted('IMPORT_ENTRIES')]
public function indexAction(Request $request, TranslatorInterface $translator)
{
$wallabag = $this->getImportService();
if (!$this->isGranted('USE_IMPORTER', $wallabag)) {
throw $this->createAccessDeniedException('You can not access this importer.');
}
$form = $this->createForm(UploadImportType::class);
$form->handleRequest($request);
$wallabag = $this->getImportService();
$wallabag->setUser($this->getUser());
if ($form->isSubmitted() && $form->isValid()) {

View file

@ -23,6 +23,7 @@ class InstapaperController extends AbstractController
#[Route(path: '/import/instapaper', name: 'import_instapaper', methods: ['GET', 'POST'])]
#[IsGranted('IMPORT_ENTRIES')]
#[IsGranted('USE_IMPORTER', subject: 'instapaper')]
public function indexAction(Request $request, InstapaperImport $instapaper, Config $craueConfig, TranslatorInterface $translator)
{
$form = $this->createForm(UploadImportType::class);

View file

@ -23,6 +23,7 @@ class OmnivoreController extends AbstractController
#[Route(path: '/import/omnivore', name: 'import_omnivore', methods: ['GET', 'POST'])]
#[IsGranted('IMPORT_ENTRIES')]
#[IsGranted('USE_IMPORTER', subject: 'omnivore')]
public function indexAction(Request $request, OmnivoreImport $omnivore, Config $craueConfig, TranslatorInterface $translator)
{
$form = $this->createForm(UploadImportType::class);

View file

@ -23,6 +23,7 @@ class PinboardController extends AbstractController
#[Route(path: '/import/pinboard', name: 'import_pinboard', methods: ['GET', 'POST'])]
#[IsGranted('IMPORT_ENTRIES')]
#[IsGranted('USE_IMPORTER', subject: 'pinboard')]
public function indexAction(Request $request, PinboardImport $pinboard, Config $craueConfig, TranslatorInterface $translator)
{
$form = $this->createForm(UploadImportType::class);

View file

@ -27,6 +27,7 @@ class PocketController extends AbstractController
#[Route(path: '/import/pocket', name: 'import_pocket', methods: ['GET'])]
#[IsGranted('IMPORT_ENTRIES')]
#[IsGranted('USE_IMPORTER', subject: 'pocketImport')]
public function indexAction(PocketImport $pocketImport)
{
$pocket = $this->getPocketImportService($pocketImport);
@ -47,6 +48,7 @@ class PocketController extends AbstractController
#[Route(path: '/import/pocket/auth', name: 'import_pocket_auth', methods: ['POST'])]
#[IsGranted('IMPORT_ENTRIES')]
#[IsGranted('USE_IMPORTER', subject: 'pocketImport')]
public function authAction(Request $request, PocketImport $pocketImport)
{
$requestToken = $this->getPocketImportService($pocketImport)
@ -76,6 +78,7 @@ class PocketController extends AbstractController
#[Route(path: '/import/pocket/callback', name: 'import_pocket_callback', methods: ['GET'])]
#[IsGranted('IMPORT_ENTRIES')]
#[IsGranted('USE_IMPORTER', subject: 'pocketImport')]
public function callbackAction(PocketImport $pocketImport, TranslatorInterface $translator)
{
$message = 'flashes.import.notice.failed';

View file

@ -23,6 +23,7 @@ class ReadabilityController extends AbstractController
#[Route(path: '/import/readability', name: 'import_readability', methods: ['GET', 'POST'])]
#[IsGranted('IMPORT_ENTRIES')]
#[IsGranted('USE_IMPORTER', subject: 'readability')]
public function indexAction(Request $request, ReadabilityImport $readability, Config $craueConfig, TranslatorInterface $translator)
{
$form = $this->createForm(UploadImportType::class);

View file

@ -22,10 +22,14 @@ abstract class WallabagController extends AbstractController
*/
public function indexAction(Request $request, TranslatorInterface $translator)
{
$wallabag = $this->getImportService();
if (!$this->isGranted('USE_IMPORTER', $wallabag)) {
throw $this->createAccessDeniedException('You can not access this importer.');
}
$form = $this->createForm(UploadImportType::class);
$form->handleRequest($request);
$wallabag = $this->getImportService();
$wallabag->setUser($this->getUser());
if ($form->isSubmitted() && $form->isValid()) {

View file

@ -21,6 +21,7 @@ abstract class AbstractImport implements ImportInterface
protected $skippedEntries = 0;
protected $importedEntries = 0;
protected $queuedEntries = 0;
protected $enabled = true;
public function __construct(
protected EntityManagerInterface $em,
@ -74,6 +75,25 @@ abstract class AbstractImport implements ImportInterface
return $this->markAsRead;
}
/**
* Get whether the import is enabled.
* If not, the importer won't be available in the UI.
*/
public function isEnabled(): bool
{
return $this->enabled;
}
/**
* Set whether the import is enabled.
*/
public function setEnabled(bool $enabled): static
{
$this->enabled = $enabled;
return $this;
}
/**
* Set whether articles should be fetched for updated content.
*

View file

@ -18,7 +18,11 @@ class ImportChain
*/
public function addImport(ImportInterface $import, $alias)
{
// if (true === $import->isEnabled()) {
$this->imports[$alias] = $import;
// }
return $this;
}
/**

View file

@ -63,4 +63,15 @@ interface ImportInterface extends LoggerAwareInterface
* @param bool $markAsRead
*/
public function setMarkAsRead($markAsRead): static;
/**
* Get whether the import is enabled.
* If not, the importer won't be available in the UI.
*/
public function isEnabled(): bool;
/**
* Set whether the import is enabled.
*/
public function setEnabled(bool $enabled): static;
}

View file

@ -0,0 +1,42 @@
<?php
namespace Wallabag\Security\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Wallabag\Entity\User;
use Wallabag\Import\AbstractImport;
class ImportVoter extends Voter
{
public const USE_IMPORTER = 'USE_IMPORTER';
protected function supports(string $attribute, $subject): bool
{
if (!$subject instanceof AbstractImport) {
return false;
}
if (!\in_array($attribute, [self::USE_IMPORTER], true)) {
return false;
}
return true;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
\assert($subject instanceof AbstractImport);
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
return match ($attribute) {
self::USE_IMPORTER => true === $subject->isEnabled(),
default => false,
};
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Security\Voter;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Wallabag\Entity\SiteCredential;
use Wallabag\Entity\User;
use Wallabag\Security\Voter\ImportVoter;
class ImportVoterTest extends TestCase
{
private $token;
private $user;
private $importVoter;
protected function setUp(): void
{
$this->token = $this->createMock(TokenInterface::class);
$this->user = new User();
$this->importVoter = new ImportVoter();
}
public function testVoteReturnsAbstainForInvalidSubject(): void
{
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->importVoter->vote($this->token, new \stdClass(), [ImportVoter::USE_IMPORTER]));
}
public function testVoteReturnsAbstainForInvalidAttribute(): void
{
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->importVoter->vote($this->token, new SiteCredential(new User()), ['INVALID']));
}
public function testVoteReturnsDeniedForNonSiteCredentialUserEdit(): void
{
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->importVoter->vote($this->token, new SiteCredential(new User()), [ImportVoter::USE_IMPORTER]));
}
public function testVoteReturnsGrantedForSiteCredentialUserEdit(): void
{
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->importVoter->vote($this->token, new SiteCredential($this->user), [ImportVoter::USE_IMPORTER]));
}
}