Issues (10)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Service/Filler.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\AniDbFillerBundle\Service;
10
11
use AnimeDb\Bundle\CatalogBundle\Plugin\Fill\Filler\Filler as FillerPlugin;
12
use AnimeDb\Bundle\AniDbBrowserBundle\Service\Browser;
13
use Doctrine\Bundle\DoctrineBundle\Registry;
14
use AnimeDb\Bundle\AppBundle\Service\Downloader;
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\Entity\Genre;
19
use AnimeDb\Bundle\AniDbFillerBundle\Form\Type\Filler as FillerForm;
20
use Symfony\Component\DomCrawler\Crawler;
21
use Knp\Menu\ItemInterface;
22
use AnimeDb\Bundle\AppBundle\Service\Downloader\Entity\EntityInterface;
23
24
class Filler extends FillerPlugin
25
{
26
    /**
27
     * @var string
28
     */
29
    const NAME = 'anidb';
30
31
    /**
32
     * @var string
33
     */
34
    const TITLE = 'AniDB.net';
35
36
    /**
37
     * RegExp for get item id.
38
     *
39
     * @var string
40
     */
41
    const REG_ITEM_ID = '#/perl\-bin/animedb\.pl\?show=anime&aid=(?<id>\d+)#';
42
43
    /**
44
     * @var Browser
45
     */
46
    protected $browser;
47
48
    /**
49
     * @var Registry
50
     */
51
    protected $doctrine;
52
53
    /**
54
     * @var Downloader
55
     */
56
    protected $downloader;
57
58
    /**
59
     * @var SummaryCleaner
60
     */
61
    protected $cleaner;
62
63
    /**
64
     * @var string
65
     */
66
    protected $locale;
67
68
    /**
69
     * AniDB category to genre.
70
     *
71
     * <code>
72
     *     { from: to, ... }
73
     * </code>
74
     *
75
     * @var array
76
     */
77
    protected $category_to_genre = [
78
        'Alternative History' => 'History',
79
        'Anti-War' => 'War',
80
        'Badminton' => 'Sport',
81
        'Bakumatsu - Meiji Period' => 'History',
82
        'Band' => 'Music',
83
        'Baseball' => 'Sport',
84
        'Basketball' => 'Sport',
85
        'Battle Royale' => 'War',
86
        'Board Games' => 'Game',
87
        'Boxing' => 'Sport',
88
        'Catholic School' => 'School',
89
        'Chess' => 'Sport',
90
        'Clubs' => 'School',
91
        'College' => 'School',
92
        'Combat' => 'Action',
93
        'Conspiracy' => 'Thriller',
94
        'Contemporary Fantasy' => 'Fantasy',
95
        'Cops' => 'Police',
96
        'Daily Life' => 'Slice of life',
97
        'Dark Elf' => 'Fantasy',
98
        'Dark Fantasy' => 'Fantasy',
99
        'Dodgeball' => 'Sport',
100
        'Dragon' => 'Fantasy',
101
        'Edo Period' => 'Fantasy',
102
        'Elementary School' => 'School',
103
        'Elf' => 'Fantasy',
104
        'Fairies' => 'Fantasy',
105
        'Fantasy World' => 'Fantasy',
106
        'Feudal Warfare' => 'War',
107
        'Football' => 'Sport',
108
        'Formula Racing' => 'Sport',
109
        'Ghost' => 'Supernatural',
110
        'Go' => 'Game',
111
        'Golf' => 'Sport',
112
        'Gunfights' => 'War',
113
        'Gymnastics' => 'Sport',
114
        'Heian Period' => 'History',
115
        'High Fantasy' => 'Fantasy',
116
        'High School' => 'School',
117
        'Historical' => 'History',
118
        'Ice Skating' => 'Sport',
119
        'Inline Skating' => 'Sport',
120
        'Jousting' => 'Sport',
121
        'Judo' => 'Sport',
122
        'Kendo' => 'Sport',
123
        'Law and Order' => 'Police',
124
        'Magic Circles' => 'Magic',
125
        'Mahjong' => 'Game',
126
        'Mahou Shoujo' => 'Mahoe shoujo',
127
        'Martial Arts' => 'Martial arts',
128
        'Military' => 'War',
129
        'Motorsport' => 'Sport',
130
        'Muay Thai' => 'Sport',
131
        'Ninja' => 'Samurai',
132
        'Pirate' => 'Adventure',
133
        'Post-apocalypse' => 'Apocalyptic fiction',
134
        'Post-War' => 'War',
135
        'Proxy Battles' => 'War',
136
        'Reverse Harem' => 'Harem',
137
        'Rugby' => 'Sport',
138
        'School Dormitory' => 'School',
139
        'School Excursion' => 'School',
140
        'School Festival' => 'School',
141
        'School Life' => 'School',
142
        'School Sports Festival' => 'School',
143
        'Sci-Fi' => 'Sci-fi',
144
        'Sengoku Period' => 'History',
145
        'Shougi' => 'Game',
146
        'Shoujo Ai' => 'Shoujo-ai',
147
        'Shounen Ai' => 'Shounen-ai',
148
        'Spellcasting' => 'Magic',
149
        'Sports' => 'Sport',
150
        'Street Racing' => 'Cars',
151
        'Swimming' => 'Sport',
152
        'Swordplay' => 'Sport',
153
        'Tennis' => 'Sport',
154
        'Victorian Period' => 'History',
155
        'Volleyball' => 'Sport',
156
        'Witch' => 'Magic',
157
        'World War I' => 'War',
158
        'World War II' => 'War',
159
        'Wrestling' => 'Action',
160
    ];
161
162
    /**
163
     * @param Browser $browser
164
     * @param Registry $doctrine
165
     * @param Downloader $downloader
166
     * @param SummaryCleaner $cleaner
167
     * @param $locale
168
     */
169
    public function __construct(
170
        Browser $browser,
171
        Registry $doctrine,
172
        Downloader $downloader,
173
        SummaryCleaner $cleaner,
174
        $locale
175
    ) {
176
        $this->browser = $browser;
177
        $this->doctrine = $doctrine;
178
        $this->downloader = $downloader;
179
        $this->cleaner = $cleaner;
180
        $this->locale = $locale;
181
    }
182
183
    /**
184
     * @return string
185
     */
186
    public function getName()
187
    {
188
        return self::NAME;
189
    }
190
191
    /**
192
     * @return string
193
     */
194
    public function getTitle()
195
    {
196
        return self::TITLE;
197
    }
198
199
    /**
200
     * @return FillerForm
201
     */
202
    public function getForm()
203
    {
204
        return new FillerForm($this->browser->getHost());
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \AnimeDb\Bund...s->browser->getHost()); (AnimeDb\Bundle\AniDbFillerBundle\Form\Type\Filler) is incompatible with the return type declared by the interface AnimeDb\Bundle\CatalogBu...illerInterface::getForm of type AnimeDb\Bundle\CatalogBu...ugin\Fill\Filler\Filler.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
205
    }
