OuvrageEditWorker   A
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 38
eloc 133
c 1
b 0
f 0
dl 0
loc 268
rs 9.36

11 Methods

Rating   Name   Duplication   Size   Complexity  
A run() 0 8 2
A checkArticleLabels() 0 19 5
A printTitle() 0 3 1
A printDebug() 0 6 1
B pageProcess() 0 53 9
A processOneCitation() 0 32 5
A makeCitationsReplacements() 0 12 2
A __construct() 0 10 1
B updateDb() 0 22 7
A editPage() 0 26 4
A normalizeAndFixWikiTypo() 0 3 1
1
<?php
2
3
/*
4
 * This file is part of dispositif/wikibot application (@github)
5
 * 2019-2023 © Philippe M./Irønie  <[email protected]>
6
 * For the full copyright and MIT license information, view the license file.
7
 */
8
9
declare(strict_types=1);
10
11
namespace App\Application\OuvrageEdit;
12
13
use App\Application\InfrastructurePorts\DbAdapterInterface;
14
use App\Application\InfrastructurePorts\MemoryInterface;
15
use App\Application\OuvrageEdit\Validators\CitationsNotEmptyValidator;
16
use App\Application\OuvrageEdit\Validators\CitationValidator;
17
use App\Application\OuvrageEdit\Validators\PageValidatorComposite;
18
use App\Application\OuvrageEdit\Validators\TalkStopValidator;
19
use App\Application\OuvrageEdit\Validators\WikiTextValidator;
20
use App\Application\WikiBotConfig;
21
use App\Application\WikiPageAction;
22
use App\Domain\Utils\WikiRefsFixer;
23
use App\Infrastructure\Monitor\NullLogger;
24
use App\Infrastructure\ServiceFactory;
25
use Codedungeon\PHPCliColors\Color;
26
use Exception;
27
use Mediawiki\Api\UsageException;
28
use Normalizer;
29
use Psr\Log\LoggerInterface;
30
use Throwable;
31
32
/**
33
 * Legacy class, to be refactored. Too big, too many responsibilities.
34
 * todo use PageOuvrageCollectionDTO.
35
 * todo chain of responsibility pattern + log decorator (+ events ?)
36
 */
