diff --git a/app/bootstrap.php b/app/bootstrap.php
index a1398ab7..507f3a3b 100644
--- a/app/bootstrap.php
+++ b/app/bootstrap.php
@@ -153,6 +153,9 @@ return function(array $configArray = []) {
$container->set('anime-collection-model', function($container) {
return new Model\AnimeCollection($container);
});
+ $container->set('manga-collection-model', function($container) {
+ return new Model\MangaCollection($container);
+ });
// Miscellaneous Classes
$container->set('auth', function($container) {
diff --git a/app/views/collection/add.php b/app/views/collection/add.php
index 9970cf26..91e46d33 100644
--- a/app/views/collection/add.php
+++ b/app/views/collection/add.php
@@ -1,6 +1,6 @@
isAuthenticated()): ?>
- Add Anime to your Collection
+ Add = ucfirst($collection_type) ?> to your Collection
-
+
\ No newline at end of file
diff --git a/app/views/main-menu.php b/app/views/main-menu.php
index 8bdc747a..7c424b57 100644
--- a/app/views/main-menu.php
+++ b/app/views/main-menu.php
@@ -32,6 +32,12 @@ $extraSegment = $lastSegment === 'list' ? '/list' : '';
) ?>]
= $whose . ucfirst($url_type) . ' Collection' ?>
+ get("show_{$other_type}_collection")): ?>
+ [= $helper->a(
+ $url->generate("{$other_type}.collection.view") . $extraSegment,
+ ucfirst($other_type) . ' Collection'
+ ) ?>]
+
[= $helper->a($urlGenerator->defaultUrl('anime') . $extraSegment, 'Anime List') ?>]
[= $helper->a($urlGenerator->defaultUrl('manga') . $extraSegment, 'Manga List') ?>]
diff --git a/migrations/20170914200308_add_manga_collection_tables.php b/migrations/20170914200308_add_manga_collection_tables.php
new file mode 100644
index 00000000..45c3863c
--- /dev/null
+++ b/migrations/20170914200308_add_manga_collection_tables.php
@@ -0,0 +1,54 @@
+table('manga_set', ['id' => FALSE, 'primary_key' => ['hummingbird_id']]);
+ $manga_set->addColumn('hummingbird_id', 'biginteger')
+ ->addColumn('slug', 'string', ['comment' => "URL slug used for image caching and generating links"])
+ ->addColumn('title', 'string')
+ ->addColumn('alternate_title', 'string', ['null' => TRUE])
+ ->addColumn('media_id', 'integer', ['default' => 3, 'null' => TRUE])
+ ->addColumn('show_type', 'string', ['default' => 'TV', 'null' => TRUE, 'comment' => "TV Series/OVA/etc"])
+ ->addColumn('age_rating', 'string', ['default' => 'PG13', 'null' => TRUE])
+ ->addColumn('cover_image', 'string', ['null' => TRUE])
+ ->addColumn('episode_count', 'integer', ['null' => TRUE])
+ ->addColumn('episode_length', 'integer', ['null' => TRUE])
+ ->addColumn('notes', 'text', ['null' => TRUE])
+ ->addForeignKey('media_id', 'media', 'id')
+ ->create();
+
+ // Create genre_manga_set_link table
+ $genre_manga_set_link = $this->table('genre_manga_set_link', ['id' => FALSE, 'primary_key' => ['hummingbird_id', 'genre_id']]);
+ $genre_manga_set_link->addColumn('hummingbird_id', 'biginteger')
+ ->addColumn('genre_id', 'integer')
+ ->addForeignKey('hummingbird_id', 'manga_set', 'hummingbird_id')
+ ->addForeignKey('genre_id', 'genres', 'id')
+ ->create();
+ }
+}
diff --git a/src/Controller/MangaCollection.php b/src/Controller/MangaCollection.php
new file mode 100644
index 00000000..b5bfcc94
--- /dev/null
+++ b/src/Controller/MangaCollection.php
@@ -0,0 +1,184 @@
+
+ * @copyright 2015 - 2017 Timothy J. Warren
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @version 4.0
+ * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
+ */
+
+namespace Aviat\AnimeClient\Controller;
+
+use Aviat\AnimeClient\Controller as BaseController;
+use Aviat\AnimeClient\Model\{
+ manga as mangaModel,
+ mangaCollection as mangaCollectionModel
+};
+use Aviat\AnimeClient\UrlGenerator;
+use Aviat\Ion\Di\ContainerInterface;
+
+/**
+ * Controller for manga collection pages
+ */
+class MangaCollection extends BaseController {
+
+ /**
+ * The manga collection model
+ * @var mangaCollectionModel $mangaCollectionModel
+ */
+ private $mangaCollectionModel;
+
+ /**
+ * The manga API model
+ * @var mangaModel $mangaModel
+ */
+ private $mangaModel;
+
+ /**
+ * Constructor
+ *
+ * @param ContainerInterface $container
+ */
+ public function __construct(ContainerInterface $container)
+ {
+ parent::__construct($container);
+
+ $this->mangaModel = $container->get('manga-model');
+ $this->mangaCollectionModel = $container->get('manga-collection-model');
+ $this->baseData = array_merge($this->baseData, [
+ 'collection_type' => 'manga',
+ 'menu_name' => 'manga-collection',
+ 'url_type' => 'manga',
+ 'other_type' => 'anime',
+ 'config' => $this->config,
+ ]);
+ }
+
+ /**
+ * Search for manga
+ *
+ * @return void
+ */
+ public function search()
+ {
+ $queryParams = $this->request->getQueryParams();
+ $query = $queryParams['query'];
+ $this->outputJSON($this->mangaModel->search($query));
+ }
+
+ /**
+ * Show the manga collection page
+ *
+ * @param string $view
+ * @return void
+ */
+ public function index($view)
+ {
+ $viewMap = [
+ '' => 'cover',
+ 'list' => 'list'
+ ];
+
+ $data = $this->mangaCollectionModel->getCollection();
+
+ $this->outputHTML('collection/' . $viewMap[$view], [
+ 'title' => $this->config->get('whose_list') . "'s Manga Collection",
+ 'sections' => $data,
+ 'genres' => $this->mangaCollectionModel->getGenreList()
+ ]);
+ }
+
+ /**
+ * Show the manga collection add/edit form
+ *
+ * @param integer|null $id
+ * @return void
+ */
+ public function form($id = NULL)
+ {
+ $this->setSessionRedirect();
+
+ $action = (is_null($id)) ? "Add" : "Edit";
+ $urlAction = strtolower($action);
+
+ $this->outputHTML('collection/' . $urlAction, [
+ 'action' => $action,
+ 'action_url' => $this->url->generate("manga.collection.{$urlAction}.post"),
+ 'title' => $this->formatTitle(
+ $this->config->get('whose_list') . "'s manga Collection",
+ $action
+ ),
+ 'media_items' => $this->mangaCollectionModel->getMediaTypeList(),
+ 'item' => ($action === "Edit") ? $this->mangaCollectionModel->get($id) : []
+ ]);
+ }
+
+ /**
+ * Update a collection item
+ *
+ * @return void
+ */
+ public function edit()
+ {
+ $data = $this->request->getParsedBody();
+ if (array_key_exists('hummingbird_id', $data))
+ {
+ $this->mangaCollectionModel->update($data);
+ $this->setFlashMessage('Successfully updated collection item.', 'success');
+ }
+ else
+ {
+ $this->setFlashMessage('Failed to update collection item', 'error');
+ }
+
+ $this->sessionRedirect();
+ }
+
+ /**
+ * Add a collection item
+ *
+ * @return void
+ */
+ public function add()
+ {
+ $data = $this->request->getParsedBody();
+ if (array_key_exists('id', $data))
+ {
+ $this->mangaCollectionModel->add($data);
+ $this->setFlashMessage('Successfully added collection item', 'success');
+ }
+ else
+ {
+ $this->setFlashMessage('Failed to add collection item.', 'error');
+ }
+
+ $this->sessionRedirect();
+ }
+
+ /**
+ * Remove a collection item
+ *
+ * @return void
+ */
+ public function delete()
+ {
+ $data = $this->request->getParsedBody();
+ if ( ! array_key_exists('hummingbird_id', $data))
+ {
+ $this->redirect("/manga-collection/view", 303);
+ }
+
+ $this->mangaCollectionModel->delete($data);
+ $this->setFlashMessage("Successfully removed manga from collection.", 'success');
+
+ $this->redirect("/manga-collection/view", 303);
+ }
+}
+// End of CollectionController.php
\ No newline at end of file
diff --git a/src/Model/AnimeCollection.php b/src/Model/AnimeCollection.php
index 5d440f80..cf2ce5fb 100644
--- a/src/Model/AnimeCollection.php
+++ b/src/Model/AnimeCollection.php
@@ -26,6 +26,23 @@ use PDO;
*/
class AnimeCollection extends Collection {
+ /**
+ * Anime API Model
+ * @var object $animeModel
+ */
+ protected $animeModel;
+
+ /**
+ * Create the collection model
+ *
+ * @param ContainerInterface $container
+ */
+ public function __construct(ContainerInterface $container)
+ {
+ parent::__construct($container);
+ $this->animeModel = $container->get('anime-model');
+ }
+
/**
* Get collection from the database, and organize by media type
*
diff --git a/src/Model/Collection.php b/src/Model/Collection.php
index 1a787b4c..50003973 100644
--- a/src/Model/Collection.php
+++ b/src/Model/Collection.php
@@ -25,14 +25,8 @@ use PDOException;
* Base model for anime and manga collections
*/
class Collection extends DB {
-
- use ContainerAware;
- /**
- * Anime API Model
- * @var object $animeModel
- */
- protected $animeModel;
+ use ContainerAware;
/**
* Whether the database is valid for querying
@@ -46,7 +40,7 @@ class Collection extends DB {
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
- {
+ {
parent::__construct($container);
try
@@ -58,7 +52,6 @@ class Collection extends DB {
//$this->validDatabase = FALSE;
//return FALSE;
}
- $this->animeModel = $container->get('anime-model');
// Is database valid? If not, set a flag so the
// app can be run without a valid database
diff --git a/src/Model/MangaCollection.php b/src/Model/MangaCollection.php
new file mode 100644
index 00000000..37ae42e6
--- /dev/null
+++ b/src/Model/MangaCollection.php
@@ -0,0 +1,304 @@
+
+ * @copyright 2015 - 2017 Timothy J. Warren
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @version 4.0
+ * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
+ */
+
+namespace Aviat\AnimeClient\Model;
+
+use Aviat\AnimeClient\API\Kitsu;
+use Aviat\Ion\Di\ContainerInterface;
+use Aviat\Ion\Json;
+use PDO;
+
+/**
+ * Model for getting anime collection data
+ */
+class MangaCollection extends Collection {
+
+ /**
+ * Manga API Model
+ * @var object $mangaModel
+ */
+ protected $mangaModel;
+
+ /**
+ * Create the collection model
+ *
+ * @param ContainerInterface $container
+ */
+ public function __construct(ContainerInterface $container)
+ {
+ parent::__construct($container);
+ $this->mangaModel = $container->get('manga-model');
+ }
+
+ /**
+ * Get collection from the database, and organize by media type
+ *
+ * @return array
+ */
+ public function getCollection()
+ {
+ $rawCollection = $this->getCollectionFromDatabase();
+
+ $collection = [];
+
+ foreach ($rawCollection as $row)
+ {
+ if (array_key_exists($row['media'], $collection))
+ {
+ $collection[$row['media']][] = $row;
+ }
+ else
+ {
+ $collection[$row['media']] = [$row];
+ }
+ }
+
+ return $collection;
+ }
+
+ /**
+ * Get list of media types
+ *
+ * @return array
+ */
+ public function getMediaTypeList()
+ {
+ $output = [];
+
+ $query = $this->db->select('id, type')
+ ->from('media')
+ ->get();
+
+ foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row)
+ {
+ $output[$row['id']] = $row['type'];
+ }
+
+ return $output;
+ }
+
+ /**
+ * Get item from collection for editing
+ *
+ * @param int $id
+ * @return array
+ */
+ public function getCollectionEntry($id)
+ {
+ $query = $this->db->from('anime_set')
+ ->where('hummingbird_id', (int)$id)
+ ->get();
+
+ return $query->fetch(PDO::FETCH_ASSOC);
+ }
+
+ /**
+ * Get full collection from the database
+ *
+ * @return array
+ */
+ private function getCollectionFromDatabase()
+ {
+ if ( ! $this->validDatabase)
+ {
+ return [];
+ }
+
+ $query = $this->db->select('hummingbird_id, slug, title, alternate_title, show_type,
+ age_rating, episode_count, episode_length, cover_image, notes, media.type as media')
+ ->from('manga_set a')
+ ->join('media', 'media.id=a.media_id', 'inner')
+ ->order_by('media')
+ ->order_by('title')
+ ->get();
+
+ return $query->fetchAll(PDO::FETCH_ASSOC);
+ }
+
+ /**
+ * Add an item to the anime collection
+ *
+ * @param array $data
+ * @return void
+ */
+ public function add($data)
+ {
+ $anime = (object)$this->mangaModel->getMangaById($data['id']);
+ $this->db->set([
+ 'hummingbird_id' => $data['id'],
+ 'slug' => $anime->slug,
+ 'title' => array_shift($anime->titles),
+ 'alternate_title' => implode('
', $anime->titles),
+ 'show_type' => $anime->show_type,
+ 'age_rating' => $anime->age_rating,
+ 'cover_image' => $anime->cover_image,
+ 'episode_count' => $anime->episode_count,
+ 'episode_length' => $anime->episode_length,
+ 'media_id' => $data['media_id'],
+ 'notes' => $data['notes']
+ ])->insert('manga_set');
+
+ $this->updateGenre($data['id']);
+ }
+
+ /**
+ * Update a collection item
+ *
+ * @param array $data
+ * @return void
+ */
+ public function update($data)
+ {
+ // If there's no id to update, don't update
+ if ( ! array_key_exists('hummingbird_id', $data))
+ {
+ return;
+ }
+
+ $id = $data['hummingbird_id'];
+ unset($data['hummingbird_id']);
+
+ $this->db->set($data)
+ ->where('hummingbird_id', $id)
+ ->update('manga_set');
+ }
+
+ /**
+ * Remove a collection item
+ *
+ * @param array $data
+ * @return void
+ */
+ public function delete($data)
+ {
+ // If there's no id to update, don't delete
+ if ( ! array_key_exists('hummingbird_id', $data))
+ {
+ return;
+ }
+
+ $this->db->where('hummingbird_id', $data['hummingbird_id'])
+ ->delete('genre_manga_set_link');
+
+ $this->db->where('hummingbird_id', $data['hummingbird_id'])
+ ->delete('manga_set');
+ }
+
+ /**
+ * Get the details of a collection item
+ *
+ * @param int $kitsuId
+ * @return array
+ */
+ public function get($kitsuId)
+ {
+ $query = $this->db->from('manga_set')
+ ->where('hummingbird_id', $kitsuId)
+ ->get();
+
+ return $query->fetch(PDO::FETCH_ASSOC);
+ }
+
+ /**
+ * Update genre information for selected manga
+ *
+ * @param int $mangaId The current manga
+ * @return void
+ */
+ private function updateGenre($mangaId)
+ {
+ $genreInfo = $this->getGenreData();
+ extract($genreInfo);
+
+ // Get api information
+ $manga = $this->mangaModel->getMangaById($mangaId);
+
+ foreach ($anime['genres'] as $genre)
+ {
+ // Add genres that don't currently exist
+ if ( ! in_array($genre, $genres))
+ {
+ $this->db->set('genre', $genre)
+ ->insert('genres');
+
+ $genres[] = $genre;
+ }
+
+ // Update link table
+ // Get id of genre to put in link table
+ $flippedGenres = array_flip($genres);
+
+ $insertArray = [
+ 'hummingbird_id' => $mangaId,
+ 'genre_id' => $flippedGenres[$genre]
+ ];
+
+ if (array_key_exists($mangaId, $links))
+ {
+ if ( ! in_array($flippedGenres[$genre], $links[$mangaId]))
+ {
+ $this->db->set($insertArray)->insert('genre_manga_set_link');
+ }
+ }
+ else
+ {
+ $this->db->set($insertArray)->insert('genre_manga_set_link');
+ }
+ }
+ }
+
+ /**
+ * Get list of existing genres
+ *
+ * @return array
+ */
+ private function getGenreData()
+ {
+ $genres = [];
+ $links = [];
+
+ // Get existing genres
+ $query = $this->db->select('id, genre')
+ ->from('genres')
+ ->get();
+ foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $genre)
+ {
+ $genres[$genre['id']] = $genre['genre'];
+ }
+
+ // Get existing link table entries
+ $query = $this->db->select('hummingbird_id, genre_id')
+ ->from('genre_manga_set_link')
+ ->get();
+ foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $link)
+ {
+ if (array_key_exists($link['hummingbird_id'], $links))
+ {
+ $links[$link['hummingbird_id']][] = $link['genre_id'];
+ }
+ else
+ {
+ $links[$link['hummingbird_id']] = [$link['genre_id']];
+ }
+ }
+
+ return [
+ 'genres' => $genres,
+ 'links' => $links
+ ];
+ }
+}
+// End of MangaCollectionModel.php
\ No newline at end of file
diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php
index 2215b1e2..8333cc62 100644
--- a/tests/ControllerTest.php
+++ b/tests/ControllerTest.php
@@ -22,7 +22,8 @@ use Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\Controller\{
Anime as AnimeController,
Character as CharacterController,
- Collection as CollectionController,
+ AnimeCollection as AnimeCollectionController,
+ MangaCollection as MangaCollectionController,
Manga as MangaController
};
@@ -71,7 +72,11 @@ class ControllerTest extends AnimeClientTestCase {
);
$this->assertInstanceOf(
'Aviat\AnimeClient\Controller',
- new CollectionController($this->container)
+ new AnimeCollectionController($this->container)
+ );
+ $this->assertInstanceOf(
+ 'Aviat\AnimeClient\Controller',
+ new MangaCollectionController($this->container)
);
}
diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php
index ce1e3f1a..c64ec0f6 100644
--- a/tests/DispatcherTest.php
+++ b/tests/DispatcherTest.php
@@ -227,7 +227,8 @@ class DispatcherTest extends AnimeClientTestCase {
'expected' => [
'anime' => 'Aviat\AnimeClient\Controller\Anime',
'manga' => 'Aviat\AnimeClient\Controller\Manga',
- 'collection' => 'Aviat\AnimeClient\Controller\Collection',
+ 'anime-collection' => 'Aviat\AnimeClient\Controller\AnimeCollection',
+ 'manga-collection' => 'Aviat\AnimeClient\Controller\MangaCollection',
'character' => 'Aviat\AnimeClient\Controller\Character',
'index' => 'Aviat\AnimeClient\Controller\Index',
]
@@ -248,7 +249,8 @@ class DispatcherTest extends AnimeClientTestCase {
'expected' => [
'anime' => 'Aviat\AnimeClient\Controller\Anime',
'manga' => 'Aviat\AnimeClient\Controller\Manga',
- 'collection' => 'Aviat\AnimeClient\Controller\Collection',
+ 'anime-collection' => 'Aviat\AnimeClient\Controller\AnimeCollection',
+ 'manga-collection' => 'Aviat\AnimeClient\Controller\MangaCollection',
'character' => 'Aviat\AnimeClient\Controller\Character',
'index' => 'Aviat\AnimeClient\Controller\Index',
]