Completed
Push — master ( 0349ad...e37ca9 )
by Dispositif
02:28
created

WikiPageAction::getRedirect()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 2
Metric Value
cc 3
eloc 3
c 2
b 0
f 2
nc 2
nop 0
dl 0
loc 7
rs 10
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;
11
12
use App\Domain\Enums\Language;
13
use App\Infrastructure\TagParser;
14
use Exception;
15
use Mediawiki\Api\MediawikiFactory;
16
use Mediawiki\DataModel\Content;
17
use Mediawiki\DataModel\EditInfo;
18
use Mediawiki\DataModel\Page;
19
use Mediawiki\DataModel\PageIdentifier;
20
use Mediawiki\DataModel\Revision;
21
use Mediawiki\DataModel\Title;
22
use Throwable;
23
24
class WikiPageAction
25
{
26
    const SKIP_LANG_INDICATOR = 'fr'; // skip {{fr}} before template
27
28
    /**
29
     * @var Page
30
     */
31
    public $page; // public for debug
32
33
    public $wiki; // api ?
34
    /**
35
     * @var string
36
     */
37
    private $title;
38
39
    /**
40
     * WikiPageAction constructor.
41
     *
42
     * @param MediawikiFactory $wiki
43
     * @param string           $title
44
     *
45
     * @throws Exception
46
     */
47
    public function __construct(MediawikiFactory $wiki, string $title)
48
    {
49
        $this->wiki = $wiki;
50
        $this->title = $title;
51
52
        try {
53
            $this->page = $wiki->newPageGetter()->getFromTitle($title);
54
        } catch (Throwable $e) {
55
            throw new Exception('Erreur construct WikiPageAction '.$e->getMessage());
56
        }
57
    }
58
59
    /**
60
     * Get wiki text from the page.
61
     *
62
     * @return string|null
63
     */
64
    public function getText(): ?string
65
    {
66
        // page doesn't exist
67
        if (empty($this->page->getRevisions()->getLatest())) {
68
            return null;
69
        }
70
71
        $latest = $this->page->getRevisions()->getLatest();
72
73
        return ($latest) ? $latest->getContent()->getData() : null;
1 ignored issue
show
introduced by
$latest is of type Mediawiki\DataModel\Revision, thus it always evaluated to true.
Loading history...
74
    }
75
76
    public function getLastRevision(): ?Revision
77
    {
78
        // page doesn't exist
79
        if (empty($this->page->getRevisions()->getLatest())) {
80
            return null;
81
        }
82
83
        return $this->page->getRevisions()->getLatest();
84
    }
85
86
    public function getLastEditor(): ?string
87
    {
88
        // page doesn't exist
89
        if (empty($this->page->getRevisions()->getLatest())) {
90
            return null;
91
        }
92
93
        $latest = $this->page->getRevisions()->getLatest();
94
95
        return ($latest) ? $latest->getUser() : null;
1 ignored issue
show
introduced by
$latest is of type Mediawiki\DataModel\Revision, thus it always evaluated to true.
Loading history...
96
    }
97
98
    /**
99
     * Check if a frwiki disambiguation page.
100
     *
101
     * @return bool
102
     */
103
    public function isPageHomonymie(): bool
104
    {
105
        return false !== stristr($this->getText(), '{{homonymie');
106
    }
107
108
    /**
109
     * Is it page with a redirection link ?
110
     *
111
     * @return bool
112
     */
113
    public function isRedirect(): bool
114
    {
115
        return !empty($this->getRedirect());
116
    }
117
118
    /**
119
     * Get redirection page title or null.
120
     *
121
     * @return string|null
122
     */
123
    public function getRedirect(): ?string
124
    {
125
        if ($this->getText() && preg_match('/^#REDIRECT(?:ION)? ?\[\[([^]]+)]]/i', $this->getText(), $matches)) {
126
            return (string)trim($matches[1]);
127
        }
128
129
        return null;
130
    }
131
132
    /**
133
     * Edit the page with new text.
134
     * Opti : EditInfo optional param ?
135
     *
136
     * @param string   $newText
137
     * @param EditInfo $editInfo
138
     *
139
     * @return bool
140
     */
141
    public function editPage(string $newText, EditInfo $editInfo): bool
142
    {
143
        $revision = $this->page->getPageIdentifier();
144
145
        $content = new Content($newText);
146
        $revision = new Revision($content, $revision);
147
148
        // TODO try/catch UsageExceptions badtoken
149
        return $this->wiki->newRevisionSaver()->save($revision, $editInfo);
150
    }
151
152
    /**
153
     * Create a new page.
154
     *
155
     * @param string $text
156
     *
157
     * @return bool
158
     * @throws Exception
159
     */
160
    public function createPage(string $text, ?EditInfo $editInfo = null): bool
161
    {
162
        if (!empty($this->page->getRevisions()->getLatest())) {
163
            throw new \Exception('That page already exists');
164
        }
165
166
        $newContent = new Content($text);
167
        // $identifier = $this->page->getPageIdentifier()
168
        $title = new Title($this->title);
169
        $identifier = new PageIdentifier($title);
170
        $revision = new Revision($newContent, $identifier);
171
172
        return $this->wiki->newRevisionSaver()->save($revision, $editInfo);
173
    }
174
175
    /**
176
     * @param string   $addText
177
     * @param EditInfo $editInfo
178
     *
179
     * @return bool success
180
     * @throws Exception
181
     */
182
    public function addToBottomOrCreatePage(string $addText, EditInfo $editInfo): bool
183
    {
184
        if (empty($this->page->getRevisions()->getLatest())) {
185
            return $this->createPage($addText, $editInfo);
186
        }
187
188
        return $this->addToBottomOfThePage($addText, $editInfo);
189
    }
190
191
    /**
192
     * Add text to the bottom of the article.
193
     *
194
     * @param string   $addText
195
     * @param EditInfo $editInfo
196
     *
197
     * @return bool success
198
     * @throws Exception
199
     */
200
    public function addToBottomOfThePage(string $addText, EditInfo $editInfo): bool
201
    {
202
        if (empty($this->page->getRevisions()->getLatest())) {
203
            throw new Exception('That page does not exist');
204
        }
205
        $oldText = $this->getText();
206
        $newText = $oldText."\n".$addText;
207
208
        return $this->editPage($newText, $editInfo);
209
    }
210
211
    /**
212
     * todo Move to WikiTextUtil ?
213
     * Replace serialized template and manage {{en}} prefix.
214
     * Don't delete {{fr}} on frwiki.
215
     *
216
     * @param string $text       wikitext of the page
217
     * @param string $tplOrigin  template text to replace
218
     * @param string $tplReplace new template text
219
     *
220
     * @return string|null
221
     */
222
    public static function replaceTemplateInText(string $text, string $tplOrigin, string $tplReplace): string
223
    {
224
        if (preg_match_all(
225
            '#(?<langTemp>{{[a-z][a-z]}} ?{{[a-z][a-z]}}) ?'.preg_quote($tplOrigin, '#').'#i',
226
            $text,
227
            $matches
228
        )
229
        ) {
230
            // Skip double lang prefix (like in "{{fr}} {{en}} {template}")
231
            echo 'SKIP ! double lang prefix !';
232
233
            return $text;
234
        }
235
236
        // hack // todo: autres patterns {{en}} ?
237
        if (preg_match_all('#(?<langTemp>{{(?<lang>[a-z][a-z])}} *)?'.preg_quote($tplOrigin, '#').'#i', $text, $matches)
238
            > 0
239
        ) {
240
            foreach ($matches[0] as $num => $mention) {
241
                $lang = $matches['lang'][$num] ?? '';
242
                if (!empty($lang)) {
243
                    $lang = Language::all2wiki($lang);
244
                }
245
246
                // detect inconsistency between lang indicator and lang param
247
                // example : {{en}} {{template|lang=ru}}
248
                // BUG: prefix {{de}}  incompatible avec langue de {{Ouvrage |langue= |prénom1=Hartmut |nom1=Atsma
249
                if (!empty($lang) && self::SKIP_LANG_INDICATOR !== $lang
250
                    && !preg_match('#lang(ue)?='.$lang.'#i', $tplReplace)
251
                    && !preg_match('#\| ?langue= ?\|#', $tplReplace)
252
                ) {
253
                    echo sprintf(
254
                        'prefix %s incompatible avec langue de %s',
255
                        $matches['langTemp'][$num],
256
                        $tplReplace
257
                    );
258
259
                    // skip all the replacements of that template
260
                    return $text; // return null ?
261
                }
262
263
                // FIX dirty : {{en}} mais pas de langue sur template...
264
                if ($lang && preg_match('#\| ?langue= ?\|#', $tplReplace) > 0) {
265
                    $previousTpl = $tplReplace;
266
                    $tplReplace = str_replace('langue=', 'langue='.$lang, $tplReplace);
267
                    $text = str_replace($previousTpl, $tplReplace, $text);
268
                }
269
270
                // don't delete {{fr}} before {template} on frwiki
271
                if (self::SKIP_LANG_INDICATOR === $lang) {
272
                    $text = str_replace($tplOrigin, $tplReplace, $text);
273
274
                    continue;
275
                }
276
277
                // replace {template} and {{lang}} {template}
278
                $text = str_replace($mention, $tplReplace, $text);
279
                $text = str_replace(
280
                    $matches['langTemp'][$num].$tplReplace,
281
                    $tplReplace,
282
                    $text
283
                ); // si 1er replace global sans
284
                // {{en}}
285
            }
286
        }
287
288
        return $text;
289
    }
290
291
    /**
292
     * Extract <ref> data from text.
293
     *
294
     * @param $text string
295
     *
296
     * @return array
297
     * @throws Exception
298
     */
299
    public function extractRefFromText(string $text): ?array
300
    {
301
        $parser = new TagParser(); // todo ParserFactory
302
        $refs = $parser->importHtml($text)->getRefValues(); // []
303
304
        return (array)$refs;
305
    }
306
307
    /**
308
     * TODO $url parameter
309
     * TODO? refactor with : parse_str() + parse_url($url, PHP_URL_QUERY)
310
     * check if any ref contains a targeted website/URL.
311
     *
312
     * @param array $refs
313
     *
314
     * @return array
315
     */
316
    public function filterRefByURL(array $refs): array
317
    {
318
        $validRef = [];
319
        foreach ($refs as $ref) {
320
            if (preg_match(
321
                    '#(?<url>https?://(?:www\.)?lemonde\.fr/[^ \]]+)#i',
322
                    $ref,
323
                    $matches
324
                ) > 0
325
            ) {
326
                $validRef[] = ['url' => $matches['url'], 'raw' => $ref];
327
            }
328
        }
329
330
        return $validRef;
331
    }
332
}
333