Completed
Push — master ( 6952ff...fc5925 )
by Dispositif
02:35
created

EditProcess   F

Complexity

Total Complexity 74

Size/Duplication

Total Lines 458
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 230
c 4
b 0
f 0
dl 0
loc 458
rs 2.48
wmc 74

11 Methods

Rating   Name   Duplication   Size   Complexity  
A initialize() 0 12 1
B dataProcess() 0 39 6
A __construct() 0 6 1
B generateSummary() 0 37 7
B sendErrorMessage() 0 48 8
A wikiLogin() 0 3 1
F pageProcess() 0 138 27
A addErrorWarning() 0 4 3
F checkErrorWarning() 0 64 15
A run() 0 11 3
A addSummaryTag() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like EditProcess often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EditProcess, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of dispositif/wikibot application
4
 * 2019 : Philippe M. <[email protected]>
5
 * For the full copyright and MIT license information, please view the LICENSE file.
6
 */
7
8
declare(strict_types=1);
9
10
namespace App\Application\Examples;
11
12
use App\Application\Bot;
13
use App\Application\Memory;
14
use App\Application\WikiPageAction;
15
use App\Domain\Utils\WikiTextUtil;
16
use App\Infrastructure\DbAdapter;
17
use App\Infrastructure\ServiceFactory;
18
use Exception;
19
use LogicException;
20
use Mediawiki\DataModel\EditInfo;
21
use Normalizer;
22
use Throwable;
23
24
//use App\Application\CLI;
25
26
include __DIR__.'/../myBootstrap.php';
27
28
// sort of process management
29
while (true) {
30
    try {
31
        echo "*** NEW EDIT PROCESS\n";
32
        $process = new EditProcess();
33
//        $process->verbose = true;
34
        $process->run();
35
    } catch (\Throwable $e) {
36
        echo $e->getMessage();
37
        unset($e);
38
    }
39
    unset($process);
40
    echo "Sleep 2h\n";
41
    sleep(60 * 60 * 2);
42
}
43
44
/**
45
 * TODO refac
46
 * Class EditProcess
47
 *
48
 * @package App\Application\Examples
49
 */
