|
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
|
|
|
namespace AnimeDb\Bundle\ShikimoriFillerBundle\Service; |
|
10
|
|
|
|
|
11
|
|
|
use AnimeDb\Bundle\CatalogBundle\Plugin\Fill\Filler\Filler as FillerPlugin; |
|
12
|
|
|
use AnimeDb\Bundle\ShikimoriBrowserBundle\Service\Browser; |
|
13
|
|
|
use Doctrine\Bundle\DoctrineBundle\Registry; |
|
14
|
|
|
use AnimeDb\Bundle\CatalogBundle\Entity\Item; |
|
15
|
|
|
use AnimeDb\Bundle\CatalogBundle\Entity\Name; |
|
16
|
|
|
use AnimeDb\Bundle\CatalogBundle\Entity\Source; |
|
17
|
|
|
use AnimeDb\Bundle\CatalogBundle\Entity\Genre; |
|
18
|
|
|
use AnimeDb\Bundle\CatalogBundle\Entity\Studio; |
|
19
|
|
|
use AnimeDb\Bundle\CatalogBundle\Entity\Image; |
|
20
|
|
|
use AnimeDb\Bundle\AppBundle\Service\Downloader; |
|
21
|
|
|
use AnimeDb\Bundle\ShikimoriFillerBundle\Form\Type\Filler as FillerForm; |
|
22
|
|
|
use Knp\Menu\ItemInterface; |
|
23
|
|
|
use AnimeDb\Bundle\AppBundle\Service\Downloader\Entity\EntityInterface; |
|
24
|
|
|
|
|
25
|
|
|
class Filler extends FillerPlugin |
|
26
|
|
|
{ |
|
27
|
|
|
/** |
|
28
|
|
|
* @var string |
|
29
|
|
|
*/ |
|
30
|
|
|
const NAME = 'shikimori'; |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* @var string |
|
34
|
|
|
*/ |
|
35
|
|
|
const TITLE = 'Shikimori.org'; |
|
36
|
|
|
|
|
37
|
|
|
/** |
|
38
|
|
|
* Path to item. |
|
39
|
|
|
* |
|
40
|
|
|
* @var string |
|
41
|
|
|
*/ |
|
42
|
|
|
const FILL_URL = '/animes/#ID#'; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* RegExp for get item id. |
|
46
|
|
|
* |
|
47
|
|
|
* @var string |
|
48
|
|
|
*/ |
|
49
|
|
|
const REG_ITEM_ID = '#/animes/z?(?<id>\d+)\-#'; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* Path to item screenshots. |
|
53
|
|
|
* |
|
54
|
|
|
* @var string |
|
55
|
|
|
*/ |
|
56
|
|
|
const FILL_IMAGES_URL = '/animes/#ID#/screenshots'; |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* World-art item url. |
|
60
|
|
|
* |
|
61
|
|
|
* @var string |
|
62
|
|
|
*/ |
|
63
|
|
|
const WORLD_ART_URL = 'http://www.world-art.ru/animation/animation.php?id=#ID#'; |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* MyAnimeList item url. |
|
67
|
|
|
* |
|
68
|
|
|
* @var string |
|
69
|
|
|
*/ |
|
70
|
|
|
const MY_ANIME_LIST_URL = 'http://myanimelist.net/anime/#ID#'; |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* AniDB item url. |
|
74
|
|
|
* |
|
75
|
|
|
* @var string |
|
76
|
|
|
*/ |
|
77
|
|
|
const ANI_DB_URL = 'http://anidb.net/perl-bin/animedb.pl?show=anime&aid=#ID#'; |
|
78
|
|
|
|
|
79
|
|
|
/** |
|
80
|
|
|
* @var Browser |
|
81
|
|
|
*/ |
|
82
|
|
|
private $browser; |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* @var Registry |
|
86
|
|
|
*/ |
|
87
|
|
|
private $doctrine; |
|
88
|
|
|
|
|
89
|
|
|
/** |
|
90
|
|
|
* @var Downloader |
|
91
|
|
|
*/ |
|
92
|
|
|
private $downloader; |
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* @var string |
|
96
|
|
|
*/ |
|
97
|
|
|
protected $locale; |
|
98
|
|
|
|
|
99
|
|
|
/** |
|
100
|
|
|
* @param Browser $browser |
|
101
|
|
|
* @param Registry $doctrine |
|
102
|
|
|
* @param Downloader $downloader |
|
103
|
|
|
* @param string $locale |
|
104
|
|
|
*/ |
|
105
|
|
|
public function __construct(Browser $browser, Registry $doctrine, Downloader $downloader, $locale) |
|
106
|
|
|
{ |
|
107
|
|
|
$this->browser = $browser; |
|
108
|
|
|
$this->doctrine = $doctrine; |
|
109
|
|
|
$this->downloader = $downloader; |
|
110
|
|
|
$this->locale = $locale; |
|
111
|
|
|
} |
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* @return string |
|
115
|
|
|
*/ |
|
116
|
|
|
public function getName() |
|
117
|
|
|
{ |
|
118
|
|
|
return self::NAME; |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
|
|
/** |
|
122
|
|
|
* @return string |
|
123
|
|
|
*/ |
|
124
|
|
|
public function getTitle() |
|
125
|
|
|
{ |
|
126
|
|
|
return self::TITLE; |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* @return FillerForm |
|
131
|
|
|
*/ |
|
132
|
|
|
public function getForm() |
|
133
|
|
|
{ |
|
134
|
|
|
return new FillerForm($this->browser->getHost()); |
|
|
|
|
|
|
135
|
|
|
} |
|
136
|
|
|
|
|
137
|
|
|
/** |
|
138
|
|
|
* @param ItemInterface $item |
|
139
|
|
|
* |
|
140
|
|
|
* @return ItemInterface |
|
141
|
|
|
*/ |
|
142
|
|
|
public function buildMenu(ItemInterface $item) |
|
143
|
|
|
{ |
|
144
|
|
|
return parent::buildMenu($item) |
|
145
|
|
|
->setLinkAttribute('class', 'icon-label icon-label-plugin-shikimori'); |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* Fill item from source. |
|
150
|
|
|
* |
|
151
|
|
|
* @param array $data |
|
152
|
|
|
* |
|
153
|
|
|
* @return Item|null |
|
154
|
|
|
*/ |
|
155
|
|
|
public function fill(array $data) |
|
156
|
|
|
{ |
|
157
|
|
|
if (empty($data['url']) || !is_string($data['url']) || |
|
158
|
|
|
strpos($data['url'], $this->browser->getHost()) !== 0 || |
|
159
|
|
|
!preg_match(self::REG_ITEM_ID, $data['url'], $match) |
|
160
|
|
|
) { |
|
161
|
|
|
return null; |
|
162
|
|
|
} |
|
163
|
|
|
$path = str_replace('#ID#', $match['id'], self::FILL_URL); |
|
164
|
|
|
$body = $this->browser->get($path); |
|
165
|
|
|
|
|
166
|
|
|
$item = new Item(); |
|
167
|
|
|
$item->setDuration($body['duration']); |
|
168
|
|
|
$item->setSummary($body['description']); |
|
169
|
|
|
$item->setDatePremiere(new \DateTime($body['aired_on'])); |
|
170
|
|
|
if ($body['released_on']) { |
|
171
|
|
|
$item->setDateEnd(new \DateTime($body['released_on'])); |
|
172
|
|
|
} |
|
173
|
|
|
$ep_num = $body['episodes_aired'] ? $body['episodes_aired'] : $body['episodes']; |
|
174
|
|
|
$item->setEpisodesNumber($ep_num.($body['ongoing'] ? '+' : '')); |
|
175
|
|
|
|
|
176
|
|
|
// set main source |
|
177
|
|
|
$source = new Source(); |
|
178
|
|
|
$source->setUrl($data['url']); |
|
179
|
|
|
$item->addSource($source); |
|
180
|
|
|
|
|
181
|
|
|
// set complex data |
|
182
|
|
|
$this->setSources($item, $body); |
|
183
|
|
|
$this->setCover($item, $body); |
|
184
|
|
|
$this->setType($item, $body); |
|
185
|
|
|
$this->setNames($item, $body); |
|
186
|
|
|
$this->setGenres($item, $body); |
|
187
|
|
|
$this->setStudio($item, $body); |
|
188
|
|
|
|
|
189
|
|
|
if (!empty($data['frames'])) { |
|
190
|
|
|
$this->setImages($item, $body); |
|
191
|
|
|
} |
|
192
|
|
|
|
|
193
|
|
|
return $item; |
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
|
|
/** |
|
197
|
|
|
* @param Item $item |
|
198
|
|
|
* @param array $body |
|
199
|
|
|
* |
|
200
|
|
|
* @return Item |
|
201
|
|
|
*/ |
|
202
|
|
|
public function setSources(Item $item, $body) |
|
203
|
|
|
{ |
|
204
|
|
|
$sources = [ |
|
205
|
|
|
'ani_db_id' => self::ANI_DB_URL, |
|
206
|
|
|
'world_art_id' => self::WORLD_ART_URL, |
|
207
|
|
|
'myanimelist_id' => self::MY_ANIME_LIST_URL, |
|
208
|
|
|
]; |
|
209
|
|
|
|
|
210
|
|
|
foreach ($sources as $key => $url) { |
|
211
|
|
|
if (!empty($body[$key])) { |
|
212
|
|
|
$source = new Source(); |
|
213
|
|
|
$source->setUrl(str_replace('#ID#', $body[$key], $url)); |
|
214
|
|
|
$item->addSource($source); |
|
215
|
|
|
} |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
return $item; |
|
219
|
|
|
} |
|
220
|
|
|
|
|
221
|
|
|
/** |
|
222
|
|
|
* @param Item $item |
|
223
|
|
|
* @param array $body |
|
224
|
|
|
* |
|
225
|
|
|
* @return Item |
|
226
|
|
|
*/ |
|
227
|
|
|
public function setNames(Item $item, $body) |
|
228
|
|
|
{ |
|
229
|
|
|
$names = []; |
|
230
|
|
|
// set a name based on the locale |
|
231
|
|
|
if ($this->locale == 'ru' && $body['russian']) { |
|
232
|
|
|
$names = array_merge( |
|
233
|
|
|
[$body['name']], |
|
234
|
|
|
$body['english'], |
|
235
|
|
|
$body['japanese'], |
|
236
|
|
|
$body['synonyms'] |
|
237
|
|
|
); |
|
238
|
|
|
$item->setName($body['russian']); |
|
239
|
|
|
} elseif ($this->locale == 'ja' && $body['japanese']) { |
|
240
|
|
|
$item->setName(array_shift($body['japanese'])); |
|
241
|
|
|
$names = array_merge( |
|
242
|
|
|
[$body['name']], |
|
243
|
|
|
[$body['russian']], |
|
244
|
|
|
$body['english'], |
|
245
|
|
|
$body['japanese'], |
|
246
|
|
|
$body['synonyms'] |
|
247
|
|
|
); |
|
248
|
|
|
} |
|
249
|
|
|
|
|
250
|
|
|
// default list names |
|
251
|
|
|
if (!$item->getName()) { |
|
252
|
|
|
$names = array_merge( |
|
253
|
|
|
[$body['russian']], |
|
254
|
|
|
$body['english'], |
|
255
|
|
|
$body['japanese'], |
|
256
|
|
|
$body['synonyms'] |
|
257
|
|
|
); |
|
258
|
|
|
$item->setName($body['name']); |
|
259
|
|
|
} |
|
260
|
|
|
|
|
261
|
|
|
foreach ($names as $value) { |
|
262
|
|
|
if ($value != $body['name']) { |
|
263
|
|
|
$item->addName((new Name())->setName($value)); |
|
264
|
|
|
} |
|
265
|
|
|
} |
|
266
|
|
|
|
|
267
|
|
|
return $item; |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
|
|
/** |
|
271
|
|
|
* @param Item $item |
|
272
|
|
|
* @param array $body |
|
273
|
|
|
* |
|
274
|
|
|
* @return Item |
|
275
|
|
|
*/ |
|
276
|
|
|
public function setCover(Item $item, $body) |
|
277
|
|
|
{ |
|
278
|
|
|
if (!empty($body['image']) && !empty($body['image']['original'])) { |
|
279
|
|
|
try { |
|
280
|
|
|
if ($path = parse_url($body['image']['original'], PHP_URL_PATH)) { |
|
281
|
|
|
$item->setCover(self::NAME.'/'.$body['id'].'/cover.'.pathinfo($path, PATHINFO_EXTENSION)); |
|
282
|
|
|
$this->uploadImage($this->browser->getHost().$body['image']['original'], $item); |
|
283
|
|
|
} |
|
284
|
|
|
} catch (\Exception $e) { |
|
285
|
|
|
// error while retrieving images is not critical |
|
286
|
|
|
} |
|
287
|
|
|
} |
|
288
|
|
|
|
|
289
|
|
|
return $item; |
|
290
|
|
|
} |
|
291
|
|
|
|
|
292
|
|
|
/** |
|
293
|
|
|
* @param Item $item |
|
294
|
|
|
* @param array $body |
|
295
|
|
|
* |
|
296
|
|
|
* @return Item |
|
297
|
|
|
*/ |
|
298
|
|
|
public function setType(Item $item, $body) |
|
299
|
|
|
{ |
|
300
|
|
|
$rename = [ |
|
301
|
|
|
'Movie' => 'Feature', |
|
302
|
|
|
'Music' => 'Music video', |
|
303
|
|
|
'Special' => 'TV-special', |
|
304
|
|
|
]; |
|
305
|
|
|
$type = ucfirst($body['kind']); |
|
306
|
|
|
$type = isset($rename[$type]) ? $rename[$type] : $type; |
|
307
|
|
|
|
|
308
|
|
|
return $item->setType( |
|
309
|
|
|
$this |
|
310
|
|
|
->doctrine |
|
311
|
|
|
->getRepository('AnimeDbCatalogBundle:Type') |
|
312
|
|
|
->findOneBy(['name' => $type]) |
|
313
|
|
|
); |
|
314
|
|
|
} |
|
315
|
|
|
|
|
316
|
|
|
/** |
|
317
|
|
|
* @param Item $item |
|
318
|
|
|
* @param array $body |
|
319
|
|
|
* |
|
320
|
|
|
* @return Item |
|
321
|
|
|
*/ |
|
322
|
|
|
public function setGenres(Item $item, $body) |
|
323
|
|
|
{ |
|
324
|
|
|
$repository = $this->doctrine->getRepository('AnimeDbCatalogBundle:Genre'); |
|
325
|
|
|
$rename = [ |
|
326
|
|
|
'Martial Arts' => 'Martial arts', |
|
327
|
|
|
'Shoujo Ai' => 'Shoujo-ai', |
|
328
|
|
|
'Shounen Ai' => 'Shounen-ai', |
|
329
|
|
|
'Sports' => 'Sport', |
|
330
|
|
|
'Slice of Life' => 'Slice of life', |
|
331
|
|
|
'Sci-Fi' => 'Sci-fi', |
|
332
|
|
|
'Historical' => 'History', |
|
333
|
|
|
'Military' => 'War', |
|
334
|
|
|
]; |
|
335
|
|
|
|
|
336
|
|
|
foreach ($body['genres'] as $genre) { |
|
337
|
|
|
$genre = isset($rename[$genre['name']]) ? $rename[$genre['name']] : $genre['name']; |
|
338
|
|
|
$genre = $repository->findOneBy(['name' => $genre]); |
|
339
|
|
|
if ($genre instanceof Genre) { |
|
340
|
|
|
$item->addGenre($genre); |
|
341
|
|
|
} |
|
342
|
|
|
} |
|
343
|
|
|
|
|
344
|
|
|
return $item; |
|
345
|
|
|
} |
|
346
|
|
|
|
|
347
|
|
|
/** |
|
348
|
|
|
* @param Item $item |
|
349
|
|
|
* @param array $body |
|
350
|
|
|
* |
|
351
|
|
|
* @return Item |
|
352
|
|
|
*/ |
|
353
|
|
|
public function setStudio(Item $item, $body) |
|
354
|
|
|
{ |
|
355
|
|
|
$repository = $this->doctrine->getRepository('AnimeDbCatalogBundle:Studio'); |
|
356
|
|
|
$rename = [ |
|
357
|
|
|
'Arms' => 'Arms Corporation', |
|
358
|
|
|
'Mushi Productions' => 'Mushi Production', |
|
359
|
|
|
'Film Roman, Inc.' => 'Film Roman', |
|
360
|
|
|
'Tezuka Production' => 'Tezuka Productions', |
|
361
|
|
|
'CoMix Wave' => 'CoMix Wave Inc.', |
|
362
|
|
|
]; |
|
363
|
|
|
|
|
364
|
|
|
foreach ($body['studios'] as $studio) { |
|
365
|
|
|
$name = isset($rename[$studio['name']]) ? $rename[$studio['name']] : $studio['name']; |
|
366
|
|
|
$name = $studio['name'] != $studio['filtered_name'] ? [$name, $studio['filtered_name']] : $name; |
|
367
|
|
|
$studio = $repository->findOneBy(['name' => $name]); |
|
368
|
|
|
if ($studio instanceof Studio) { |
|
369
|
|
|
$item->setStudio($studio); |
|
370
|
|
|
break; |
|
371
|
|
|
} |
|
372
|
|
|
} |
|
373
|
|
|
|
|
374
|
|
|
return $item; |
|
375
|
|
|
} |
|
376
|
|
|
|
|
377
|
|
|
/** |
|
378
|
|
|
* @param Item $item |
|
379
|
|
|
* @param array $body |
|
380
|
|
|
* |
|
381
|
|
|
* @return Item |
|
382
|
|
|
*/ |
|
383
|
|
|
public function setImages(Item $item, $body) |
|
384
|
|
|
{ |
|
385
|
|
|
$images = $this->browser->get(str_replace('#ID#', $body['id'], self::FILL_IMAGES_URL)); |
|
386
|
|
|
|
|
387
|
|
|
foreach ($images as $image) { |
|
388
|
|
|
if ($path = parse_url($image['original'], PHP_URL_PATH)) { |
|
389
|
|
|
$image = new Image(); |
|
390
|
|
|
$image->setSource(self::NAME.'/'.$body['id'].'/'.pathinfo($path, PATHINFO_BASENAME)); |
|
391
|
|
|
if ($this->uploadImage($this->browser->getHost().$image['original'], $image)) { |
|
392
|
|
|
$item->addImage($image); |
|
393
|
|
|
} |
|
394
|
|
|
} |
|
395
|
|
|
} |
|
396
|
|
|
|
|
397
|
|
|
return $item; |
|
398
|
|
|
} |
|
399
|
|
|
|
|
400
|
|
|
/** |
|
401
|
|
|
* @param string $url |
|
402
|
|
|
* @param EntityInterface $entity |
|
403
|
|
|
* |
|
404
|
|
|
* @return bool |
|
405
|
|
|
*/ |
|
406
|
|
|
protected function uploadImage($url, EntityInterface $entity) |
|
407
|
|
|
{ |
|
408
|
|
|
return $this->downloader->image($url, $this->downloader->getRoot().$entity->getWebPath()); |
|
409
|
|
|
} |
|
410
|
|
|
|
|
411
|
|
|
/** |
|
412
|
|
|
* @param string $url |
|
413
|
|
|
* |
|
414
|
|
|
* @return bool |
|
415
|
|
|
*/ |
|
416
|
|
|
public function isSupportedUrl($url) |
|
417
|
|
|
{ |
|
418
|
|
|
return strpos($url, $this->browser->getHost()) === 0; |
|
419
|
|
|
} |
|
420
|
|
|
} |
|
421
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.