Filler   F
last analyzed

Complexity

Total Complexity 108

Size/Duplication

Total Lines 891
Duplicated Lines 1.35 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 108
lcom 1
cbo 11
dl 12
loc 891
c 0
b 0
f 0
ccs 0
cts 378
cp 0
rs 3.9999

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getName() 0 4 1
A getTitle() 0 4 1
A getForm() 0 4 1
A buildMenu() 0 5 1
D fill() 0 80 16
A getTypeByName() 0 8 2
D getFrames() 0 33 9
A getAttrAsArray() 0 9 2
A setCover() 0 6 1
D fillHeadData() 0 103 31
C fillBodyData() 0 61 19
A uploadImage() 0 4 1
A getCountryByName() 0 8 2
A getGenreByName() 0 8 2
A getNodeValueAsText() 0 7 1
B getStudio() 0 14 5
A getItemType() 0 10 3
A fillAnimationNames() 4 21 2
A fillCinemaNames() 4 22 3
A getCoverUrl() 4 11 3
A isSupportedUrl() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Filler 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Filler, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * AnimeDb package.
4
 *
5
 * @author    Peter Gribanov <[email protected]>
6
 * @copyright Copyright (c) 2011, Peter Gribanov
7
 * @license   http://opensource.org/licenses/GPL-3.0 GPL v3
8
 */
