Completed
Push — master ( 511693...0349ad )
by Dispositif
04:19
created

WikiPageAction::addToBottomOrCreatePage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 3
c 1
b 0
f 1
nc 2
nop 2
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 getLastEditor(): ?string
77
    {
78
        // page doesn't exist
79
        if (empty($this->page->getRevisions()->getLatest())) {
80
            return null;
81
        }
82
83
        $latest = $this->page->getRevisions()->getLatest();
84
85
        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...
86
    }
87
88
    /**
89
     * Check if a frwiki disambiguation page.
90
     *
91
     * @return bool
92
     */
93
    public function isPageHomonymie(): bool
94
    {
95
        return false !== stristr($this->getText(), '{{homonymie');
96
    }
97
98
    /**
99
     * Is it page with a redirection link ?
100
     *
101
     * @return bool
102
     */
103
    public function isRedirect(): bool
104
    {
105
        return !empty($this->getRedirect());
106
    }
107
108
    /**
109
     * Get redirection page title or null.
110
     *
111
     * @return string|null
112
     */
113
    public function getRedirect(): ?string
114
    {
115
        if ($this->getText() && preg_match('/^#REDIRECT(?:ION)? ?\[\[([^]]+)]]/i', $this->getText(), $matches)) {
116
            return (string)trim($matches[1]);
117
        }
118
119
        return null;
120
    }
121
122
    /**
123
     * Edit the page with new text.
124
     * Opti : EditInfo optional param ?
125
     *
126
     * @param string   $newText
127
     * @param EditInfo $editInfo
128
     *
129
     * @return bool
130
     */
131
    public function editPage(string $newText, EditInfo $editInfo): bool
132
    {
133
        $revision = $this->page->getPageIdentifier();
134
135
        $content = new Content($newText);
136
        $revision = new Revision($content, $revision);
137
138
        // TODO try/catch UsageExceptions badtoken
139
        return $this->wiki->newRevisionSaver()->save($revision, $editInfo);
140
    }
141
142
    /**
143
     * Create a new page.
144
     *
145
     * @param string   $text
146
     *
147
     * @return bool
148
     * @throws Exception
149
     */
150
    public function createPage(string $text, ?EditInfo $editInfo=null): bool
151
    {
152
        if (!empty($this->page->getRevisions()->getLatest())) {
153
            throw new \Exception('That page already exists');
154
        }
155
156
        $newContent = new Content($text);
157
        // $identifier = $this->page->getPageIdentifier()
158
        $title = new Title($this->title);
159
        $identifier = new PageIdentifier($title);
160
        $revision = new Revision($newContent, $identifier);
161
        return $this->wiki->newRevisionSaver()->save($revision, $editInfo);
162
    }
163
164
    /**
165
     * @param string   $addText
166
     * @param EditInfo $editInfo
167
     *
168
     * @return bool
169
     * @throws Exception
170
     */
171
    public function addToBottomOrCreatePage(string $addText, EditInfo $editInfo): bool
172
    {
173
        if (empty($this->page->getRevisions()->getLatest())) {
174
            return $this->createPage($addText, $editInfo);
175
        }
176
177
        return $this->addToBottomOfThePage($addText, $editInfo);
178
    }
179
180
    /**
181
     * Add text to the bottom of the article.
182
     *
183
     * @param string   $addText
184
     * @param EditInfo $editInfo
185
     *
186
     * @return bool
187
     * @throws Exception
188
     */
189
    public function addToBottomOfThePage(string $addText, EditInfo $editInfo): bool
190
    {
191
        if (empty($this->page->getRevisions()->getLatest())) {
192
            throw new Exception('That page does not exist');
193
        }
194
        $oldText = $this->getText();
195
        $newText = $oldText."\n".$addText;
196
197
        return $this->editPage($newText, $editInfo);
198
    }
199
200
    /**
201
     * todo Move to WikiTextUtil ?
202
     * Replace serialized template and manage {{en}} prefix.
203
     * Don't delete {{fr}} on frwiki.
204
     *
205
     * @param string $text       wikitext of the page
206
     * @param string $tplOrigin  template text to replace
207
     * @param string $tplReplace new template text
208
     *
209
     * @return string|null
210
     */