50
class EditProcess
51
{
52
    const TASK_NAME = 'Amélioration bibliographique';
53
    /**
54
     * poster ou pas le message en PD signalant les erreurs à résoudre
55
     */
56
    const EDIT_SIGNALEMENT = true;
57
58
    const CITATION_LIMIT         = 150;
59
    const DELAY_BOTFLAG_SECONDS  = 10;
60
    const DELAY_NOBOT_IN_SECONDS = 60;
61
    const ERROR_MSG_TEMPLATE     = __DIR__.'/../templates/message_errors.wiki';
62
63
    public $verbose = false;
64
    private $db;
65
    private $bot;
66
    private $wiki;
67
    private $wikiText;
68
69
    private $citationSummary;
70
    private $citationVersion = '';
71
    private $errorWarning = [];
72
    private $importantSummary = [];
73
74
    private $nbRows;
75
76
    // Minor flag on edit
77
    private $minorFlag = true;
78
    // Bot flag on edit
79
    private $botFlag = true;
80
81
    public function __construct()
82
    {
83
        $this->db = new DbAdapter();
84
        $this->bot = new Bot();
85
86
        $this->wikiLogin(true);
87
    }
88
89
    public function run(): void
90
    {
91
        $memory = new Memory();
92
        while (true) {
93
            echo "\n-------------------------------------\n\n";
94
            echo date("Y-m-d H:i")."\n";
95
            if ($this->verbose) {
96
                $memory->echoMemory(true);
97
            }
98
99
            $this->pageProcess();
100
        }
101
    }
102
103
    private function pageProcess()
104
    {
105
        $this->initialize();
106
107
        // get a random queue line
108
        $json = $this->db->getAllRowsToEdit(self::CITATION_LIMIT);
109
        $data = json_decode($json, true);
110
111
        if (empty($data)) {
112
            echo "SKIP : no row to process\n";
113
            throw new \Exception('no row to process');
114
        }
115
116
        try {
117
            $title = $data[0]['page'];
118
            echo "$title \n";
119
            $page = new WikiPageAction($this->wiki, $title);
120
        } catch (Exception $e) {
121
            echo "*** WikiPageAction error : $title \n";
122
            sleep(20);
123
124
            return false;
125
        }
126
127
        // TODO : HACK
128
        if (in_array($page->getLastEditor(), [getenv('BOT_NAME'), getenv('BOT_OWNER')])) {
129
            echo "SKIP : édité recemment par bot/dresseur.\n";
130
            $this->db->skipArticle($title);
131
132
            return false;
133
        }
134
        $this->wikiText = $page->getText();
135
136
        if (BOT::isEditionRestricted($this->wikiText)) {
137
            echo "SKIP : protection/3R.\n";
138
            $this->db->skipArticle($title);
139
        }
140
141
        if ($this->bot->minutesSinceLastEdit($title) < 15) {
142
            echo "SKIP : édition humaine dans les dernières 15 minutes.\n";
143
144
            return false;
145
        }
146
147
        // Skip AdQ
148
        if (preg_match('#{{ ?En-tête label#i', $this->wikiText) > 0) {
149
            echo "SKIP : AdQ ou BA.\n";
150
            $this->db->skipArticle($title);
151
152
            return false;
153
        }
154
155
        // GET all article lines from db
156
        echo sprintf(">> %s rows to process\n", count($data));
157
158
        // foreach line
159
        $changed = false;
160
        foreach ($data as $dat) {
161
            // hack temporaire pour éviter articles dont CompleteProcess incomplet
162
            if (empty($dat['opti']) || empty($dat['optidate']) || $dat['optidate'] < DbAdapter::OPTI_VALID_DATE) {
163
                echo "SKIP : Complètement incomplet de l'article \n";
164
165
                return false;
166
            }
167
            $success = $this->dataProcess($dat);
168
            $changed = ($success) ? true : $changed;
169
        }
170
        if (!$changed) {
171
            echo "Rien à changer...\n\n";
172
            $this->db->skipArticle($title);
173
174
            return false;
175
        }
176
177
        // EDIT THE PAGE
178
        if (!$this->wikiText) {
179
            return false;
180
        }
181
182
        $miniSummary = $this->generateSummary();
183
        echo $miniSummary."\n\n";
184
        if ($this->verbose) {
185
            echo "sleep 20...\n";
186
        }
187
        sleep(30);
188
189
        pageEdit:
190
191
        try {
192
            $editInfo = new EditInfo($miniSummary, $this->minorFlag, $this->botFlag);
193
            $success = $page->editPage(Normalizer::normalize($this->wikiText), $editInfo);
194
        } catch (\Throwable $e) {
195
            // Invalid CSRF token.
196
            if (strpos($e->getMessage(), 'Invalid CSRF token') !== false) {
197
                echo "*** Invalid CSRF token \n";
198
                throw new \Exception('Invalid CSRF token');
199
            } else {
200
                dump($e); // todo log
201
                sleep(60);
202
203
                return false;
204
            }
205
        }
206
207
        if ($this->verbose) {
208
            echo ($success) ? "Ok\n" : "***** Erreur edit\n";
209
        }
210
211
        if ($success) {
212
            // updata DB
213
            foreach ($data as $dat) {
214
                $this->db->sendEditedData(['id' => $dat['id']]);
215
            }
216
217
            try {
218
                if (self::EDIT_SIGNALEMENT) {
219
                    $this->sendErrorMessage($data);
220
                }
221
            } catch (Throwable $e) {
222
                dump($e);
223
                unset($e);
224
            }
225
226
            if (!$this->botFlag) {
227
                if ($this->verbose) {
228
                    echo "sleep ".self::DELAY_NOBOT_IN_SECONDS."\n";
229
                }
230
                sleep(self::DELAY_NOBOT_IN_SECONDS);
231
            }
232
            if ($this->botFlag) {
233
                if ($this->verbose) {
234
                    echo "sleep ".self::DELAY_BOTFLAG_SECONDS."\n";
235
                }
236
                sleep(self::DELAY_BOTFLAG_SECONDS);
237
            }
238
        }
239
240
        return $success;
241
    }
242
243
    private function dataProcess(array $data): bool
244
    {
245
        $origin = $data['raw'];
246
        $completed = $data['opti'];
247
248
        dump($origin, $completed, $data['modifs'], $data['version']);
249
250
        if (WikiTextUtil::isCommented($origin)) {
251
            echo "SKIP: template avec commentaire HTML\n";
252
            $this->db->skipRow(intval($data['id']));
253
254
            return false;
255
        }
256
257
        $find = mb_strpos($this->wikiText, $origin);
258
        if ($find === false) {
259
            echo "String non trouvée. \n\n";
260
            $this->db->skipRow(intval($data['id']));
261
262
            return false;
263
        }
264
265
        $this->checkErrorWarning($data);
266
267
        // Replace text
268
        $newText = WikiPageAction::replaceTemplateInText($this->wikiText, $origin, $completed);
269
270
        if (!$newText || $newText === $this->wikiText) {
271
            echo "newText error\n";
272
273
            return false;
274
        }
275
        $this->wikiText = $newText;
276
        $this->minorFlag = ('1' === $data['major']) ? false : $this->minorFlag;
277
        $this->citationVersion = $data['version'];
278
        $this->citationSummary[] = $data['modifs'];
279
        $this->nbRows++;
280
281
        return true;
282
    }
283
284
    /**
285
     * Generate wiki edition summary.
286
     *
287
     * @return string
288
     */
289
    public function generateSummary(): string
290
    {
291
        // Start summary with "Bot" when using botflag, else "*"
292
        $prefix = ($this->botFlag) ? 'bot' : '☆';
293
        // add "/!\" when errorWarning
294
        $prefix .= (!empty($this->errorWarning)) ? ' ⚠' : '';
295
296
297
        // basic modifs
298
        $citeSummary = implode(' ', $this->citationSummary);
299
        // replace by list of modifs to verify by humans
300
        if (!empty($this->importantSummary)) {
301
            $citeSummary = implode(', ', $this->importantSummary);
302
        }
303
304
        $summary = sprintf(
305
            '%s [%s/%s] %s %s : %s',
306
            $prefix,
307
            str_replace('v', '', $this->bot::getGitVersion()),
308
            str_replace('v0.', '', $this->citationVersion),
309
            self::TASK_NAME,
310
            $this->nbRows,
311
            $citeSummary
312
        );
313
314
        if (!empty($this->importantSummary)) {
315
            $summary .= '...';
316
        }
317
318
        // shrink long summary if no important details to verify
319
        if (empty($this->importantSummary)) {
320
            $length = strlen($summary);
321
            $summary = mb_substr($summary, 0, 80);
322
            $summary .= ($length > strlen($summary)) ? '…' : '';
323
        }
324
325
        return $summary;
326
    }
327
328
    /**
329
     * Vérifie alerte d'erreurs humaines.
330
     *
331
     * @param array $data
332
     *
333
     * @throws Exception
334
     */
335
    private function checkErrorWarning(array $data): void
336
    {
337
        if (!isset($data['opti'])) {
338
            throw new LogicException('Opti NULL');
339
        }
340
341
        // paramètre inconnu
342
        if (preg_match_all(
343
                "#\|[^|]+<!-- ?(PARAMETRE [^>]+ N'EXISTE PAS|VALEUR SANS NOM DE PARAMETRE) ?-->#",
344
                $data['opti'],
345
                $matches
346
            ) > 0
347
        ) {
348
            foreach ($matches[0] as $line) {
349
                $this->addErrorWarning($data['page'], $line);
350
            }
351
            //  $this->botFlag = false;
352
            $this->addSummaryTag('paramètre non corrigé');
353
        }
354
355
        // ISBN invalide
356
        if (preg_match("#isbn invalide ?=[^|}]+#i", $data['opti'], $matches) > 0) {
357
            $this->addErrorWarning($data['page'], $matches[0]);
358
            $this->botFlag = false;
359
            $this->addSummaryTag('ISBN invalide');
360
        }
361
362
        // Edits avec ajout conséquent de donnée
363
        if (preg_match('#distinction des auteurs#', $data['modifs']) > 0) {
364
            $this->botFlag = false;
365
            $this->addSummaryTag('distinction des auteurs');
366
        }
367
        // prédiction paramètre correct
368
        if (preg_match('#[^,]+(=>|⇒)[^,]+#', $data['modifs'], $matches) > 0) {
369
            $this->botFlag = false;
370
            $this->addSummaryTag(sprintf('%s', $matches[0]));
371
        }
372
        if (preg_match('#\+\+sous-titre#', $data['modifs']) > 0) {
373
            $this->botFlag = false;
374
            $this->addSummaryTag('+sous-titre');
375
        }
376
        if (preg_match('#\+lieu#', $data['modifs']) > 0) {
377
            $this->addSummaryTag('+lieu');
378
        }
379
        if (preg_match('#présentation en ligne#', $data['modifs']) > 0) {
380
            $this->addSummaryTag('+présentation en ligne');
381
        }
382
        if (preg_match('#distinction auteurs#', $data['modifs']) > 0) {
383
            $this->addSummaryTag('distinction auteurs');
384
        }
385
        if (preg_match('#\+lire en ligne#', $data['modifs']) > 0) {
386
            $this->addSummaryTag('+lire en ligne');
387
        }
388
389
        if (preg_match('#\+éditeur#', $data['modifs']) > 0) {
390
            $this->addSummaryTag('éditeur');
391
        }
392
        //        if (preg_match('#\+langue#', $data['modifs']) > 0) {
393
        //            $this->addSummaryTag('langue');
394
        //        }
395
396
        // mention BnF si ajout donnée + ajout identifiant bnf=
397
        if (!empty($this->importantSummary) && preg_match('#BnF#i', $data['modifs'], $matches) > 0) {
398
            $this->addSummaryTag('(BnF)');
399
        }
400
    }
401
402
    /**
403
     * Pour éviter les doublons dans signalements d'erreur.
404
     *
405
     * @param string $page
406
     * @param string $text
407
     */
408
    private function addErrorWarning(string $page, string $text): void
409
    {
410
        if (!isset($this->errorWarning[$page]) || !in_array($text, $this->errorWarning[$page])) {
411
            $this->errorWarning[$page][] = $text;
412
        }
413
    }
414
415
    /**
416
     * For substantive or ambiguous modifications done.
417
     *
418
     * @param string $tag
419
     */
420
    private function addSummaryTag(string $tag)
421
    {
422
        if (!in_array($tag, $this->importantSummary)) {
423
            $this->importantSummary[] = $tag;
424
        }
425
    }
426
427
    /**
428
     * @param array $rows Collection of citations
429
     *
430
     * @return bool
431
     */
432
    private function sendErrorMessage(array $rows): bool
433
    {
434
        if (!isset($rows[0]) || empty($this->errorWarning[$rows[0]['page']])) {
435
            return false;
436
        }
437
        $mainTitle = $rows[0]['page'];
438
        if (!$this->botFlag) {
439
            echo "** Send Error Message on talk page. Wait 3... \n";
440
        }
441
        sleep(3);
442
443
        // format wiki message
444
        $errorList = '';
445
        foreach ($this->errorWarning[$mainTitle] as $error) {
446
            $errorList .= sprintf("* <span style=\"background:#FCDFE8\"><nowiki>%s</nowiki></span> \n", $error);
447
        }
448
449
        $diffStr = '';
450
        try {
451
            // get last bot revision ID
452
            $main = new WikiPageAction($this->wiki, $mainTitle);
453
            if (getenv('BOT_NAME') === $main->getLastRevision()->getUser()) {
454
                $id = $main->getLastRevision()->getId();
455
                $diffStr = sprintf(
456
                    ' ([https://fr.wikipedia.org/w/index.php?title=%s&diff=%s diff])',
457
                    str_replace(' ', '_', $mainTitle),
458
                    $id
459
                );
460
            }
461
        } catch (\Throwable $e) {
462
            unset($e);
463
        }
464
465
        $errorMessage = file_get_contents(self::ERROR_MSG_TEMPLATE);
466
        $errorMessage = str_replace('##ERROR LIST##', trim($errorList), $errorMessage);
467
        $errorMessage = str_replace('##ARTICLE##', $mainTitle, $errorMessage);
468
        $errorMessage = str_replace('##DIFF##', $diffStr, $errorMessage);
469
470
        // Edit wiki talk page
471
        try {
472
            $talkPage = new WikiPageAction($this->wiki, 'Discussion:'.$mainTitle);
473
            $editInfo = new EditInfo('Signalement erreur {ouvrage}', false, false);
474
475
            return $talkPage->addToBottomOrCreatePage($errorMessage, $editInfo);
476
        } catch (Throwable $e) {
477
            dump($e);
478
479
            return false;
480
        }
481
    }
482
483
    /**
484
     * @throws \Mediawiki\Api\UsageException
485
     */
486
    private function initialize(): void
487
    {
488
        // initialisation vars
489
        $this->botFlag = true;
490
        $this->errorWarning = [];
491
        $this->wikiText = null;
492
        $this->citationSummary = [];
493
        $this->importantSummary = [];
494
        $this->minorFlag = true;
495
        $this->nbRows = 0;
496
497
        $this->bot->checkStopOnTalkpage(true);
498
    }
499
500
    /**
501
     * @param bool $forceLogin
502
     *
503
     * @throws \Mediawiki\Api\UsageException
504
     */
505
    private function wikiLogin($forceLogin = false): void
506
    {
507
        $this->wiki = ServiceFactory::wikiApi($forceLogin);
508
    }
509
510
}
511