Passed
Push — master ( d644e9...e142f8 )
by Dispositif
15:55
created

OuvrageEditWorker::normalizeAndFixWikiTypo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 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\WikiTextUtil;
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 DELAY_MINUTES_AFTER_HUMAN_EDIT = 10;
51
    final public const ERROR_MSG_TEMPLATE = __DIR__ . '/templates/message_errors.wiki';
52
    protected const ALWAYS_NO_BOTFLAG_ON_BA = true;
53
    protected const ALWAYS_NO_BOTFLAG_ON_ADQ = true;
54
55
    /**
56
     * @var PageWorkStatus
57
     */
58
    protected $pageWorkStatus;
59
60
    /**
61
     * @var WikiPageAction
62
     */
63
    protected $wikiPageAction = null;
64
65
    protected $db;
66
    /**
67
     * @var WikiBotConfig
68
     */
69
    protected $bot;
70
    /**
71
     * @var MemoryInterface
72
     */
73
    protected $memory;
74
    /**
75
     * @var ImportantSummaryCreator
76
     */
77
    protected $summaryCreator;
78
79
    public function __construct(
80
        DbAdapterInterface $dbAdapter,
81
        WikiBotConfig      $bot,
82
        MemoryInterface    $memory,
83
        protected LoggerInterface   $log = new NullLogger()
84
    )
85
    {
86
        $this->db = $dbAdapter;
87
        $this->bot = $bot;
88
        $this->memory = $memory;
89
    }
90
91
    /**
92
     * @throws Exception
93
     */
94
    public function run(): void
95
    {
96
        while (true) {
97
            echo "\n-------------------------------------\n\n";
98
            echo date("Y-m-d H:i:s") . " ";
99
            $this->log->info($this->memory->getMemory(true));
100
            $this->pageProcess();
101
            sleep(2); // précaution boucle infinie
102
        }
103
    }
104
105
    /**
106
     * @throws UsageException
107
     * @throws Exception
108
     */
109
    protected function pageProcess(): bool
110
    {
111
        if (!(new TalkStopValidator($this->bot))->validate()) { // move up ?
112
            return false;
113
        }
114
115
        // get a random queue line
116
        $json = $this->db->getAllRowsOfOneTitleToEdit(self::CITATION_LIMIT);
117
        $pageCitationCollection = $json ? json_decode((string) $json, true, 512, JSON_THROW_ON_ERROR) : [];
118
119
        if (!(new CitationsNotEmptyValidator($pageCitationCollection, $this->log))->validate()) {
120
            return false;
121
        }
122
123
        $this->pageWorkStatus = new PageWorkStatus($pageCitationCollection[0]['page']);
124
        $this->printTitle($this->pageWorkStatus->getTitle());
125
126
        // Find on wikipedia the page to edit
127
        try {
128
            $this->wikiPageAction = ServiceFactory::wikiPageAction($this->pageWorkStatus->getTitle()); // , true ?
129
        } catch (Exception) {
130
            $this->log->warning("*** WikiPageAction error : " . $this->pageWorkStatus->getTitle() . " \n");
131
            sleep(20);
132
133
            return false;
134
        }
135
        $pageValidator = new PageValidatorComposite(
136
            $this->bot, $pageCitationCollection, $this->db, $this->wikiPageAction
137
        );
138
        if (!$pageValidator->validate()) {
139
            return false;
140
        }
141
142
        $this->pageWorkStatus->wikiText = $this->wikiPageAction->getText();
143
        $this->checkArticleLabels($this->pageWorkStatus->getTitle());
144
        // or do that at the end of ArticleValidForEditValidator if PageWorkStatus injected ?
145
146
        // print total number of rows completed in db
147
        $rowNumber = is_countable($pageCitationCollection) ? count($pageCitationCollection) : 0;
148
        $this->log->info(sprintf("%s rows to process\n", $rowNumber));
149
150
        // Make citations replacements
151
        if (!$this->makeCitationsReplacements($pageCitationCollection)) {
152
            return false;
153
        }
154
155
        if ($this->editPage()) {
156
            $this->updateDb($pageCitationCollection);
157
158
            return true;
159
        }
160
161
        return false;
162
    }
163
164
    protected function printTitle(string $title): void
165
    {
166
        echo Color::BG_CYAN . $title . Color::NORMAL . " \n";
167
    }
168
169
    protected function checkArticleLabels($title): void
170
    {
171
        // Featured/Good article (AdQ/BA) todo event listener
172
        if (preg_match('#{{ ?En-tête label ?\| ?AdQ#i', (string) $this->pageWorkStatus->wikiText)) {
173
            $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

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

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