211
    public static function replaceTemplateInText(string $text, string $tplOrigin, string $tplReplace): string
212
    {
213
        if (preg_match_all(
214
            '#(?<langTemp>{{[a-z][a-z]}} ?{{[a-z][a-z]}}) ?'.preg_quote($tplOrigin, '#').'#i',
215
            $text,
216
            $matches
217
        )
218
        ) {
219
            // Skip double lang prefix (like in "{{fr}} {{en}} {template}")
220
            echo 'SKIP ! double lang prefix !';
221
222
            return $text;
223
        }
224
225
        // hack // todo: autres patterns {{en}} ?
226
        if (preg_match_all('#(?<langTemp>{{(?<lang>[a-z][a-z])}} *)?'.preg_quote($tplOrigin, '#').'#i', $text, $matches)
227
            > 0
228
        ) {
229
            foreach ($matches[0] as $num => $mention) {
230
                $lang = $matches['lang'][$num] ?? '';
231
                if(!empty($lang)){
232
                    $lang = Language::all2wiki($lang);
233
                }
234
235
                // detect inconsistency between lang indicator and lang param
236
                // example : {{en}} {{template|lang=ru}}
237
                // BUG: prefix {{de}}  incompatible avec langue de {{Ouvrage |langue= |prénom1=Hartmut |nom1=Atsma
238
                if (!empty($lang) && self::SKIP_LANG_INDICATOR !== $lang
239
                    && !preg_match('#lang(ue)?='.$lang.'#i', $tplReplace)
240
                    && !preg_match('#\| ?langue= ?\|#', $tplReplace)
241
                ) {
242
                    echo sprintf(
243
                        'prefix %s incompatible avec langue de %s',
244
                        $matches['langTemp'][$num],
245
                        $tplReplace
246
                    );
247
248
                    // skip all the replacements of that template
249
                    return $text; // return null ?
250
                }
251
252
                // FIX dirty : {{en}} mais pas de langue sur template...
253
                if ($lang && preg_match('#\| ?langue= ?\|#', $tplReplace) > 0) {
254
                    $previousTpl = $tplReplace;
255
                    $tplReplace = str_replace('langue=', 'langue='.$lang, $tplReplace);
256
                    $text = str_replace($previousTpl, $tplReplace, $text);
257
                }
258
259
                // don't delete {{fr}} before {template} on frwiki
260
                if (self::SKIP_LANG_INDICATOR === $lang) {
261
                    $text = str_replace($tplOrigin, $tplReplace, $text);
262
263
                    continue;
264
                }
265
266
                // replace {template} and {{lang}} {template}
267
                $text = str_replace($mention, $tplReplace, $text);
268
                $text = str_replace(
269
                    $matches['langTemp'][$num].$tplReplace,
270
                    $tplReplace,
271
                    $text
272
                ); // si 1er replace global sans
273
                // {{en}}
274
            }
275
        }
276
277
        return $text;
278
    }
279
280
    /**
281
     * Extract <ref> data from text.
282
     *
283
     * @param $text string
284
     *
285
     * @return array
286
     * @throws Exception
287
     */
288
    public function extractRefFromText(string $text): ?array
289
    {
290
        $parser = new TagParser(); // todo ParserFactory
291
        $refs = $parser->importHtml($text)->getRefValues(); // []
292
293
        return (array)$refs;
294
    }
295
296
    /**
297
     * TODO $url parameter
298
     * TODO? refactor with : parse_str() + parse_url($url, PHP_URL_QUERY)
299
     * check if any ref contains a targeted website/URL.
300
     *
301
     * @param array $refs
302
     *
303
     * @return array
304
     */
305
    public function filterRefByURL(array $refs): array
306
    {
307
        $validRef = [];
308
        foreach ($refs as $ref) {
309
            if (preg_match(
310
                    '#(?<url>https?://(?:www\.)?lemonde\.fr/[^ \]]+)#i',
311
                    $ref,
312
                    $matches
313
                ) > 0
314
            ) {
315
                $validRef[] = ['url' => $matches['url'], 'raw' => $ref];
316
            }
317
        }
318
319
        return $validRef;
320
    }
321
}
322