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

OuvrageComplete   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 392
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 166
c 8
b 0
f 0
dl 0
loc 392
rs 3.2
wmc 65

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getResult() 0 5 1
A mainBookTitle() 0 7 2
B processSousTitre() 0 35 11
A getLog() 0 3 1
A hasSameISBN() 0 13 4
C googleBookProcess() 0 48 13
A log() 0 4 2
B complete() 0 60 9
A authorsFromBook() 0 27 3
A hasSameBookTitles() 0 32 6
A hasSameAuthors() 0 19 4
A stripAll() 0 7 1
A charsFromBigTitle() 0 5 1
A __construct() 0 4 1
A predictSameBook() 0 10 6

How to fix   Complexity   

Complex Class

Complex classes like OuvrageComplete often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OuvrageComplete, and based on these observations, apply Extract Interface, too.

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

279
        $isbn1 = IsbnFacade::isbn2ean(/** @scrutinizer ignore-type */ $this->origin->getParam('isbn'));
Loading history...
280
        $isbn2 = IsbnFacade::isbn2ean($this->book->getParam('isbn'));
281
        if ($isbn1 === $isbn2) {
282
            return true;
283
        }
284
285
        return false;
286
    }
287
288
    /**
289
     * Add or extract subtitle like in second book.
290
     *
291
     * @throws Exception
292
     */
293
    private function processSousTitre()
294
    {
295
        if (empty($this->book->getParam('sous-titre'))) {
296
            return;
297
        }
298
299
        // Skip pour éviter conflit entre 'sous-titre' et 'collection' ou 'titre volume'
300
        if (!empty($this->origin->getParam('titre volume'))
301
            || !empty($this->origin->getParam('titre chapitre'))
302
            || !empty($this->origin->getParam('titre tome'))
303
            || !empty($this->origin->getParam('collection'))
304
            || !empty($this->origin->getParam('nature ouvrage'))
305
        ) {
306
            return;
307
        }
308
309
        // simple : titres identiques mais sous-titre manquant
310
        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

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

313
                $this->origin->setParam('sous-titre', /** @scrutinizer ignore-type */ $this->book->getParam('sous-titre'));
Loading history...
314
                $this->log('++sous-titre');
315
                $this->major = true;
316
                $this->notCosmetic = true;
317
318
                return;
319
            }
320
        }
321
322
        // compliqué : sous-titre inclus dans titre original => on copie titre/sous-titre de book
323
        if ($this->charsFromBigTitle($this->origin) === $this->charsFromBigTitle($this->book)) {
324
            if (empty($this->origin->getParam('sous-titre'))) {
325
                $this->origin->setParam('titre', $this->book->getParam('titre'));
326
                $this->origin->setParam('sous-titre', $this->book->getParam('sous-titre'));
327
                $this->log('>titre>sous-titre');
328
            }
329
        }
330
    }
331
332
    /**
333
     * @return bool
334
     * @throws Exception
335
     */
336
    private function hasSameBookTitles(): bool
337
    {
338
        if ($this->charsFromBigTitle($this->origin) === $this->charsFromBigTitle($this->book)) {
339
            return true;
340
        }
341
342
        // if there is only 2 chars of difference (i.e. typo error)
343
        if (levenshtein($this->charsFromBigTitle($this->origin), $this->charsFromBigTitle($this->book)) <= 2) {
344
            //            $this->log('typo titre?'); // TODO Normalize:: text from external API
345
346
            return true;
347
        }
348
349
        // si l'un des ouvrages ne comporte pas le sous-titre
350
        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

350
        if ($this->stripAll(/** @scrutinizer ignore-type */ $this->origin->getParam('titre')) === $this->stripAll($this->book->getParam('titre'))) {
Loading history...
351
            return true;
352
        }
353
354
        // sous-titre inclus dans le titre
355
        // "Loiret : un département à l'élégance naturelle" <=> "Loiret"
356
        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

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