Rereading?
diff --git a/app/views/manga/list.php b/app/views/manga/list.php
index dbf00076..7a44d851 100644
--- a/app/views/manga/list.php
+++ b/app/views/manga/list.php
@@ -10,9 +10,9 @@
- is_authenticated()): ?>
+ is_authenticated()): ?>
-
+
Title
Rating
Completed Chapters
@@ -23,16 +23,18 @@
- is_authenticated()): ?>
+ is_authenticated()): ?>
">Edit
-
+
- = $item['manga']['title'] ?>
+ = array_shift($item['manga']['titles']) ?>
- = ( ! is_null($item['manga']['alternate_title'])) ? " · " . $item['manga']['alternate_title'] : "" ?>
+
+ = $title ?>
+
= $item['user_rating'] ?> / 10
= $item['chapters']['read'] ?> / = $item['chapters']['total'] ?>
diff --git a/index.php b/index.php
index ca589e6f..fff3616a 100644
--- a/index.php
+++ b/index.php
@@ -53,12 +53,11 @@ $whoops = new Run();
$defaultHandler = new PrettyPageHandler();
$whoops->pushHandler($defaultHandler);
-// Set up json handler for ajax errors
-//$jsonHandler = new JsonResponseHandler();
-//$whoops->pushHandler($jsonHandler);
-
// Register as the error handler
-$whoops->register();
+if (array_key_exists('whoops', $_GET))
+{
+ $whoops->register();
+}
// -----------------------------------------------------------------------------
// Dependency Injection setup
diff --git a/public/css/base.css b/public/css/base.css
index cd393a9b..b80a42b1 100644
--- a/public/css/base.css
+++ b/public/css/base.css
@@ -1085,7 +1085,9 @@ a:hover, a:active {
display:block;
}
-.media > .name > a {
+.media > .name a,
+ .media > .name a small
+ {
background:none;
color:#fff;
text-shadow:1px 2px 1px rgba(0, 0, 0, .85);
diff --git a/public/css/base.myth.css b/public/css/base.myth.css
index 5c89e603..b91f8be5 100644
--- a/public/css/base.myth.css
+++ b/public/css/base.myth.css
@@ -354,7 +354,9 @@ a:hover, a:active {
display:block;
}
- .media > .name > a {
+ .media > .name a,
+ .media > .name a small
+ {
background:none;
color:#fff;
text-shadow: var(--shadow);
diff --git a/src/API/Kitsu.php b/src/API/Kitsu.php
index 9753e993..e89357c6 100644
--- a/src/API/Kitsu.php
+++ b/src/API/Kitsu.php
@@ -16,13 +16,20 @@
namespace Aviat\AnimeClient\API;
-use Aviat\AnimeClient\API\Kitsu\Enum\AnimeWatchingStatus;
+use Aviat\AnimeClient\API\Kitsu\Enum\{AnimeAiringStatus, AnimeWatchingStatus};
+use DateTimeImmutable;
/**
* Constants and mappings for the Kitsu API
*/
class Kitsu {
+ const AUTH_URL = 'https://kitsu.io/api/oauth/token';
+ /**
+ * Map of Kitsu status to label for select menus
+ *
+ * @return array
+ */
public static function getStatusToSelectMap()
{
return [
@@ -33,4 +40,147 @@ class Kitsu {
AnimeWatchingStatus::DROPPED => 'Dropped'
];
}
+
+ /**
+ * Determine whether an anime is airing, finished airing, or has not yet aired
+ *
+ * @param string $startDate
+ * @param string $endDate
+ * @return string
+ */
+ public static function getAiringStatus(string $startDate = null, string $endDate = null): string
+ {
+ $startAirDate = new DateTimeImmutable($startDate ?? 'tomorrow');
+ $endAirDate = new DateTimeImmutable($endDate ?? 'tomorrow');
+ $now = new DateTimeImmutable();
+
+ $isDoneAiring = $now > $endAirDate;
+ $isCurrentlyAiring = ($now > $startAirDate) && ! $isDoneAiring;
+
+ switch (true)
+ {
+ case $isCurrentlyAiring:
+ return AnimeAiringStatus::AIRING;
+
+ case $isDoneAiring:
+ return AnimeAiringStatus::FINISHED_AIRING;
+
+ default:
+ return AnimeAiringStatus::NOT_YET_AIRED;
+ }
+ }
+
+ /**
+ * Filter out duplicate and very similar names from
+ *
+ * @param array $data The 'attributes' section of the api data response
+ * @return array List of alternate titles
+ */
+ public static function filterTitles(array $data): array
+ {
+ // The 'canonical' title is always returned
+ $valid = [$data['canonicalTitle']];
+
+ if (array_key_exists('titles', $data))
+ {
+ foreach($data['titles'] as $alternateTitle)
+ {
+ if (self::titleIsUnique($alternateTitle, $valid))
+ {
+ $valid[] = $alternateTitle;
+ }
+ }
+ }
+
+ return $valid;
+ }
+
+ /**
+ * Reorganizes 'included' data to be keyed by
+ * type => [
+ * id => data/attributes,
+ * ]
+ *
+ * @param array $includes
+ * @return array
+ */
+ public static function organizeIncludes(array $includes): array
+ {
+ $organized = [];
+
+ foreach ($includes as $item)
+ {
+ $type = $item['type'];
+ $id = $item['id'];
+ $organized[$type] = $organized[$type] ?? [];
+ $organized[$type][$id] = $item['attributes'];
+
+ if (array_key_exists('relationships', $item))
+ {
+ $organized[$type][$id]['relationships'] = self::organizeRelationships($item['relationships']);
+ }
+ }
+
+ return $organized;
+ }
+
+ /**
+ * Reorganize relationship mappings to make them simpler to use
+ *
+ * Remove verbose structure, and just map:
+ * type => [ idArray ]
+ *
+ * @param array $relationships
+ * @return array
+ */
+ public static function organizeRelationships(array $relationships): array
+ {
+ $organized = [];
+
+ foreach($relationships as $key => $data)
+ {
+ if ( ! array_key_exists('data', $data))
+ {
+ continue;
+ }
+
+ $organized[$key] = $organized[$key] ?? [];
+
+ foreach ($data['data'] as $item)
+ {
+ $organized[$key][] = $item['id'];
+ }
+ }
+
+ return $organized;
+ }
+
+ /**
+ * Determine if an alternate title is unique enough to list
+ *
+ * @param string $title
+ * @param array $existingTitles
+ * @return bool
+ */
+ private static function titleIsUnique(string $title = null, array $existingTitles): bool
+ {
+ if (empty($title))
+ {
+ return false;
+ }
+
+ foreach($existingTitles as $existing)
+ {
+ $isSubset = stripos($existing, $title) !== FALSE;
+ $diff = levenshtein($existing, $title);
+ $onlydifferentCase = (mb_strtolower($existing) === mb_strtolower($title));
+
+ if ($diff < 3 || $isSubset || $onlydifferentCase)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
\ No newline at end of file
diff --git a/src/API/Kitsu/Auth.php b/src/API/Kitsu/Auth.php
index e940d878..113d5fd8 100644
--- a/src/API/Kitsu/Auth.php
+++ b/src/API/Kitsu/Auth.php
@@ -1,108 +1,108 @@
-
- * @copyright 2015 - 2016 Timothy J. Warren
- * @license http://www.opensource.org/licenses/mit-license.html MIT License
- * @version 4.0
- * @link https://github.com/timw4mail/HummingBirdAnimeClient
- */
-
-namespace Aviat\AnimeClient\API\Kitsu;
-
-use Aviat\AnimeClient\AnimeClient;
-use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
-
-/**
- * Kitsu API Authentication
- */
-class Auth {
-
- use ContainerAware;
-
- /**
- * Anime API Model
- *
- * @var \Aviat\AnimeClient\API\Kitsu\Model
- */
- protected $model;
-
- /**
- * Session object
- *
- * @var Aura\Session\Segment
- */
- protected $segment;
-
- /**
- * Constructor
- *
- * @param ContainerInterface $container
- */
- public function __construct(ContainerInterface $container)
- {
- $this->setContainer($container);
- $this->segment = $container->get('session')
- ->getSegment(AnimeClient::SESSION_SEGMENT);
- $this->model = $container->get('kitsu-model');
- }
-
- /**
- * Make the appropriate authentication call,
- * and save the resulting auth token if successful
- *
- * @param string $password
- * @return boolean
- */
- public function authenticate($password)
- {
- $config = $this->container->get('config');
- $username = $config->get(['kitsu_username']);
- $auth_token = $this->model->authenticate($username, $password);
-
- if (FALSE !== $auth_token)
- {
- $this->segment->set('auth_token', $auth_token);
- return TRUE;
- }
-
- return FALSE;
- }
-
- /**
- * Check whether the current user is authenticated
- *
- * @return boolean
- */
- public function is_authenticated()
- {
- return ($this->get_auth_token() !== FALSE);
- }
-
- /**
- * Clear authentication values
- *
- * @return void
- */
- public function logout()
- {
- $this->segment->clear();
- }
-
- /**
- * Retrieve the authentication token from the session
- *
- * @return string|false
- */
- public function get_auth_token()
- {
- return $this->segment->get('auth_token', FALSE);
- }
-}
+
+ * @copyright 2015 - 2016 Timothy J. Warren
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @version 4.0
+ * @link https://github.com/timw4mail/HummingBirdAnimeClient
+ */
+
+namespace Aviat\AnimeClient\API\Kitsu;
+
+use Aviat\AnimeClient\AnimeClient;
+use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
+
+/**
+ * Kitsu API Authentication
+ */
+class Auth {
+
+ use ContainerAware;
+
+ /**
+ * Anime API Model
+ *
+ * @var \Aviat\AnimeClient\API\Kitsu\Model
+ */
+ protected $model;
+
+ /**
+ * Session object
+ *
+ * @var Aura\Session\Segment
+ */
+ protected $segment;
+
+ /**
+ * Constructor
+ *
+ * @param ContainerInterface $container
+ */
+ public function __construct(ContainerInterface $container)
+ {
+ $this->setContainer($container);
+ $this->segment = $container->get('session')
+ ->getSegment(AnimeClient::SESSION_SEGMENT);
+ $this->model = $container->get('kitsu-model');
+ }
+
+ /**
+ * Make the appropriate authentication call,
+ * and save the resulting auth token if successful
+ *
+ * @param string $password
+ * @return boolean
+ */
+ public function authenticate($password)
+ {
+ $config = $this->container->get('config');
+ $username = $config->get(['kitsu_username']);
+ $auth_token = $this->model->authenticate($username, $password);
+
+ if (FALSE !== $auth_token)
+ {
+ $this->segment->set('auth_token', $auth_token);
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Check whether the current user is authenticated
+ *
+ * @return boolean
+ */
+ public function is_authenticated()
+ {
+ return ($this->get_auth_token() !== FALSE);
+ }
+
+ /**
+ * Clear authentication values
+ *
+ * @return void
+ */
+ public function logout()
+ {
+ $this->segment->clear();
+ }
+
+ /**
+ * Retrieve the authentication token from the session
+ *
+ * @return string|false
+ */
+ public function get_auth_token()
+ {
+ return $this->segment->get('auth_token', FALSE);
+ }
+}
// End of KitsuAuth.php
\ No newline at end of file
diff --git a/src/API/Kitsu/KitsuModel.php b/src/API/Kitsu/KitsuModel.php
index f3a9f85f..2d8124f4 100644
--- a/src/API/Kitsu/KitsuModel.php
+++ b/src/API/Kitsu/KitsuModel.php
@@ -17,6 +17,7 @@
namespace Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\AnimeClient;
+use Aviat\AnimeClient\API\Kitsu as K;
use Aviat\AnimeClient\API\Kitsu\Transformer\{
AnimeTransformer, AnimeListTransformer, MangaTransformer, MangaListTransformer
};
@@ -92,7 +93,7 @@ class KitsuModel {
*/
public function authenticate(string $username, string $password)
{
- $data = $this->postRequest(AnimeClient::KITSU_AUTH_URL, [
+ $data = $this->postRequest(K::AUTH_URL, [
'form_params' => [
'grant_type' => 'password',
'username' => $username,
@@ -164,7 +165,7 @@ class KitsuModel {
'media_type' => 'Anime',
'status' => $status,
],
- 'include' => 'media',
+ 'include' => 'media,media.genres',
'page' => [
'offset' => 0,
'limit' => 200
@@ -174,10 +175,21 @@ class KitsuModel {
];
$data = $this->getRequest('library-entries', $options);
+ $included = K::organizeIncludes($data['included']);
+/*?>= print_r($included, TRUE) ?> &$item)
{
- $item['anime'] = $data['included'][$i];
+ $item['anime'] = $included['anime'][$item['relationships']['media']['data']['id']];
+
+ $animeGenres = $item['anime']['relationships']['genres'];
+
+ foreach($animeGenres as $id)
+ {
+ $item['genres'][] = $included['genres'][$id]['name'];
+ }
+
+ // $item['genres'] = array_pluck($genres, 'name');
}
$transformed = $this->animeListTransformer->transformCollection($data['data']);
@@ -246,7 +258,9 @@ class KitsuModel {
'filter' => [
'slug' => $slug
],
- 'include' => 'genres,mappings,streamingLinks',
+ 'include' => ($type === 'anime')
+ ? 'genres,mappings,streamingLinks'
+ : 'genres,mappings',
]
];
diff --git a/src/API/Kitsu/KitsuTrait.php b/src/API/Kitsu/KitsuTrait.php
index 204e7516..85397f4a 100644
--- a/src/API/Kitsu/KitsuTrait.php
+++ b/src/API/Kitsu/KitsuTrait.php
@@ -18,7 +18,6 @@ namespace Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\AnimeClient;
use Aviat\AnimeClient\API\GuzzleTrait;
-use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Json;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
diff --git a/src/API/Kitsu/Transformer/AnimeListTransformer.php b/src/API/Kitsu/Transformer/AnimeListTransformer.php
index 29f358b3..deb01987 100644
--- a/src/API/Kitsu/Transformer/AnimeListTransformer.php
+++ b/src/API/Kitsu/Transformer/AnimeListTransformer.php
@@ -16,6 +16,7 @@
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
+use Aviat\AnimeClient\API\Kitsu;
use Aviat\Ion\Transformer\AbstractTransformer;
/**
@@ -34,49 +35,34 @@ class AnimeListTransformer extends AbstractTransformer {
{
/* ?>= print_r($item, TRUE) ?> = 5)
- {
- $alternate_title = $anime['titles']['en_jp'];
- }
- }
-
return [
'id' => $item['id'],
'episodes' => [
'watched' => $item['attributes']['progress'],
'total' => $total_episodes,
- 'length' => $anime['attributes']['episodeLength'],
+ 'length' => $anime['episodeLength'],
],
'airing' => [
- 'status' => $anime['status'] ?? '',
- 'started' => $anime['attributes']['startDate'],
- 'ended' => $anime['attributes']['endDate']
+ 'status' => Kitsu::getAiringStatus($anime['startDate'], $anime['endDate']),
+ 'started' => $anime['startDate'],
+ 'ended' => $anime['endDate']
],
'anime' => [
- 'age_rating' => $anime['attributes']['ageRating'],
- 'title' => $anime['attributes']['canonicalTitle'],
- 'alternate_title' => $alternate_title,
- 'slug' => $anime['attributes']['slug'],
- 'url' => $anime['attributes']['url'] ?? '',
- 'type' => $anime['attributes']['showType'],
- 'image' => $anime['attributes']['posterImage']['small'],
+ 'age_rating' => $anime['ageRating'],
+ 'titles' => Kitsu::filterTitles($anime),
+ 'slug' => $anime['slug'],
+ 'url' => $anime['url'] ?? '',
+ 'type' => $this->string($anime['showType'])->upperCaseFirst()->__toString(),
+ 'image' => $anime['posterImage']['small'],
'genres' => $genres,
],
'watching_status' => $item['attributes']['status'],
diff --git a/src/API/Kitsu/Transformer/AnimeTransformer.php b/src/API/Kitsu/Transformer/AnimeTransformer.php
index 4fa22b6c..a4351857 100644
--- a/src/API/Kitsu/Transformer/AnimeTransformer.php
+++ b/src/API/Kitsu/Transformer/AnimeTransformer.php
@@ -16,6 +16,7 @@
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
+use Aviat\AnimeClient\API\Kitsu;
use Aviat\Ion\Transformer\AbstractTransformer;
/**
@@ -32,15 +33,12 @@ class AnimeTransformer extends AbstractTransformer {
*/
public function transform($item)
{
- ?>= print_r($item, TRUE) ?> $item['canonicalTitle'],
- 'en_title' => $item['titles']['en_jp'],
- 'jp_title' => $item['titles']['ja_jp'],
+ 'titles' => Kitsu::filterTitles($item),
+ 'status' => Kitsu::getAiringStatus($item['startDate'], $item['endDate']),
'cover_image' => $item['posterImage']['small'],
'show_type' => $item['showType'],
'episode_count' => $item['episodeCount'],
diff --git a/src/API/Kitsu/Transformer/MangaListTransformer.php b/src/API/Kitsu/Transformer/MangaListTransformer.php
index 251f745c..0e648fe1 100644
--- a/src/API/Kitsu/Transformer/MangaListTransformer.php
+++ b/src/API/Kitsu/Transformer/MangaListTransformer.php
@@ -16,6 +16,7 @@
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
+use Aviat\AnimeClient\API\Kitsu;
use Aviat\Ion\StringWrapper;
use Aviat\Ion\Transformer\AbstractTransformer;
@@ -34,6 +35,7 @@ class MangaListTransformer extends AbstractTransformer {
*/
public function transform($item)
{
+/*?>= print_r($item, TRUE) ?> $total_volumes
],
'manga' => [
- 'title' => $manga['attributes']['canonicalTitle'],
+ 'titles' => Kitsu::filterTitles($manga['attributes']),
'alternate_title' => NULL,
- 'slug' => $manga['id'],
- 'url' => 'https://kitsu.io/manga/' . $manga['id'],
+ 'slug' => $manga['attributes']['slug'],
+ 'url' => 'https://kitsu.io/manga/' . $manga['attributes']['slug'],
'type' => $manga['attributes']['mangaType'],
'image' => $manga['attributes']['posterImage']['small'],
'genres' => [], //$manga['genres'],
diff --git a/src/AnimeClient.php b/src/AnimeClient.php
index 4eac8c43..0ffaca69 100644
--- a/src/AnimeClient.php
+++ b/src/AnimeClient.php
@@ -25,7 +25,6 @@ define('SRC_DIR', realpath(__DIR__));
*/
class AnimeClient {
- const KITSU_AUTH_URL = 'https://kitsu.io/api/oauth/token';
const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth';
const DEFAULT_CONTROLLER_NAMESPACE = 'Aviat\AnimeClient\Controller';
const DEFAULT_CONTROLLER = 'Aviat\AnimeClient\Controller\Anime';
diff --git a/src/Controller/Anime.php b/src/Controller/Anime.php
index 3113402e..238a9c1e 100644
--- a/src/Controller/Anime.php
+++ b/src/Controller/Anime.php
@@ -293,7 +293,7 @@ class Anime extends BaseController {
$data = $this->model->getAnime($anime_id);
$this->outputHTML('anime/details', [
- 'title' => 'Anime · ' . $data['title'],
+ 'title' => 'Anime · ' . $data['titles'][0],
'data' => $data,
]);
}
diff --git a/src/Controller/Manga.php b/src/Controller/Manga.php
index 72e99c9b..d8149885 100644
--- a/src/Controller/Manga.php
+++ b/src/Controller/Manga.php
@@ -161,7 +161,7 @@ class Manga extends Controller {
public function edit($id, $status = "All")
{
$this->set_session_redirect();
- $item = $this->model->get_library_item($id, $status);
+ $item = $this->model->getLibraryItem($id);
$title = $this->config->get('whose_list') . "'s Manga List · Edit";
$this->outputHTML('manga/edit', [
diff --git a/src/Model/API.php b/src/Model/API.php
index 6d523d8d..a0ff29db 100644
--- a/src/Model/API.php
+++ b/src/Model/API.php
@@ -69,7 +69,7 @@ class API extends Model {
foreach ($array as $key => $item)
{
- $sort[$key] = $item[$sort_key]['title'];
+ $sort[$key] = $item[$sort_key]['titles'][0];
}
array_multisort($sort, SORT_ASC, $array);
diff --git a/src/Model/Manga.php b/src/Model/Manga.php
index 736d6ea2..6d62ac3e 100644
--- a/src/Model/Manga.php
+++ b/src/Model/Manga.php
@@ -83,6 +83,18 @@ class Manga extends API
return $this->kitsuModel->getManga($manga_id);
}
+ /**
+ * Get information about a specific list item
+ * for editing/updating that item
+ *
+ * @param string $itemId
+ * @return array
+ */
+ public function getLibraryItem(string $itemId): array
+ {
+ return $this->kitsuModel->getListItem($itemId);
+ }
+
/**
* Map transformed anime data to be organized by reading status
*