206
207
    /**
208
     * Build menu for plugin.
209
     *
210
     * @param ItemInterface $item
211
     *
212
     * @return ItemInterface
213
     */
214
    public function buildMenu(ItemInterface $item)
215
    {
216
        return parent::buildMenu($item)
217
            ->setLinkAttribute('class', 'icon-label icon-label-plugin-anidb');
218
    }
219
220
    /**
221
     * Fill item from source.
222
     *
223
     * @param array $data
224
     *
225
     * @return Item|null
226
     */
227
    public function fill(array $data)
228
    {
229
        if (empty($data['url']) || !is_string($data['url']) ||
230
            strpos($data['url'], $this->browser->getHost()) !== 0 ||
231
            !preg_match(self::REG_ITEM_ID, $data['url'], $match)
232
        ) {
233
            return null;
234
        }
235
236
        $body = $this->browser->get('anime', ['aid' => $match['id']]);
237
238
        $item = new Item();
239
        $item->setEpisodesNumber($body->filter('episodecount')->text());
240
        $item->setDatePremiere(new \DateTime($body->filter('startdate')->text()));
241
        $item->setDateEnd(new \DateTime($body->filter('enddate')->text()));
242
        $item->setSummary($this->cleaner->clean($body->filter('description')->text()));
243
244
        // set main source
245
        $source = new Source();
246
        $source->setUrl($data['url']);
247
        $item->addSource($source);
248
249
        // add url to offsite
250
        $source = new Source();
251
        $source->setUrl($body->filter('url')->text());
252
        $item->addSource($source);
253
254
        // set complex data
255
        $this->setCover($item, $body, $match['id']);
256
        $this->setNames($item, $body);
257
        $this->setEpisodes($item, $body);
258
        $this->setType($item, $body);
259
        $this->setGenres($item, $body);
260
261
        return $item;
262
    }
263
264
    /**
265
     * @param Item $item
266
     * @param Crawler $body
267
     *
268
     * @return Item
269
     */
270
    public function setNames(Item $item, Crawler $body)
271
    {
272
        $titles = $body->filter('titles > title');
273
        $names = [];
274
        /* @var $title \DOMElement */
275
        foreach ($titles as $title) {
276
            $lang = substr($title->attributes->item(0)->nodeValue, 0, 2);
277
            if ($lang != 'x-') {
278
                $names[$lang][$title->getAttribute('type')] = $title->nodeValue;
279
            }
280
        }
281
282
        // set main name
283
        if (!empty($names[$this->locale])) {
284
            $item->setName($this->getNameForLocale($this->locale, $names));
285 View Code Duplication
        } elseif ($this->locale != 'en' && !empty($names['en'])) {
0 ignored issues
show
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...
286
            $item->setName($this->getNameForLocale('en', $names));
287
        } else {
288
            $item->setName($this->getNameForLocale(array_keys($names)[0], $names));
289
        }
290
291
        // set other names
292
        $other = [];
293
        foreach ($names as $locales) {
294
            foreach ($locales as $name) {
295
                $other[] = $name;
296
            }
297
        }
298
        $other = array_unique($other);
299
        sort($other);
300
301
        foreach ($other as $name) {
302
            $item->addName((new Name())->setName($name));
303
        }
304
305
        return $item;
306
    }
307
308
    /**
309
     * @param Item $item
310
     * @param Crawler $body
311
     * @param string $id
312
     *
313
     * @return Item
314
     */
315
    public function setCover(Item $item, Crawler $body, $id)
316
    {
317
        if ($image = $body->filter('picture')->text()) {
318
            try {
319
                $image = $this->browser->getImageUrl($image);
320
                if ($path = parse_url($image, PHP_URL_PATH)) {
321
                    $ext = pathinfo($path, PATHINFO_EXTENSION);
322
                    $item->setCover(self::NAME.'/'.$id.'/cover.'.$ext);
323
                    $this->uploadImageFromUrl($image, $item);
324
                }
325
            } catch (\Exception $e) {
326
                // error while retrieving images is not critical
327
            }
328
        }
329
330
        return $item;
331
    }
332
333
    /**
334
     * @param Item $item
335
     * @param Crawler $body
336
     *
337
     * @return Item
338
     */
339
    public function setEpisodes(Item $item, Crawler $body)
340
    {
341
        $episodes = '';
342
        foreach ($body->filter('episodes > episode') as $episode) {
343
            $episode = new Crawler($episode);
344
            $episodes .= $episode->filter('epno')->text().'. '.$this->getEpisodeTitle($episode)."\n";
345
        }
346
        $item->setEpisodes(trim($episodes));
347
348
        return $item;
349
    }
350
351
    /**
352
     * @param Crawler $episode
353
     *
354
     * @return string
355
     */
356
    protected function getEpisodeTitle(Crawler $episode)
357
    {
358
        $titles = [];
359
        /* @var $title \DOMElement */
360
        foreach ($episode->filter('title') as $title) {
361
            $lang = substr($title->attributes->item(0)->nodeValue, 0, 2);
362
            if ($lang == $this->locale) {
363
                return $title->nodeValue;
364
            }
365
            if ($lang != 'x-') {
366
                $titles[$lang] = $title->nodeValue;
367
            }
368
        }
369
370
        // get EN lang or first
371
        if (!empty($titles['en'])) {
372
            return $titles['en'];
373
        } else {
374
            return array_shift($titles);
375
        }
376
    }
377
378
    /**
379
     * @param Item $item
380
     * @param Crawler $body
381
     *
382
     * @return Item
383
     */
384
    public function setType(Item $item, Crawler $body)
385
    {
386
        $rename = [
387
            'TV Series' => 'TV',
388
            'Movie' => 'Feature',
389
            'Web' => 'ONA',
390
        ];
391
        $type = $body->filter('anime > type')->text();
392
        $type = isset($rename[$type]) ? $rename[$type] : $type;
393
394
        return $item->setType(
395
            $this
396
                ->doctrine
397
                ->getRepository('AnimeDbCatalogBundle:Type')
398
                ->findOneBy(['name' => $type])
399
        );
400
    }
401
402
    /**
403
     * @param Item $item
404
     * @param Crawler $body
405
     *
406
     * @return Item
407
     */
408
    public function setGenres(Item $item, Crawler $body)
409
    {
410
        $repository = $this->doctrine->getRepository('AnimeDbCatalogBundle:Genre');
411
        $categories = $body->filter('categories > category > name');
412
        foreach ($categories as $category) {
413
            if (isset($this->category_to_genre[$category->nodeValue])) {
414
                $genre = $repository->findOneBy(['name' => $this->category_to_genre[$category->nodeValue]]);
415
            } else {
416
                $genre = $repository->findOneBy(['name' => $category->nodeValue]);
417
            }
418
            if ($genre instanceof Genre) {
419
                $item->addGenre($genre);
420
            }
421
        }
422
423
        return $item;
424
    }
425
426
    /**
427
     * @param string $url
428
     * @param EntityInterface $entity
429
     *
430
     * @return bool
431
     */
432
    protected function uploadImageFromUrl($url, EntityInterface $entity)
433
    {
434
        return $this->downloader->image($url, $this->downloader->getRoot().$entity->getWebPath());
435
    }
436
437
    /**
438
     * @param string $locale
439
     * @param array $names
440
     *
441
     * @return string
442
     */
443 View Code Duplication
    protected function getNameForLocale($locale, &$names)
0 ignored issues
show
This method seems to be duplicated in 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...
444
    {
445
        if (isset($names[$locale]['main'])) {
446
            $name = $names[$locale]['main'];
447
            unset($names[$locale]['main']);
448
        } elseif (isset($names[$locale]['official'])) {
449
            $name = $names[$locale]['official'];
450
            unset($names[$locale]['official']);
451
        } else {
452
            $name = array_shift($names[$locale]);
453
        }
454
455
        return $name;
456
    }
457
458
    /**
459
     * @param string $url
460
     *
461
     * @return bool
462
     */
463
    public function isSupportedUrl($url)
464
    {
465
        return strpos($url, $this->browser->getHost()) === 0;
466
    }
467
}
468