Completed
Push — master ( 81bc9f...cbc9cf )
by Dispositif
02:15
created

OuvrageComplete::mainBookTitle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
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\Domain;
11
12
use App\Domain\Models\Wiki\GoogleLivresTemplate;
13
use App\Domain\Models\Wiki\OuvrageTemplate;
14
use App\Domain\Utils\TextUtil;
15
use App\Domain\Utils\WikiTextUtil;
16
use Exception;
17
18
class OuvrageComplete
19
{
20
    const WIKI_LANGUAGE = 'fr';
21
    /**
22
     * @var OuvrageTemplate
23
     */
24
    private $origin;
25
26
    private $book;
27
28
    public $major = false;
29
30
    public $notCosmetic = false;
31
32
    private $log = [];
33
34
    private $sameBook;
35
36
    //todo: injection référence base ou mapping ? (Google
37
    public function __construct(OuvrageTemplate $origin, OuvrageTemplate $book)
38
    {
39
        $this->origin = clone $origin;
40
        $this->book = $book;
41
    }
42
43
    public function getLog(): array
44
    {
45
        return $this->log;
46
    }
47
48
    /**
49
     * @return OuvrageTemplate
50
     * @throws Exception
51
     */
52
    public function getResult()
53
    {
54
        $this->complete();
55
56
        return $this->origin;
57
    }
58
59
    /**
60
     * @return bool
61
     * @throws Exception
62
     */
63
    private function complete()
64
    {
65
        // si livre suspect, on stoppe
66
        $this->sameBook = $this->predictSameBook();
67
        if (!$this->sameBook) {
68
            dump('not same book');
69
70
            return false;
71
        }
72
73
        $skipParam = [
74
            'auteurs',
75
            'auteur1',
76
            'prénom1',
77
            'nom1',
78
            'auteur2',
79
            'prénom2',
80
            'nom2',
81
            'auteur3',
82
            'prénom3',
83
            'nom3',
84
            'auteur4',
85
            'prénom4',
86
            'nom4',
87
            'lire en ligne',
88
            'présentation en ligne',
89
            'année',
90
            'date',
91
            'sous-titre',
92
        ];
93
94
        // completion automatique
95
        foreach ($this->book->toArray() as $param => $value) {
96
            if (empty($this->origin->getParam($param))) {
97
                if (in_array($param, $skipParam)) {
98
                    continue;
99
                }
100
101
                $this->origin->setParam($param, $value);
102
103
                if ('langue' === $param && static::WIKI_LANGUAGE === $value) {
104
                    //$this->log('fr'.$param);
105
                    continue;
106
                }
107
108
                $this->log('++'.$param);
109
                $this->major = true;
110
                $this->notCosmetic = true;
111
            }
112
        }
113
114
        //        $this->dateComplete();
115
        $this->googleBookProcess();
116
        $this->processSousTitre();
117
118
        return true;
119
    }
120
121
    private function log(string $string): void
122
    {
123
        if (!empty($string)) {
124
            $this->log[] = trim($string);
125
        }
126
    }
127
128
    /**
129
     * todo: test + refactor dirty logic/duplicate.
130
     * todo: bistro specs
131
     * Gestion doublon et accessibilité document Google Book.
132
     *
133
     * @throws Exception
134
     */
135
    private function googleBookProcess()
136
    {
137
        $lire = $this->origin->getParam('lire en ligne') ?? false;
138
        if (!empty($lire) && GoogleLivresTemplate::isGoogleBookValue($lire)) {
139
            //            if (!empty($this->book->getParam('lire en ligne'))) {
140
            //                // idem
141
            //            }
142
            if (!empty($this->book->getParam('présentation en ligne'))) {
143
                // PARTIAL
144
                // on déplace sur présentation
145
                $this->origin->setParam('présentation en ligne', $lire);
146
                $this->origin->unsetParam('lire en ligne');
147
                $this->log('Google partiel');
148
                $this->notCosmetic = true;
149
150
                return; // ?
151
            }
152
            //            if (empty($this->book->getParam('lire en ligne'))) {
153
            //                // todo : delete lire en ligne ?
154
            //                // $this->major = true;
155
            ////                $this->log('non accessible sur Google!');
156
            ////                $this->notCosmetic = true;
157
            //            }
158
        }
159
        // completion basique
160
        $booklire = $this->book->getParam('lire en ligne');
161
        if (empty($lire) && !empty($booklire)) {
162
            $this->origin->setParam('lire en ligne', $booklire);
163
            $this->log('+lire en ligne');
164
            $this->notCosmetic = true;
165
            $this->major = true;
166
        }
167
        unset($lire, $booklire);
168
169
        $presentation = $this->origin->getParam('présentation en ligne') ?? false;
170
        if (!empty($presentation) && GoogleLivresTemplate::isGoogleBookValue($presentation)) {
171
            //            if (!empty($this->book->getParam('présentation en ligne'))) {
172
            //                // idem
173
            //            }
174
            if (!empty($this->book->getParam('lire en ligne'))) {
175
                // TOTAL
176
                // on déplace sur lire en ligne
177
                $this->origin->setParam('lire en ligne', $presentation);
178
                $this->origin->unsetParam('présentation en ligne');
179
                $this->log('Google accessible');
180
                $this->notCosmetic = true;
181
            }
182
            //            if (empty($this->book->getParam('présentation en ligne'))) {
183
            //                // todo: delete présentation en ligne ?
184
            ////                $this->log('non accessible sur Google!');
185
            //            }
186
        }
187
        // todo: completion pertinente si consultation partielle ??
188
        // completion basique
189
        // $bookpresentation = $this->book->getParam('présentation en ligne');
190
    }
191
192
    /**
193
     * @return bool
194
     * @throws Exception
195
     */
196
    private function predictSameBook()
197
    {
198
        if ($this->hasSameISBN() && ($this->hasSameBookTitles() || $this->hasSameAuthors())) {
199
            return true;
200
        }
201
        if ($this->hasSameBookTitles() && $this->hasSameAuthors()) {
202
            return true;
203
        }
204
205
        return false;
206
    }
207
208
    /**
209
     * @return bool
210
     * @throws Exception
211
     */
212
    private function hasSameAuthors(): bool
213
    {
214
        if ($this->authorsFromBook($this->origin) === $this->authorsFromBook($this->book)) {
215
            return true;
216
        }
217
218
        // if there is only 2 char of difference (i.e. typo error)
219
        if (levenshtein($this->authorsFromBook($this->origin), $this->authorsFromBook($this->book)) <= 2) {
220
            $this->log('typo auteurs?');
221
222
            return true;
223
        }
224
225
        // Si auteur manquant sur wikipedia
226
        if (empty($this->authorsFromBook($this->origin))) {
227
            return true;
228
        }
229
230
        return false;
231
    }
232
233
    /**
234
     * @param OuvrageTemplate $ouv
235
     *
236
     * @return string
237
     * @throws Exception
238
     */
239
    private function authorsFromBook(OuvrageTemplate $ouv)
240
    {
241
        $text = '';
242
        $paramAuteurs = [
243
            'auteurs',
244
            'auteur1',
245
            'prénom1',
246
            'nom1',
247
            'auteur2',
248
            'prénom2',
249
            'nom2',
250
            'auteur3',
251
            'prénom3',
252
            'nom3',
253
            'auteur4',
254
            'prénom4',
255
            'nom4',
256
        ];
257
        foreach ($paramAuteurs as $param) {
258
            $value = $ouv->getParam($param);
259
            // retire wikilien sur auteur
260
            if (!empty($value)) {
261
                $text .= WikiTextUtil::unWikify($value);
262
            }
263
        }
264
265
        return $this->stripAll($text);
266
    }
267
268
    /**
269
     * @return bool
270
     * @throws Exception
271
     */
272
    private function hasSameISBN(): bool
273
    {
274
        if (empty($this->origin->getParam('isbn')) || empty($this->book->getParam('isbn'))) {
275
            return false;
276
        }
277
        // TODO replace with calcul isbn13
278
        $isbn1 = IsbnFacade::isbn2ean($this->origin->getParam('isbn'));
1 ignored issue
show
Bug introduced by
It seems like $this->origin->getParam('isbn') can also be of type null; however, parameter $isbn of App\Domain\IsbnFacade::isbn2ean() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

278
        $isbn1 = IsbnFacade::isbn2ean(/** @scrutinizer ignore-type */ $this->origin->getParam('isbn'));
Loading history...
279
        $isbn2 = IsbnFacade::isbn2ean($this->book->getParam('isbn'));
280
        if ($isbn1 === $isbn2) {
281
            return true;
282
        }
283
284
        return false;
285
    }
286
287
    /**
288
     * Add or extract subtitle like in second book.
289
     *
290
     * @throws Exception
291
     */
292
    private function processSousTitre()
293
    {
294
        if (empty($this->book->getParam('sous-titre'))) {
295
            return;
296
        }
297
298
        // Skip pour éviter conflit entre 'sous-titre' et 'collection' ou 'titre volume'
299
        if (!empty($this->origin->getParam('titre volume'))
300
            || !empty($this->origin->getParam('titre chapitre'))
301
            || !empty($this->origin->getParam('titre tome'))
302
            || !empty($this->origin->getParam('collection'))
303
            || !empty($this->origin->getParam('nature ouvrage'))
304
        ) {
305
            return;
306
        }
307
308
        // simple : titres identiques mais sous-titre manquant
309
        if ($this->stripAll($this->origin->getParam('titre')) === $this->stripAll($this->book->getParam('titre'))) {
0 ignored issues
show
Bug introduced by
It seems like $this->origin->getParam('titre') can also be of type null; however, parameter $text of App\Domain\OuvrageComplete::stripAll() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

309
        if ($this->stripAll(/** @scrutinizer ignore-type */ $this->origin->getParam('titre')) === $this->stripAll($this->book->getParam('titre'))) {
Loading history...
310
            // même titre mais sous-titre manquant
311
            if (empty($this->origin->getParam('sous-titre'))) {
312
                $this->origin->setParam('sous-titre', $this->book->getParam('sous-titre'));
0 ignored issues
show
Bug introduced by
It seems like $this->book->getParam('sous-titre') can also be of type null; however, parameter $value of App\Domain\Models\Wiki\A...ikiTemplate::setParam() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

312
                $this->origin->setParam('sous-titre', /** @scrutinizer ignore-type */ $this->book->getParam('sous-titre'));
Loading history...
313
                $this->log('++sous-titre');
314
                $this->major = true;
315
                $this->notCosmetic = true;
316
317
                return;
318
            }
319
        }
320
321
        // compliqué : sous-titre inclus dans titre original => on copie titre/sous-titre de book
322
        if ($this->charsFromBigTitle($this->origin) === $this->charsFromBigTitle($this->book)) {
323
            if (empty($this->origin->getParam('sous-titre'))) {
324
                $this->origin->setParam('titre', $this->book->getParam('titre'));
325
                $this->origin->setParam('sous-titre', $this->book->getParam('sous-titre'));
326
                $this->log('>titre>sous-titre');
327
            }
328
        }
329
    }
330
331
    /**
332
     * @return bool
333
     * @throws Exception
334
     */
335
    private function hasSameBookTitles(): bool
336
    {
337
        if ($this->charsFromBigTitle($this->origin) === $this->charsFromBigTitle($this->book)) {
338
            return true;
339
        }
340
341
        // if there is only 2 chars of difference (i.e. typo error)
342
        if (levenshtein($this->charsFromBigTitle($this->origin), $this->charsFromBigTitle($this->book)) <= 2) {
343
            //            $this->log('typo titre?'); // TODO Normalize:: text from external API
344
345
            return true;
346
        }
347
348
        // si l'un des ouvrages ne comporte pas le sous-titre
349
        if ($this->stripAll($this->origin->getParam('titre')) === $this->stripAll($this->book->getParam('titre'))) {
0 ignored issues
show
Bug introduced by
It seems like $this->origin->getParam('titre') can also be of type null; however, parameter $text of App\Domain\OuvrageComplete::stripAll() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

349
        if ($this->stripAll(/** @scrutinizer ignore-type */ $this->origin->getParam('titre')) === $this->stripAll($this->book->getParam('titre'))) {
Loading history...
350
            return true;
351
        }
352
353
        // sous-titre inclus dans le titre
354
        // "Loiret : un département à l'élégance naturelle" <=> "Loiret"
355
        if ($this->stripAll($this->mainBookTitle($this->origin->getParam('titre'))) === $this->stripAll(
0 ignored issues
show
Bug introduced by
It seems like $this->origin->getParam('titre') can also be of type null; however, parameter $str of App\Domain\OuvrageComplete::mainBookTitle() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

355
        if ($this->stripAll($this->mainBookTitle(/** @scrutinizer ignore-type */ $this->origin->getParam('titre'))) === $this->stripAll(
Loading history...
356
                $this->mainBookTitle($this->origin->getParam('titre'))
357
            )
358
        ) {
359
            return true;
360
        }
361
        // titre manquant sur wiki
362
        if (empty($this->charsFromBigTitle($this->origin))) {
363
            return true;
364
        }
365
366
        return false;
367
    }
368
369
    /**
370
     * Give string before ":" (or same string if no ":").
371
     *
372
     * @param string $str
373
     *
374
     * @return string
375
     */
376
    private function mainBookTitle(string $str)
377
    {
378
        if (($pos = mb_strpos($str, ':'))) {
379
            $str = trim(mb_substr($str, 0, $pos));
380
        }
381
382
        return $str;
383
    }
384
385
    /**
386
     * @param OuvrageTemplate $ouvrage
387
     *
388
     * @return string
389
     * @throws Exception
390
     */
391
    private function charsFromBigTitle(OuvrageTemplate $ouvrage): string
392
    {
393
        $text = $ouvrage->getParam('titre').$ouvrage->getParam('sous-titre');
394
395
        return $this->stripAll(\Normalizer::normalize($text));
396
    }
397
398
    /**
399
     * @param string $text
400
     *
401
     * @return string
402
     */
403
    private function stripAll(string $text): string
404
    {
405
        $text = str_replace([' and ', ' et ', '&'], '', $text);
406
        $text = str_replace(' ', '', $text);
407
        $text = mb_strtolower(TextUtil::stripPunctuation(TextUtil::stripAccents($text)));
408
409
        return $text;
410
    }
411
}
412