9
10
namespace AnimeDb\Bundle\WorldArtFillerBundle\Service;
11
12
use AnimeDb\Bundle\AppBundle\Service\Downloader;
13
use AnimeDb\Bundle\AppBundle\Service\Downloader\Entity\EntityInterface;
14
use AnimeDb\Bundle\CatalogBundle\Entity\Image;
15
use AnimeDb\Bundle\CatalogBundle\Entity\Item;
16
use AnimeDb\Bundle\CatalogBundle\Entity\Name;
17
use AnimeDb\Bundle\CatalogBundle\Entity\Source;
18
use AnimeDb\Bundle\CatalogBundle\Plugin\Fill\Filler\Filler as FillerPlugin;
19
use AnimeDb\Bundle\WorldArtFillerBundle\Form\Type\Filler as FillerForm;
20
use Doctrine\Bundle\DoctrineBundle\Registry;
21
use Knp\Menu\ItemInterface;
22
23
class Filler extends FillerPlugin
24
{
25
    /**
26
     * Name.
27
     *
28
     * @var string
29
     */
30
    const NAME = 'world-art';
31
32
    /**
33
     * Title.
34
     *
35
     * @var string
36
     */
37
    const TITLE = 'World-Art.ru';
38
39
    /**
40
     * XPath for fill item.
41
     *
42
     * @var string
43
     */
44
    const XPATH_FOR_FILL = '//center/table[@height="58%"]/tr/td/table[1]/tr/td';
45
46
    /**
47
     * Item type animation.
48
     *
49
     * @var string
50
     */
51
    const ITEM_TYPE_ANIMATION = 'animation';
52
53
    /**
54
     * Item type cinema.
55
     *
56
     * @var string
57
     */
58
    const ITEM_TYPE_CINEMA = 'cinema';
59
60
    /**
61
     * Browser.
62
     *
63
     * @var \AnimeDb\Bundle\WorldArtFillerBundle\Service\Browser
64
     */
65
    protected $browser;
66
67
    /**
68
     * Doctrine.
69
     *
70
     * @var \Doctrine\Bundle\DoctrineBundle\Registry
71
     */
72
    protected $doctrine;
73
74
    /**
75
     * Downloader.
76
     *
77
     * @var \AnimeDb\Bundle\AppBundle\Service\Downloader
78
     */
79
    protected $downloader;
80
81
    /**
82
     * World-Art genres.
83
     *
84
     * @var array
85
     */
86
    protected $genres = [
87
        'боевик' => 'Action',
88
        'фильм действия' => 'Action',
89
        'боевые искусства' => 'Martial arts',
90
        'вампиры' => 'Vampire',
91
        'война' => 'War',
92
        'детектив' => 'Detective',
93
        'для детей' => 'Kids',
94
        'дзёсэй' => 'Josei',
95
        'драма' => 'Drama',
96
        'история' => 'History',
97
        'киберпанк' => 'Cyberpunk',
98
        'комедия' => 'Comedy',
99
        'махо-сёдзё' => 'Mahoe shoujo',
100
        'меха' => 'Mecha',
101
        'мистерия' => 'Mystery',
102
        'мистика' => 'Mystery',
103
        'музыкальный' => 'Music',
104
        'образовательный' => 'Educational',
105
        'пародия' => 'Parody',
106
        'cтимпанк' => 'Steampunk',
107
        'паропанк' => 'Steampunk',
108
        'повседневность' => 'Slice of life',
109
        'полиция' => 'Police',
110
        'постапокалиптика' => 'Apocalyptic fiction',
111
        'приключения' => 'Adventure',
112
        'приключенческий фильм' => 'Adventure',
113
        'психология' => 'Psychological',
114
        'романтика' => 'Romance',
115
        'самурайский боевик' => 'Samurai',
116
        'сёдзё' => 'Shoujo',
117
        'сёдзё-ай' => 'Shoujo-ai',
118
        'сёнэн' => 'Shounen',
119
        'сёнэн-ай' => 'Shounen-ai',
120
        'сказка' => 'Fable',
121
        'спорт' => 'Sport',
122
        'сэйнэн' => 'Senen',
123
        'триллер' => 'Thriller',
124
        'школа' => 'School',
125
        'фантастика' => 'Sci-fi',
126
        'кинофантазия' => 'Fantastic',
127
        'фэнтези' => 'Fantasy',
128
        'эротика' => 'Erotica',
129
        'этти' => 'Ecchi',
130
        'ужасы' => 'Horror',
131
        'хентай' => 'Hentai',
132
        'юри' => 'Yuri',
133
        'яой' => 'Yaoi',
134
    ];
135
136
    /**
137
     * World-Art types.
138
     *
139
     * @var array
140
     */
141
    protected $types = [
142
        'ТВ' => 'tv',
143
        'ТВ-спэшл' => 'special',
144
        'OVA' => 'ova',
145
        'ONA' => 'ona',
146
        'OAV' => 'ova',
147
        'полнометражный фильм' => 'feature',
148
        'короткометражный фильм' => 'featurette',
149
        'музыкальное видео' => 'music',
150
        'рекламный ролик' => 'commercial',
151
    ];
152
153
    /**
154
     * World-Art studios.
155
     *
156
     * @var array
157
     */
158
    protected $studios = [
159
        1 => 'Studio Ghibli',
160
        3 => 'Gainax',
161
        4 => 'AIC',
162
        6 => 'KSS',
163
        14 => 'TMS Entertainment',
164
        20 => 'Bones',
165
        21 => 'Clamp',
166
        22 => 'Studio DEEN',
167
        24 => 'J.C.Staff',
168
        25 => 'Madhouse',
169
        26 => 'animate',
170
        29 => 'OLM, Inc.',
171
        30 => 'Tezuka Productions',
172
        31 => 'Production I.G',
173
        32 => 'Gonzo',
174
        34 => 'Sunrise',
175
        37 => 'Agent 21',
176
        41 => 'Toei Animation',
177
        44 => 'APPP',
178
        54 => 'Radix',
179
        56 => 'Pierrot',
180
        59 => 'XEBEC',
181
        64 => 'Satelight',
182
        74 => 'Oh! Production',
183
        78 => 'Triangle Staff',
184
        82 => 'Bee Train',
185
        84 => 'Animax',
186
        87 => 'Daume',
187
        89 => 'Kitty Films',
188
        92 => 'Ajia-do',
189
        96 => 'Studio 4°C',
190
        106 => 'CoMix Wave Inc.',
191
        116 => 'Fox Animation Studios',
192
        117 => 'Blue Sky Studios',
193
        118 => 'Pacific Data Images',
194
        120 => 'Pixar',
195
        152 => 'Mushi Production',
196
        154 => 'Aardman Animations',
197
        159 => 'DR Movie',
198
        171 => 'Tatsunoko Productions',
199
        178 => 'Paramount Animation',
200
        193 => 'Hal Film Maker',
201
        198 => 'Studio Fantasia',
202
        210 => 'Arms Corporation',
203
        212 => 'Green Bunny',
204
        236 => 'Pink Pineapple',
205
        244 => 'Production Reed',
206
        // reverse links
207
        250 => 'Melnitsa Animation Studio',
208
        252 => 'Nippon Animation',
209
        255 => 'Artland',
210
        267 => 'SHAFT',
211
        278 => 'March Entertainment',
212
        296 => 'Gallop',
213
        315 => 'DreamWorks Animation',
214
        351 => 'TNK',
215
        398 => 'A.C.G.T.',
216
        436 => 'Kyoto Animation',
217
        439 => 'Studio Comet',
218
        463 => 'Magic Bus',
219
        639 => 'Industrial Light & Magic',
220
        689 => 'ZEXCS',
221
        724 => 'Six Point Harness',
222
        753 => 'Pentamedia Graphics',
223
        795 => 'Rough Draft Studios',
224
        802 => 'Shin-Ei Animation',
225
        821 => 'Warner Bros. Animation',
226
        1066 => 'Animal Logic',
227
        1161 => 'Marvel Animation Studios',
228
        1168 => 'Klasky Csupo',
229
        1654 => 'Digital Frontier',
230
        1663 => 'Mac Guff',
231
        1689 => 'Manglobe',
232
        1778 => 'CinéGroupe',
233
        1889 => 'Film Roman, Inc.',
234
        1890 => 'AKOM',
235
        1901 => 'Brain\'s Base',
236
        1961 => 'feel.',
237
        2058 => 'Eiken',
238
        2229 => 'Studio Hibari',
239
        2370 => 'IMAGIN',
240
        2379 => 'Folimage',
241
        2381 => 'DisneyToon Studios',
242
        2491 => 'ufotable',
243
        3058 => 'Asahi Production',
244
        3096 => 'Mook Animation',
245
        3113 => 'Walt Disney Television Animation',
246
        3420 => 'Metro-Goldwyn-Mayer Animation',
247
        3530 => 'Seven Arcs',
248
        3742 => 'Nomad',
249
        3748 => 'Dygra Films',
250
        3773 => 'Dogakobo',
251
        3816 => 'EMation',
252
        4013 => 'Toon City',
253
        5423 => 'O Entertainment/Omation Animation Studio',
254
        6081 => 'Sony Pictures Animation',
255
        6474 => 'Wang Film Productions',
256
        6475 => 'Creative Capers Entertainment',
257
        6701 => 'Arc Productions',
258
        7092 => 'Millimages',
259
        7194 => 'Mondo TV',
260
        7298 => 'A-1 Pictures Inc.',
261
        7372 => 'Diomedea',
262
        7388 => 'Williams Street Studios',
263
        7801 => 'National Film Board of Canada',
264
        7933 => 'Titmouse',
265
        8590 => 'Rhythm and Hues Studios',
266
        8639 => 'Bagdasarian Productions',
267
        9298 => 'Toonz',
268
        9900 => 'Savage Studios Ltd.',
269
        10664 => 'A. Film',
270
        11077 => 'Vanguard Animation',
271
        11213 => 'bolexbrothers',
272
        11827 => 'Zinkia Entertainment',
273
        12209 => 'P.A. Works',
274
        12268 => 'Universal Animation Studios',
275
        12280 => 'Reel FX',
276
        12281 => 'Walt Disney Animation Studios',
277
        12299 => 'LAIKA',
278
        12825 => 'White Fox',
279
        13269 => 'David Production',
280
        13301 => 'Silver Link',
281
        13329 => 'Kinema Citrus',
282
        13906 => 'GoHands',
283
        13957 => 'Khara',
284
        14617 => 'Ordet',
285
        15102 => 'TYO Animations',
286
        15334 => 'Dong Woo Animation',
287
        16112 => 'Studio Gokumi',
288
        16433 => 'Nickelodeon Animation Studios',
289
        16961 => 'Renegade Animation',
290
        17049 => 'Curious Pictures',
291
        17235 => 'Trigger',
292
        17322 => 'Wit Studio',
293
    ];
294
295
    /**
296
     * Construct.
297
     *
298
     * @param \AnimeDb\Bundle\WorldArtFillerBundle\Service\Browser $browser
299
     * @param \Doctrine\Bundle\DoctrineBundle\Registry             $doctrine
300
     * @param \AnimeDb\Bundle\AppBundle\Service\Downloader         $downloader
301
     */
302
    public function __construct(Browser $browser, Registry $doctrine, Downloader $downloader)
303
    {
304
        $this->browser = $browser;
305
        $this->doctrine = $doctrine;
306
        $this->downloader = $downloader;
307
    }
308
309
    /**
310
     * Get name.
311
     *
312
     * @return string
313
     */
314
    public function getName()
315
    {
316
        return self::NAME;
317
    }
318
319
    /**
320
     * Get title.
321
     *
322
     * @return string
323
     */
324
    public function getTitle()
325
    {
326
        return self::TITLE;
327
    }
328
329
    /**
330
     * Get form.
331
     *
332
     * @return \AnimeDb\Bundle\WorldArtFillerBundle\Form\Type\Filler
333
     */
334
    public function getForm()
335
    {
336
        return new FillerForm($this->browser->getHost());
337
    }
338
339
    /**
340
     * Build menu for plugin.
341
     *
342
     * @param \Knp\Menu\ItemInterface $item
343
     *
344
     * @return \Knp\Menu\ItemInterface
345
     */
346
    public function buildMenu(ItemInterface $item)
347
    {
348
        return parent::buildMenu($item)
349
            ->setLinkAttribute('class', 'icon-label icon-label-plugin-world-art');
350
    }
351
352
    /**
353
     * Fill item from source.
354
     *
355
     * @param array $data
356
     *
357
     * @return \AnimeDb\Bundle\CatalogBundle\Entity\Item|null
358
     */
359
    public function fill(array $data)
360
    {
361
        if (empty($data['url']) || !is_string($data['url']) || strpos($data['url'], $this->browser->getHost()) !== 0) {
362
            return;
363
        }
364
365
        $dom = $this->browser->getDom(substr($data['url'], strlen($this->browser->getHost())));
366
        if (!($dom instanceof \DOMDocument)) {
367
            return;
368
        }
369
        $xpath = new \DOMXPath($dom);
370
        $nodes = $xpath->query(self::XPATH_FOR_FILL);
371
372
        // get item type
373
        if (!($type = $this->getItemType($data['url']))) {
374
            return;
375
        }
376
377
        /* @var $body \DOMElement */
378
        if (!($body = $nodes->item(5))) {
379
            throw new \LogicException('Incorrect data structure at source');
380
        }
381
382
        // item id from source
383
        $id = 0;
384
        if (preg_match('/id=(?<id>\d+)/', $data['url'], $mat)) {
385
            $id = (int) $mat['id'];
386
        }
387
388
        $item = new Item();
389
390
        // add source link on world-art
391
        $item->addSource((new Source())->setUrl($data['url']));
392
393
        // add other source links
394
        /* @var $links \DOMNodeList */
395
        $links = $xpath->query('a', $nodes->item(1));
396
        for ($i = 0; $i < $links->length; ++$i) {
397
            $link = $this->getAttrAsArray($links->item($i));
0 ignored issues
show
Compatibility introduced by
$links->item($i) of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
398
            if (strpos($link['href'], 'http://') !== false &&
399
                strpos($link['href'], $this->browser->getHost()) === false
400
            ) {
401
                $item->addSource((new Source())->setUrl($link['href']));
402
            }
403
        }
404
405
        // add cover
406
        $this->setCover($item, $id, $type);
407
408
        // fill item studio
409
        if ($studio = $this->getStudio($xpath, $body)) {
410
            $item->setStudio($studio);
411
        }
412
413
        // fill main data
414
        if ($type == self::ITEM_TYPE_ANIMATION) {
415
            $head = $xpath->query('table[3]/tr[2]/td[3]', $body);
416
            if (!$head->length) {
417
                $head = $xpath->query('table[2]/tr[1]/td[3]', $body);
418
            }
419
        } else {
420
            $head = $xpath->query('table[3]/tr[1]/td[3]', $body);
421
        }
422
423
        if ($head->length) {
424
            switch ($type) {
425
                case self::ITEM_TYPE_ANIMATION:
426
                    $this->fillAnimationNames($item, $xpath, $head->item(0));
0 ignored issues
show
Compatibility introduced by
$head->item(0) of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
427
                    break;
428
                default:
429
                    $this->fillCinemaNames($item, $xpath, $head->item(0));
0 ignored issues
show
Compatibility introduced by
$head->item(0) of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
430
            }
431
        }
432
        $this->fillHeadData($item, $xpath, $head->item(0));
0 ignored issues
show
Compatibility introduced by
$head->item(0) of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
433
434
        // fill body data
435
        $this->fillBodyData($item, $xpath, $body, $id, !empty($data['frames']), $type);
0 ignored issues
show
Compatibility introduced by
$body of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
436
437
        return $item;
438
    }
439
440
    /**
441
     * Get element attributes as array.
442
     *
443
     * @param \DOMElement $element
444
     *
445
     * @return array
446
     */
447
    private function getAttrAsArray(\DOMElement $element)
448
    {
449
        $return = [];
450
        for ($i = 0; $i < $element->attributes->length; ++$i) {
0 ignored issues
show
Bug introduced by
The property length does not seem to exist in DOMNamedNodeMap.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
451
            $return[$element->attributes->item($i)->nodeName] = $element->attributes->item($i)->nodeValue;
452
        }
453
454
        return $return;
455
    }
456
457
    /**
458
     * Get cover from source id.
459
     *
460
     * @param \AnimeDb\Bundle\CatalogBundle\Entity\Item $item
461
     * @param string                                    $id
462
     * @param string                                    $type
463
     *
464
     * @return bool
465
     */
466
    private function setCover(Item $item, $id, $type)
467
    {
468
        $item->setCover(self::NAME.'/'.$id.'/1.jpg');
469
470
        return $this->uploadImage($this->getCoverUrl($id, $type), $item);
471
    }
472
473
    /**
474
     * Fill head data.
475
     *
476
     * @param \AnimeDb\Bundle\CatalogBundle\Entity\Item $item
477
     * @param \DOMXPath                                 $xpath
478
     * @param \DOMElement                               $head
479
     *
480
     * @return \AnimeDb\Bundle\CatalogBundle\Entity\Item
481
     */
482
    private function fillHeadData(Item $item, \DOMXPath $xpath, \DOMElement $head)
483
    {
484
        /* @var $data \DOMElement */
485
        $data = $xpath->query('font', $head)->item(0);
486
        $length = $data->childNodes->length;
487
        for ($i = 0; $i < $length; ++$i) {
488
            if ($data->childNodes->item($i)->nodeName == 'b') {
489
                switch ($data->childNodes->item($i)->nodeValue) {
490
                    // set country
491
                    case 'Производство':
492
                        $j = 1;
493
                        do {
494
                            if ($data->childNodes->item($i + $j)->nodeName == 'a') {
495
                                $country_name = trim($data->childNodes->item($i + $j)->nodeValue);
496
                                if ($country_name && $country = $this->getCountryByName($country_name)) {
497
                                    $item->setCountry($country);
498
                                }
499
                                break;
500
                            }
501
                            ++$j;
502
                        } while ($data->childNodes->item($i + $j)->nodeName != 'br');
503
                        $i += $j;
504
                        break;
505
                    // add genre
506
                    case 'Жанр':
507
                        $j = 2;
508
                        do {
509
                            if ($data->childNodes->item($i + $j)->nodeName == 'a' &&
510
                                ($genre = $this->getGenreByName($data->childNodes->item($i + $j)->nodeValue))
511
                            ) {
512
                                $item->addGenre($genre);
513
                            }
514
                            ++$j;
515
                        } while ($data->childNodes->item($i + $j)->nodeName != 'br');
516
                        $i += $j;
517
                        break;
518
                    // set type and add file info
519
                    case 'Тип':
520
                        $type = $data->childNodes->item($i + 1)->nodeValue;
521
                        if (preg_match('/(?<type>[\w\s]+)(?: \((?:(?<episodes_number>>?\d+) эп.)?(?<file_info>.*)\))?(, (?<duration>\d{1,3}) мин\.)?$/u', $type, $match)) {
522
                            // add type
523
                            if ($type = $this->getTypeByName(trim($match['type']))) {
524
                                $item->setType($type);
525
                            }
526
                            // add duration
527
                            if (!empty($match['duration'])) {
528
                                $item->setDuration((int) $match['duration']);
529
                            }
530
                            // add number of episodes
531
                            if (!empty($match['episodes_number'])) {
532
                                if ($match['episodes_number'][0] == '>') {
533
                                    $item->setEpisodesNumber(substr($match['episodes_number'], 1).'+');
534
                                } else {
535
                                    $item->setEpisodesNumber((int) $match['episodes_number']);
536
                                }
537
                            } elseif ($item->getType()->getId() != 'tv') {
538
                                // everything except the TV series consist of a single episode
539
                                $item->setEpisodesNumber(1);
540
                            }
541
                            // add file info
542
                            if (!empty($match['file_info'])) {
543
                                $file_info = $item->getFileInfo();
544
                                $item->setFileInfo(($file_info ? $file_info."\n" : '').trim($match['file_info']));
545
                            }
546
                        }
547
                        ++$i;
548
                        break;
549
                    // set date premiere and date end if exists
550
                    case 'Премьера':
551
                    case 'Выпуск':
552
                        $j = 1;
553
                        $date = '';
554
                        do {
555
                            $date .= $data->childNodes->item($i + $j)->nodeValue;
556
                            ++$j;
557
                        } while ($length > $i + $j && $data->childNodes->item($i + $j)->nodeName != 'br');
558
                        $i += $j;
559
560
                        $reg = '/(?<start>(?:(?:\d{2})|(?:\?\?)).\d{2}.\d{4})'.
561
                            '(?:.*(?<end>(?:(?:\d{2})|(?:\?\?)).\d{2}.\d{4}))?/';
562
                        if (preg_match($reg, $date, $match)) {
563
                            $item->setDatePremiere(new \DateTime(str_replace('??', '01', $match['start'])));
564
                            if (isset($match['end'])) {
565
                                $item->setDateEnd(new \DateTime($match['end']));
566
                            }
567
                        }
568
                        break;
569
                    case 'Хронометраж':
570
                        if (preg_match('/(?<duration>\d+)/', $data->childNodes->item($i + 1)->nodeValue, $match)) {
571
                            $item->setDuration((int) $match['duration']);
572
                        }
573
                        break;
574
                    case 'Кол-во серий':
575
                        $number = trim($data->childNodes->item($i + 1)->nodeValue, ' :');
576
                        if (strpos($number, '>') !== false) {
577
                            $number = str_replace('>', '', $number).'+';
578
                        }
579
                        $item->setEpisodesNumber($number);
580
                        break;
581
                }
582
            }
583
        }
584
    }
585
586
    /**
587
     * Fill body data.
588
     *
589
     * @param \AnimeDb\Bundle\CatalogBundle\Entity\Item $item
590
     * @param \DOMXPath                                 $xpath
591
     * @param \DOMElement                               $body
592
     * @param int                                       $id
593
     * @param bool                                      $frames
594
     * @param string                                    $type
595
     *
596
     * @return \AnimeDb\Bundle\CatalogBundle\Entity\Item
597
     */
598
    private function fillBodyData(Item $item, \DOMXPath $xpath, \DOMElement $body, $id, $frames, $type)
599
    {
600
        for ($i = 0; $i < $body->childNodes->length; ++$i) {
601
            if ($value = trim($body->childNodes->item($i)->nodeValue)) {
602
                switch ($value) {
603
                    // get summary
604
                    case 'Краткое содержание:':
605
                        $summary = $xpath->query('tr/td/p[1]', $body->childNodes->item($i + 2));
606
                        if ($summary->length) {
607
                            $item->setSummary($this->getNodeValueAsText($summary->item(0)));
608
                        }
609
                        $i += 2;
610
                        break;
611
                    // get episodes
612
                    case 'Эпизоды:':
613
                        if (!trim($body->childNodes->item($i + 1)->nodeValue)) { // simple list
614
                            $item->setEpisodes($this->getNodeValueAsText($body->childNodes->item($i + 2)));
615
                            $i += 2;
616
                        } else { // episodes in table
617
                            $rows = $xpath->query('tr/td[2]', $body->childNodes->item($i + 1));
618
                            $episodes = '';
619
                            for ($j = 1; $j < $rows->length; ++$j) {
620
                                $episode = $xpath->query('font', $rows->item($j));
621
                                $episodes .= $j.'. '.$episode->item(0)->nodeValue;
622
                                if ($rows->length > 1) {
623
                                    $episodes .= ' ('.$episode->item(1)->nodeValue.')';
624
                                }
625
                                $episodes .= "\n";
626
                            }
627
                            $item->setEpisodes($episodes);
628
                            ++$i;
629
                        }
630
                        break;
631
                    // get date premiere
632
                    case 'Даты премьер и релизов':
633
                        $rows = $xpath->query('tr/td/table/tr/td[3]', $body->childNodes->item($i + 1));
634
                        foreach ($rows as $row) {
635
                            if (preg_match('/\d{4}\.\d{2}\.\d{2}/', $row->nodeValue, $match)) {
636
                                $date = new \DateTime(str_replace('.', '-', $match[0]));
637
                                if (!$item->getDatePremiere() || $item->getDatePremiere() > $date) {
638
                                    $item->setDatePremiere($date);
639
                                }
640
                            }
641
                        }
642
                        break;
643
                    default:
644
                        // get frames
645
                        if (
646
                            (
647
                                strpos($value, 'кадры из аниме') !== false ||
648
                                strpos($value, 'Кадры из фильма') !== false
649
                            ) && $id && $frames
650
                        ) {
651
                            foreach ($this->getFrames($id, $type) as $frame) {
652
                                $item->addImage($frame);
653
                            }
654
                        }
655
                }
656
            }
657
        }
658
    }
659
660
    /**
661
     * Upload image from url.
662
     *
663
     * @param string                                                              $url
664
     * @param \AnimeDb\Bundle\AppBundle\Service\Downloader\Entity\EntityInterface $entity
665
     *
666
     * @return bool
667
     */
668
    public function uploadImage($url, EntityInterface $entity)
669
    {
670
        return $this->downloader->image($url, $this->downloader->getRoot().$entity->getWebPath());
671
    }
672
673
    /**
674
     * Get real country by name.
675
     *
676
     * @param string $name
677
     *
678
     * @return \AnimeDb\Bundle\CatalogBundle\Entity\Country|null
679
     */
680
    private function getCountryByName($name)
681
    {
682
        $name = str_replace('Южная Корея', 'Республика Корея', $name);
683
        $rep = $this->doctrine->getRepository('AnimeDbCatalogBundle:CountryTranslation');
684
        if ($country = $rep->findOneBy(['locale' => 'ru', 'content' => $name])) {
685
            return $country->getObject();
686
        }
687
    }
688
689
    /**
690
     * Get real genre by name.
691
     *
692
     * @param string $name
693
     *
694
     * @return \AnimeDb\Bundle\CatalogBundle\Entity\Genre|null
695
     */
696
    private function getGenreByName($name)
697
    {
698
        if (isset($this->genres[$name])) {
699
            return $this->doctrine
700
                ->getRepository('AnimeDbCatalogBundle:Genre')
701
                ->findOneByName($this->genres[$name]);
702
        }
703
    }
704
705
    /**
706
     * Get real type by name.
707
     *
708
     * @param string $name
709
     *
710
     * @return \AnimeDb\Bundle\CatalogBundle\Entity\Type|null
711
     */
712
    private function getTypeByName($name)
713
    {
714
        if (isset($this->types[$name])) {
715
            return $this->doctrine
716
                ->getRepository('AnimeDbCatalogBundle:Type')
717
                ->find($this->types[$name]);
718
        }
719
    }
720
721
    /**
722
     * Get item frames.
723
     *
724
     * @param int    $id
725
     * @param string $type
726
     *
727
     * @return array
728
     */
729
    public function getFrames($id, $type)
730
    {
731
        $dom = $this->browser->getDom('/'.$type.'/'.$type.'_photos.php?id='.$id);
732
        if (!$dom) {
733
            return [];
734
        }
735
        $images = (new \DOMXPath($dom))->query('//table//table//table//img');
736
        $frames = [];
737
        foreach ($images as $image) {
738
            $src = $this->getAttrAsArray($image)['src'];
739
            $entity = new Image();
740
            if ($type == self::ITEM_TYPE_ANIMATION) {
741
                $src = str_replace('optimize_b', 'optimize_d', $src);
742
                if (strpos($src, 'http://') === false) {
743
                    $src = $this->browser->getHost().'/'.$type.'/'.$src;
744
                }
745
                if (preg_match('/\-(?<image>\d+)\-optimize_d(?<ext>\.jpe?g|png|gif)/', $src, $mat)) {
746
                    $entity->setSource(self::NAME.'/'.$id.'/'.$mat['image'].$mat['ext']);
747
                    if ($this->uploadImage($src, $entity)) {
748
                        $frames[] = $entity;
749
                    }
750
                }
751
            } elseif (preg_match('/_(?<round>\d+)\/.+\/(?<id>\d+)-(?<image>\d+)-.+(?<ext>\.jpe?g|png|gif)/', $src, $mat)) {
752
                $src = $this->browser->getHost().'/'.$type.'/img/'.$mat['round'].'/'.$mat['id'].'/'.$mat['image'].$mat['ext'];
753
                $entity->setSource(self::NAME.'/'.$id.'/'.$mat['image'].$mat['ext']);
754
                if ($this->uploadImage($src, $entity)) {
755
                    $frames[] = $entity;
756
                }
757
            }
758
        }
759
760
        return $frames;
761
    }
762
763
    /**
764
     * Get node value as text.
765
     *
766
     * @param \DOMNode $node
767
     *
768
     * @return string
769
     */
770
    private function getNodeValueAsText(\DOMNode $node)
771
    {
772
        $text = $node->ownerDocument->saveHTML($node);
773
        $text = str_replace(["<br>\n", "\n", '<br>'], ['<br>', ' ', "\n"], $text);
774
775
        return trim(strip_tags($text));
776
    }
777
778
    /**
779
     * Get item studio.
780
     *
781
     * @param \DOMXPath $xpath
782
     * @param \DOMNode  $body
783
     *
784
     * @return \AnimeDb\Bundle\CatalogBundle\Entity\Studio|null
785
     */
786
    private function getStudio(\DOMXPath $xpath, \DOMNode $body)
787
    {
788
        $studios = $xpath->query('//img[starts-with(@src,"http://www.world-art.ru/img/company_new/")]', $body);
789
        if ($studios->length) {
790
            foreach ($studios as $studio) {
791
                $url = $studio->attributes->getNamedItem('src')->nodeValue;
792
                if (preg_match('/\/(\d+)\./', $url, $mat) && isset($this->studios[$mat[1]])) {
793
                    return $this->doctrine
794
                        ->getRepository('AnimeDbCatalogBundle:Studio')
795
                        ->findOneByName($this->studios[$mat[1]]);
796
                }
797
            }
798
        }
799
    }
800
801
    /**
802
     * Get item type by URL.
803
     *
804
     * @param string $url
805
     *
806
     * @return string
807
     */
808
    public function getItemType($url)
809
    {
810
        if (strpos($url, self::ITEM_TYPE_ANIMATION.'/'.self::ITEM_TYPE_ANIMATION) !== false) {
811
            return self::ITEM_TYPE_ANIMATION;
812
        } elseif (strpos($url, self::ITEM_TYPE_CINEMA.'/'.self::ITEM_TYPE_CINEMA) !== false) {
813
            return self::ITEM_TYPE_CINEMA;
814
        } else {
815
            return;
816
        }
817
    }
818
819
    /**
820
     * Fill names for Animation type.
821
     *
822
     * @param \AnimeDb\Bundle\CatalogBundle\Entity\Item $item
823
     * @param \DOMXPath                                 $xpath
824
     * @param \DOMElement                               $head
825
     *
826
     * @return \AnimeDb\Bundle\CatalogBundle\Entity\Item
827
     */
828
    protected function fillAnimationNames(Item $item, \DOMXPath $xpath, \DOMElement $head)
829
    {
830
        // add main name
831
        $names = $xpath->query('table[1]/tr/td', $head)->item(0)->nodeValue;
832
        $names = explode("\n", trim($names));
833
834
        // clear
835
        $name = preg_replace('/\[\d{4}\]/', '', array_shift($names)); // example: [2011]
836
        $name = preg_replace('/\[?(ТВ|OVA|ONA)(\-\d)?\]?/', '', $name); // example: [TV-1]
837
        $name = preg_replace('/\(фильм \w+\)/u', '', $name); // example: (фильм седьмой)
838
        $name = preg_replace('/ - Фильм$/iu', '', $name); // example: - Фильм
839
        $item->setName(trim($name));
840
841
        // add other names
842 View Code Duplication
        foreach ($names as $name) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
843
            $name = trim(preg_replace('/(\(\d+\))?/', '', $name));
844
            $item->addName((new Name())->setName($name));
845
        }
846
847
        return $item;
848
    }
