Do you wish to register an account?
API client for Kitsu.io, with optional Anime collection, and optional Anilist syncing.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

357 lines
7.9KB

  1. <?php declare(strict_types=1);
  2. /**
  3. * Hummingbird Anime List Client
  4. *
  5. * An API client for Kitsu to manage anime and manga watch lists
  6. *
  7. * PHP version 7.1
  8. *
  9. * @package HummingbirdAnimeClient
  10. * @author Timothy J. Warren <tim@timshomepage.net>
  11. * @copyright 2015 - 2018 Timothy J. Warren
  12. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  13. * @version 4.1
  14. * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
  15. */
  16. namespace Aviat\AnimeClient\Controller;
  17. use Aviat\AnimeClient\Controller;
  18. use Aviat\AnimeClient\API\Kitsu\Transformer\MangaListTransformer;
  19. use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
  20. use Aviat\AnimeClient\Model\Manga as MangaModel;
  21. use Aviat\AnimeClient\Types\FormItem;
  22. use Aviat\Ion\Di\ContainerInterface;
  23. use Aviat\Ion\{Json, StringWrapper};
  24. /**
  25. * Controller for manga list
  26. */
  27. final class Manga extends Controller {
  28. use StringWrapper;
  29. /**
  30. * The manga model
  31. * @var MangaModel $model
  32. */
  33. protected $model;
  34. /**
  35. * Constructor
  36. *
  37. * @param ContainerInterface $container
  38. * @throws \Aviat\Ion\Di\ContainerException
  39. * @throws \Aviat\Ion\Di\NotFoundException
  40. */
  41. public function __construct(ContainerInterface $container)
  42. {
  43. parent::__construct($container);
  44. $this->model = $container->get('manga-model');
  45. $this->baseData = array_merge($this->baseData, [
  46. 'menu_name' => 'manga_list',
  47. 'config' => $this->config,
  48. 'url_type' => 'manga',
  49. 'other_type' => 'anime'
  50. ]);
  51. }
  52. /**
  53. * Get a section of the manga list
  54. *
  55. * @param string $status
  56. * @param string $view
  57. * @throws \Aviat\Ion\Di\ContainerException
  58. * @throws \Aviat\Ion\Di\NotFoundException
  59. * @throws \InvalidArgumentException
  60. * @return void
  61. */
  62. public function index($status = 'all', $view = ''): void
  63. {
  64. $statusTitle = MangaReadingStatus::ROUTE_TO_TITLE[$status];
  65. $title = $this->formatTitle(
  66. $this->config->get('whose_list') . "'s Manga List",
  67. $statusTitle
  68. );
  69. $view_map = [
  70. '' => 'cover',
  71. 'list' => 'list'
  72. ];
  73. $data = ($status !== 'all')
  74. ? [ $statusTitle => $this->model->getList($statusTitle) ]
  75. : $this->model->getList('All');
  76. $this->outputHTML('manga/' . $view_map[$view], [
  77. 'title' => $title,
  78. 'sections' => $data,
  79. ]);
  80. }
  81. /**
  82. * Form to add an manga
  83. *
  84. * @throws \Aviat\Ion\Di\ContainerException
  85. * @throws \Aviat\Ion\Di\NotFoundException
  86. * @throws \Aura\Router\Exception\RouteNotFound
  87. * @throws \InvalidArgumentException
  88. * @return void
  89. */
  90. public function addForm(): void
  91. {
  92. $statuses = MangaReadingStatus::KITSU_TO_TITLE;
  93. $this->setSessionRedirect();
  94. $this->outputHTML('manga/add', [
  95. 'title' => $this->formatTitle(
  96. $this->config->get('whose_list') . "'s Manga List",
  97. 'Add'
  98. ),
  99. 'action_url' => $this->url->generate('manga.add.post'),
  100. 'status_list' => $statuses
  101. ]);
  102. }
  103. /**
  104. * Add an manga to the list
  105. *
  106. * @throws \Aviat\Ion\Di\ContainerException
  107. * @throws \Aviat\Ion\Di\NotFoundException
  108. * @return void
  109. */
  110. public function add(): void
  111. {
  112. $data = $this->request->getParsedBody();
  113. if ( ! array_key_exists('id', $data))
  114. {
  115. $this->redirect('manga/add', 303);
  116. }
  117. if (empty($data['mal_id']))
  118. {
  119. unset($data['mal_id']);
  120. }
  121. $result = $this->model->createLibraryItem($data);
  122. if ($result)
  123. {
  124. $this->setFlashMessage('Added new manga to list', 'success');
  125. $this->cache->clear();
  126. }
  127. else
  128. {
  129. $this->setFlashMessage('Failed to add new manga to list' . $result['body'], 'error');
  130. }
  131. $this->sessionRedirect();
  132. }
  133. /**
  134. * Show the manga edit form
  135. *
  136. * @param string $id
  137. * @param string $status
  138. * @throws \Aviat\Ion\Di\ContainerException
  139. * @throws \Aviat\Ion\Di\NotFoundException
  140. * @throws \Aura\Router\Exception\RouteNotFound
  141. * @throws \InvalidArgumentException
  142. * @return void
  143. */
  144. public function edit($id, $status = 'All'): void
  145. {
  146. $this->setSessionRedirect();
  147. $item = $this->model->getLibraryItem($id);
  148. $title = $this->formatTitle(
  149. $this->config->get('whose_list') . "'s Manga List",
  150. 'Edit'
  151. );
  152. $this->outputHTML('manga/edit', [
  153. 'title' => $title,
  154. 'status_list' => MangaReadingStatus::KITSU_TO_TITLE,
  155. 'item' => $item,
  156. 'action' => $this->url->generate('update.post', [
  157. 'controller' => 'manga'
  158. ]),
  159. ]);
  160. }
  161. /**
  162. * Search for a manga to add to the list
  163. *
  164. * @return void
  165. */
  166. public function search(): void
  167. {
  168. $queryParams = $this->request->getQueryParams();
  169. $query = $queryParams['query'];
  170. $this->outputJSON($this->model->search($query));
  171. }
  172. /**
  173. * Update an manga item via a form submission
  174. *
  175. * @throws \Aviat\Ion\Di\ContainerException
  176. * @throws \Aviat\Ion\Di\NotFoundException
  177. * @return void
  178. */
  179. public function formUpdate(): void
  180. {
  181. $data = $this->request->getParsedBody();
  182. // Do some minor data manipulation for
  183. // large form-based updates
  184. $transformer = new MangaListTransformer();
  185. $post_data = $transformer->untransform($data);
  186. $full_result = $this->model->updateLibraryItem(new FormItem($post_data));
  187. if ($full_result['statusCode'] === 200)
  188. {
  189. $this->setFlashMessage("Successfully updated manga.", 'success');
  190. $this->cache->clear();
  191. }
  192. else
  193. {
  194. $this->setFlashMessage('Failed to update manga.', 'error');
  195. }
  196. $this->sessionRedirect();
  197. }
  198. /**
  199. * Increment the progress of a manga item
  200. */
  201. public function increment(): void
  202. {
  203. if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE)
  204. {
  205. $data = Json::decode((string)$this->request->getBody());
  206. }
  207. else
  208. {
  209. $data = $this->request->getParsedBody();
  210. }
  211. $response = $this->model->incrementLibraryItem(new FormItem($data));
  212. $this->cache->clear();
  213. $this->outputJSON($response['body'], $response['statusCode']);
  214. }
  215. /**
  216. * Remove an manga from the list
  217. *
  218. * @throws \Aviat\Ion\Di\ContainerException
  219. * @throws \Aviat\Ion\Di\NotFoundException
  220. * @return void
  221. */
  222. public function delete(): void
  223. {
  224. $body = $this->request->getParsedBody();
  225. $response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']);
  226. if ($response)
  227. {
  228. $this->setFlashMessage('Successfully deleted manga.', 'success');
  229. $this->cache->clear();
  230. }
  231. else
  232. {
  233. $this->setFlashMessage('Failed to delete manga.', 'error');
  234. }
  235. $this->sessionRedirect();
  236. }
  237. /**
  238. * View details of an manga
  239. *
  240. * @param string $manga_id
  241. * @throws \Aviat\Ion\Di\ContainerException
  242. * @throws \Aviat\Ion\Di\NotFoundException
  243. * @throws \InvalidArgumentException
  244. * @return void
  245. */
  246. public function details($manga_id): void
  247. {
  248. $data = $this->model->getManga($manga_id);
  249. $staff = [];
  250. $characters = [];
  251. if (empty($data))
  252. {
  253. $this->notFound(
  254. $this->config->get('whose_list') .
  255. "'s Manga List &middot; Manga &middot; " .
  256. 'Manga not found',
  257. 'Manga Not Found'
  258. );
  259. return;
  260. }
  261. if (array_key_exists('characters', $data['included']))
  262. {
  263. foreach ($data['included']['characters'] as $id => $character)
  264. {
  265. $characters[$id] = $character['attributes'];
  266. }
  267. }
  268. if (array_key_exists('mediaStaff', $data['included']))
  269. {
  270. foreach ($data['included']['mediaStaff'] as $id => $person)
  271. {
  272. $personDetails = [];
  273. foreach ($person['relationships']['person']['people'] as $p)
  274. {
  275. $personDetails = $p['attributes'];
  276. }
  277. $role = $person['attributes']['role'];
  278. if ( ! array_key_exists($role, $staff))
  279. {
  280. $staff[$role] = [];
  281. }
  282. $staff[$role][$id] = [
  283. 'name' => $personDetails['name'] ?? '??',
  284. 'image' => $personDetails['image'],
  285. ];
  286. }
  287. }
  288. uasort($characters, function ($a, $b) {
  289. return $a['name'] <=> $b['name'];
  290. });
  291. // dump($staff);
  292. $this->outputHTML('manga/details', [
  293. 'title' => $this->formatTitle(
  294. $this->config->get('whose_list') . "'s Manga List",
  295. 'Manga',
  296. $data['title']
  297. ),
  298. 'characters' => $characters,
  299. 'data' => $data,
  300. 'staff' => $staff,
  301. ]);
  302. }
  303. /**
  304. * Find manga matching the selected genre
  305. *
  306. * @param string $genre
  307. */
  308. public function genre(string $genre): void
  309. {
  310. // @TODO: implement
  311. }
  312. }
  313. // End of MangaController.php