Completed
Push — master ( ecc147...02d12e )
by Dispositif
02:42
created

OuvrageComplete::hasSameISBN()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 7
c 1
b 0
f 0
nc 3
nop 0
dl 0
loc 13
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
use Normalizer;
18
19
class OuvrageComplete
20
{
21
    const ADD_PRESENTATION_EN_LIGNE = true;
22
23
    const WIKI_LANGUAGE = 'fr';
24
25
    /**
26
     * @var OuvrageTemplate
27
     */
28
    private $origin;
29
30
    private $book;
31
32
    public $major = false;
33
34
    public $notCosmetic = false;
35
36
    private $log = [];
37
38
    private $sameBook;
39
40
    //todo: injection référence base ou mapping ? (Google
41
    public function __construct(OuvrageTemplate $origin, OuvrageTemplate $book)
42
    {
43
        $this->origin = clone $origin;
44
        $this->book = $book;
45
    }
46
47
    public function getLog(): array
48
    {
49
        return $this->log;
50
    }
51
52
    /**
53
     * @return OuvrageTemplate
54
     * @throws Exception
55
     */
56
    public function getResult()
57
    {
58
        $this->complete();
59
60
        return $this->origin;
61
    }
62
63
    /**
64
     * @return bool
65
     * @throws Exception
66
     */
67
    private function complete()
68
    {
69
        // si livre suspect, on stoppe
70
        $this->sameBook = $this->predictSameBook();
71
        if (!$this->sameBook) {
72
            dump('not same book');
73
74
            return false;
75
        }
76
77
        $skipParam = [
78
            'isbn invalide',
79
            'auteurs',
80
            'auteur1',
81
            'prénom1',
82
            'nom1',
83
            'auteur2',
84
            'prénom2',
85
            'nom2',
86
            'auteur3',
87
            'prénom3',
88
            'nom3',
89
            'auteur4',
90
            'prénom4',
91
            'nom4',
92
            'lire en ligne',
93
            'présentation en ligne',
94
            'date',
95
            'sous-titre',
96
        ];
97
98
        // completion automatique
99
        foreach ($this->book->toArray() as $param => $value) {
100
            if (empty($this->origin->getParam($param))) {
101
                if (in_array($param, $skipParam)) {
102
                    continue;
103
                }
104
                // skip 'année' if 'date' not empty
105
                if ('année' === $param && !empty($this->origin->getParam('date'))) {
106
                    continue;
107
                }
108
109
                $this->origin->setParam($param, $value);
110
111
                if ('langue' === $param && static::WIKI_LANGUAGE === $value) {
112
                    //$this->log('fr'.$param);
113
                    continue;
114
                }
115
116
                $this->log('++'.$param);
117
                $this->major = true;
118
                $this->notCosmetic = true;
119
            }
120
        }
121
122
        //        $this->dateComplete();
123
        $this->googleBookProcess();
124
        $this->processSousTitre();
125
126
        if ($this->notCosmetic && 'BnF' === $this->book->getSource()) {
127
            $this->log('(BnF)');
128
        }
129
130
        return true;
131
    }
132
133
    private function log(string $string): void
134
    {
135
        if (!empty($string)) {
136
            $this->log[] = trim($string);
137
        }
138
    }
139
140
    /**
141
     * Complétion lire/présentation en ligne, si vide.
142
     * Passe Google Book en accès partiel en 'lire en ligne' (sondage)
143
     *
144
     * @throws Exception
145
     */
146
    private function googleBookProcess()
147
    {
148
        // si déjà lire/présentation en ligne => on touche à rien
149
        if (!empty($this->origin->getParam('lire en ligne'))
150
            || !empty($this->origin->getParam('présentation en ligne'))
151
        ) {
152
            return;
153
        }
154
155
        // completion basique
156
        $booklire = $this->book->getParam('lire en ligne');
157
        if ($booklire) {
158
            $this->origin->setParam('lire en ligne', $booklire);
159
            $this->log('+lire en ligne');
160
            $this->notCosmetic = true;
161
            $this->major = true;
162
163
            return;
164
        }
165
166
        $presentation = $this->book->getParam('présentation en ligne') ?? false;
167
        // Ajout du partial Google => mis en lire en ligne
168
        // plutôt que 'présentation en ligne' selon sondage
169
        if (!empty($presentation) && GoogleLivresTemplate::isGoogleBookValue($presentation)) {
170
            $this->origin->setParam('lire en ligne', $presentation);
171
            $this->log('+lire en ligne');
172
            $this->notCosmetic = true;
173
            $this->major = true;
174
        }
175
    }
176
177
    /**
178
     * @return bool
179
     * @throws Exception
180
     */
181
    private function predictSameBook()
182
    {
183
        if ($this->hasSameISBN() && ($this->hasSameBookTitles() || $this->hasSameAuthors())) {
184
            return true;
185
        }
186
        if ($this->hasSameBookTitles() && $this->hasSameAuthors()) {
187
            return true;
188
        }
189
190
        return false;
191
    }
192
193
    /**
194
     * @return bool
195
     * @throws Exception
196
     */
197
    private function hasSameAuthors(): bool
198
    {
199
        if ($this->authorsFromBook($this->origin) === $this->authorsFromBook($this->book)) {
200
            return true;
201
        }
202
203
        // if there is only 2 char of difference (i.e. typo error)
204
        if (levenshtein($this->authorsFromBook($this->origin), $this->authorsFromBook($this->book)) <= 2) {
205
            $this->log('typo auteurs?');
206
207
            return true;
208
        }
209
210
        // Si auteur manquant sur wikipedia
211
        if (empty($this->authorsFromBook($this->origin))) {
212
            return true;
213
        }
214
215
        return false;
216
    }
217
218
    /**
219
     * @param OuvrageTemplate $ouv
220
     *
221
     * @return string
222
     * @throws Exception
223
     */
224
    private function authorsFromBook(OuvrageTemplate $ouv)
225
    {
226
        $text = '';
227
        $paramAuteurs = [
228
            'auteurs',
229
            'auteur1',
230
            'prénom1',
231
            'nom1',
232
            'auteur2',
233
            'prénom2',
234
            'nom2',
235
            'auteur3',
236
            'prénom3',
237
            'nom3',
238
            'auteur4',
239
            'prénom4',
240
            'nom4',
241
        ];
242
        foreach ($paramAuteurs as $param) {
243
            $value = $ouv->getParam($param);
244
            // retire wikilien sur auteur
245
            if (!empty($value)) {
246
                $text .= WikiTextUtil::unWikify($value);
247
            }
248
        }
249
250
        return $this->stripAll($text);
251
    }
252
253
    /**
254
     * @return bool
255
     * @throws Exception
256
     */
257
    private function hasSameISBN(): bool
258
    {
259
        if (empty($this->origin->getParam('isbn')) || empty($this->book->getParam('isbn'))) {
260
            return false;
261
        }
262
        // TODO replace with calcul isbn13
263
        $isbn1 = IsbnFacade::isbn2ean($this->origin->getParam('isbn'));
264
        $isbn2 = IsbnFacade::isbn2ean($this->book->getParam('isbn'));
265
        if ($isbn1 === $isbn2) {
266
            return true;
267
        }
268
269
        return false;
270
    }
271
272
    /**
273
     * Add or extract subtitle like in second book.
274
     *
275
     * @throws Exception
276
     */
277
    private function processSousTitre()
278
    {
279
        if (empty($this->book->getParam('sous-titre'))) {
280
            return;
281
        }
282
283
        // Skip pour éviter conflit entre 'sous-titre' et 'collection' ou 'titre volume'
284
        if (!empty($this->origin->getParam('titre volume'))
285
            || !empty($this->origin->getParam('titre chapitre'))
286
            || !empty($this->origin->getParam('titre tome'))
287
            || !empty($this->origin->getParam('collection'))
288
            || !empty($this->origin->getParam('nature ouvrage'))
289
        ) {
290
            return;
291
        }
292
293
        // simple : titres identiques mais sous-titre manquant
294
        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

294
        if ($this->stripAll(/** @scrutinizer ignore-type */ $this->origin->getParam('titre')) === $this->stripAll($this->book->getParam('titre'))) {
Loading history...
295
            // même titre mais sous-titre manquant
296
            if (empty($this->origin->getParam('sous-titre'))) {
297
                $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

297
                $this->origin->setParam('sous-titre', /** @scrutinizer ignore-type */ $this->book->getParam('sous-titre'));
Loading history...
298
                $this->log('++sous-titre');
299
                $this->major = true;
300
                $this->notCosmetic = true;
301
302
                return;
303
            }
304
        }
305
306
        // compliqué : sous-titre inclus dans titre original => on copie titre/sous-titre de book
307
        if ($this->charsFromBigTitle($this->origin) === $this->charsFromBigTitle($this->book)) {
308
            if (empty($this->origin->getParam('sous-titre'))) {
309
                $this->origin->setParam('titre', $this->book->getParam('titre'));
310
                $this->origin->setParam('sous-titre', $this->book->getParam('sous-titre'));
311
                $this->log('>titre>sous-titre');
312
            }
313
        }
314
    }
315
316
    /**
317
     * @return bool
318
     * @throws Exception
319
     */
320
    private function hasSameBookTitles(): bool
321
    {
322
        if ($this->charsFromBigTitle($this->origin) === $this->charsFromBigTitle($this->book)) {
323
            return true;
324
        }
325
326
        // if there is only 2 chars of difference (i.e. typo error)
327
        if (levenshtein($this->charsFromBigTitle($this->origin), $this->charsFromBigTitle($this->book)) <= 2) {
328
            //            $this->log('typo titre?'); // TODO Normalize:: text from external API
329
330
            return true;
331
        }
332
333
        // si l'un des ouvrages ne comporte pas le sous-titre
334
        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

334
        if ($this->stripAll(/** @scrutinizer ignore-type */ $this->origin->getParam('titre')) === $this->stripAll($this->book->getParam('titre'))) {
Loading history...
335
            return true;
336
        }
337
338
        // sous-titre inclus dans le titre
339
        // "Loiret : un département à l'élégance naturelle" <=> "Loiret"
340
        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

340
        if ($this->stripAll($this->mainBookTitle(/** @scrutinizer ignore-type */ $this->origin->getParam('titre'))) === $this->stripAll(
Loading history...
341
                $this->mainBookTitle($this->origin->getParam('titre'))
342
            )
343
        ) {
344
            return true;
345
        }
346
        // titre manquant sur wiki
347
        if (empty($this->charsFromBigTitle($this->origin))) {
348
            return true;
349
        }
350
351
        return false;
352
    }
353
354
    /**
355
     * Give string before ":" (or same string if no ":").
356
     *
357
     * @param string $str
358
     *
359
     * @return string
360
     */
361
    private function mainBookTitle(string $str)
362
    {
363
        if (($pos = mb_strpos($str, ':'))) {
364
            $str = trim(mb_substr($str, 0, $pos));
365
        }
366
367
        return $str;
368
    }
369
370
    /**
371
     * @param OuvrageTemplate $ouvrage
372
     *
373
     * @return string
374
     * @throws Exception
375
     */
376
    private function charsFromBigTitle(OuvrageTemplate $ouvrage): string
377
    {
378
        $text = $ouvrage->getParam('titre').$ouvrage->getParam('sous-titre');
379
380
        return $this->stripAll(Normalizer::normalize($text));
381
    }
382
383
    /**
384
     * @param string $text
385
     *
386
     * @return string
387
     */
388
    private function stripAll(string $text): string
389
    {
390
        $text = str_replace([' and ', ' et ', '&'], '', $text);
391
        $text = str_replace(' ', '', $text);
392
        $text = mb_strtolower(TextUtil::stripPunctuation(TextUtil::stripAccents($text)));
393
394
        return $text;
395
    }
396
}
397