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)); |
|
|
|
|
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)); |
|
|
|
|
427
|
|
|
break; |
428
|
|
|
default: |
429
|
|
|
$this->fillCinemaNames($item, $xpath, $head->item(0)); |
|
|
|
|
430
|
|
|
} |
431
|
|
|
} |
432
|
|
|
$this->fillHeadData($item, $xpath, $head->item(0)); |
|
|
|
|
433
|
|
|
|
434
|
|
|
// fill body data |
435
|
|
|
$this->fillBodyData($item, $xpath, $body, $id, !empty($data['frames']), $type); |
|
|
|
|
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) { |
|
|
|
|
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) { |
|
|
|
|
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) { |
|
|
|
|
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: |
|
|
|
|
894
|
|
|
return $this->browser->getHost().'/'.$type.'/img/'.(ceil($id / 1000) * 1000).'/'.$id.'/1.jpg'; |
895
|
|
View Code Duplication |
case self::ITEM_TYPE_CINEMA: |
|
|
|
|
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
|
|
|
|
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.