37
class OuvrageEditWorker
38
{
39
    use OuvrageEditSummaryTrait, TalkPageEditTrait;
0 ignored issues
show
Bug introduced by
The trait App\Application\OuvrageEdit\TalkPageEditTrait requires the property $errorWarning which is not provided by App\Application\OuvrageEdit\OuvrageEditWorker.
Loading history...
introduced by
The trait App\Application\OuvrageE...OuvrageEditSummaryTrait requires some properties which are not provided by App\Application\OuvrageEdit\OuvrageEditWorker: $importantSummary, $nbRows, $errorWarning
Loading history...
40
41
    final public const TASK_NAME = '📗 Amélioration bibliographique'; // 📖📔📘📗
42
    final public const LUCKY_MESSAGE = ' 🇺🇦'; // ☘️
43
    /**
44
     * poster ou pas le message en PD signalant les erreurs à résoudre
45
     */
46
    final public const EDIT_SIGNALEMENT = true;
47
    final public const CITATION_LIMIT = 150;
48
    final public const DELAY_BOTFLAG_SECONDS = 120;
49
    final public const DELAY_NO_BOTFLAG_SECONDS = 120;
50
    final public const ERROR_MSG_TEMPLATE = __DIR__ . '/templates/message_errors.wiki';
51
    protected const ALWAYS_NO_BOTFLAG_ON_BA = true;
52
    protected const ALWAYS_NO_BOTFLAG_ON_ADQ = true;
53
54
    /**
55
     * @var PageWorkStatus
56
     */
57
    protected $pageWorkStatus;
58
59
    /**
60
     * @var WikiPageAction
61
     */
62
    protected $wikiPageAction = null;
63
64
    protected $db;
65
    /**
66
     * @var WikiBotConfig
67
     */
68
    protected $bot;
69
    /**
70
     * @var MemoryInterface
71
     */
72
    protected $memory;
73
    /**
74
     * @var ImportantSummaryCreator
75
     */
76
    protected $summaryCreator;
77
78
    public function __construct(
79
        DbAdapterInterface $dbAdapter,
80
        WikiBotConfig      $bot,
81
        MemoryInterface    $memory,
82
        protected LoggerInterface   $log = new NullLogger()
83
    )
84
    {
85
        $this->db = $dbAdapter;
86
        $this->bot = $bot;
87
        $this->memory = $memory;
88
    }
89
90
    /**
91
     * @throws Exception
92
     */
93
    public function run(): void
94
    {
95
        while (true) {
96
            echo "\n-------------------------------------\n\n";
97
            echo date("Y-m-d H:i:s") . " ";
98
            $this->log->info($this->memory->getMemory(true));
99
            $this->pageProcess();
100
            sleep(2); // précaution boucle infinie
101
        }
102
    }
103
104
    /**
105
     * @throws UsageException
106
     * @throws Exception
107
     */
108
    protected function pageProcess(): bool
109
    {
110
        if (!(new TalkStopValidator($this->bot))->validate()) { // move up ?
111
            return false;
112
        }
113
114
        // get a random queue line
115
        $json = $this->db->getAllRowsOfOneTitleToEdit(self::CITATION_LIMIT);
116
        $pageCitationCollection = $json ? json_decode((string) $json, true, 512, JSON_THROW_ON_ERROR) : [];
117
118
        if (!(new CitationsNotEmptyValidator($pageCitationCollection, $this->log))->validate()) {
119
            return false;
120
        }
121
122
        $this->pageWorkStatus = new PageWorkStatus($pageCitationCollection[0]['page']);
123
        $this->printTitle($this->pageWorkStatus->getTitle());
124
125
        // Find on wikipedia the page to edit
126
        try {
127
            $this->wikiPageAction = ServiceFactory::wikiPageAction($this->pageWorkStatus->getTitle()); // , true ?
128
        } catch (Exception) {
129
            $this->log->warning("*** WikiPageAction error : " . $this->pageWorkStatus->getTitle() . " \n");
130
            sleep(20);
131
132
            return false;
133
        }
134
        $pageValidator = new PageValidatorComposite(
135
            $this->bot, $pageCitationCollection, $this->db, $this->wikiPageAction
136
        );
137
        if (!$pageValidator->validate()) {
138
            return false;
139
        }
140
141
        $this->pageWorkStatus->wikiText = $this->wikiPageAction->getText();
142
        $this->checkArticleLabels($this->pageWorkStatus->getTitle());
143
        // or do that at the end of ArticleValidForEditValidator if PageWorkStatus injected ?
144
145
        // print total number of rows completed in db
146
        $rowNumber = is_countable($pageCitationCollection) ? count($pageCitationCollection) : 0;
147
        $this->log->info(sprintf("%s rows to process\n", $rowNumber));
148
149
        // Make citations replacements
150
        if (!$this->makeCitationsReplacements($pageCitationCollection)) {
151
            return false;
152
        }
153
154
        if ($this->editPage()) {
155
            $this->updateDb($pageCitationCollection);
156
157
            return true;
158
        }
159
160
        return false;
161
    }
162
163
    protected function printTitle(string $title): void
164
    {
165
        echo Color::BG_CYAN . $title . Color::NORMAL . " \n";
166
    }
167
168
    protected function checkArticleLabels($title): void
169
    {
170
        // Featured/Good article (AdQ/BA) todo event listener
171
        if (preg_match('#{{ ?En-tête label ?\| ?AdQ#i', (string) $this->pageWorkStatus->wikiText)) {
172
            $this->db->setLabel($title, 2);
0 ignored issues
show
Bug introduced by
The method setLabel() does not exist on App\Application\Infrastr...orts\DbAdapterInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to App\Application\Infrastr...orts\DbAdapterInterface. ( Ignorable by Annotation )

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

172
            $this->db->/** @scrutinizer ignore-call */ 
173
                       setLabel($title, 2);
Loading history...
173
            $this->log->warning("Article de Qualité !\n");
174
            if (self::ALWAYS_NO_BOTFLAG_ON_ADQ) {
175
                $this->pageWorkStatus->botFlag = false;
176
            }
177
            $this->pageWorkStatus->featured_article = true; // to add star in edit summary
178
        }
179
        if (preg_match('#{{ ?En-tête label ?\| ?BA#i', (string) $this->pageWorkStatus->wikiText)) {
180
            $this->db->setLabel($title, 1);
181
            if (self::ALWAYS_NO_BOTFLAG_ON_BA) {
182
                $this->pageWorkStatus->botFlag = false;
183
            }
184
            $this->pageWorkStatus->botFlag = false;
185
            $this->pageWorkStatus->featured_article = true; // to add star in edit summary
186
            $this->log->warning("Bon article !!\n");
187
        }
188
    }
189
190
    protected function makeCitationsReplacements(array $pageCitationCollection): bool
191
    {
192
        $oldText = $this->pageWorkStatus->wikiText;
193
        $this->summaryCreator = new ImportantSummaryCreator($this->pageWorkStatus);
194
        foreach ($pageCitationCollection as $dat) {
195
            $this->processOneCitation($dat); // that modify PageWorkStatus->wikiText
196
        }
197
        $newWikiTextValidator = new WikiTextValidator(
198
            $this->pageWorkStatus->wikiText, $oldText, $this->log, $this->pageWorkStatus->getTitle(), $this->db
199
        );
200
201
        return $newWikiTextValidator->validate();
202
    }
203
204
    /**
205
     * @throws Exception
206
     */
207
    protected function processOneCitation(array $ouvrageData): bool
208
    {
209
        $origin = $ouvrageData['raw'];
210
        $completed = $ouvrageData['opti'];
211
        $this->printDebug($ouvrageData);
212
213
        $citationValidator = new CitationValidator(
214
            $ouvrageData,
215
            $this->pageWorkStatus->wikiText,
216
            $this->log,
217
            $this->db
218
        );
219
        if (!$citationValidator->validate()) {
220
            return false;
221
        }
222
223
        // Replace text
224
        $newText = WikiPageAction::replaceTemplateInText($this->pageWorkStatus->wikiText, $origin, $completed);
225
226
        if (empty($newText) || $newText === $this->pageWorkStatus->wikiText) {
227
            $this->log->warning("newText error");
228
229
            return false;
230
        }
231
        $this->summaryCreator->processPageOuvrage($ouvrageData);
232
        $this->pageWorkStatus->wikiText = $newText;
233
        $this->pageWorkStatus->minorFlag = ('1' === $ouvrageData['major']) ? false : $this->pageWorkStatus->minorFlag;
234
        $this->pageWorkStatus->citationVersion = $ouvrageData['version']; // todo gérer versions différentes
235
        $this->pageWorkStatus->citationSummary[] = $ouvrageData['modifs'];
236
        $this->pageWorkStatus->nbRows++;
237
238
        return true;
239
    }
240
241
    protected function printDebug(array $data)
242
    {
243
        $this->log->debug('origin: ' . $data['raw']);
244
        $this->log->debug('completed: ' . $data['opti']);
245
        $this->log->debug('modifs: ' . $data['modifs']);
246
        $this->log->debug('version: ' . $data['version']);
247
    }
248
249
    protected function editPage(): bool
250
    {
251
        $miniSummary = $this->generateFinalSummary();
252
253
        $this->log->debug("sleep 2...");
254
        sleep(2); // todo ???
255
256
        try {
257
            $editInfo = ServiceFactory::editInfo($miniSummary, $this->pageWorkStatus->minorFlag, $this->pageWorkStatus->botFlag);
258
            $cleanWikiText = $this->normalizeAndFixWikiTypo($this->pageWorkStatus->wikiText);
259
            $success = $this->wikiPageAction->editPage($cleanWikiText, $editInfo);
260
        } catch (Throwable $e) {
261
            // Invalid CSRF token.
262
            if (str_contains($e->getMessage(), 'Invalid CSRF token')) {
263
                $this->log->alert("*** Invalid CSRF token \n");
264
                throw new Exception('Invalid CSRF token', $e->getCode(), $e);
265
            } else {
266
                $this->log->warning('Exception in editPage() ' . $e->getMessage());
267
                sleep(10);
268
269
                return false;
270
            }
271
        }
272
        $this->log->info($success ? "Edition Ok\n" : "***** Edition KO !\n");
273
274
        return $success;
275
    }
276
277
    protected function updateDb(array $pageOuvrageCollection)
278
    {
279
        $title = $pageOuvrageCollection[0]['page'];
280
        foreach ($pageOuvrageCollection as $ouvrageData) {
281
            $this->db->sendEditedData(['id' => $ouvrageData['id']]);
0 ignored issues
show
Bug introduced by
The method sendEditedData() does not exist on App\Application\Infrastr...orts\DbAdapterInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to App\Application\Infrastr...orts\DbAdapterInterface. ( Ignorable by Annotation )

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

281
            $this->db->/** @scrutinizer ignore-call */ 
282
                       sendEditedData(['id' => $ouvrageData['id']]);
Loading history...
282
        }
283
        try {
284
            if (self::EDIT_SIGNALEMENT && !empty($this->pageWorkStatus->errorWarning[$title])) {
285
                $this->sendOuvrageErrorsOnTalkPage($pageOuvrageCollection, $this->log);
286
            }
287
        } catch (Throwable $e) {
288
            $this->log->warning('Exception in editPage() ' . $e->getMessage());
289
            unset($e);
290
        }
291
292
        if (!$this->pageWorkStatus->botFlag) {
293
            $this->log->debug("sleep " . self::DELAY_NO_BOTFLAG_SECONDS);
294
            sleep(self::DELAY_NO_BOTFLAG_SECONDS);
295
        }
296
        if ($this->pageWorkStatus->botFlag) {
297
            $this->log->debug("sleep " . self::DELAY_BOTFLAG_SECONDS);
298
            sleep(self::DELAY_BOTFLAG_SECONDS);
299
        }
300
    }
301
302
    protected function normalizeAndFixWikiTypo(string $newText): string
303
    {
304
        return Normalizer::normalize(WikiRefsFixer::fixRefWikiSyntax($newText));
305
    }
306
}
307