1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* This file is part of the Superdesk Web Publisher Core Bundle. |
7
|
|
|
* |
8
|
|
|
* Copyright 2019 Sourcefabric z.ú. and contributors. |
9
|
|
|
* |
10
|
|
|
* For the full copyright and license information, please see the |
11
|
|
|
* AUTHORS and LICENSE files distributed with this source code. |
12
|
|
|
* |
13
|
|
|
* @copyright 2019 Sourcefabric z.ú |
14
|
|
|
* @license http://www.superdesk.org/license |
15
|
|
|
*/ |
16
|
|
|
|
17
|
|
|
namespace SWP\Bundle\CoreBundle\Command; |
18
|
|
|
|
19
|
|
|
use Doctrine\ORM\EntityManagerInterface; |
20
|
|
|
use SWP\Bundle\ContentBundle\Model\ArticleInterface; |
21
|
|
|
use SWP\Bundle\CoreBundle\Model\Article; |
22
|
|
|
use SWP\Bundle\CoreBundle\Repository\ArticleRepositoryInterface; |
23
|
|
|
use Symfony\Component\Console\Command\Command; |
24
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
25
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
26
|
|
|
use Symfony\Component\Console\Input\InputOption; |
27
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
28
|
|
|
use Symfony\Component\DomCrawler\Crawler; |
29
|
|
|
|
30
|
|
|
class RemoveMissingEmbeddedImagesCommand extends Command |
31
|
|
|
{ |
32
|
|
|
protected static $defaultName = 'swp:fixer:remove-missing-embedded-images'; |
33
|
|
|
|
34
|
|
|
private $entityManager; |
35
|
|
|
|
36
|
|
|
private $articleRepository; |
37
|
|
|
|
38
|
|
|
public function __construct( |
39
|
|
|
EntityManagerInterface $entityManager, |
40
|
|
|
ArticleRepositoryInterface $articleRepository |
41
|
|
|
) { |
42
|
|
|
parent::__construct(); |
43
|
|
|
|
44
|
|
|
$this->entityManager = $entityManager; |
45
|
|
|
$this->articleRepository = $articleRepository; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
protected function configure(): void |
49
|
|
|
{ |
50
|
|
|
$this |
51
|
|
|
->setName(self::$defaultName) |
52
|
|
|
->setDescription('Remove from article body missing embedded images') |
53
|
|
|
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Do not execute anything, just show what was found') |
54
|
|
|
->addOption('limit', null, InputOption::VALUE_REQUIRED, 'Limit for searched articles') |
55
|
|
|
->addArgument('term', InputArgument::REQUIRED, 'Article body fragment') |
56
|
|
|
->addArgument('parent', InputArgument::REQUIRED, 'Found element parent to be removed'); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int |
60
|
|
|
{ |
61
|
|
|
$limit = 2000; |
62
|
|
|
if (null !== $input->getOption('limit')) { |
63
|
|
|
$limit = (int) $input->getOption('limit'); |
64
|
|
|
} |
65
|
|
|
/** @var ArticleInterface[] $brokenArticles */ |
66
|
|
|
$brokenArticles = $this->getArticlesByBodyContent($this->entityManager, $input->getArgument('term'), $limit); |
|
|
|
|
67
|
|
|
foreach ($brokenArticles as $article) { |
68
|
|
|
$output->writeln(sprintf('Cleaning article: %s', $article->getTitle())); |
69
|
|
|
$articleBody = $article->getBody(); |
70
|
|
|
$crawler = new Crawler($articleBody); |
71
|
|
|
$crawler->filter('img') |
72
|
|
|
->reduce(function (Crawler $node, $i) use ($input) { |
|
|
|
|
73
|
|
|
if (false === strpos($node->attr('src'), $input->getArgument('term'))) { |
74
|
|
|
return false; |
75
|
|
|
} |
76
|
|
|
}) |
77
|
|
|
->each(static function (Crawler $crawler, $i) use ($input) { |
|
|
|
|
78
|
|
|
$closest = $crawler->closest($input->getArgument('parent')); |
|
|
|
|
79
|
|
|
if (is_iterable($closest)) { |
80
|
|
|
foreach ($closest as $node) { |
|
|
|
|
81
|
|
|
$node->parentNode->removeChild($node); |
82
|
|
|
} |
83
|
|
|
} |
84
|
|
|
}); |
85
|
|
|
|
86
|
|
|
$newContent = $crawler->filter('html body')->each(static function (Crawler $crawler) { |
87
|
|
|
return $crawler->html(); |
88
|
|
|
}); |
89
|
|
|
|
90
|
|
|
$article->setBody(implode('', $newContent)); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
if (true !== $input->getOption('dry-run')) { |
94
|
|
|
$this->entityManager->flush(); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
$output->writeln('<bg=green;options=bold>Done. In total processed '.\count($brokenArticles).' articles.</>'); |
98
|
|
|
|
99
|
|
|
return 0; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
private function getArticlesByBodyContent(EntityManagerInterface $manager, string $content, int $limit): array |
103
|
|
|
{ |
104
|
|
|
$queryBuilder = $manager->createQueryBuilder(); |
105
|
|
|
$queryBuilder->select('a')->from(Article::class, 'a'); |
106
|
|
|
$like = $queryBuilder->expr()->like('a.body', $queryBuilder->expr()->literal('%'.$content.'%')); |
107
|
|
|
$queryBuilder->andWhere($like); |
108
|
|
|
$queryBuilder->setMaxResults($limit); |
109
|
|
|
|
110
|
|
|
return $queryBuilder->getQuery()->getResult(); |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.