mirror of
https://github.com/wallabag/wallabag.git
synced 2025-08-06 17:41:01 +00:00
Move source files directly under src/ directory
This commit is contained in:
parent
804261bc26
commit
a37b385c23
190 changed files with 19 additions and 21 deletions
114
src/Command/CleanDownloadedImagesCommand.php
Normal file
114
src/Command/CleanDownloadedImagesCommand.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Wallabag\CoreBundle\Helper\DownloadImages;
|
||||
use Wallabag\CoreBundle\Repository\EntryRepository;
|
||||
|
||||
class CleanDownloadedImagesCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:clean-downloaded-images';
|
||||
protected static $defaultDescription = 'Cleans downloaded images which are no more associated to an entry';
|
||||
|
||||
private EntryRepository $entryRepository;
|
||||
private DownloadImages $downloadImages;
|
||||
|
||||
public function __construct(EntryRepository $entryRepository, DownloadImages $downloadImages)
|
||||
{
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->downloadImages = $downloadImages;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->addOption(
|
||||
'dry-run',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Do not remove images, just dump counters'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$dryRun = (bool) $input->getOption('dry-run');
|
||||
|
||||
if ($dryRun) {
|
||||
$io->text('Dry run mode <info>enabled</info> (no images will be removed)');
|
||||
}
|
||||
|
||||
$baseFolder = $this->downloadImages->getBaseFolder();
|
||||
|
||||
$io->text('Retrieve existing images');
|
||||
|
||||
// retrieve _existing_ folders in the image folder
|
||||
$finder = new Finder();
|
||||
$finder
|
||||
->directories()
|
||||
->ignoreDotFiles(true)
|
||||
->depth(2)
|
||||
->in($baseFolder);
|
||||
|
||||
$existingPaths = [];
|
||||
foreach ($finder as $file) {
|
||||
$existingPaths[] = $file->getFilename();
|
||||
}
|
||||
|
||||
$io->text(sprintf(' -> <info>%d</info> images found', \count($existingPaths)));
|
||||
|
||||
$io->text('Retrieve valid folders attached to a user');
|
||||
|
||||
$entries = $this->entryRepository->findAllEntriesIdByUserId();
|
||||
|
||||
// retrieve _valid_ folders from existing entries
|
||||
$validPaths = [];
|
||||
foreach ($entries as $entry) {
|
||||
$path = $this->downloadImages->getRelativePath($entry['id']);
|
||||
|
||||
if (!file_exists($baseFolder . '/' . $path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// only store the hash, not the full path
|
||||
$validPaths[] = explode('/', $path)[2];
|
||||
}
|
||||
|
||||
$io->text(sprintf(' -> <info>%d</info> folders found', \count($validPaths)));
|
||||
|
||||
$deletedCount = 0;
|
||||
|
||||
$io->text('Remove images');
|
||||
|
||||
// check if existing path are valid, if not, remove all images and the folder
|
||||
foreach ($existingPaths as $existingPath) {
|
||||
if (!\in_array($existingPath, $validPaths, true)) {
|
||||
$fullPath = $baseFolder . '/' . $existingPath[0] . '/' . $existingPath[1] . '/' . $existingPath;
|
||||
$files = glob($fullPath . '/*.*');
|
||||
|
||||
if (!$dryRun) {
|
||||
array_map('unlink', $files);
|
||||
rmdir($fullPath);
|
||||
}
|
||||
|
||||
$deletedCount += \count($files);
|
||||
|
||||
$io->text(sprintf('Deleted images in <info>%s</info>: <info>%d</info>', $existingPath, \count($files)));
|
||||
}
|
||||
}
|
||||
|
||||
$io->success(sprintf('Finished cleaning. %d deleted images', $deletedCount));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
125
src/Command/CleanDuplicatesCommand.php
Normal file
125
src/Command/CleanDuplicatesCommand.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Wallabag\CoreBundle\Entity\Entry;
|
||||
use Wallabag\CoreBundle\Entity\User;
|
||||
use Wallabag\CoreBundle\Repository\EntryRepository;
|
||||
use Wallabag\CoreBundle\Repository\UserRepository;
|
||||
|
||||
class CleanDuplicatesCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:clean-duplicates';
|
||||
protected static $defaultDescription = 'Cleans the database for duplicates';
|
||||
|
||||
protected SymfonyStyle $io;
|
||||
protected int $duplicates = 0;
|
||||
private EntityManagerInterface $entityManager;
|
||||
private EntryRepository $entryRepository;
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, EntryRepository $entryRepository, UserRepository $userRepository)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setHelp('This command helps you to clean your articles list in case of duplicates')
|
||||
->addArgument(
|
||||
'username',
|
||||
InputArgument::OPTIONAL,
|
||||
'User to clean'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
|
||||
$username = $input->getArgument('username');
|
||||
|
||||
if ($username) {
|
||||
try {
|
||||
$user = $this->getUser($username);
|
||||
$this->cleanDuplicates($user);
|
||||
} catch (NoResultException $e) {
|
||||
$this->io->error(sprintf('User "%s" not found.', $username));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->io->success('Finished cleaning.');
|
||||
} else {
|
||||
$users = $this->userRepository->findAll();
|
||||
|
||||
$this->io->text(sprintf('Cleaning through <info>%d</info> user accounts', \count($users)));
|
||||
|
||||
foreach ($users as $user) {
|
||||
$this->io->text(sprintf('Processing user <info>%s</info>', $user->getUsername()));
|
||||
$this->cleanDuplicates($user);
|
||||
}
|
||||
$this->io->success(sprintf('Finished cleaning. %d duplicates found in total', $this->duplicates));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function cleanDuplicates(User $user)
|
||||
{
|
||||
$entries = $this->entryRepository->findAllEntriesIdAndUrlByUserId($user->getId());
|
||||
|
||||
$duplicatesCount = 0;
|
||||
$urls = [];
|
||||
foreach ($entries as $entry) {
|
||||
$url = $this->similarUrl($entry['url']);
|
||||
|
||||
/* @var $entry Entry */
|
||||
if (\in_array($url, $urls, true)) {
|
||||
++$duplicatesCount;
|
||||
|
||||
$this->entityManager->remove($this->entryRepository->find($entry['id']));
|
||||
$this->entityManager->flush(); // Flushing at the end of the loop would require the instance not being online
|
||||
} else {
|
||||
$urls[] = $entry['url'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->duplicates += $duplicatesCount;
|
||||
|
||||
$this->io->text(sprintf('Cleaned <info>%d</info> duplicates for user <info>%s</info>', $duplicatesCount, $user->getUserName()));
|
||||
}
|
||||
|
||||
private function similarUrl($url)
|
||||
{
|
||||
if (\in_array(substr($url, -1), ['/', '#'], true)) { // get rid of "/" and "#" and the end of urls
|
||||
return substr($url, 0, \strlen($url));
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a user from its username.
|
||||
*
|
||||
* @param string $username
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
private function getUser($username)
|
||||
{
|
||||
return $this->userRepository->findOneByUserName($username);
|
||||
}
|
||||
}
|
94
src/Command/ExportCommand.php
Normal file
94
src/Command/ExportCommand.php
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command;
|
||||
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Wallabag\CoreBundle\Helper\EntriesExport;
|
||||
use Wallabag\CoreBundle\Repository\EntryRepository;
|
||||
use Wallabag\CoreBundle\Repository\UserRepository;
|
||||
|
||||
class ExportCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:export';
|
||||
protected static $defaultDescription = 'Export all entries for an user';
|
||||
|
||||
private EntryRepository $entryRepository;
|
||||
private UserRepository $userRepository;
|
||||
private EntriesExport $entriesExport;
|
||||
private string $projectDir;
|
||||
|
||||
public function __construct(EntryRepository $entryRepository, UserRepository $userRepository, EntriesExport $entriesExport, string $projectDir)
|
||||
{
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->entriesExport = $entriesExport;
|
||||
$this->projectDir = $projectDir;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setHelp('This command helps you to export all entries for an user')
|
||||
->addArgument(
|
||||
'username',
|
||||
InputArgument::REQUIRED,
|
||||
'User from which to export entries'
|
||||
)
|
||||
->addArgument(
|
||||
'filepath',
|
||||
InputArgument::OPTIONAL,
|
||||
'Path of the exported file'
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
try {
|
||||
$user = $this->userRepository->findOneByUserName($input->getArgument('username'));
|
||||
} catch (NoResultException $e) {
|
||||
$io->error(sprintf('User "%s" not found.', $input->getArgument('username')));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$entries = $this->entryRepository
|
||||
->getBuilderForAllByUser($user->getId())
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
$io->text(sprintf('Exporting <info>%d</info> entrie(s) for user <info>%s</info>...', \count($entries), $user->getUserName()));
|
||||
|
||||
$filePath = $input->getArgument('filepath');
|
||||
|
||||
if (!$filePath) {
|
||||
$filePath = $this->projectDir . '/' . sprintf('%s-export.json', $user->getUsername());
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->entriesExport
|
||||
->setEntries($entries)
|
||||
->updateTitle('All')
|
||||
->updateAuthor('All')
|
||||
->exportJsonData();
|
||||
file_put_contents($filePath, $data);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$io->error(sprintf('Error: "%s"', $e->getMessage()));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$io->success('Done.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
103
src/Command/GenerateUrlHashesCommand.php
Normal file
103
src/Command/GenerateUrlHashesCommand.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Wallabag\CoreBundle\Entity\User;
|
||||
use Wallabag\CoreBundle\Helper\UrlHasher;
|
||||
use Wallabag\CoreBundle\Repository\EntryRepository;
|
||||
use Wallabag\CoreBundle\Repository\UserRepository;
|
||||
|
||||
class GenerateUrlHashesCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:generate-hashed-urls';
|
||||
protected static $defaultDescription = 'Generates hashed urls for each entry';
|
||||
|
||||
protected OutputInterface $output;
|
||||
private EntityManagerInterface $entityManager;
|
||||
private EntryRepository $entryRepository;
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, EntryRepository $entryRepository, UserRepository $userRepository)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setHelp('This command helps you to generates hashes of the url of each entry, to check through API if an URL is already saved')
|
||||
->addArgument('username', InputArgument::OPTIONAL, 'User to process entries');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
$username = (string) $input->getArgument('username');
|
||||
|
||||
if ($username) {
|
||||
try {
|
||||
$user = $this->getUser($username);
|
||||
$this->generateHashedUrls($user);
|
||||
} catch (NoResultException $e) {
|
||||
$output->writeln(sprintf('<error>User "%s" not found.</error>', $username));
|
||||
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
$users = $this->userRepository->findAll();
|
||||
|
||||
$output->writeln(sprintf('Generating hashed urls for "%d" users', \count($users)));
|
||||
|
||||
foreach ($users as $user) {
|
||||
$output->writeln(sprintf('Processing user: %s', $user->getUsername()));
|
||||
$this->generateHashedUrls($user);
|
||||
}
|
||||
$output->writeln('Finished generated hashed urls');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function generateHashedUrls(User $user)
|
||||
{
|
||||
$entries = $this->entryRepository->findByEmptyHashedUrlAndUserId($user->getId());
|
||||
|
||||
$i = 1;
|
||||
foreach ($entries as $entry) {
|
||||
$entry->setHashedUrl(UrlHasher::hashUrl($entry->getUrl()));
|
||||
$this->entityManager->persist($entry);
|
||||
|
||||
if (0 === ($i % 20)) {
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->output->writeln(sprintf('Generated hashed urls for user: %s', $user->getUserName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a user from its username.
|
||||
*
|
||||
* @param string $username
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
private function getUser($username)
|
||||
{
|
||||
return $this->userRepository->findOneByUserName($username);
|
||||
}
|
||||
}
|
186
src/Command/Import/ImportCommand.php
Normal file
186
src/Command/Import/ImportCommand.php
Normal file
|
@ -0,0 +1,186 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command\Import;
|
||||
|
||||
use Doctrine\DBAL\Driver\Middleware;
|
||||
use Doctrine\DBAL\Logging\Middleware as LoggingMiddleware;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Config\Definition\Exception\Exception;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
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\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Wallabag\CoreBundle\Entity\User;
|
||||
use Wallabag\CoreBundle\Import\ChromeImport;
|
||||
use Wallabag\CoreBundle\Import\DeliciousImport;
|
||||
use Wallabag\CoreBundle\Import\ElcuratorImport;
|
||||
use Wallabag\CoreBundle\Import\FirefoxImport;
|
||||
use Wallabag\CoreBundle\Import\InstapaperImport;
|
||||
use Wallabag\CoreBundle\Import\PinboardImport;
|
||||
use Wallabag\CoreBundle\Import\PocketHtmlImport;
|
||||
use Wallabag\CoreBundle\Import\ReadabilityImport;
|
||||
use Wallabag\CoreBundle\Import\ShaarliImport;
|
||||
use Wallabag\CoreBundle\Import\WallabagV1Import;
|
||||
use Wallabag\CoreBundle\Import\WallabagV2Import;
|
||||
use Wallabag\CoreBundle\Repository\UserRepository;
|
||||
|
||||
class ImportCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:import';
|
||||
protected static $defaultDescription = 'Import entries from a JSON export';
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
private TokenStorageInterface $tokenStorage;
|
||||
private UserRepository $userRepository;
|
||||
private WallabagV2Import $wallabagV2Import;
|
||||
private FirefoxImport $firefoxImport;
|
||||
private ChromeImport $chromeImport;
|
||||
private ReadabilityImport $readabilityImport;
|
||||
private InstapaperImport $instapaperImport;
|
||||
private PinboardImport $pinboardImport;
|
||||
private DeliciousImport $deliciousImport;
|
||||
private WallabagV1Import $wallabagV1Import;
|
||||
private ElcuratorImport $elcuratorImport;
|
||||
private ShaarliImport $shaarliImport;
|
||||
private PocketHtmlImport $pocketHtmlImport;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $entityManager,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
UserRepository $userRepository,
|
||||
WallabagV2Import $wallabagV2Import,
|
||||
FirefoxImport $firefoxImport,
|
||||
ChromeImport $chromeImport,
|
||||
ReadabilityImport $readabilityImport,
|
||||
InstapaperImport $instapaperImport,
|
||||
PinboardImport $pinboardImport,
|
||||
DeliciousImport $deliciousImport,
|
||||
WallabagV1Import $wallabagV1Import,
|
||||
ElcuratorImport $elcuratorImport,
|
||||
ShaarliImport $shaarliImport,
|
||||
PocketHtmlImport $pocketHtmlImport
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->wallabagV2Import = $wallabagV2Import;
|
||||
$this->firefoxImport = $firefoxImport;
|
||||
$this->chromeImport = $chromeImport;
|
||||
$this->readabilityImport = $readabilityImport;
|
||||
$this->instapaperImport = $instapaperImport;
|
||||
$this->pinboardImport = $pinboardImport;
|
||||
$this->deliciousImport = $deliciousImport;
|
||||
$this->wallabagV1Import = $wallabagV1Import;
|
||||
$this->elcuratorImport = $elcuratorImport;
|
||||
$this->shaarliImport = $shaarliImport;
|
||||
$this->pocketHtmlImport = $pocketHtmlImport;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->addArgument('username', InputArgument::REQUIRED, 'User to populate')
|
||||
->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file')
|
||||
->addOption('importer', null, InputOption::VALUE_OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, delicious, readability, firefox, chrome, elcurator, shaarli or pocket', 'v1')
|
||||
->addOption('markAsRead', null, InputOption::VALUE_OPTIONAL, 'Mark all entries as read', false)
|
||||
->addOption('useUserId', null, InputOption::VALUE_NONE, 'Use user id instead of username to find account')
|
||||
->addOption('disableContentUpdate', null, InputOption::VALUE_NONE, 'Disable fetching updated content from URL')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$output->writeln('Start : ' . (new \DateTime())->format('d-m-Y G:i:s') . ' ---');
|
||||
|
||||
if (!file_exists($input->getArgument('filepath'))) {
|
||||
throw new Exception(sprintf('File "%s" not found', $input->getArgument('filepath')));
|
||||
}
|
||||
|
||||
// Turning off doctrine default logs queries for saving memory
|
||||
$middlewares = $this->entityManager->getConnection()->getConfiguration()->getMiddlewares();
|
||||
$middlewaresWithoutLogging = array_filter($middlewares, function (Middleware $middleware) {
|
||||
return !$middleware instanceof LoggingMiddleware;
|
||||
});
|
||||
$this->entityManager->getConnection()->getConfiguration()->setMiddlewares($middlewaresWithoutLogging);
|
||||
|
||||
if ($input->getOption('useUserId')) {
|
||||
$entityUser = $this->userRepository->findOneById($input->getArgument('username'));
|
||||
} else {
|
||||
$entityUser = $this->userRepository->findOneByUsername($input->getArgument('username'));
|
||||
}
|
||||
|
||||
if (!\is_object($entityUser)) {
|
||||
throw new Exception(sprintf('User "%s" not found', $input->getArgument('username')));
|
||||
}
|
||||
|
||||
// Authenticate user for paywalled websites
|
||||
$token = new UsernamePasswordToken(
|
||||
$entityUser,
|
||||
null,
|
||||
'main',
|
||||
$entityUser->getRoles());
|
||||
|
||||
$this->tokenStorage->setToken($token);
|
||||
$user = $this->tokenStorage->getToken()->getUser();
|
||||
|
||||
switch ($input->getOption('importer')) {
|
||||
case 'v2':
|
||||
$import = $this->wallabagV2Import;
|
||||
break;
|
||||
case 'firefox':
|
||||
$import = $this->firefoxImport;
|
||||
break;
|
||||
case 'chrome':
|
||||
$import = $this->chromeImport;
|
||||
break;
|
||||
case 'readability':
|
||||
$import = $this->readabilityImport;
|
||||
break;
|
||||
case 'instapaper':
|
||||
$import = $this->instapaperImport;
|
||||
break;
|
||||
case 'pinboard':
|
||||
$import = $this->pinboardImport;
|
||||
break;
|
||||
case 'delicious':
|
||||
$import = $this->deliciousImport;
|
||||
break;
|
||||
case 'elcurator':
|
||||
$import = $this->elcuratorImport;
|
||||
break;
|
||||
case 'shaarli':
|
||||
$import = $this->shaarliImport;
|
||||
break;
|
||||
case 'pocket':
|
||||
$import = $this->pocketHtmlImport;
|
||||
break;
|
||||
default:
|
||||
$import = $this->wallabagV1Import;
|
||||
}
|
||||
|
||||
$import->setMarkAsRead($input->getOption('markAsRead'));
|
||||
$import->setDisableContentUpdate($input->getOption('disableContentUpdate'));
|
||||
$import->setUser($user);
|
||||
|
||||
$res = $import
|
||||
->setFilepath($input->getArgument('filepath'))
|
||||
->import();
|
||||
|
||||
if (true === $res) {
|
||||
$summary = $import->getSummary();
|
||||
$output->writeln('<info>' . $summary['imported'] . ' imported</info>');
|
||||
$output->writeln('<comment>' . $summary['skipped'] . ' already saved</comment>');
|
||||
}
|
||||
|
||||
$this->entityManager->clear();
|
||||
|
||||
$output->writeln('End : ' . (new \DateTime())->format('d-m-Y G:i:s') . ' ---');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
57
src/Command/Import/RedisWorkerCommand.php
Normal file
57
src/Command/Import/RedisWorkerCommand.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command\Import;
|
||||
|
||||
use Simpleue\Worker\QueueWorker;
|
||||
use Symfony\Component\Config\Definition\Exception\Exception;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
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\DependencyInjection\ContainerInterface;
|
||||
|
||||
class RedisWorkerCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:import:redis-worker';
|
||||
protected static $defaultDescription = 'Launch Redis worker';
|
||||
|
||||
private $container;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->addArgument('serviceName', InputArgument::REQUIRED, 'Service to use: wallabag_v1, wallabag_v2, pocket, readability, pinboard, delicious, firefox, chrome or instapaper')
|
||||
->addOption('maxIterations', '', InputOption::VALUE_OPTIONAL, 'Number of iterations before stopping', false)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$output->writeln('Worker started at: ' . (new \DateTime())->format('d-m-Y G:i:s'));
|
||||
$output->writeln('Waiting for message ...');
|
||||
|
||||
$serviceName = $input->getArgument('serviceName');
|
||||
|
||||
if (!$this->container->has('wallabag_core.queue.redis.' . $serviceName) || !$this->container->has('wallabag_core.consumer.redis.' . $serviceName)) {
|
||||
throw new Exception(sprintf('No queue or consumer found for service name: "%s"', $input->getArgument('serviceName')));
|
||||
}
|
||||
|
||||
$worker = new QueueWorker(
|
||||
$this->container->get('wallabag_core.queue.redis.' . $serviceName),
|
||||
$this->container->get('wallabag_core.consumer.redis.' . $serviceName),
|
||||
(int) $input->getOption('maxIterations')
|
||||
);
|
||||
|
||||
$worker->start();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
429
src/Command/InstallCommand.php
Normal file
429
src/Command/InstallCommand.php
Normal file
|
@ -0,0 +1,429 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Exception\DriverException;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use FOS\UserBundle\Event\UserEvent;
|
||||
use FOS\UserBundle\FOSUserEvents;
|
||||
use FOS\UserBundle\Model\UserManagerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Wallabag\CoreBundle\Entity\IgnoreOriginInstanceRule;
|
||||
use Wallabag\CoreBundle\Entity\InternalSetting;
|
||||
use Wallabag\CoreBundle\Entity\User;
|
||||
|
||||
class InstallCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:install';
|
||||
protected static $defaultDescription = 'wallabag installer.';
|
||||
|
||||
private InputInterface $defaultInput;
|
||||
private SymfonyStyle $io;
|
||||
private array $functionExists = [
|
||||
'curl_exec',
|
||||
'curl_multi_init',
|
||||
];
|
||||
private bool $runOtherCommands = true;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
private UserManagerInterface $userManager;
|
||||
private string $databaseDriver;
|
||||
private string $databaseName;
|
||||
private array $defaultSettings;
|
||||
private array $defaultIgnoreOriginInstanceRules;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, EventDispatcherInterface $dispatcher, UserManagerInterface $userManager, string $databaseDriver, string $databaseName, array $defaultSettings, array $defaultIgnoreOriginInstanceRules)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->userManager = $userManager;
|
||||
$this->databaseDriver = $databaseDriver;
|
||||
$this->databaseName = $databaseName;
|
||||
$this->defaultSettings = $defaultSettings;
|
||||
$this->defaultIgnoreOriginInstanceRules = $defaultIgnoreOriginInstanceRules;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function disableRunOtherCommands(): void
|
||||
{
|
||||
$this->runOtherCommands = false;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->addOption(
|
||||
'reset',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Reset current database'
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->defaultInput = $input;
|
||||
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
|
||||
$this->io->title('wallabag installer');
|
||||
|
||||
$this
|
||||
->checkRequirements()
|
||||
->setupDatabase()
|
||||
->setupAdmin()
|
||||
->setupConfig()
|
||||
;
|
||||
|
||||
$this->io->success('wallabag has been successfully installed.');
|
||||
$this->io->success('You can now configure your web server, see https://doc.wallabag.org');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function checkRequirements()
|
||||
{
|
||||
$this->io->section('Step 1 of 4: Checking system requirements.');
|
||||
|
||||
$rows = [];
|
||||
|
||||
// testing if database driver exists
|
||||
$fulfilled = true;
|
||||
$label = '<comment>PDO Driver (%s)</comment>';
|
||||
$status = '<info>OK!</info>';
|
||||
$help = '';
|
||||
|
||||
if (!\extension_loaded($this->databaseDriver)) {
|
||||
$fulfilled = false;
|
||||
$status = '<error>ERROR!</error>';
|
||||
$help = 'Database driver "' . $this->databaseDriver . '" is not installed.';
|
||||
}
|
||||
|
||||
$rows[] = [sprintf($label, $this->databaseDriver), $status, $help];
|
||||
|
||||
// testing if connection to the database can be established
|
||||
$label = '<comment>Database connection</comment>';
|
||||
$status = '<info>OK!</info>';
|
||||
$help = '';
|
||||
|
||||
$conn = $this->entityManager->getConnection();
|
||||
|
||||
try {
|
||||
$conn->connect();
|
||||
} catch (\Exception $e) {
|
||||
if (!str_contains($e->getMessage(), 'Unknown database')
|
||||
&& !str_contains($e->getMessage(), 'database "' . $this->databaseName . '" does not exist')) {
|
||||
$fulfilled = false;
|
||||
$status = '<error>ERROR!</error>';
|
||||
$help = 'Can\'t connect to the database: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
$rows[] = [$label, $status, $help];
|
||||
|
||||
// check MySQL & PostgreSQL version
|
||||
$label = '<comment>Database version</comment>';
|
||||
$status = '<info>OK!</info>';
|
||||
$help = '';
|
||||
|
||||
// now check if MySQL isn't too old to handle utf8mb4
|
||||
if ($conn->isConnected() && $conn->getDatabasePlatform() instanceof MySQLPlatform) {
|
||||
$version = $conn->query('select version()')->fetchOne();
|
||||
$minimalVersion = '5.5.4';
|
||||
|
||||
if (false === version_compare($version, $minimalVersion, '>')) {
|
||||
$fulfilled = false;
|
||||
$status = '<error>ERROR!</error>';
|
||||
$help = 'Your MySQL version (' . $version . ') is too old, consider upgrading (' . $minimalVersion . '+).';
|
||||
}
|
||||
}
|
||||
|
||||
// testing if PostgreSQL > 9.1
|
||||
if ($conn->isConnected() && $conn->getDatabasePlatform() instanceof PostgreSQLPlatform) {
|
||||
// return version should be like "PostgreSQL 9.5.4 on x86_64-apple-darwin15.6.0, compiled by Apple LLVM version 8.0.0 (clang-800.0.38), 64-bit"
|
||||
$version = $conn->query('SELECT version();')->fetchOne();
|
||||
|
||||
preg_match('/PostgreSQL ([0-9\.]+)/i', $version, $matches);
|
||||
|
||||
if (isset($matches[1]) & version_compare($matches[1], '9.2.0', '<')) {
|
||||
$fulfilled = false;
|
||||
$status = '<error>ERROR!</error>';
|
||||
$help = 'PostgreSQL should be greater than 9.1 (actual version: ' . $matches[1] . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$rows[] = [$label, $status, $help];
|
||||
|
||||
foreach ($this->functionExists as $functionRequired) {
|
||||
$label = '<comment>' . $functionRequired . '</comment>';
|
||||
$status = '<info>OK!</info>';
|
||||
$help = '';
|
||||
|
||||
if (!\function_exists($functionRequired)) {
|
||||
$fulfilled = false;
|
||||
$status = '<error>ERROR!</error>';
|
||||
$help = 'You need the ' . $functionRequired . ' function activated';
|
||||
}
|
||||
|
||||
$rows[] = [$label, $status, $help];
|
||||
}
|
||||
|
||||
$this->io->table(['Checked', 'Status', 'Recommendation'], $rows);
|
||||
|
||||
if (!$fulfilled) {
|
||||
throw new \RuntimeException('Some system requirements are not fulfilled. Please check output messages and fix them.');
|
||||
}
|
||||
|
||||
$this->io->success('Success! Your system can run wallabag properly.');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setupDatabase()
|
||||
{
|
||||
$this->io->section('Step 2 of 4: Setting up database.');
|
||||
|
||||
// user want to reset everything? Don't care about what is already here
|
||||
if (true === $this->defaultInput->getOption('reset')) {
|
||||
$this->io->text('Dropping database, creating database and schema, clearing the cache');
|
||||
|
||||
$this
|
||||
->runCommand('doctrine:database:drop', ['--force' => true])
|
||||
->runCommand('doctrine:database:create')
|
||||
->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true])
|
||||
->runCommand('cache:clear')
|
||||
;
|
||||
|
||||
$this->io->newLine();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!$this->isDatabasePresent()) {
|
||||
$this->io->text('Creating database and schema, clearing the cache');
|
||||
|
||||
$this
|
||||
->runCommand('doctrine:database:create')
|
||||
->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true])
|
||||
->runCommand('cache:clear')
|
||||
;
|
||||
|
||||
$this->io->newLine();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->io->confirm('It appears that your database already exists. Would you like to reset it?', false)) {
|
||||
$this->io->text('Dropping database, creating database and schema...');
|
||||
|
||||
$this
|
||||
->runCommand('doctrine:database:drop', ['--force' => true])
|
||||
->runCommand('doctrine:database:create')
|
||||
->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true])
|
||||
;
|
||||
} elseif ($this->isSchemaPresent()) {
|
||||
if ($this->io->confirm('Seems like your database contains schema. Do you want to reset it?', false)) {
|
||||
$this->io->text('Dropping schema and creating schema...');
|
||||
|
||||
$this
|
||||
->runCommand('doctrine:schema:drop', ['--force' => true])
|
||||
->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true])
|
||||
;
|
||||
}
|
||||
} else {
|
||||
$this->io->text('Creating schema...');
|
||||
|
||||
$this
|
||||
->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true])
|
||||
;
|
||||
}
|
||||
|
||||
$this->io->text('Clearing the cache...');
|
||||
$this->runCommand('cache:clear');
|
||||
|
||||
$this->io->newLine();
|
||||
$this->io->text('<info>Database successfully setup.</info>');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setupAdmin()
|
||||
{
|
||||
$this->io->section('Step 3 of 4: Administration setup.');
|
||||
|
||||
if (!$this->io->confirm('Would you like to create a new admin user (recommended)?', true)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$user = $this->userManager->createUser();
|
||||
\assert($user instanceof User);
|
||||
|
||||
$user->setUsername($this->io->ask('Username', 'wallabag'));
|
||||
|
||||
$question = new Question('Password', 'wallabag');
|
||||
$question->setHidden(true);
|
||||
$user->setPlainPassword($this->io->askQuestion($question));
|
||||
|
||||
$user->setEmail($this->io->ask('Email', 'wallabag@wallabag.io'));
|
||||
|
||||
$user->setEnabled(true);
|
||||
$user->addRole('ROLE_SUPER_ADMIN');
|
||||
|
||||
$this->entityManager->persist($user);
|
||||
|
||||
// dispatch a created event so the associated config will be created
|
||||
$this->dispatcher->dispatch(new UserEvent($user), FOSUserEvents::USER_CREATED);
|
||||
|
||||
$this->io->text('<info>Administration successfully setup.</info>');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setupConfig()
|
||||
{
|
||||
$this->io->section('Step 4 of 4: Config setup.');
|
||||
|
||||
// cleanup before insert new stuff
|
||||
$this->entityManager->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\InternalSetting')->execute();
|
||||
$this->entityManager->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\IgnoreOriginInstanceRule')->execute();
|
||||
|
||||
foreach ($this->defaultSettings as $setting) {
|
||||
$newSetting = new InternalSetting();
|
||||
$newSetting->setName($setting['name']);
|
||||
$newSetting->setValue($setting['value']);
|
||||
$newSetting->setSection($setting['section']);
|
||||
|
||||
$this->entityManager->persist($newSetting);
|
||||
}
|
||||
|
||||
foreach ($this->defaultIgnoreOriginInstanceRules as $ignore_origin_instance_rule) {
|
||||
$newIgnoreOriginInstanceRule = new IgnoreOriginInstanceRule();
|
||||
$newIgnoreOriginInstanceRule->setRule($ignore_origin_instance_rule['rule']);
|
||||
|
||||
$this->entityManager->persist($newIgnoreOriginInstanceRule);
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->io->text('<info>Config successfully setup.</info>');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command.
|
||||
*
|
||||
* @param string $command
|
||||
* @param array $parameters Parameters to this command (usually 'force' => true)
|
||||
*/
|
||||
private function runCommand($command, $parameters = [])
|
||||
{
|
||||
if (!$this->runOtherCommands) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$parameters = array_merge(
|
||||
['command' => $command],
|
||||
$parameters,
|
||||
[
|
||||
'--no-debug' => true,
|
||||
'--env' => $this->defaultInput->getOption('env') ?: 'dev',
|
||||
]
|
||||
);
|
||||
|
||||
if ($this->defaultInput->getOption('no-interaction')) {
|
||||
$parameters = array_merge($parameters, ['--no-interaction' => true]);
|
||||
}
|
||||
|
||||
$this->getApplication()->setAutoExit(false);
|
||||
|
||||
$output = new BufferedOutput();
|
||||
$exitCode = $this->getApplication()->run(new ArrayInput($parameters), $output);
|
||||
|
||||
// PDO does not always close the connection after Doctrine commands.
|
||||
// See https://github.com/symfony/symfony/issues/11750.
|
||||
$this->entityManager->getConnection()->close();
|
||||
|
||||
if (0 !== $exitCode) {
|
||||
$this->getApplication()->setAutoExit(true);
|
||||
|
||||
throw new \RuntimeException('The command "' . $command . "\" generates some errors: \n\n" . $output->fetch());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the database already exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isDatabasePresent()
|
||||
{
|
||||
$connection = $this->entityManager->getConnection();
|
||||
$databaseName = $connection->getDatabase();
|
||||
|
||||
try {
|
||||
$schemaManager = $connection->createSchemaManager();
|
||||
} catch (\Exception $exception) {
|
||||
// mysql & sqlite
|
||||
if (str_contains($exception->getMessage(), sprintf("Unknown database '%s'", $databaseName))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// pgsql
|
||||
if (str_contains($exception->getMessage(), sprintf('database "%s" does not exist', $databaseName))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
// custom verification for sqlite, since `getListDatabasesSQL` doesn't work for sqlite
|
||||
if ($connection->getDatabasePlatform() instanceof SqlitePlatform) {
|
||||
$params = $connection->getParams();
|
||||
|
||||
if (isset($params['path']) && file_exists($params['path'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return \in_array($databaseName, $schemaManager->listDatabases(), true);
|
||||
} catch (DriverException $e) {
|
||||
// it means we weren't able to get database list, assume the database doesn't exist
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the schema is already created.
|
||||
* If we found at least one table, it means the schema exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isSchemaPresent()
|
||||
{
|
||||
$schemaManager = $this->entityManager->getConnection()->createSchemaManager();
|
||||
|
||||
return \count($schemaManager->listTableNames()) > 0 ? true : false;
|
||||
}
|
||||
}
|
72
src/Command/ListUserCommand.php
Normal file
72
src/Command/ListUserCommand.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
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\Style\SymfonyStyle;
|
||||
use Wallabag\CoreBundle\Repository\UserRepository;
|
||||
|
||||
class ListUserCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:user:list';
|
||||
protected static $defaultDescription = 'List all users';
|
||||
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(UserRepository $userRepository)
|
||||
{
|
||||
$this->userRepository = $userRepository;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setHelp('This command list all existing users')
|
||||
->addArgument('search', InputArgument::OPTIONAL, 'Filter list by given search term')
|
||||
->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Max number of displayed users', 100)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$users = $this->userRepository
|
||||
->getQueryBuilderForSearch($input->getArgument('search'))
|
||||
->setMaxResults($input->getOption('limit'))
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
$nbUsers = $this->userRepository
|
||||
->getSumUsers();
|
||||
|
||||
$rows = [];
|
||||
foreach ($users as $user) {
|
||||
$rows[] = [
|
||||
$user->getUsername(),
|
||||
$user->getEmail(),
|
||||
$user->isEnabled() ? 'yes' : 'no',
|
||||
$user->hasRole('ROLE_SUPER_ADMIN') || $user->hasRole('ROLE_ADMIN') ? 'yes' : 'no',
|
||||
];
|
||||
}
|
||||
|
||||
$io->table(['username', 'email', 'is enabled?', 'is admin?'], $rows);
|
||||
|
||||
$io->success(
|
||||
sprintf(
|
||||
'%s/%s%s user(s) displayed.',
|
||||
\count($users),
|
||||
$nbUsers,
|
||||
null === $input->getArgument('search') ? '' : ' (filtered)'
|
||||
)
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
115
src/Command/ReloadEntryCommand.php
Normal file
115
src/Command/ReloadEntryCommand.php
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
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\Style\SymfonyStyle;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Wallabag\CoreBundle\Event\EntrySavedEvent;
|
||||
use Wallabag\CoreBundle\Helper\ContentProxy;
|
||||
use Wallabag\CoreBundle\Repository\EntryRepository;
|
||||
use Wallabag\CoreBundle\Repository\UserRepository;
|
||||
|
||||
class ReloadEntryCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:entry:reload';
|
||||
protected static $defaultDescription = 'Reload entries';
|
||||
|
||||
private EntryRepository $entryRepository;
|
||||
private UserRepository $userRepository;
|
||||
private EntityManagerInterface $entityManager;
|
||||
private ContentProxy $contentProxy;
|
||||
private EventDispatcherInterface $dispatcher;
|
||||
|
||||
public function __construct(EntryRepository $entryRepository, UserRepository $userRepository, EntityManagerInterface $entityManager, ContentProxy $contentProxy, EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->contentProxy = $contentProxy;
|
||||
$this->dispatcher = $dispatcher;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setHelp('This command reload entries')
|
||||
->addArgument('username', InputArgument::OPTIONAL, 'Reload entries only for the given user')
|
||||
->addOption(
|
||||
'only-not-parsed',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Only reload entries which have `is_not_parsed` set to `true`'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$onlyNotParsed = (bool) $input->getOption('only-not-parsed');
|
||||
$userId = null;
|
||||
if ($username = $input->getArgument('username')) {
|
||||
try {
|
||||
$userId = $this->userRepository
|
||||
->findOneByUserName($username)
|
||||
->getId();
|
||||
} catch (NoResultException $e) {
|
||||
$io->error(sprintf('User "%s" not found.', $username));
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
$methodName = $onlyNotParsed ? 'findAllEntriesIdByUserIdAndNotParsed' : 'findAllEntriesIdByUserId';
|
||||
$entryIds = $this->entryRepository->$methodName($userId);
|
||||
|
||||
$nbEntries = \count($entryIds);
|
||||
if (!$nbEntries) {
|
||||
$io->success('No entry to reload.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$io->note(
|
||||
sprintf(
|
||||
"You're going to reload %s entries. Depending on the number of entry to reload, this could be a very long process.",
|
||||
$nbEntries
|
||||
)
|
||||
);
|
||||
|
||||
if (!$io->confirm('Are you sure you want to proceed?')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$progressBar = $io->createProgressBar($nbEntries);
|
||||
|
||||
$progressBar->start();
|
||||
foreach ($entryIds as $entryId) {
|
||||
$entry = $this->entryRepository->find($entryId);
|
||||
|
||||
$this->contentProxy->updateEntry($entry, $entry->getUrl());
|
||||
$this->entityManager->persist($entry);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->dispatcher->dispatch(new EntrySavedEvent($entry), EntrySavedEvent::NAME);
|
||||
$progressBar->advance();
|
||||
|
||||
$this->entityManager->detach($entry);
|
||||
}
|
||||
$progressBar->finish();
|
||||
|
||||
$io->newLine(2);
|
||||
$io->success('Done.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
82
src/Command/ShowUserCommand.php
Normal file
82
src/Command/ShowUserCommand.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command;
|
||||
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Wallabag\CoreBundle\Entity\User;
|
||||
use Wallabag\CoreBundle\Repository\UserRepository;
|
||||
|
||||
class ShowUserCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:user:show';
|
||||
protected static $defaultDescription = 'Show user details';
|
||||
|
||||
protected SymfonyStyle $io;
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(UserRepository $userRepository)
|
||||
{
|
||||
$this->userRepository = $userRepository;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setHelp('This command shows the details for an user')
|
||||
->addArgument(
|
||||
'username',
|
||||
InputArgument::REQUIRED,
|
||||
'User to show details for'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
|
||||
$username = $input->getArgument('username');
|
||||
|
||||
try {
|
||||
$user = $this->getUser($username);
|
||||
$this->showUser($user);
|
||||
} catch (NoResultException $e) {
|
||||
$this->io->error(sprintf('User "%s" not found.', $username));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function showUser(User $user)
|
||||
{
|
||||
$this->io->listing([
|
||||
sprintf('Username: %s', $user->getUsername()),
|
||||
sprintf('Email: %s', $user->getEmail()),
|
||||
sprintf('Display name: %s', $user->getName()),
|
||||
sprintf('Creation date: %s', $user->getCreatedAt()->format('Y-m-d H:i:s')),
|
||||
sprintf('Last login: %s', null !== $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : 'never'),
|
||||
sprintf('2FA (email) activated: %s', $user->isEmailTwoFactor() ? 'yes' : 'no'),
|
||||
sprintf('2FA (OTP) activated: %s', $user->isGoogleAuthenticatorEnabled() ? 'yes' : 'no'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a user from its username.
|
||||
*
|
||||
* @param string $username
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
private function getUser($username)
|
||||
{
|
||||
return $this->userRepository->findOneByUserName($username);
|
||||
}
|
||||
}
|
84
src/Command/TagAllCommand.php
Normal file
84
src/Command/TagAllCommand.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Wallabag\CoreBundle\Entity\User;
|
||||
use Wallabag\CoreBundle\Helper\RuleBasedTagger;
|
||||
use Wallabag\CoreBundle\Repository\UserRepository;
|
||||
|
||||
class TagAllCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:tag:all';
|
||||
protected static $defaultDescription = 'Tag all entries using the tagging rules.';
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
private RuleBasedTagger $ruleBasedTagger;
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, RuleBasedTagger $ruleBasedTagger, UserRepository $userRepository)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->ruleBasedTagger = $ruleBasedTagger;
|
||||
$this->userRepository = $userRepository;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->addArgument(
|
||||
'username',
|
||||
InputArgument::REQUIRED,
|
||||
'User to tag entries for.'
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
try {
|
||||
$user = $this->getUser($input->getArgument('username'));
|
||||
} catch (NoResultException $e) {
|
||||
$io->error(sprintf('User "%s" not found.', $input->getArgument('username')));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$io->text(sprintf('Tagging entries for user <info>%s</info>...', $user->getUserName()));
|
||||
|
||||
$entries = $this->ruleBasedTagger->tagAllForUser($user);
|
||||
|
||||
$io->text('Persist ' . \count($entries) . ' entries... ');
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$this->entityManager->persist($entry);
|
||||
}
|
||||
$this->entityManager->flush();
|
||||
|
||||
$io->success('Done.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a user from its username.
|
||||
*
|
||||
* @param string $username
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
private function getUser($username)
|
||||
{
|
||||
return $this->userRepository->findOneByUserName($username);
|
||||
}
|
||||
}
|
71
src/Command/UpdatePicturesPathCommand.php
Normal file
71
src/Command/UpdatePicturesPathCommand.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Command;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Wallabag\CoreBundle\Repository\EntryRepository;
|
||||
|
||||
class UpdatePicturesPathCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'wallabag:update-pictures-path';
|
||||
protected static $defaultDescription = 'Update the path of the pictures for each entry when you changed your wallabag instance URL.';
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
private EntryRepository $entryRepository;
|
||||
private string $wallabagUrl;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, EntryRepository $entryRepository, $wallabagUrl)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->entryRepository = $entryRepository;
|
||||
$this->wallabagUrl = $wallabagUrl;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->addArgument(
|
||||
'old-url',
|
||||
InputArgument::REQUIRED,
|
||||
'URL to replace'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$oldUrl = $input->getArgument('old-url');
|
||||
|
||||
$query = $this->entryRepository->createQueryBuilder('e')->getQuery();
|
||||
$io->text('Retrieve existing entries');
|
||||
$i = 1;
|
||||
foreach ($query->toIterable() as $entry) {
|
||||
$content = $entry->getContent();
|
||||
if (null !== $content) {
|
||||
$entry->setContent(str_replace($oldUrl, $this->wallabagUrl, $content));
|
||||
}
|
||||
|
||||
$previewPicture = $entry->getPreviewPicture();
|
||||
if (null !== $previewPicture) {
|
||||
$entry->setPreviewPicture(str_replace($oldUrl, $this->wallabagUrl, $previewPicture));
|
||||
}
|
||||
|
||||
if (0 === ($i % 20)) {
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
$this->entityManager->flush();
|
||||
|
||||
$io->success('Finished updating.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue