Passed
Branch master (309757)
by Dispositif
03:20 queued 54s
created

OuvrageEditWorker::pageProcess()   F

Complexity

Conditions 30
Paths 295

Size

Total Lines 176
Code Lines 103

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 103
dl 0
loc 176
rs 1.7166
c 0
b 0
f 0
cc 30
nc 295
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\WikiBotConfig;
16
use App\Application\WikiPageAction;
17
use App\Domain\Utils\WikiTextUtil;
18
use App\Infrastructure\ServiceFactory;
19
use Codedungeon\PHPCliColors\Color;
20
use Exception;
21
use LogicException;
22
use Mediawiki\Api\UsageException;
23
use Normalizer;
24
use Psr\Log\LoggerInterface;
25
use Psr\Log\NullLogger;
26
use Throwable;
27
28
/**
29
 * Legacy class, to be refactored. To big, too many responsibilities.
30
 * todo use PageOuvrageCollectionDTO.
31
 */
32
class OuvrageEditWorker
33
{
34
    use OuvrageEditSummaryTrait, TalkPageEditTrait;
1 ignored issue
show
Bug introduced by
The trait App\Application\OuvrageE...OuvrageEditSummaryTrait requires the property $nbRows which is not provided by App\Application\OuvrageEdit\OuvrageEditWorker.
Loading history...
35
36
    public const TASK_NAME = '📗 Amélioration bibliographique'; // 📖📔📘📗
37
    public const LUCKY_MESSAGE = ' 🇺🇦'; // ☘️
38
    /**
39
     * poster ou pas le message en PD signalant les erreurs à résoudre
40
     */
41
    public const EDIT_SIGNALEMENT = true;
42
    public const CITATION_LIMIT = 150;
43
    public const DELAY_BOTFLAG_SECONDS = 60;
44
    public const DELAY_NO_BOTFLAG_SECONDS = 60;
45
    public const DELAY_MINUTES_AFTER_HUMAN_EDIT = 10;
46
    public const ERROR_MSG_TEMPLATE = __DIR__ . '/../templates/message_errors.wiki';
47
48
    /**
49
     * @var PageWorkStatus
50
     */
51
    protected $pageWorkStatus;
52
53
    private $db;
54
    /**
55
     * @var WikiBotConfig
56
     */
57
    private $bot;
58
    /**
59
     * @var MemoryInterface
60
     */
61
    private $memory;
62
63
    /**
64
     * @var LoggerInterface
65
     */
66
    private $log;
67
68
    public function __construct(
69
        DbAdapterInterface $dbAdapter,
70
        WikiBotConfig      $bot,
71
        MemoryInterface    $memory,
72
        ?LoggerInterface   $log = null
73
    )
74
    {
75
        $this->db = $dbAdapter;
76
        $this->bot = $bot;
77
        $this->memory = $memory;
78
        $this->log = $log ?? new NullLogger();
79
    }
80
81
    /**
82
     * @throws Exception
83
     */
84
    public function run(): void
85
    {
86
        while (true) {
87
            echo "\n-------------------------------------\n\n";
88
            echo date("Y-m-d H:i:s") . " ";
89
            $this->log->info($this->memory->getMemory(true));
90
            $this->pageProcess();
91
            sleep(2); // précaution boucle infinie
92
        }
93
    }
94
95
    /**
96
     * @throws UsageException
97
     * @throws Exception
98
     */
99
    private function pageProcess(): bool
100
    {
101
        $e = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $e is dead and can be removed.
Loading history...
102
        $this->pageWorkStatus = new PageWorkStatus();
103
        $this->bot->checkStopOnTalkpage(true);
104
105
        // get a random queue line
106
        $json = $this->db->getAllRowsToEdit(self::CITATION_LIMIT);
0 ignored issues
show
Bug introduced by
The method getAllRowsToEdit() 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

106
        /** @scrutinizer ignore-call */ 
107
        $json = $this->db->getAllRowsToEdit(self::CITATION_LIMIT);
Loading history...
107
        $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
108
109
        if (empty($data)) {
110
            $this->log->alert("SKIP : OuvrageEditWorker / getAllRowsToEdit() no row to process\n");
111
            sleep(60);
112
            throw new Exception('no row to process');
113
        }
114
115
        try {
116
            $title = $data[0]['page'];
117
            echo Color::BG_CYAN . $title . Color::NORMAL . " \n";
118
            $page = ServiceFactory::wikiPageAction($title, false); // , true ?
119
        } catch (Exception $e) {
120
            $this->log->warning("*** WikiPageAction error : " . $title . " \n");
121
            sleep(20);
122
123
            return false;
124
        }
125
126
        // Page supprimée ? todo event listener
127
        if ($page->getLastRevision() === null) {
128
            $this->log->warning("SKIP : page supprimée !\n");
129
            $this->db->deleteArticle($title);
0 ignored issues
show
Bug introduced by
The method deleteArticle() 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

129
            $this->db->/** @scrutinizer ignore-call */ 
130
                       deleteArticle($title);
Loading history...
130
131
            return false;
132
        }
133
134
        // HACK
135
        if ($page->getLastEditor() == getenv('BOT_NAME')) {
136
            $this->log->notice("SKIP : édité recemment par bot.\n");
137
            $this->db->skipArticle($title);
0 ignored issues
show
Bug introduced by
The method skipArticle() 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

137
            $this->db->/** @scrutinizer ignore-call */ 
138
                       skipArticle($title);
Loading history...
138
139
            return false;
140
        }
141
        // todo include a sandbox page ?
142
        if ($page->getNs() !== 0) {
143
            $this->log->notice("SKIP : page n'est pas dans Main (ns 0)\n");
144
            $this->db->skipArticle($title);
145
146
            return false;
147
        }
148
        $this->pageWorkStatus->wikiText = $page->getText();
149
150
        if (empty($this->pageWorkStatus->wikiText)) {
151
            $this->log->warning("SKIP : this->wikitext vide\n");
152
            $this->db->skipArticle($title);
153
            return false;
154
        }
155
156
        // Featured/Good article (AdQ/BA) todo event listener
157
        if (preg_match('#{{ ?En-tête label ?\| ?AdQ#i', $this->pageWorkStatus->wikiText)) {
158
            $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

158
            $this->db->/** @scrutinizer ignore-call */ 
159
                       setLabel($title, 2);
Loading history...
159
            $this->log->warning("Article de Qualité !\n");
160
            $this->pageWorkStatus->botFlag = false;
161
            $this->pageWorkStatus->featured_article = true; // to add star in edit summary
162
        }
163
        if (preg_match('#{{ ?En-tête label ?\| ?BA#i', $this->pageWorkStatus->wikiText)) {
164
            $this->db->setLabel($title, 1);
165
            $this->pageWorkStatus->botFlag = false;
166
            $this->pageWorkStatus->featured_article = true; // to add star in edit summary
167
            $this->log->warning("Bon article !!\n");
168
        }
169
170
        // todo event listener
171
        if (WikiBotConfig::isEditionTemporaryRestrictedOnWiki($this->pageWorkStatus->wikiText)) {
172
            // TODO Gestion d'une repasse dans X jours
173
            $this->log->info("SKIP : protection/3R/travaux.\n");
174
            $this->db->skipArticle($title);
175
176
            return false;
177
        }
178
179
        if ($this->bot->minutesSinceLastEdit($title) < 10) {
180
            // TODO Gestion d'une repasse dans X jours
181
            $this->log->notice(
182
                sprintf(
183
                    "SKIP : édition humaine dans les dernières %s minutes.\n",
184
                    self::DELAY_MINUTES_AFTER_HUMAN_EDIT
185
                )
186
            );
187
            sleep(60 * self::DELAY_MINUTES_AFTER_HUMAN_EDIT); // hack: waiting cycles
188
189
            return false;
190
        }
191
192
193
        // GET all article lines from db
194
        $this->log->info(sprintf("%s rows to process\n", is_countable($data) ? count($data) : 0));
195
196
        // foreach line
197
        $changed = false;
198
        foreach ($data as $dat) {
199
            // hack temporaire pour éviter articles dont CompleteProcess incomplet
200
            if (empty($dat['opti']) || empty($dat['optidate']) || $dat['optidate'] < $this->db->getOptiValidDate()) {
201
                $this->log->notice("SKIP : Amélioration incomplet de l'article. sleep 10min");
202
                sleep(600);
203
204
                return false;
205
            }
206
            $success = $this->dataProcess($dat);
207
            $changed = ($success) ? true : $changed;
208
        }
209
        if (!$changed) {
210
            $this->log->debug("Rien à changer...");
211
            $this->db->skipArticle($title);
212
213
            return false;
214
        }
215
216
        // EDIT THE PAGE
217
        if ($this->pageWorkStatus->wikiText === '' || $this->pageWorkStatus->wikiText === '0') {
218
            return false;
219
        }
220
221
        $miniSummary = $this->generateSummary();
222
        $this->log->notice($miniSummary);
223
        $this->log->debug("sleep 2...");
224
        sleep(2); // todo ???
225
226
        pageEdit:
227
228
        try {
229
            // corona Covid :)
230
            //$miniSummary .= (date('H:i') === '20:00') ? ' 🏥' : ''; // 🏥🦠
231
232
            $editInfo = ServiceFactory::editInfo($miniSummary, $this->pageWorkStatus->minorFlag, $this->pageWorkStatus->botFlag, 5);
233
            $success = $page->editPage(Normalizer::normalize($this->pageWorkStatus->wikiText), $editInfo);
234
        } catch (Throwable $e) {
235
            // Invalid CSRF token.
236
            if (strpos($e->getMessage(), 'Invalid CSRF token') !== false) {
237
                $this->log->alert("*** Invalid CSRF token \n");
238
                throw new Exception('Invalid CSRF token', $e->getCode(), $e);
239
            } else {
240
                $this->log->warning('Exception in editPage() ' . $e->getMessage());
241
                sleep(10);
242
243
                return false;
244
            }
245
        }
246
247
        $this->log->info($success ? "Edition Ok\n" : "***** Edition KO !\n");
248
249
        if ($success) {
250
            // updata DB
251
            foreach ($data as $dat) {
252
                $this->db->sendEditedData(['id' => $dat['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

252
                $this->db->/** @scrutinizer ignore-call */ 
253
                           sendEditedData(['id' => $dat['id']]);
Loading history...
253
            }
254
255
            try {
256
                if (self::EDIT_SIGNALEMENT && !empty($this->pageWorkStatus->errorWarning[$title])) {
257
                    $this->sendOuvrageErrorsOnTalkPage($data, $this->log);
258
                }
259
            } catch (Throwable $e) {
260
                $this->log->warning('Exception in editPage() ' . $e->getMessage());
261
                unset($e);
262
            }
263
264
            if (!$this->pageWorkStatus->botFlag) {
265
                $this->log->debug("sleep " . self::DELAY_NO_BOTFLAG_SECONDS);
266
                sleep(self::DELAY_NO_BOTFLAG_SECONDS);
267
            }
268
            if ($this->pageWorkStatus->botFlag) {
269
                $this->log->debug("sleep " . self::DELAY_BOTFLAG_SECONDS);
270
                sleep(self::DELAY_BOTFLAG_SECONDS);
271
            }
272
        }
273
274
        return $success;
275
    }
276
277
    /**
278
     * @param array $data
279
     *
280
     * @return bool
281
     * @throws Exception
282
     */
283
    private function dataProcess(array $data): bool
284
    {
285
        $origin = $data['raw'];
286
        $completed = $data['opti'];
287
        $this->printDebug($data);
288
289
        if (WikiTextUtil::isCommented($origin) || $this->isTextCreatingError($origin)) {
290
            $this->log->notice("SKIP: template avec commentaire HTML ou modèle problématique.");
291
            $this->db->skipRow((int)$data['id']);
292
293
            return false;
294
        }
295
296
        $find = mb_strpos($this->pageWorkStatus->wikiText, $origin);
297
        if ($find === false) {
298
            $this->log->notice("String non trouvée.");
299
            $this->db->skipRow((int)$data['id']);
300
301
            return false;
302
        }
303
304
        $this->checkErrorWarning($data);
305
306
        // Replace text
307
        $newText = WikiPageAction::replaceTemplateInText($this->pageWorkStatus->wikiText, $origin, $completed);
308
309
        if (!$newText || $newText === $this->pageWorkStatus->wikiText) {
310
            $this->log->warning("newText error");
311
312
            return false;
313
        }
314
        $this->pageWorkStatus->wikiText = $newText;
315
        $this->pageWorkStatus->minorFlag = ('1' === $data['major']) ? false : $this->pageWorkStatus->minorFlag;
316
        $this->pageWorkStatus->citationVersion = $data['version'];
317
        $this->pageWorkStatus->citationSummary[] = $data['modifs'];
318
        $this->pageWorkStatus->nbRows++;
319
320
        return true;
321
    }
322
323
    private function isTextCreatingError(string $string): bool
324
    {
325
        // mauvaise Modèle:Sp
326
        return (preg_match('#\{\{-?(sp|s|sap)-?\|#', $string) === 1);
327
    }
328
329
    /**
330
     * todo extract
331
     * Vérifie alerte d'erreurs humaines.
332
     *
333
     * @param array $data
334
     *
335
     * @throws Exception
336
     */
337
    private function checkErrorWarning(array $data): void
338
    {
339
        if (!isset($data['opti'])) {
340
            throw new LogicException('Opti NULL');
341
        }
342
343
        // paramètre inconnu
344
        if (preg_match_all(
345
                "#\|[^|]+<!-- ?(PARAMETRE [^>]+ N'EXISTE PAS|VALEUR SANS NOM DE PARAMETRE|ERREUR [^>]+) ?-->#",
346
                $data['opti'],
347
                $matches
348
            ) > 0
349
        ) {
350
            foreach ($matches[0] as $line) {
351
                $this->addErrorWarning($data['page'], $line);
352
            }
353
            //  $this->pageWorkStatus->botFlag = false;
354
            $this->addSummaryTag('paramètre non corrigé');
355
        }
356
357
        // ISBN invalide
358
        if (preg_match("#isbn invalide ?=[^|}]+#i", $data['opti'], $matches) > 0) {
359
            $this->addErrorWarning($data['page'], $matches[0]);
360
            $this->pageWorkStatus->botFlag = false;
361
            $this->addSummaryTag('ISBN invalide 💩');
362
        }
363
364
        // Edits avec ajout conséquent de donnée
365
        if (preg_match('#distinction des auteurs#', $data['modifs']) > 0) {
366
            $this->pageWorkStatus->botFlag = false;
367
            $this->addSummaryTag('distinction auteurs 🧠');
368
        }
369
        // prédiction paramètre correct
370
        if (preg_match('#[^,]+(=>|⇒)[^,]+#', $data['modifs'], $matches) > 0) {
371
            $this->pageWorkStatus->botFlag = false;
372
            $this->addSummaryTag($matches[0]);
373
        }
374
        if (preg_match('#\+\+sous-titre#', $data['modifs']) > 0) {
375
            $this->pageWorkStatus->botFlag = false;
376
            $this->addSummaryTag('+sous-titre');
377
        }
378
        if (preg_match('#\+lieu#', $data['modifs']) > 0) {
379
            $this->addSummaryTag('+lieu');
380
        }
381
        if (preg_match('#tracking#', $data['modifs']) > 0) {
382
            $this->addSummaryTag('tracking');
383
        }
384
        if (preg_match('#présentation en ligne#', $data['modifs']) > 0) {
385
            $this->addSummaryTag('+présentation en ligne✨');
386
        }
387
        if (preg_match('#distinction auteurs#', $data['modifs']) > 0) {
388
            $this->addSummaryTag('distinction auteurs 🧠');
389
        }
390
        if (preg_match('#\+lire en ligne#', $data['modifs']) > 0) {
391
            $this->addSummaryTag('+lire en ligne✨');
392
        }
393
        if (preg_match('#\+lien #', $data['modifs']) > 0) {
394
            $this->addSummaryTag('wikif');
395
        }
396
397
        if (preg_match('#\+éditeur#', $data['modifs']) > 0) {
398
            $this->addSummaryTag('éditeur');
399
        }
400
        //        if (preg_match('#\+langue#', $data['modifs']) > 0) {
401
        //            $this->addSummaryTag('langue');
402
        //        }
403
404
        // mention BnF si ajout donnée + ajout identifiant bnf=
405
        if (!empty($this->pageWorkStatus->importantSummary) && preg_match('#BnF#i', $data['modifs'], $matches) > 0) {
406
            $this->addSummaryTag('©[[BnF]]');
407
        }
408
    }
409
410
    /**
411
     * todo extract
412
     * Pour éviter les doublons dans signalements d'erreur.
413
     *
414
     * @param string $page
415
     * @param string $text
416
     */
417
    private function addErrorWarning(string $page, string $text): void
418
    {
419
        if (!isset($this->pageWorkStatus->errorWarning[$page]) || !in_array($text, $this->pageWorkStatus->errorWarning[$page])) {
420
            $this->pageWorkStatus->errorWarning[$page][] = $text;
421
        }
422
    }
423
424
    private function printDebug(array $data)
425
    {
426
        $this->log->debug('origin: ' . $data['raw']);
427
        $this->log->debug('completed: ' . $data['opti']);
428
        $this->log->debug('modifs: ' . $data['modifs']);
429
        $this->log->debug('version: ' . $data['version']);
430
    }
431
}
432