Passed
Push — master ( 95294c...e75f8e )
by Dispositif
09:41
created

OuvrageEditWorker::run()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 7
ccs 0
cts 2
cp 0
crap 6
rs 10
1
<?php
2
3
/**
4
 * This file is part of dispositif/wikibot application (@github)
5
 * 2019/2020 © Philippe M. <[email protected]>
6
 * For the full copyright and MIT license information, please view the license file.
7
 */
8
9
declare(strict_types=1);
10
11
namespace App\Application;
12
13
use App\Domain\Utils\WikiTextUtil;
14
use App\Infrastructure\DbAdapter;
15
use App\Infrastructure\Memory;
16
use App\Infrastructure\ServiceFactory;
17
use Codedungeon\PHPCliColors\Color;
18
use Exception;
19
use LogicException;
20
use Mediawiki\Api\UsageException;
21
use Normalizer;
22
use Psr\Log\LoggerInterface;
23
use Psr\Log\NullLogger;
24
use Throwable;
25
26
/**
27
 * Class OuvrageEditWorker
28
 *
29
 * @package App\Application\Examples
30
 */
31
class OuvrageEditWorker
32
{
33
    use EditSummaryTrait, TalkPageEditTrait;
34
35
    const TASK_NAME = 'Amélioration bibliographique';
36
    /**
37
     * poster ou pas le message en PD signalant les erreurs à résoudre
38
     */
39
    const EDIT_SIGNALEMENT = true;
40
41
    const CITATION_LIMIT         = 150;
42
    const DELAY_BOTFLAG_SECONDS    = 20;
43
    const DELAY_NO_BOTFLAG_SECONDS = 50;
44
    const ERROR_MSG_TEMPLATE       = __DIR__.'/templates/message_errors.wiki';
45
46
    private $db;
47
    private $bot;
48
    private $wikiText;
49
50
    private $citationSummary;
51
    private $errorWarning = [];
52
    private $importantSummary = [];
53
54
    private $nbRows;
55
56
    // Minor flag on edit
57
    private $minorFlag = true;
58
    // WikiBotConfig flag on edit
59
    private $botFlag = true;
60
61
    /**
62
     * @var Memory
63
     */
64
    private $memory;
65
66
    /**
67
     * @var LoggerInterface|NullLogger
68
     */
69
    private $log;
70
    /**
71
     * @var mixed
72
     */
73
    private $citationVersion;
74
75
    /**
76
     * OuvrageEditWorker constructor.
77
     *
78
     * @param DbAdapter            $dbAdapter
79
     * @param WikiBotConfig        $bot
80
     * @param Memory               $memory
81
     * @param LoggerInterface|null $log
82
     */
83
    public function __construct(
84
        DbAdapter $dbAdapter,
85
        WikiBotConfig $bot,
86
        Memory $memory,
87
        ?LoggerInterface $log = null
88
    ) {
89
        $this->db = $dbAdapter;
90
        $this->bot = $bot;
91
        $this->memory = $memory;
92
        $this->log = $log ?? new NullLogger();
93
    }
94
95
    /**
96
     * @throws Exception
97
     */
98
    public function run(): void
99
    {
100
        while (true) {
101
            echo "\n-------------------------------------\n\n";
102
            echo date("Y-m-d H:i")."\n";
103
            $this->log->notice($this->memory->getMemory(true));
104
            $this->pageProcess();
105
        }
106
    }
107
108
    /**
109
     * @return bool
110
     * @throws UsageException
111
     * @throws Exception
112
     * @throws Exception
113
     */
114
    private function pageProcess()
115
    {
116
        $this->initialize();
117
118
        // get a random queue line
119
        $json = $this->db->getAllRowsToEdit(self::CITATION_LIMIT);
120
        $data = json_decode($json, true);
121
122
        if (empty($data)) {
123
            $this->log->alert("SKIP : no row to process\n");
124
            throw new Exception('no row to process');
125
        }
126
127
        try {
128
            $title = $data[0]['page'];
129
            echo Color::BG_CYAN.$title.Color::NORMAL." \n";
130
            $page = ServiceFactory::wikiPageAction($title, true);
131
        } catch (Exception $e) {
132
            $this->log->warning("*** WikiPageAction error : ".$title." \n");
133
            sleep(20);
134
135
            return false;
136
        }
137
138
        // HACK
139
        if (in_array($page->getLastEditor(), [getenv('BOT_NAME'), getenv('BOT_OWNER')])) {
140
            $this->log->notice("SKIP : édité recemment par bot/dresseur.\n");
141
            $this->db->skipArticle($title);
142
143
            return false;
144
        }
145
        if ($page->getNs() !== 0) {
146
            $this->log->notice("SKIP : page n'est pas dans Main (ns 0)\n");
147
            $this->db->skipArticle($title);
148
149
            return false;
150
        }
151
        $this->wikiText = $page->getText();
152
153
        if (empty($this->wikiText)) {
154
            return false;
155
        }
156
        if (WikiBotConfig::isEditionRestricted($this->wikiText)) {
157
            $this->log->info("SKIP : protection/3R.\n");
158
            $this->db->skipArticle($title);
159
160
            return false;
161
        }
162
163
        if ($this->bot->minutesSinceLastEdit($title) < 15) {
164
            $this->log->info("SKIP : édition humaine dans les dernières 15 minutes.\n");
165
166
            return false;
167
        }
168
169
        // Skip AdQ
170
        if (preg_match('#{{ ?En-tête label#i', $this->wikiText) > 0) {
171
            $this->log->info("SKIP : AdQ ou BA.\n");
172
            $this->db->skipArticle($title);
173
174
            return false;
175
        }
176
177
        // GET all article lines from db
178
        $this->log->info(sprintf("%s rows to process\n", count($data)));
179
180
        // foreach line
181
        $changed = false;
182
        foreach ($data as $dat) {
183
            // hack temporaire pour éviter articles dont CompleteProcess incomplet
184
            if (empty($dat['opti']) || empty($dat['optidate']) || $dat['optidate'] < DbAdapter::OPTI_VALID_DATE) {
185
                $this->log->notice("SKIP : Complètement incomplet de l'article");
186
187
                return false;
188
            }
189
            $success = $this->dataProcess($dat);
190
            $changed = ($success) ? true : $changed;
191
        }
192
        if (!$changed) {
193
            $this->log->debug("Rien à changer...");
194
            $this->db->skipArticle($title);
195
196
            return false;
197
        }
198
199
        // Conversion <ref>http//books.google
200
        //        try {
201
        //            $this->wikiText = $this->refGooConverter->process($this->wikiText);
202
        //        } catch (Throwable $e) {
203
        //            $this->log->warning('refGooConverter->process exception : '.$e->getMessage());
204
        //            unset($e);
205
        //        }
206
207
        // EDIT THE PAGE
208
        if (!$this->wikiText) {
209
            return false;
210
        }
211
212
        $miniSummary = $this->generateSummary();
213
        $this->log->notice($miniSummary);
214
        $this->log->debug("sleep 2...");
215
        sleep(2); // todo ???
216
217
        pageEdit:
218
219
        try {
220
            // corona Covid :)
221
            //$miniSummary .= (date('H:i') === '20:00') ? ' 🏥' : ''; // 🏥🦠
222
223
            $editInfo = ServiceFactory::editInfo($miniSummary, $this->minorFlag, $this->botFlag, 5);
224
            $success = $page->editPage(Normalizer::normalize($this->wikiText), $editInfo);
225
        } catch (Throwable $e) {
226
            // Invalid CSRF token.
227
            if (strpos($e->getMessage(), 'Invalid CSRF token') !== false) {
228
                $this->log->alert("*** Invalid CSRF token \n");
229
                throw new Exception('Invalid CSRF token');
230
            } else {
231
                $this->log->warning('Exception in editPage() '.$e->getMessage());
232
                sleep(10);
233
234
                return false;
235
            }
236
        }
237
238
        $this->log->info($success ? "Edition Ok\n" : "***** Edition KO !\n");
239
240
        if ($success) {
241
            // updata DB
242
            foreach ($data as $dat) {
243
                $this->db->sendEditedData(['id' => $dat['id']]);
244
            }
245
246
            try {
247
                if (self::EDIT_SIGNALEMENT && !empty($this->errorWarning[$title])) {
248
                    $this->sendOuvrageErrorsOnTalkPage($data, $this->log);
249
                }
250
            } catch (Throwable $e) {
251
                $this->log->warning('Exception in editPage() '.$e->getMessage());
252
                unset($e);
253
            }
254
255
            if (!$this->botFlag) {
256
                $this->log->debug("sleep ".self::DELAY_NO_BOTFLAG_SECONDS);
257
                sleep(self::DELAY_NO_BOTFLAG_SECONDS);
258
            }
259
            if ($this->botFlag) {
260
                $this->log->debug("sleep ".self::DELAY_BOTFLAG_SECONDS);
261
                sleep(self::DELAY_BOTFLAG_SECONDS);
262
            }
263
        }
264
265
        return $success;
266
    }
267
268
    /**
269
     * @throws UsageException
270
     */
271
    private function initialize(): void
272
    {
273
        // initialisation vars
274
        $this->botFlag = true;
275
        $this->errorWarning = [];
276
        $this->wikiText = null;
277
        $this->citationSummary = [];
278
        $this->importantSummary = [];
279
        $this->minorFlag = true;
280
        $this->nbRows = 0;
281
282
        $this->bot->checkStopOnTalkpage(true);
283
    }
284
285
    /**
286
     * @param array $data
287
     *
288
     * @return bool
289
     * @throws Exception
290
     */
291
    private function dataProcess(array $data): bool
292
    {
293
        $origin = $data['raw'];
294
        $completed = $data['opti'];
295
296
        $this->log->debug('origin: '.$origin);
297
        $this->log->debug('completed: '.$completed);
298
        $this->log->debug('modifs: '.$data['modifs']);
299
        $this->log->debug('version: '.$data['version']);
300
301
        if (WikiTextUtil::isCommented($origin)) {
302
            $this->log->notice("SKIP: template avec commentaire HTML.");
303
            $this->db->skipRow(intval($data['id']));
304
305
            return false;
306
        }
307
308
        $find = mb_strpos($this->wikiText, $origin);
309
        if ($find === false) {
310
            $this->log->notice("String non trouvée.");
311
            $this->db->skipRow(intval($data['id']));
312
313
            return false;
314
        }
315
316
        $this->checkErrorWarning($data);
317
318
        // Replace text
319
        $newText = WikiPageAction::replaceTemplateInText($this->wikiText, $origin, $completed);
320
321
        if (!$newText || $newText === $this->wikiText) {
322
            $this->log->warning("newText error");
323
324
            return false;
325
        }
326
        $this->wikiText = $newText;
327
        $this->minorFlag = ('1' === $data['major']) ? false : $this->minorFlag;
328
        $this->citationVersion = $data['version'];
329
        $this->citationSummary[] = $data['modifs'];
330
        $this->nbRows++;
331
332
        return true;
333
    }
334
335
    /**
336
     * todo extract
337
     * Vérifie alerte d'erreurs humaines.
338
     *
339
     * @param array $data
340
     *
341
     * @throws Exception
342
     */
343
    private function checkErrorWarning(array $data): void
344
    {
345
        if (!isset($data['opti'])) {
346
            throw new LogicException('Opti NULL');
347
        }
348
349
        // paramètre inconnu
350
        if (preg_match_all(
351
                "#\|[^|]+<!-- ?(PARAMETRE [^>]+ N'EXISTE PAS|VALEUR SANS NOM DE PARAMETRE|ERREUR [^>]+) ?-->#",
352
                $data['opti'],
353
                $matches
354
            ) > 0
355
        ) {
356
            foreach ($matches[0] as $line) {
357
                $this->addErrorWarning($data['page'], $line);
358
            }
359
            //  $this->botFlag = false;
360
            $this->addSummaryTag('paramètre non corrigé');
361
        }
362
363
        // ISBN invalide
364
        if (preg_match("#isbn invalide ?=[^|}]+#i", $data['opti'], $matches) > 0) {
365
            $this->addErrorWarning($data['page'], $matches[0]);
366
            $this->botFlag = false;
367
            $this->addSummaryTag('ISBN invalide');
368
        }
369
370
        // Edits avec ajout conséquent de donnée
371
        if (preg_match('#distinction des auteurs#', $data['modifs']) > 0) {
372
            $this->botFlag = false;
373
            $this->addSummaryTag('distinction des auteurs');
374
        }
375
        // prédiction paramètre correct
376
        if (preg_match('#[^,]+(=>|⇒)[^,]+#', $data['modifs'], $matches) > 0) {
377
            $this->botFlag = false;
378
            $this->addSummaryTag(sprintf('%s', $matches[0]));
379
        }
380
        if (preg_match('#\+\+sous-titre#', $data['modifs']) > 0) {
381
            $this->botFlag = false;
382
            $this->addSummaryTag('+sous-titre');
383
        }
384
        if (preg_match('#\+lieu#', $data['modifs']) > 0) {
385
            $this->addSummaryTag('+lieu');
386
        }
387
        if (preg_match('#tracking#', $data['modifs']) > 0) {
388
            $this->addSummaryTag('tracking');
389
        }
390
        if (preg_match('#présentation en ligne#', $data['modifs']) > 0) {
391
            $this->addSummaryTag('+présentation en ligne');
392
        }
393
        if (preg_match('#distinction auteurs#', $data['modifs']) > 0) {
394
            $this->addSummaryTag('distinction auteurs');
395
        }
396
        if (preg_match('#\+lire en ligne#', $data['modifs']) > 0) {
397
            $this->addSummaryTag('+lire en ligne');
398
        }
399
        if (preg_match('#\+lien #', $data['modifs']) > 0) {
400
            $this->addSummaryTag('wikif');
401
        }
402
403
        if (preg_match('#\+éditeur#', $data['modifs']) > 0) {
404
            $this->addSummaryTag('éditeur');
405
        }
406
        //        if (preg_match('#\+langue#', $data['modifs']) > 0) {
407
        //            $this->addSummaryTag('langue');
408
        //        }
409
410
        // mention BnF si ajout donnée + ajout identifiant bnf=
411
        if (!empty($this->importantSummary) && preg_match('#BnF#i', $data['modifs'], $matches) > 0) {
412
            $this->addSummaryTag('©[[BnF]]');
413
        }
414
    }
415
416
    /**
417
     * todo extract
418
     * Pour éviter les doublons dans signalements d'erreur.
419
     *
420
     * @param string $page
421
     * @param string $text
422
     */
423
    private function addErrorWarning(string $page, string $text): void
424
    {
425
        if (!isset($this->errorWarning[$page]) || !in_array($text, $this->errorWarning[$page])) {
426
            $this->errorWarning[$page][] = $text;
427
        }
428
    }
429
430
}
431