Browse Source

Rough start of Manga collection...need to set up proper structure for manga collection items

tags/v4.0.0-rc-2
Timothy Warren 2 years ago
parent
commit
d798a057c7
10 changed files with 584 additions and 16 deletions
  1. +3
    -0
      app/bootstrap.php
  2. +3
    -3
      app/views/collection/add.php
  3. +6
    -0
      app/views/main-menu.php
  4. +54
    -0
      migrations/20170914200308_add_manga_collection_tables.php
  5. +184
    -0
      src/Controller/MangaCollection.php
  6. +17
    -0
      src/Model/AnimeCollection.php
  7. +2
    -9
      src/Model/Collection.php
  8. +304
    -0
      src/Model/MangaCollection.php
  9. +7
    -2
      tests/ControllerTest.php
  10. +4
    -2
      tests/DispatcherTest.php

+ 3
- 0
app/bootstrap.php View File

@@ -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) {

+ 3
- 3
app/views/collection/add.php View File

@@ -1,6 +1,6 @@
<?php if ($auth->isAuthenticated()): ?>
<main>
<h2>Add Anime to your Collection</h2>
<h2>Add <?= ucfirst($collection_type) ?> to your Collection</h2>
<form action="<?= $action_url ?>" method="post">
<section>
<div class="cssload-loader" hidden="hidden">
@@ -8,7 +8,7 @@
<div class="cssload-inner cssload-two"></div>
<div class="cssload-inner cssload-three"></div>
</div>
<label for="search">Search for anime by name:&nbsp;&nbsp;&nbsp;&nbsp;<input type="search" id="search" name="search" /></label>
<label for="search">Search for <?= $collection_type ?> by name:&nbsp;&nbsp;&nbsp;&nbsp;<input type="search" id="search" name="search" /></label>
<section id="series_list" class="media-wrap">
</section>
</section>
@@ -39,5 +39,5 @@
</table>
</form>
</main>
<script defer="defer" src="<?= $urlGenerator->assetUrl('js.php/g/anime_collection') ?>"></script>
<script defer="defer" src="<?= $urlGenerator->assetUrl("js.php/g/{$collection_type}_collection") ?>"></script>
<?php endif ?>

+ 6
- 0
app/views/main-menu.php View File

@@ -32,6 +32,12 @@ $extraSegment = $lastSegment === 'list' ? '/list' : '';
) ?>]
<?php else: ?>
<?= $whose . ucfirst($url_type) . ' Collection' ?>
<?php if($config->get("show_{$other_type}_collection")): ?>
[<?= $helper->a(
$url->generate("{$other_type}.collection.view") . $extraSegment,
ucfirst($other_type) . ' Collection'
) ?>]
<?php endif ?>
[<?= $helper->a($urlGenerator->defaultUrl('anime') . $extraSegment, 'Anime List') ?>]
[<?= $helper->a($urlGenerator->defaultUrl('manga') . $extraSegment, 'Manga List') ?>]
<?php endif ?>

+ 54
- 0
migrations/20170914200308_add_manga_collection_tables.php View File

@@ -0,0 +1,54 @@
<?php

use Phinx\Migration\AbstractMigration;

class AddMangaCollectionTables extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
// Create manga_set table
$manga_set = $this->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();
}
}

+ 184
- 0
src/Controller/MangaCollection.php View File

@@ -0,0 +1,184 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @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

+ 17
- 0
src/Model/AnimeCollection.php View File

@@ -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
*

+ 2
- 9
src/Model/Collection.php View File

@@ -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

+ 304
- 0
src/Model/MangaCollection.php View File

@@ -0,0 +1,304 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @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('<br />', $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

+ 7
- 2
tests/ControllerTest.php View File

@@ -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)
);
}


+ 4
- 2
tests/DispatcherTest.php View File

@@ -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',
]

Loading…
Cancel
Save