Passed
Push — master ( 27d925...adfd7e )
by Dispositif
02:31
created

AbstractBotTaskWorker::generateSummaryText()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
ccs 0
cts 0
cp 0
crap 2
1
<?php
2
/*
3
 * This file is part of dispositif/wikibot application (@github)
4
 * 2019-2023 © Philippe M./Irønie  <[email protected]>
5
 * For the full copyright and MIT license information, view the license file.
6
 */
7
8
declare(strict_types=1);
9
10
namespace App\Application;
11
12
use App\Application\Traits\BotWorkerTrait;
13
use App\Application\Traits\WorkerAnalyzedTitlesTrait;
14
use App\Application\Traits\WorkerCLITrait;
15
use App\Domain\Exceptions\ConfigException;
16
use App\Domain\Exceptions\StopActionException;
17
use App\Domain\Models\Summary;
18
use App\Infrastructure\Logger;
19
use App\Infrastructure\PageListInterface;
20
use App\Infrastructure\ServiceFactory;
21
use Exception;
22
use Mediawiki\Api\MediawikiFactory;
23
use Mediawiki\DataModel\EditInfo;
24
use Psr\Log\LoggerInterface;
25
use Throwable;
26
27
abstract class AbstractBotTaskWorker
28
{
29
    use WorkerCLITrait, BotWorkerTrait, WorkerAnalyzedTitlesTrait;
30
31
    public const TASK_BOT_FLAG = false;
32
    public const SLEEP_AFTER_EDITION = 60;
33
    public const MINUTES_DELAY_AFTER_LAST_HUMAN_EDIT = 15;
34
    public const CHECK_EDIT_CONFLICT = true;
35
    public const ARTICLE_ANALYZED_FILENAME = __DIR__ . '/resources/article_edited.txt';
36
    public const SKIP_LASTEDIT_BY_BOT = true;
37
    public const SKIP_NOT_IN_MAIN_WIKISPACE = true;
38
    public const SKIP_ADQ = true;
39
    public const THROTTLE_DELAY_AFTER_EACH_TITLE = 1; //secs
40
41
    /**
42
     * @var PageListInterface
43
     */
44
    protected $pageListGenerator;
45
    /**
46
     * @var WikiBotConfig
47
     */
48
    protected $bot;
49
    /**
50
     * @var MediawikiFactory
51
     */
52
    protected $wiki;
53
    /**
54
     * @var WikiPageAction
55
     */
56
    protected $pageAction;
57
    protected $defaultTaskname;
58
    protected $titleTaskname;
59
    protected $modeAuto = false;
60
    protected $maxLag = 5;
61
    /**
62
     * @var Logger|LoggerInterface
63
     */
64
    protected $log;
65
    /**
66
     * @var array titles previously processed
67
     */
68
    protected $pastAnalyzed = [];
69
    /**
70
     * @var Summary
71
     */
72
    protected $summary;
73
74
    public function __construct(WikiBotConfig $bot, MediawikiFactory $wiki, ?PageListInterface $pagesGen = null)
75
    {
76
        $this->log = $bot->log;
77
        $this->wiki = $wiki;
78
        $this->bot = $bot;
79
        if ($pagesGen !== null) {
80
            $this->pageListGenerator = $pagesGen;
81
        }
82
        $this->setUpInConstructor();
83
84
        $this->defaultTaskname = $bot->taskName;
85
86
        $this->initializePastAnalyzedTitles();
87
88
        // @throw exception on "Invalid CSRF token"
89
        $this->run();//todo delete that and use (Worker)->run($duration) or process management
90
    }
91
92
    protected function setUpInConstructor(): void
93
    {
94
        // optional implementation
95
    }
96
97
    /**
98
     * @throws ConfigException
99
     * @throws Throwable
100
     * @throws StopActionException
101
     */
102
    final public function run(): void
103
    {
104
        echo date('d-m-Y H:i:s') . " *** NEW WORKER ***\n";
105
        foreach ($this->getTitles() as $title) {
106
            try {
107
                $this->titleProcess($title);
108
            } catch (Exception $exception) {
109
                $this->log->error($exception->getMessage());
110
                if ($exception instanceof StopActionException) {
111
112
                    // just stop without fatal error, when "stop" action from talk page
113
                    return;
114
                }
115
116
                throw $exception;
117
            }
118
119
            sleep(self::THROTTLE_DELAY_AFTER_EACH_TITLE);
120
        }
121
    }
122
123
    /**
124
     * @throws ConfigException
125
     */
126
    protected function getTitles(): array
127
    {
128
        if ($this->pageListGenerator === null) {
129
            throw new ConfigException('Empty PageListGenerator');
130
        }
131
132
        return $this->pageListGenerator->getPageTitles();
133
    }
134
135
    protected function titleProcess(string $title): void
136
    {
137
        $this->titleTaskname = $this->defaultTaskname;
138
        $this->printTitle($title);
139
140
        // move up ?
141
        if ($this->checkAlreadyAnalyzed($title)) {
142
            echo "Skip : déjà analysé\n";
143
144
            return;
145
        }
146
147
        $text = $this->getTextFromWikiAction($title);
148
        if (!$this->canProcessTitleArticle($title, $text)) {
149
            return;
150
        }
151
152
        $this->summary = new Summary($this->defaultTaskname);
153
        $this->summary->setBotFlag(static::TASK_BOT_FLAG);
154
        $newText = $this->processWithDomainWorker($title, $text);
0 ignored issues
show
Bug introduced by
It seems like $text can also be of type null; however, parameter $text of App\Application\Abstract...ocessWithDomainWorker() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

154
        $newText = $this->processWithDomainWorker($title, /** @scrutinizer ignore-type */ $text);
Loading history...
155
        $this->memorizeAndSaveAnalyzedTitle($title); // improve : optionnal ?
156
157
        if ($this->isSomethingToChange($text, $newText) && $this->autoOrYesConfirmation()) {
158
            $this->doEdition($newText);
0 ignored issues
show
Bug introduced by
It seems like $newText can also be of type null; however, parameter $newText of App\Application\AbstractBotTaskWorker::doEdition() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

158
            $this->doEdition(/** @scrutinizer ignore-type */ $newText);
Loading history...
159
        }
160
    }
161
162
    /**
163
     * todo DI
164
     * @throws Exception
165
     * @throws Exception
166
     */
167
    protected function getTextFromWikiAction(string $title): ?string
168
    {
169
        $this->pageAction = ServiceFactory::wikiPageAction($title);
170
        if (static::SKIP_NOT_IN_MAIN_WIKISPACE && $this->pageAction->getNs() !== 0) {
171
            throw new Exception("La page n'est pas dans Main (ns!==0)");
172
        }
173
174
        return $this->pageAction->getText();
175
    }
176
177
    /**
178
     * return $newText for editing
179
     */
180
    abstract protected function processWithDomainWorker(string $title, string $text): ?string;
181
182
    /**
183
     * @throws Exception
184
     */
185
    protected function doEdition(string $newText): void
186
    {
187
        try {
188
            $result = $this->pageAction->editPage(
189
                $newText,
190
                new EditInfo(
191
                    $this->generateSummaryText(),
192
                    $this->summary->isMinorFlag(),
193
                    $this->summary->isBotFlag(),
194
                    $this->maxLag
195
                ),
196
                static::CHECK_EDIT_CONFLICT
197
            );
198
        } catch (Throwable $e) {
199
            if (preg_match('#Invalid CSRF token#', $e->getMessage())) {
200
                throw new Exception('Invalid CSRF token', $e->getCode(), $e);
201
            }
202
203
            // If not a critical edition error
204
            // example : Wiki Conflict : Page has been edited after getText()
205
            echo "Error : " . $e->getMessage() . "\n";
206
            $this->log->warning($e->getMessage());
207
208
            return;
209
        }
210
211
        dump($result);
212
        echo "Sleep " . static::SLEEP_AFTER_EDITION . "\n";
213
        sleep(static::SLEEP_AFTER_EDITION);
214
    }
215
216
    /**
217
     * Minimalist summary as "bot: taskname".
218
     * ACHTUNG ! rewriting by some workers (ex: ExternRefWorker).
219
     */
220
    protected function generateSummaryText(): string
221
    {
222
        return $this->summary->serializePrefixAndTaskname();
223
    }
224
}
225