849
850
    /**
851
     * Fill names for Cinema type.
852
     *
853
     * @param \AnimeDb\Bundle\CatalogBundle\Entity\Item $item
854
     * @param \DOMXPath                                 $xpath
855
     * @param \DOMElement                               $head
856
     *
857
     * @return \AnimeDb\Bundle\CatalogBundle\Entity\Item
858
     */
859
    protected function fillCinemaNames(Item $item, \DOMXPath $xpath, \DOMElement $head)
860
    {
861
        // get list names
862
        $names = [];
863
        foreach ($xpath->query('table[1]/tr/td/table/tr/td', $head) as $name) {
864
            $names[] = $name->nodeValue;
865
        }
866
867
        // clear
868
        $name = preg_replace('/\[\d{4}\]/', '', array_shift($names)); // example: [2011]
869
        $name = preg_replace('/\[?(ТВ|OVA|ONA)(\-\d)?\]?/', '', $name); // example: [TV-1]
870
        $name = preg_replace('/\(фильм \w+\)/u', '', $name); // example: (фильм седьмой)
871
        $item->setName(trim($name));
872
873
        // add other names
874 View Code Duplication
        foreach ($names as $name) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
875
            $name = trim(preg_replace('/(\(\d+\))/', '', $name));
876
            $item->addName((new Name())->setName($name));
877
        }
878
879
        return $item;
880
    }
881
882
    /**
883
     * Get cover URL.
884
     *
885
     * @param string $id
886
     * @param string $type
887
     *
888
     * @return string|null
889
     */
890
    public function getCoverUrl($id, $type)
891
    {
892
        switch ($type) {
893 View Code Duplication
            case self::ITEM_TYPE_ANIMATION:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
894
                return $this->browser->getHost().'/'.$type.'/img/'.(ceil($id / 1000) * 1000).'/'.$id.'/1.jpg';
895 View Code Duplication
            case self::ITEM_TYPE_CINEMA:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
896
                return $this->browser->getHost().'/'.$type.'/img/'.(ceil($id / 10000) * 10000).'/'.$id.'/1.jpg';
897
            default:
898
                return;
899
        }
900
    }
901
902
    /**
903
     * Is supported URL.
904
     *
905
     * @param string $url
906
     *
907
     * @return bool
908
     */
909
    public function isSupportedUrl($url)
910
    {
911
        return strpos($url, $this->browser->getHost()) === 0;
912
    }
913
}
914