/**
  * @covers ::parse
  */
 public function testParse()
 {
     $mangaContents = file_get_contents(__DIR__ . '/../InputSamples/manga-11977-mine.html');
     $manga = MangaParser::parse($mangaContents);
     $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Manga', $manga);
     /* 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(11977, $manga->getId());
     $this->assertEquals('Bambino!', $manga->getTitle());
     $otherTitles = $manga->getOtherTitles();
     $this->assertArrayHasKey('japanese', $otherTitles);
     $this->assertContains('バンビ~ノ!', $otherTitles['japanese']);
     $this->assertInternalType('integer', $manga->getRank());
     $this->assertLessThan(1200, $manga->getRank());
     $this->assertGreaterThan(0, $manga->getRank());
     $this->assertInternalType('integer', $manga->getPopularityRank());
     $this->assertLessThan(2500, $manga->getPopularityRank());
     $this->assertGreaterThan(0, $manga->getPopularityRank());
     $this->assertEquals('https://myanimelist.cdn-dena.com/images/manga/5/74977.jpg', $manga->getImageUrl());
     $this->assertEquals('Manga', $manga->getType());
     $this->assertEquals(164, $manga->getChapters());
     $this->assertEquals(15, $manga->getVolumes());
     $this->assertEquals('finished', $manga->getstatus());
     $this->assertInternalType('float', $manga->getMembersScore());
     $this->assertGreaterThan(7.0, $manga->getMembersScore());
     $this->assertGreaterThan(2250, $manga->getMembersCount());
     $this->assertGreaterThan(30, $manga->getFavoritedCount());
     $this->assertStringStartsWith('Shogo Ban, a college student from Fukuoka', $manga->getSynopsis());
     $this->assertContains('Drama', $manga->getGenres());
     $this->assertContains('Seinen', $manga->getGenres());
     $this->assertEmpty($manga->getTags());
     $this->assertEmpty($manga->getAnimeAdaptations());
     $relatedManga = $manga->getRelatedManga();
     $this->assertInternalType('array', $relatedManga[0]);
     $this->assertEquals('19008', $relatedManga[0]['manga_id']);
     $this->assertStringStartsWith('Bambino! Secondo', $relatedManga[0]['title']);
     $this->assertContains('manga/19008', $relatedManga[0]['url']);
     $this->assertEmpty($manga->getAlternativeVersions());
     $this->assertEquals('completed', $manga->getReadStatus('string'));
     $this->assertEquals(164, $manga->getChaptersRead());
     $this->assertEquals(15, $manga->getVolumesRead());
     $this->assertEquals(7, $manga->getScore());
     $mangaContents = file_get_contents(__DIR__ . '/../InputSamples/manga-137.html');
     $manga = MangaParser::parse($mangaContents);
     $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Manga', $manga);
     $otherTitles = $manga->getOtherTitles();
     $this->assertArrayHasKey('english', $otherTitles);
     $this->assertContains('Read or Die', $otherTitles['english']);
     $this->assertArrayHasKey('synonyms', $otherTitles);
     $this->assertContains('RoD', $otherTitles['synonyms']);
     $animeAdaptations = $manga->getAnimeAdaptations();
     $this->assertInternalType('array', $animeAdaptations[0]);
     $this->assertEquals('208', $animeAdaptations[0]['anime_id']);
     $this->assertStringStartsWith('R.O.D OVA', $animeAdaptations[0]['title']);
     $this->assertContains('anime/208', $animeAdaptations[0]['url']);
     $mangaContents = file_get_contents(__DIR__ . '/../InputSamples/manga-44347.html');
     $manga = MangaParser::parse($mangaContents);
     $this->assertInstanceOf('Atarashii\\APIBundle\\Model\\Manga', $manga);
     $this->assertNull($manga->getChapters());
     $this->assertNull($manga->getVolumes());
 }
 /**
  * @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;
         }
     }
 }
 /**
  * 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;
     }
 }
 /**
  * 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;
     }
 }