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:
parent
cad5a24fb6
commit
7e7674a4a6
17 changed files with 332 additions and 3 deletions
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
106
migrations/Version20250526113708.php
Normal file
106
migrations/Version20250526113708.php
Normal 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.');
|
||||
}
|
||||
}
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -18,7 +18,11 @@ class ImportChain
|
|||
*/
|
||||
public function addImport(ImportInterface $import, $alias)
|
||||
{
|
||||
// if (true === $import->isEnabled()) {
|
||||
$this->imports[$alias] = $import;
|
||||
|
||||
// }
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
42
src/Security/Voter/ImportVoter.php
Normal file
42
src/Security/Voter/ImportVoter.php
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
45
tests/Security/Voter/ImportVoterTest.php
Normal file
45
tests/Security/Voter/ImportVoterTest.php
Normal 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]));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue