/**
  * Get the details of an anime or manga.
  *
  * @param int     $id          The ID of the anime or manga as assigned by MyAnimeList
  * @param string  $apiVersion  The API version of the request
  * @param Request $request     HTTP Request object
  * @param string  $requestType The anime or manga request string
  *
  * @return View
  */
 public function getAction($id, $apiVersion, $requestType, Request $request)
 {
     //General information (and basic personal information) at:
     // http://myanimelist.net/anime/#{id}
     // http://myanimelist.net/manga/#{id}
     //Detailed personal information at:
     // http://myanimelist.net/ownlist/anime/{id}/edit?hideLayout
     // http://myanimelist.net/ownlist/manga/{id}/edit?hideLayout
     $usepersonal = (int) $request->query->get('mine');
     $downloader = $this->get('atarashii_api.communicator');
     if ($usepersonal) {
         //get the credentials we received
         $username = $this->getRequest()->server->get('PHP_AUTH_USER');
         $password = $this->getRequest()->server->get('PHP_AUTH_PW');
         try {
             if (!$downloader->cookieLogin($username, $password)) {
                 $view = $this->view(array('error' => 'unauthorized'), 401);
                 $view->setHeader('WWW-Authenticate', 'Basic realm="myanimelist.net"');
                 return $view;
             }
         } catch (Exception\CurlException $e) {
             return $this->view(array('error' => 'network-error'), 500);
         }
     }
     try {
         $recordDetails = $downloader->fetch('/' . $requestType . '/' . $id);
     } catch (Exception\CurlException $e) {
         return $this->view(array('error' => 'network-error'), 500);
     } catch (Exception\ClientErrorResponseException $e) {
         $recordDetails = $e->getResponse();
     }
     if (strpos($recordDetails, 'No series found') !== false || strpos($recordDetails, 'This page doesn\'t exist') !== false) {
         return $this->view(array('error' => 'not-found'), 404);
     } else {
         if ($requestType === 'anime') {
             $record = AnimeParser::parse($recordDetails, $apiVersion);
         } else {
             $record = MangaParser::parse($recordDetails, $apiVersion);
         }
         //Parse extended personal details if API 2.0 or better and personal details are requested
         if ($apiVersion >= '2.0' && $usepersonal) {
             try {
                 $recordDetails = $downloader->fetch('/ownlist/' . $requestType . '/' . $id . '/edit?hideLayout');
             } catch (Exception\CurlException $e) {
                 return $this->view(array('error' => 'network-error'), 500);
             }
             if (strpos($recordDetails, 'delete-form') !== false) {
                 if ($requestType === 'anime') {
                     $record = AnimeParser::parseExtendedPersonal($recordDetails, $record);
                 } else {
                     $record = MangaParser::parseExtendedPersonal($recordDetails, $record);
                 }
             }
         }
         $response = new Response();
         $serializationContext = SerializationContext::create();
         $serializationContext->setVersion($apiVersion);
         //For compatibility, API 1.0 explicitly passes null parameters.
         if ($apiVersion == '1.0') {
             $serializationContext->setSerializeNull(true);
         }
         //Only include cache info if it doesn't include personal data.
         if (!$usepersonal) {
             $response->setPublic();
             $response->setMaxAge(3600);
             //One hour
             $response->headers->addCacheControlDirective('must-revalidate', true);
             $response->setEtag($requestType . '/' . $id);
             //Also, set "expires" header for caches that don't understand Cache-Control
             $date = new \DateTime();
             $date->modify('+3600 seconds');
             //One hour
             $response->setExpires($date);
         }
         $view = $this->view($record);
         $view->setSerializationContext($serializationContext);
         $view->setResponse($response);
         $view->setStatusCode(200);
         return $view;
     }
 }
 /**
  * @covers ::parse
  */
 public function testParse()
 {
     $animeContents = file_get_contents(__DIR__ . '/../InputSamples/anime-1887-mine.html');
     $apiVersion = '2.1';
     $anime = AnimeParser::parse($animeContents, $apiVersion);
     $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Anime', $anime);
     /* As this test is against a static downloaded copy, we know what the exact values should be. As such, rather
      * than testing based on type for many of the items, we're checking for exact values. Obviously, this test
      * will need to be updated when the source file is re-downloaded to update any values.
      */
     $this->assertEquals('1887', $anime->getId());
     $this->assertEquals('Lucky☆Star', $anime->getTitle());
     $this->assertContains('youtube.com/embed/b8bSIgE8epA', $anime->getPreview());
     $oTitles = $anime->getOtherTitles();
     $this->assertArrayHasKey('english', $oTitles);
     $this->assertContains('Lucky☆Star', $oTitles['english']);
     $this->assertArrayHasKey('synonyms', $oTitles);
     $this->assertContains('Lucky Star', $oTitles['synonyms']);
     $this->assertArrayHasKey('japanese', $oTitles);
     $this->assertContains('らき☆すた', $oTitles['japanese']);
     $this->assertInternalType('integer', $anime->getRank());
     $this->assertLessThan(800, $anime->getRank());
     $this->assertGreaterThan(0, $anime->getRank());
     $this->assertInternalType('integer', $anime->getPopularityRank());
     $this->assertLessThan(100, $anime->getPopularityRank());
     $this->assertGreaterThan(0, $anime->getPopularityRank());
     $this->assertEquals('https://myanimelist.cdn-dena.com/images/anime/13/15010.jpg', $anime->getImageUrl());
     $this->assertEquals('TV', $anime->getType());
     $this->assertEquals(24, $anime->getEpisodes());
     $this->assertEquals('finished airing', $anime->getStatus());
     $this->assertEquals('2007-04-08', $anime->getStartDate());
     $this->assertEquals('2007-09-17', $anime->getEndDate());
     $this->assertEquals(24, $anime->getDuration());
     $this->assertEquals(null, $anime->getBroadcast());
     $this->assertEquals(24, $anime->getDuration());
     $this->assertEquals('PG-13 - Teens 13 or older', $anime->getClassification());
     $this->assertInternalType('float', $anime->getMembersScore());
     $this->assertGreaterThan(6.0, $anime->getMembersScore());
     $this->assertGreaterThan(230000, $anime->getMembersCount());
     $this->assertGreaterThan(7200, $anime->getFavoritedCount());
     $externalLinks = $anime->getExternalLinks();
     $this->assertEquals('http://www.lucky-ch.com/', $externalLinks['Official Site']);
     $this->assertEquals('http://anidb.info/perl-bin/animedb.pl?show=anime&aid=4777', $externalLinks['AnimeDB']);
     $this->assertEquals('http://www.animenewsnetwork.com/encyclopedia/anime.php?id=7222', $externalLinks['AnimeNewsNetwork']);
     $this->assertEquals('http://en.wikipedia.org/wiki/Lucky_Star_%28manga%29', $externalLinks['Wikipedia']);
     $this->assertContains('the daily lives of four cute high school girls', $anime->getSynopsis());
     $this->assertStringStartsWith('<i>Lucky Star</i> also has audio CDs', $anime->getBackground());
     $this->assertContains('Lucky Paradise', $anime->getProducers());
     $this->assertContains('Lantis', $anime->getProducers());
     $this->assertContains('Comedy', $anime->getGenres());
     $this->assertContains('Parody', $anime->getGenres());
     $mangaAdaptations = $anime->getMangaAdaptations();
     $this->assertInternalType('array', $mangaAdaptations[0]);
     $this->assertEquals('587', $mangaAdaptations[0]['manga_id']);
     $this->assertEquals('Lucky☆Star', $mangaAdaptations[0]['title']);
     $this->assertContains('manga/587', $mangaAdaptations[0]['url']);
     $this->assertEmpty($anime->getPrequels());
     $sequels = $anime->getSequels();
     $this->assertInternalType('array', $sequels[0]);
     $this->assertEquals('4472', $sequels[0]['anime_id']);
     $this->assertStringStartsWith('Lucky☆Star', $sequels[0]['title']);
     $this->assertContains('anime/4472', $sequels[0]['url']);
     $this->assertEmpty($anime->getSideStories());
     $charAnime = $anime->getCharacterAnime();
     $this->assertInternalType('array', $charAnime[0]);
     $this->assertEquals('3080', $charAnime[0]['anime_id']);
     $this->assertEquals('Anime Tenchou', $charAnime[0]['title']);
     $this->assertContains('anime/3080', $charAnime[0]['url']);
     $spinOffs = $anime->getSpinOffs();
     $this->assertInternalType('array', $spinOffs[0]);
     $this->assertEquals('17637', $spinOffs[0]['anime_id']);
     $this->assertStringStartsWith('Miyakawa-ke', $spinOffs[0]['title']);
     $this->assertContains('anime/17637', $spinOffs[0]['url']);
     $this->assertEmpty($anime->getSummaries());
     $this->assertEmpty($anime->getAlternativeVersions());
     $this->assertEmpty($anime->getOther());
     $openingTheme = $anime->getOpeningTheme();
     $this->assertEquals('"Motteke! Sailor Fuku" by Aya Hirano, Emiri Katou, Kaori Fukuhara & Aya Endo', $openingTheme[0]);
     $endingTheme = $anime->getEndingTheme();
     $this->assertEquals('#01: "Uchuu Tetsujin Kyoodain (宇宙鉄人キョーダイン)" by Aya Hirano (ep 1)', $endingTheme[0]);
     $this->assertEquals('#23: "Mikuru Henshin! Soshite Sentou! (ミクル変身!そして戦闘!)" by Minoru Shiraishi (ep 23)', $endingTheme[23]);
     $recommedations = $anime->getRecommendations();
     $recommedations = $recommedations[0];
     $this->assertEquals(66, $recommedations->getId());
     $this->assertEquals('Azumanga Daioh', $recommedations->getTitle());
     $this->assertEquals('https://myanimelist.cdn-dena.com/images/anime/1/66.jpg', $recommedations->getImageUrl());
     $animeContents = file_get_contents(__DIR__ . '/../InputSamples/anime-10236.html');
     $anime = AnimeParser::parse($animeContents, $apiVersion);
     $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Anime', $anime);
     $this->assertEquals('1981', $anime->getEndDate());
     $animeContents = file_get_contents(__DIR__ . '/../InputSamples/anime-47.html');
     $anime = AnimeParser::parse($animeContents, $apiVersion);
     $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Anime', $anime);
     $this->assertEquals('124', $anime->getDuration());
     $animeContents = file_get_contents(__DIR__ . '/../InputSamples/anime-10758.html');
     $anime = AnimeParser::parse($animeContents, $apiVersion);
     $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Anime', $anime);
     $this->assertNull($anime->getDuration());
     $animeContents = file_get_contents(__DIR__ . '/../InputSamples/anime-18617.html');
     $anime = AnimeParser::parse($animeContents, $apiVersion);
     $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Anime', $anime);
     $this->assertEquals('120', $anime->getDuration());
 }
 /**
  * @param         $apiVersion  The API version of the request
  * @param         $requestType The anime or manga request string
  * @param Request $request     HTTP Request object
  *
  * @return \FOS\RestBundle\View\View
  */
 public function getBrowseAction($apiVersion, $requestType, Request $request)
 {
     $downloader = $this->get('atarashii_api.communicator');
     $page = (int) $request->query->get('page');
     if ($page <= 0) {
         $page = 1;
     }
     // Create URL parts supported by MAL
     $pagePart = '&show=' . ($page * 50 - 50);
     $keyword = '&q=' . $request->query->get('keyword');
     $score = '&score=' . (int) $request->query->get('score');
     $reverse = '&w=' . (int) $request->query->get('reverse');
     $rating = '&r=' . (int) $request->query->get('rating');
     $genreType = '&gx=' . (int) $request->query->get('genre_type');
     $status = '&status=' . $this->getStatusId($request->query->get('status'));
     $endDateArray = explode('-', $request->query->get('end_date'));
     if (count($endDateArray) == 3) {
         $endDate = '&ey=' . $endDateArray[0] . '&em=' . $endDateArray[1] . '&ed=' . $endDateArray[2];
     } else {
         $endDate = '';
     }
     $startDateArray = explode('-', $request->query->get('start_date'));
     if (count($startDateArray) == 3) {
         $startDate = '&sy=' . $startDateArray[0] . '&sm=' . $startDateArray[1] . '&sd=' . $startDateArray[2];
     } else {
         $startDate = '';
     }
     if ($requestType === 'anime') {
         $type = '&type=' . Anime::getTypeId($request->query->get('type'));
         $genres = Anime::getGenresId($request->query->get('genres'));
         $sort = '&o=' . Anime::getColumnId($request->query->get('sort'), $requestType);
     } else {
         $type = '&type=' . Manga::getTypeId($request->query->get('type'));
         $genres = Manga::getGenresId($request->query->get('genres'));
         $sort = '&o=' . Manga::getColumnId($request->query->get('sort'), $requestType);
     }
     // Combine all URL parts for the request
     $url = $genres . $sort . $reverse . $endDate . $startDate . $rating . $status . $type . $keyword . $score . $genreType . $pagePart;
     try {
         $content = $downloader->fetch('/' . $requestType . '.php?c[]=a&c[]=b&c[]=c&c[]=d&c[]=e&c[]=f&c[]=g&c[]=g' . $url);
     } catch (Exception\CurlException $e) {
         return $this->view(array('error' => 'network-error'), 500);
     } catch (Exception\ClientErrorResponseException $e) {
         $content = $e->getResponse();
     }
     $response = new Response();
     $serializationContext = SerializationContext::create();
     $serializationContext->setVersion($apiVersion);
     $response->setPublic();
     $response->setMaxAge(86400);
     //One day
     $response->headers->addCacheControlDirective('must-revalidate', true);
     $response->setEtag($type . '/' . $requestType . '?' . $url);
     //Also, set "expires" header for caches that don't understand Cache-Control
     $date = new \DateTime();
     $date->modify('+86400 seconds');
     //One day
     $response->setExpires($date);
     // MAL does contain a bug where excluded genres allow the same amount of pages as normal without warning
     // To avoid issues we check if the page number does match the content page number.
     preg_match('/>\\[(\\d+?)\\]/', $content, $matches);
     if (strpos($content, 'No titles that matched') !== false || strpos($content, 'This page doesn\'t exist') !== false) {
         return $this->view(array('error' => 'not-found'), 404);
     } else {
         if (count($matches) > 1 && (int) $matches[1] !== $page) {
             return $this->view(array(), 200);
         } else {
             //MAL now returns 404 on a single result. Workaround
             if (method_exists($content, 'getStatusCode') && $content->getStatusCode() === 404) {
                 $location = $content->getHeader('Location');
                 try {
                     $content = $downloader->fetch($location);
                     if ($type === 'anime') {
                         $searchResult = array(AnimeParser::parse($content));
                     } else {
                         $searchResult = array(MangaParser::parse($content));
                     }
                 } catch (Exception\CurlException $e) {
                     return $this->view(array('error' => 'network-error'), 500);
                 }
             } else {
                 if ($downloader->wasRedirected()) {
                     if ($type === 'anime') {
                         $searchResult = array(AnimeParser::parse($content));
                     } else {
                         $searchResult = array(MangaParser::parse($content));
                     }
                 } else {
                     $searchResult = Upcoming::parse($content, $requestType);
                 }
             }
             $view = $this->view($searchResult);
             $view->setSerializationContext($serializationContext);
             $view->setResponse($response);
             $view->setStatusCode(200);
             return $view;
         }
     }
 }
 /**
  * Request search data using the unOfficial.
  *
  * With the unOfficial way the API will get the data from the search page instead of the official API.
  *
  * @param $type       The type is a string which can be 'anime' or 'manga'
  * @param $page       Integer which is used to get the desired page
  * @param $query      The title or keywords to seach for the record
  * @param $apiVersion The API version which was used for the request
  *
  * @return \FOS\RestBundle\View\View
  */
 private function unOfficialSearch($type, $page, $query, $apiVersion)
 {
     // http://myanimelist.net/anime.php?c[]=a&c[]=b&c[]=c&c[]=d&c[]=e&c[]=f&c[]=g&q=#{name}&show=#{page}
     // http://myanimelist.net/manga.php?c[]=a&c[]=b&c[]=c&c[]=d&c[]=e&c[]=f&c[]=g&q=#{name}&show=#{page}
     if ($page <= 0) {
         $page = 1;
     }
     $downloader = $this->get('atarashii_api.communicator');
     try {
         $content = $downloader->fetch('/' . $type . '.php?c[]=a&c[]=b&c[]=c&c[]=d&c[]=e&c[]=f&c[]=g&q=' . $query . '&show=' . ($page * 50 - 50));
     } catch (Exception\CurlException $e) {
         return $this->view(array('error' => 'network-error'), 500);
     } catch (Exception\ClientErrorResponseException $e) {
         //MAL now returns 404 on searches without results.
         //We still need the content for logic purposes.
         $content = $e->getResponse();
     }
     $response = new Response();
     $serializationContext = SerializationContext::create();
     $serializationContext->setVersion($apiVersion);
     $response->setPublic();
     $response->setMaxAge(3600);
     //One hour
     $response->headers->addCacheControlDirective('must-revalidate', true);
     $response->setEtag($type . '/search?q=' . urlencode($query) . 'page=' . $page);
     //Also, set "expires" header for caches that don't understand Cache-Control
     $date = new \DateTime();
     $date->modify('+3600 seconds');
     //One hour
     $response->setExpires($date);
     if (strpos($content, 'No titles that matched') !== false || strpos($content, 'This page doesn\'t exist') !== false) {
         $view = $this->view(array('error' => 'not-found'));
         $view->setResponse($response);
         $view->setStatusCode(404);
         return $view;
     } else {
         //For compatibility, API 1.0 explicitly passes null parameters.
         if ($apiVersion == '1.0') {
             $serializationContext->setSerializeNull(true);
         }
         //MAL now returns 404 on a single result. Workaround
         if (method_exists($content, 'getStatusCode') && $content->getStatusCode() === 404) {
             $location = $content->getHeader('Location');
             try {
                 $content = $downloader->fetch($location);
                 if ($type === 'anime') {
                     $searchResult = array(AnimeParser::parse($content));
                 } else {
                     $searchResult = array(MangaParser::parse($content));
                 }
             } catch (Exception\CurlException $e) {
                 return $this->view(array('error' => 'network-error'), 500);
             }
         } else {
             if ($downloader->wasRedirected()) {
                 if ($type === 'anime') {
                     $searchResult = array(AnimeParser::parse($content));
                 } else {
                     $searchResult = array(MangaParser::parse($content));
                 }
             } else {
                 $searchResult = Upcoming::parse($content, $type);
             }
         }
         $view = $this->view($searchResult);
         $view->setSerializationContext($serializationContext);
         $view->setResponse($response);
         $view->setStatusCode(200);
         return $view;
     }
 }