Version 5.1 - All the GraphQL #32
@ -31,14 +31,14 @@ A self-hosted client that allows custom formatting of data from the hummingbird
|
||||
|
||||
### Requirements
|
||||
|
||||
* PHP 5.4+
|
||||
* PHP 5.5+
|
||||
* PDO SQLite (For collection tab)
|
||||
* GD
|
||||
|
||||
### Installation
|
||||
|
||||
1. Install dependencies via composer: `composer install`
|
||||
2. Configure settings in `app/config/config.php` and `app/config/routing.php` to your liking
|
||||
2. Configure settings in `app/config/config.php` to your liking
|
||||
3. Create the following directories if they don't exist, and make sure they are world writable
|
||||
* app/cache
|
||||
* public/images/manga
|
||||
|
@ -7,6 +7,7 @@ namespace Aviat\AnimeClient;
|
||||
|
||||
use \Whoops\Handler\PrettyPageHandler;
|
||||
use \Whoops\Handler\JsonResponseHandler;
|
||||
use Aura\Html\HelperLocatorFactory;
|
||||
use \Aura\Web\WebFactory;
|
||||
use \Aura\Router\RouterFactory;
|
||||
use \Aura\Session\SessionFactory;
|
||||
@ -51,6 +52,15 @@ $di = function() {
|
||||
$aura_router = (new RouterFactory())->newInstance();
|
||||
$container->set('aura-router', $aura_router);
|
||||
|
||||
// Create Html helper Object
|
||||
$html_helper = (new HelperLocatorFactory)->newInstance();
|
||||
$html_helper->set('menu', function() use ($container) {
|
||||
$menu_helper = new Helper\Menu();
|
||||
$menu_helper->setContainer($container);
|
||||
return $menu_helper;
|
||||
});
|
||||
$container->set('html-helper', $html_helper);
|
||||
|
||||
// Create Request/Response Objects
|
||||
$web_factory = new WebFactory([
|
||||
'_GET' => $_GET,
|
||||
|
@ -13,6 +13,7 @@ $base_config = [
|
||||
'img_cache_path' => _dir(ROOT_DIR, 'public/images'),
|
||||
|
||||
// Included config files
|
||||
'routes' => require __DIR__ . '/routes.php',
|
||||
'database' => require __DIR__ . '/database.php',
|
||||
'menus' => require __DIR__ . '/menus.php',
|
||||
'routes' => require __DIR__ . '/routes.php',
|
||||
];
|
@ -14,9 +14,12 @@ $config = [
|
||||
// General config
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// do you wish to show the anime collection tab?
|
||||
// do you wish to show the anime collection?
|
||||
'show_anime_collection' => TRUE,
|
||||
|
||||
// do you wish to show the manga collection?
|
||||
'show_manga_collection' => TRUE,
|
||||
|
||||
// path to public directory on the server
|
||||
'asset_dir' => realpath(__DIR__ . '/../../public'),
|
||||
|
||||
|
61
app/config/menus.php
Normal file
61
app/config/menus.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'top' => [
|
||||
'default' => '',
|
||||
'items' => [
|
||||
'anime_list' => '{anime_list}',
|
||||
'manga_list' => '{manga_list}',
|
||||
'collection' => '{collection}'
|
||||
]
|
||||
],
|
||||
'view_type' => [
|
||||
'is_parent' => FALSE,
|
||||
'default' => 'cover_view',
|
||||
'items' => [
|
||||
'cover_view' => '{parent}',
|
||||
'list_view' => '{parent}/list'
|
||||
]
|
||||
],
|
||||
'anime_list' => [
|
||||
'default' => '',
|
||||
'route_prefix' => '/anime',
|
||||
'items' => [
|
||||
'watching' => '/watching',
|
||||
'plan_to_watch' => '/plan_to_watch',
|
||||
'on_hold' => '/on_hold',
|
||||
'dropped' => '/dropped',
|
||||
'completed' => '/completed',
|
||||
'all' => '/all'
|
||||
],
|
||||
'children' => [
|
||||
'view_type'
|
||||
]
|
||||
],
|
||||
'manga_list' => [
|
||||
'default' => '',
|
||||
'route_prefix' => '/manga',
|
||||
'items' => [
|
||||
'reading' => '/reading',
|
||||
'plan_to_read' => '/plan_to_read',
|
||||
'on_hold' => '/on_hold',
|
||||
'dropped' => '/dropped',
|
||||
'completed' => '/completed',
|
||||
'all' => '/all'
|
||||
],
|
||||
'children' => [
|
||||
'view_type'
|
||||
]
|
||||
],
|
||||
'collection' => [
|
||||
'default' => '',
|
||||
'route_prefix' => '/collection',
|
||||
'items' => [
|
||||
'anime' => '/anime',
|
||||
'manga' => '/manga',
|
||||
],
|
||||
'children' => [
|
||||
'view_type'
|
||||
]
|
||||
]
|
||||
];
|
@ -9,99 +9,56 @@ return [
|
||||
// Routes on all controllers
|
||||
'common' => [
|
||||
'update' => [
|
||||
'path' => '/update',
|
||||
'action' => ['update'],
|
||||
'path' => '/{controller}/update',
|
||||
'action' => 'update',
|
||||
'verb' => 'post'
|
||||
],
|
||||
'login_form' => [
|
||||
'path' => '/login',
|
||||
'action' => ['login'],
|
||||
'path' => '/{controller}/login',
|
||||
'action' => 'login',
|
||||
'verb' => 'get'
|
||||
],
|
||||
'login_action' => [
|
||||
'path' => '/login',
|
||||
'action' => ['login_action'],
|
||||
'path' => '/{controller}/login',
|
||||
'action' => 'login_action',
|
||||
'verb' => 'post'
|
||||
],
|
||||
'logout' => [
|
||||
'path' => '/logout',
|
||||
'action' => ['logout']
|
||||
'path' => '/{controller}/logout',
|
||||
'action' => 'logout'
|
||||
],
|
||||
],
|
||||
// Routes on collection controller
|
||||
'collection' => [
|
||||
'collection_add_form' => [
|
||||
'path' => '/collection/add',
|
||||
'action' => ['form'],
|
||||
'action' => 'form',
|
||||
'params' => [],
|
||||
],
|
||||
'collection_edit_form' => [
|
||||
'path' => '/collection/edit/{id}',
|
||||
'action' => ['form'],
|
||||
'action' => 'form',
|
||||
'tokens' => [
|
||||
'id' => '[0-9]+'
|
||||
]
|
||||
],
|
||||
'collection_add' => [
|
||||
'path' => '/collection/add',
|
||||
'action' => ['add'],
|
||||
'action' => 'add',
|
||||
'verb' => 'post'
|
||||
],
|
||||
'collection_edit' => [
|
||||
'path' => '/collection/edit',
|
||||
'action' => ['edit'],
|
||||
'action' => 'edit',
|
||||
'verb' => 'post'
|
||||
],
|
||||
'collection' => [
|
||||
'path' => '/collection/view{/view}',
|
||||
'action' => ['index'],
|
||||
'action' => 'index',
|
||||
'params' => [],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
],
|
||||
// Routes on anime controller
|
||||
'anime' => [
|
||||
'index' => [
|
||||
'path' => '/',
|
||||
'action' => ['redirect'],
|
||||
'params' => [
|
||||
'url' => '', // Determined by config
|
||||
'code' => '301',
|
||||
'type' => 'anime'
|
||||
]
|
||||
],
|
||||
'search' => [
|
||||
'path' => '/anime/search',
|
||||
'action' => ['search'],
|
||||
],
|
||||
'anime_list' => [
|
||||
'path' => '/anime/{type}{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'tokens' => [
|
||||
'type' => '[a-z_]+',
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
],
|
||||
'manga' => [
|
||||
'index' => [
|
||||
'path' => '/',
|
||||
'action' => ['redirect'],
|
||||
'params' => [
|
||||
'url' => '', // Determined by config
|
||||
'code' => '301',
|
||||
'type' => 'manga'
|
||||
]
|
||||
],
|
||||
'manga_list' => [
|
||||
'path' => '/manga/{type}{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'tokens' => [
|
||||
'type' => '[a-z_]+',
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
@ -5,7 +5,7 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
// Subfolder prefix for url
|
||||
// Subfolder prefix for url, if in a subdirectory of the web root
|
||||
'subfolder_prefix' => '',
|
||||
|
||||
// Path to public directory, where images/css/javascript are located,
|
||||
@ -16,9 +16,9 @@ return [
|
||||
'default_list' => 'anime', // anime or manga
|
||||
|
||||
// Default pages for anime/manga
|
||||
'default_anime_path' => "/anime/watching",
|
||||
'default_manga_path' => '/manga/all',
|
||||
'default_anime_list_path' => "watching", // watching|plan_to_watch|on_hold|dropped|completed|all
|
||||
'default_manga_list_path' => "all", // reading|plan_to_read|on_hold|dropped|completed|all
|
||||
|
||||
// Default to list view?
|
||||
'default_to_list_view' => FALSE,
|
||||
// Default view type (cover_view/list_view)
|
||||
'default_view_type' => 'cover_view',
|
||||
];
|
@ -11,6 +11,6 @@
|
||||
<template name="clean" />
|
||||
</transformations>
|
||||
<files>
|
||||
<directory>src/Aviat/AnimeClient</directory>
|
||||
<directory>src/Aviat</directory>
|
||||
</files>
|
||||
</phpdoc>
|
@ -4,8 +4,6 @@
|
||||
*/
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aura\Web\ResponseSender;
|
||||
|
||||
use \Aviat\Ion\Di\ContainerInterface;
|
||||
use \Aviat\Ion\View\HttpView;
|
||||
use \Aviat\Ion\View\HtmlView;
|
||||
@ -97,6 +95,7 @@ class Controller {
|
||||
public function load_partial($view, $template, $data=[])
|
||||
{
|
||||
$errorHandler = $this->container->get('error-handler');
|
||||
$errorHandler->addDataTable('Template Data', $data);
|
||||
$router = $this->container->get('router');
|
||||
|
||||
if (isset($this->base_data))
|
||||
@ -107,7 +106,7 @@ class Controller {
|
||||
$route = $router->get_route();
|
||||
$data['route_path'] = ($route) ? $router->get_route()->path : "";
|
||||
|
||||
$errorHandler->addDataTable('Template Data', $data);
|
||||
|
||||
$template_path = _dir($this->config->__get('view_path'), "{$template}.php");
|
||||
|
||||
if ( ! is_file($template_path))
|
||||
|
@ -7,6 +7,7 @@ namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\AnimeClient\Enum\Hummingbird\AnimeWatchingStatus;
|
||||
use Aviat\AnimeClient\Model\Anime as AnimeModel;
|
||||
use Aviat\AnimeClient\Model\AnimeCollection as AnimeCollectionModel;
|
||||
|
||||
@ -72,6 +73,11 @@ class Anime extends BaseController {
|
||||
]);
|
||||
}
|
||||
|
||||
public function index($type="watching", $view='')
|
||||
{
|
||||
return $this->anime_list($type, $view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for anime
|
||||
*
|
||||
@ -87,11 +93,10 @@ class Anime extends BaseController {
|
||||
* Show a portion, or all of the anime list
|
||||
*
|
||||
* @param string $type - The section of the list
|
||||
* @param string $title - The title of the page
|
||||
* @param string $view - List or cover view
|
||||
* @return void
|
||||
*/
|
||||
public function anime_list($type, $view)
|
||||
protected function anime_list($type, $view)
|
||||
{
|
||||
$type_title_map = [
|
||||
'all' => 'All',
|
||||
@ -103,12 +108,12 @@ class Anime extends BaseController {
|
||||
];
|
||||
|
||||
$model_map = [
|
||||
'watching' => 'currently-watching',
|
||||
'plan_to_watch' => 'plan-to-watch',
|
||||
'on_hold' => 'on-hold',
|
||||
'watching' => AnimeWatchingStatus::WATCHING,
|
||||
'plan_to_watch' => AnimeWatchingStatus::PLAN_TO_WATCH,
|
||||
'on_hold' => AnimeWatchingStatus::ON_HOLD,
|
||||
'all' => 'all',
|
||||
'dropped' => 'dropped',
|
||||
'completed' => 'completed'
|
||||
'dropped' => AnimeWatchingStatus::DROPPED,
|
||||
'completed' => AnimeWatchingStatus::COMPLETED
|
||||
];
|
||||
|
||||
$title = $this->config->whose_list . "'s Anime List · {$type_title_map[$type]}";
|
||||
|
@ -58,6 +58,11 @@ class Manga extends Controller {
|
||||
]);
|
||||
}
|
||||
|
||||
public function index($status="all", $view="")
|
||||
{
|
||||
return $this->manga_list($status, $view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an anime item
|
||||
*
|
||||
@ -75,15 +80,15 @@ class Manga extends Controller {
|
||||
* @param string $view
|
||||
* @return void
|
||||
*/
|
||||
public function manga_list($status, $view)
|
||||
protected function manga_list($status, $view)
|
||||
{
|
||||
$map = [
|
||||
'all' => 'All',
|
||||
'plan_to_read' => 'Plan to Read',
|
||||
'reading' => 'Reading',
|
||||
'completed' => 'Completed',
|
||||
'dropped' => 'Dropped',
|
||||
'on_hold' => 'On Hold'
|
||||
'plan_to_read' => MangaModel::PLAN_TO_READ,
|
||||
'reading' => MangaModel::READING,
|
||||
'completed' => MangaModel::COMPLETED,
|
||||
'dropped' => MangaModel::DROPPED,
|
||||
'on_hold' => MangaModel::ON_HOLD
|
||||
];
|
||||
|
||||
$title = $this->config->whose_list . "'s Manga List · {$map[$status]}";
|
||||
@ -97,6 +102,8 @@ class Manga extends Controller {
|
||||
? [$map[$status] => $this->model->get_list($map[$status])]
|
||||
: $this->model->get_all_lists();
|
||||
|
||||
//throw new \ErrorException("Data :" . print_r($data, TRUE));
|
||||
|
||||
$this->outputHTML('manga/' . $view_map[$view], [
|
||||
'title' => $title,
|
||||
'sections' => $data,
|
||||
|
20
src/Aviat/AnimeClient/Helper/Menu.php
Normal file
20
src/Aviat/AnimeClient/Helper/Menu.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\AnimeClient\Helper;
|
||||
|
||||
use Aura\Html\Helper\AbstractHelper;
|
||||
|
||||
use Aviat\AnimeClient\MenuGenerator;
|
||||
|
||||
class Menu extends AbstractHelper {
|
||||
|
||||
use \Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
public function __invoke($menu_name)
|
||||
{
|
||||
$generator = new MenuGenerator($this->container);
|
||||
return $generator->generate($menu_name);
|
||||
}
|
||||
|
||||
}
|
||||
// End of Menu.php
|
19
src/Aviat/AnimeClient/Helper/UrlHelper.php
Normal file
19
src/Aviat/AnimeClient/Helper/UrlHelper.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\AnimeClient\Helper;
|
||||
|
||||
use Aura\Html\Helper\AbstractHelper;
|
||||
use Aviat\AnimeClient\UrlGenerator;
|
||||
|
||||
class UrlHelper extends AbstractHelper {
|
||||
|
||||
/**
|
||||
* Helper entry point
|
||||
*
|
||||
* @return UrlHelper
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
106
src/Aviat/AnimeClient/MenuGenerator.php
Normal file
106
src/Aviat/AnimeClient/MenuGenerator.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Helper object to manage menu creation and selection
|
||||
*/
|
||||
class MenuGenerator extends RoutingBase {
|
||||
|
||||
use \Aviat\Ion\Di\ContainerAware;
|
||||
use \Aviat\Ion\StringWrapper;
|
||||
use \Aviat\Ion\ArrayWrapper;
|
||||
|
||||
/**
|
||||
* Html generation helper
|
||||
*
|
||||
* @var Aura\Html\HelperLocator
|
||||
*/
|
||||
protected $helper;
|
||||
|
||||
/**
|
||||
* Menu config array
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $menus;
|
||||
|
||||
/**
|
||||
* Create menu generator
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->menus = $this->config->menus;
|
||||
$this->helper = $container->get('html-helper');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the full menu structure from the config files
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_config()
|
||||
{
|
||||
// Note: Children menus have urls based on the
|
||||
// current url path
|
||||
/*
|
||||
$parsed = [
|
||||
'menu_name' => [
|
||||
'items' => [
|
||||
'title' => 'full_url_path',
|
||||
],
|
||||
'children' => [
|
||||
'title' => 'full_url_path'
|
||||
]
|
||||
]
|
||||
]
|
||||
*/
|
||||
|
||||
$parsed = [];
|
||||
|
||||
foreach($this->menus as $name => $menu)
|
||||
{
|
||||
$parsed[$name] = [];
|
||||
foreach($menu['items'] as $path_name => $partial_path)
|
||||
{
|
||||
$title = $this->string($path_name)->humanize()->titleize();
|
||||
$parsed[$name]['items'][$title] = $this->string($menu['route_prefix'])->append($partial_path);
|
||||
}
|
||||
|
||||
// @TODO: Handle child menu(s)
|
||||
if (count($menu['children']) > 0)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the html structure of the menu selected
|
||||
*
|
||||
* @param string $menu
|
||||
* @return string
|
||||
*/
|
||||
public function generate($menu)
|
||||
{
|
||||
$parsed_config = $this->parse_config();
|
||||
$menu_config = $parsed_config[$menu];
|
||||
|
||||
// Array of list items to add to the main menu
|
||||
$main_menu = [];
|
||||
|
||||
|
||||
// Start the menu list
|
||||
$helper->ul();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
// End of MenuGenerator.php
|
@ -4,14 +4,16 @@
|
||||
*/
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use abeautifulsite\SimpleImage;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Common base for all Models
|
||||
*/
|
||||
class Model {
|
||||
|
||||
use \Aviat\Ion\StringWrapper;
|
||||
|
||||
/**
|
||||
* The global configuration object
|
||||
* @var Config
|
||||
@ -65,11 +67,11 @@ class Model {
|
||||
// Cache the file if it doesn't already exist
|
||||
if ( ! file_exists($cached_path))
|
||||
{
|
||||
if (ini_get('allow_url_fopen'))
|
||||
/*if (ini_get('allow_url_fopen'))
|
||||
{
|
||||
copy($api_path, $cached_path);
|
||||
}
|
||||
elseif (function_exists('curl_init'))
|
||||
else*/if (function_exists('curl_init'))
|
||||
{
|
||||
$ch = curl_init($api_path);
|
||||
$fp = fopen($cached_path, 'wb');
|
||||
@ -79,7 +81,7 @@ class Model {
|
||||
]);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
fclose($ch);
|
||||
fclose($fp);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -4,8 +4,10 @@
|
||||
*/
|
||||
namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use \GuzzleHttp\Client;
|
||||
use \GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Model as BaseModel;
|
||||
|
||||
@ -42,7 +44,8 @@ class API extends BaseModel {
|
||||
parent::__construct($container);
|
||||
$this->cookieJar = new CookieJar();
|
||||
$this->client = new Client([
|
||||
'base_url' => $this->base_url,
|
||||
'base_uri' => $this->base_url,
|
||||
'cookies' => TRUE,
|
||||
'defaults' => [
|
||||
'cookies' => $this->cookieJar,
|
||||
'headers' => [
|
||||
|
@ -27,6 +27,18 @@ class Anime extends API {
|
||||
*/
|
||||
protected $base_url = "https://hummingbird.me/api/v1/";
|
||||
|
||||
/**
|
||||
* Map of API status constants to display constants
|
||||
* @var array
|
||||
*/
|
||||
protected $const_map = [
|
||||
AnimeWatchingStatus::WATCHING => self::WATCHING,
|
||||
AnimeWatchingStatus::PLAN_TO_WATCH => self::PLAN_TO_WATCH,
|
||||
AnimeWatchingStatus::ON_HOLD => self::ON_HOLD,
|
||||
AnimeWatchingStatus::DROPPED => self::DROPPED,
|
||||
AnimeWatchingStatus::COMPLETED => self::COMPLETED,
|
||||
];
|
||||
|
||||
/**
|
||||
* Update the selected anime
|
||||
*
|
||||
@ -59,32 +71,11 @@ class Anime extends API {
|
||||
self::COMPLETED => [],
|
||||
];
|
||||
|
||||
$data = $this->_get_list();
|
||||
$data = $this->_get_list_from_api();
|
||||
|
||||
foreach($data as $datum)
|
||||
{
|
||||
switch($datum['status'])
|
||||
{
|
||||
case AnimeWatchingStatus::COMPLETED:
|
||||
$output[self::COMPLETED][] = $datum;
|
||||
break;
|
||||
|
||||
case AnimeWatchingStatus::PLAN_TO_WATCH:
|
||||
$output[self::PLAN_TO_WATCH][] = $datum;
|
||||
break;
|
||||
|
||||
case AnimeWatchingStatus::DROPPED:
|
||||
$output[self::DROPPED][] = $datum;
|
||||
break;
|
||||
|
||||
case AnimeWatchingStatus::ON_HOLD:
|
||||
$output[self::ON_HOLD][] = $datum;
|
||||
break;
|
||||
|
||||
case AnimeWatchingStatus::WATCHING:
|
||||
$output[self::WATCHING][] = $datum;
|
||||
break;
|
||||
}
|
||||
$output[$this->const_map[$datum['watching_status']]][] = $datum;
|
||||
}
|
||||
|
||||
// Sort anime by name
|
||||
@ -104,19 +95,11 @@ class Anime extends API {
|
||||
*/
|
||||
public function get_list($status)
|
||||
{
|
||||
$map = [
|
||||
AnimeWatchingStatus::WATCHING => self::WATCHING,
|
||||
AnimeWatchingStatus::PLAN_TO_WATCH => self::PLAN_TO_WATCH,
|
||||
AnimeWatchingStatus::ON_HOLD => self::ON_HOLD,
|
||||
AnimeWatchingStatus::DROPPED => self::DROPPED,
|
||||
AnimeWatchingStatus::COMPLETED => self::COMPLETED,
|
||||
];
|
||||
|
||||
$data = $this->_get_list_From_api($status);
|
||||
$this->sort_by_name($data);
|
||||
|
||||
$output = [];
|
||||
$output[$map[$status]] = $data;
|
||||
$output[$this->const_map[$status]] = $data;
|
||||
|
||||
return $output;
|
||||
}
|
||||
@ -170,10 +153,11 @@ class Anime extends API {
|
||||
/**
|
||||
* Retrieve data from the api
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $status
|
||||
* @return array
|
||||
*/
|
||||
private function _get_list_from_api($status="all")
|
||||
protected function _get_list_from_api($status="all")
|
||||
{
|
||||
$config = [
|
||||
'allow_redirects' => FALSE
|
||||
@ -198,29 +182,30 @@ class Anime extends API {
|
||||
/**
|
||||
* Handle caching of transformed api data
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $status
|
||||
* @param \GuzzleHttp\Message\Response
|
||||
* @return array
|
||||
*/
|
||||
private function _check_cache($status, $response)
|
||||
protected function _check_cache($status, $response)
|
||||
{
|
||||
$cache_file = "{$this->config->data_cache_path}/anime-{$status}.json";
|
||||
$transformed_cache_file = "{$this->config->data_cache_path}/anime-{$status}-transformed.json";
|
||||
$cache_file = _dir($this->config->data_cache_path, "anime-{$status}.json");
|
||||
$transformed_cache_file = _dir($this->config->data_cache_path, "anime-{$status}-transformed.json");
|
||||
|
||||
$cached = json_decode(file_get_contents($cache_file), TRUE);
|
||||
$api = $response->json();
|
||||
$api_data = json_decode($response->getBody(), TRUE);
|
||||
|
||||
if ($api !== $cached)
|
||||
if ($api_data === $cached && file_exists($transformed_cache_file))
|
||||
{
|
||||
file_put_contents($cache_file, json_encode($api));
|
||||
$transformer = new AnimeListTransformer();
|
||||
$transformed = $transformer->transform_collection($api);
|
||||
file_put_contents($transformed_cache_file, json_encode($transformed));
|
||||
return $transformed;
|
||||
return json_decode(file_get_contents($transformed_cache_file),TRUE);
|
||||
}
|
||||
else
|
||||
{
|
||||
return json_decode(file_get_contents($transformed_cache_file),TRUE);
|
||||
file_put_contents($cache_file, json_encode($api_data));
|
||||
$transformer = new AnimeListTransformer();
|
||||
$transformed = $transformer->transform_collection($api_data);
|
||||
file_put_contents($transformed_cache_file, json_encode($transformed));
|
||||
return $transformed;
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,7 +215,7 @@ class Anime extends API {
|
||||
* @param array $array
|
||||
* @return void
|
||||
*/
|
||||
private function sort_by_name(&$array)
|
||||
protected function sort_by_name(&$array)
|
||||
{
|
||||
$sort = array();
|
||||
|
||||
|
@ -6,12 +6,31 @@ namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use Aviat\AnimeClient\Model\API;
|
||||
use Aviat\AnimeClient\Transformer\Hummingbird;
|
||||
use Aviat\AnimeClient\Enum\Hummingbird\MangaReadingStatus;
|
||||
|
||||
/**
|
||||
* Model for handling requests dealing with the manga list
|
||||
*/
|
||||
class Manga extends API {
|
||||
|
||||
const READING = 'Reading';
|
||||
const PLAN_TO_READ = 'Plan to Read';
|
||||
const DROPPED = 'Dropped';
|
||||
const ON_HOLD = 'On Hold';
|
||||
const COMPLETED = 'Completed';
|
||||
|
||||
/**
|
||||
* Map API constants to display constants
|
||||
* @var array
|
||||
*/
|
||||
protected $const_map = [
|
||||
MangaReadingStatus::READING => self::READING,
|
||||
MangaReadingStatus::PLAN_TO_READ => self::PLAN_TO_READ,
|
||||
MangaReadingStatus::ON_HOLD => self::ON_HOLD,
|
||||
MangaReadingStatus::DROPPED => self::DROPPED,
|
||||
MangaReadingStatus::COMPLETED => self::COMPLETED
|
||||
];
|
||||
|
||||
/**
|
||||
* The base url for api requests
|
||||
* @var string
|
||||
@ -44,9 +63,9 @@ class Manga extends API {
|
||||
*/
|
||||
public function get_all_lists()
|
||||
{
|
||||
$data = $this->_get_list();
|
||||
$data = $this->_get_list_from_api();
|
||||
|
||||
foreach ($data as $key => &$val)
|
||||
foreach($data as $key => &$val)
|
||||
{
|
||||
$this->sort_by_name($val);
|
||||
}
|
||||
@ -62,24 +81,14 @@ class Manga extends API {
|
||||
*/
|
||||
public function get_list($status)
|
||||
{
|
||||
$data = $this->_get_list($status);
|
||||
|
||||
$data = $this->_get_list_from_api($status);
|
||||
$this->sort_by_name($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Massage the list of manga entries into something more usable
|
||||
*
|
||||
* @param string $status
|
||||
* @return array
|
||||
*/
|
||||
private function _get_list($status="all")
|
||||
private function _get_list_from_api($status="All")
|
||||
{
|
||||
$errorHandler = $this->container->get('error-handler');
|
||||
|
||||
$cache_file = _dir($this->config->data_cache_path, 'manga.json');
|
||||
|
||||
$config = [
|
||||
'query' => [
|
||||
@ -89,81 +98,70 @@ class Manga extends API {
|
||||
];
|
||||
|
||||
$response = $this->client->get('manga_library_entries', $config);
|
||||
$data = $this->_check_cache($status, $response);
|
||||
$output = $this->map_by_status($data);
|
||||
|
||||
$errorHandler->addDataTable('response', (array)$response);
|
||||
|
||||
if ($response->getStatusCode() != 200)
|
||||
{
|
||||
if ( ! file_exists($cache_file))
|
||||
{
|
||||
throw new DomainException($response->getEffectiveUrl());
|
||||
}
|
||||
else
|
||||
{
|
||||
$raw_data = json_decode(file_get_contents($cache_file), TRUE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reorganize data to be more usable
|
||||
$raw_data = $response->json();
|
||||
|
||||
// Attempt to create the cache dir if it doesn't exist
|
||||
if ( ! is_dir($this->config->data_cache_path))
|
||||
{
|
||||
mkdir($this->config->data_cache_path);
|
||||
}
|
||||
|
||||
// Cache data in case of downtime
|
||||
file_put_contents($cache_file, json_encode($raw_data));
|
||||
return (array_key_exists($status, $output)) ? $output[$status] : $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the status of the cache and return the appropriate response
|
||||
*
|
||||
* @param string $status
|
||||
* @param \GuzzleHttp\Message\Response $response
|
||||
* @return array
|
||||
*/
|
||||
private function _check_cache($status, $response)
|
||||
{
|
||||
// Bail out early if there isn't any manga data
|
||||
if ( ! array_key_exists('manga', $raw_data)) return [];
|
||||
$api_data = json_decode($response->getBody(), TRUE);
|
||||
if ( ! array_key_exists('manga', $api_data)) return [];
|
||||
|
||||
$data = [
|
||||
'Reading' => [],
|
||||
'Plan to Read' => [],
|
||||
'On Hold' => [],
|
||||
'Dropped' => [],
|
||||
'Completed' => [],
|
||||
$cache_file = _dir($this->config->data_cache_path, 'manga.json');
|
||||
$transformed_cache_file = _dir($this->config->data_cache_path, 'manga-transformed.json');
|
||||
|
||||
$cached_data = json_decode(file_get_contents($cache_file), TRUE);
|
||||
|
||||
if ($cached_data === $api_data && file_exists($transformed_cache_file))
|
||||
{
|
||||
return json_decode(file_get_contents($transformed_cache_file), TRUE);
|
||||
}
|
||||
else
|
||||
{
|
||||
file_put_contents($cache_file, json_encode($api_data));
|
||||
|
||||
$zippered_data = $this->zipper_lists($api_data);
|
||||
$transformer = new Hummingbird\MangaListTransformer();
|
||||
$transformed_data = $transformer->transform_collection($zippered_data);
|
||||
file_put_contents($transformed_cache_file, json_encode($transformed_data));
|
||||
return $transformed_data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map transformed anime data to be organized by reading status
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
private function map_by_status($data)
|
||||
{
|
||||
$output = [
|
||||
self::READING => [],
|
||||
self::PLAN_TO_READ => [],
|
||||
self::ON_HOLD => [],
|
||||
self::DROPPED => [],
|
||||
self::COMPLETED => [],
|
||||
];
|
||||
|
||||
// Massage the two lists into one
|
||||
$manga_data = $this->zipper_lists($raw_data);
|
||||
|
||||
// Filter data by status
|
||||
foreach($manga_data as &$entry)
|
||||
foreach($data as &$entry)
|
||||
{
|
||||
// Cache poster images
|
||||
$entry['manga']['poster_image'] = $this->get_cached_image($entry['manga']['poster_image'], $entry['manga']['id'], 'manga');
|
||||
|
||||
switch($entry['status'])
|
||||
{
|
||||
case "Plan to Read":
|
||||
$data['Plan to Read'][] = $entry;
|
||||
break;
|
||||
|
||||
case "Dropped":
|
||||
$data['Dropped'][] = $entry;
|
||||
break;
|
||||
|
||||
case "On Hold":
|
||||
$data['On Hold'][] = $entry;
|
||||
break;
|
||||
|
||||
case "Currently Reading":
|
||||
$data['Reading'][] = $entry;
|
||||
break;
|
||||
|
||||
case "Completed":
|
||||
default:
|
||||
$data['Completed'][] = $entry;
|
||||
break;
|
||||
}
|
||||
$entry['manga']['image'] = $this->get_cached_image($entry['manga']['image'], $entry['manga']['slug'], 'manga');
|
||||
$key = $this->const_map[$entry['reading_status']];
|
||||
$output[$key][] = $entry;
|
||||
}
|
||||
|
||||
return (array_key_exists($status, $data)) ? $data[$status] : $data;
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -189,7 +187,7 @@ class Manga extends API {
|
||||
|
||||
foreach($array as $key => $item)
|
||||
{
|
||||
$sort[$key] = $item['manga']['romaji_title'];
|
||||
$sort[$key] = $item['manga']['title'];
|
||||
}
|
||||
|
||||
array_multisort($sort, SORT_ASC, $array);
|
||||
|
21
src/Aviat/Ion/ArrayWrapper.php
Normal file
21
src/Aviat/Ion/ArrayWrapper.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion;
|
||||
|
||||
use Aviat\Ion\Type\ArrayType;
|
||||
|
||||
trait ArrayWrapper {
|
||||
|
||||
/**
|
||||
* Convenience method for wrapping an array
|
||||
* with the array type class
|
||||
*
|
||||
* @param array $arr
|
||||
* @return ArrayType
|
||||
*/
|
||||
public function arr(array $arr)
|
||||
{
|
||||
return new ArrayType($arr);
|
||||
}
|
||||
}
|
||||
// End of ArrayWrapper.php
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Aviat\Ion;
|
||||
|
||||
use Stringy\Stringy as S;
|
||||
use Aviat\Ion\Type\StringType;
|
||||
|
||||
trait StringWrapper {
|
||||
|
||||
@ -10,11 +10,11 @@ trait StringWrapper {
|
||||
* Wrap the String in the Stringy class
|
||||
*
|
||||
* @param string $str
|
||||
* @return Stringy\Stringy
|
||||
* @return StringType
|
||||
*/
|
||||
public function string($str)
|
||||
{
|
||||
return S::create($str);
|
||||
return StringType::create($str);
|
||||
}
|
||||
}
|
||||
// End of StringWrapper.php
|
142
src/Aviat/Ion/Type/ArrayType.php
Normal file
142
src/Aviat/Ion/Type/ArrayType.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\Type;
|
||||
|
||||
/**
|
||||
* Wrapper class for native array methods for convenience
|
||||
*/
|
||||
class ArrayType {
|
||||
|
||||
/**
|
||||
* The current array
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $arr;
|
||||
|
||||
/**
|
||||
* Map generated methods to their native implementations
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $native_methods = [
|
||||
'chunk' => 'array_chunk',
|
||||
'pluck' => 'array_column',
|
||||
'assoc_diff' => 'array_diff_assoc',
|
||||
'key_diff' => 'array_diff_key',
|
||||
'diff' => 'array_diff',
|
||||
'filter' => 'array_filter',
|
||||
'flip' => 'array_flip',
|
||||
'intersect' => 'array_intersect',
|
||||
'has_key' => 'array_key_exists',
|
||||
'keys' => 'array_keys',
|
||||
'merge' => 'array_merge',
|
||||
'pad' => 'array_pad',
|
||||
'pop' => 'array_pop',
|
||||
'product' => 'array_product',
|
||||
'push' => 'array_push',
|
||||
'random' => 'array_rand',
|
||||
'reduce' => 'array_reduce',
|
||||
'reverse' => 'array_reverse',
|
||||
'shift' => 'array_shift',
|
||||
'sum' => 'array_sum',
|
||||
'unique' => 'array_unique',
|
||||
'unshift' => 'array_unshift',
|
||||
'values' => 'array_values',
|
||||
];
|
||||
|
||||
/**
|
||||
* Native methods that modify the passed in array
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $native_in_place_methods = [
|
||||
'shuffle' => 'shuffle',
|
||||
];
|
||||
|
||||
/**
|
||||
* Create an ArrayType wrapper class
|
||||
*
|
||||
* @param array $arr
|
||||
*/
|
||||
public function __construct(Array $arr)
|
||||
{
|
||||
$this->arr =& $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call one of the dynamically created methods
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
// Simple mapping for the majority of methods
|
||||
if (array_key_exists($method, $this->native_methods))
|
||||
{
|
||||
$func = $this->native_methods[$method];
|
||||
// Set the current array as the first argument of the method
|
||||
array_unshift($args, $this->arr);
|
||||
return call_user_func_array($func, $args);
|
||||
}
|
||||
|
||||
// Mapping for in-place methods
|
||||
if (array_key_exists($method, $this->native_in_place_methods))
|
||||
{
|
||||
$func = $this->native_in_place_methods[$method];
|
||||
$func($this->arr);
|
||||
return $this->arr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill an array with the specified value
|
||||
*
|
||||
* @param int $start_index
|
||||
* @param int $num
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
public function fill($start_index, $num, $value)
|
||||
{
|
||||
return array_fill($start_index, $num, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a callback on each item of the array
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return array
|
||||
*/
|
||||
public function map(callable $callback)
|
||||
{
|
||||
return array_map($callback, $this->arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an array key by its associated value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param bool $strict
|
||||
* @return string
|
||||
*/
|
||||
public function search($value, $strict=FALSE)
|
||||
{
|
||||
return array_search($value, $this->arr, $strict);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the array has the passed value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param bool $strict
|
||||
* @return bool
|
||||
*/
|
||||
public function has($value, $strict=FALSE)
|
||||
{
|
||||
return in_array($value, $this->arr, $strict);
|
||||
}
|
||||
}
|
||||
// End of ArrayType.php
|
10
src/Aviat/Ion/Type/StringType.php
Normal file
10
src/Aviat/Ion/Type/StringType.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\Type;
|
||||
|
||||
use Stringy\Stringy;
|
||||
|
||||
class StringType extends Stringy {
|
||||
|
||||
}
|
||||
// End of StringType.php
|
@ -2,19 +2,27 @@
|
||||
|
||||
namespace Aviat\Ion\View;
|
||||
|
||||
use Aura\Html\HelperLocatorFactory;
|
||||
|
||||
use Aviat\Ion\View\HttpView;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
class HtmlView extends HttpView {
|
||||
|
||||
/**
|
||||
* HTML generator/escaper helper
|
||||
*
|
||||
* @var Aura\Html\HelperLocator
|
||||
*/
|
||||
protected $helper;
|
||||
|
||||
/**
|
||||
* Create the Html View
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->helper = (new HelperLocatorFactory)->newInstance();
|
||||
$this->helper = $container->get('html-helper');
|
||||
}
|
||||
|
||||
/**
|
||||
|
54
tests/Ion/Type/ArrayTypeTest.php
Normal file
54
tests/Ion/Type/ArrayTypeTest.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
class ArrayTypeTest extends AnimeClient_TestCase {
|
||||
use Aviat\Ion\ArrayWrapper;
|
||||
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function testMerge()
|
||||
{
|
||||
$obj = $this->arr([1, 3, 5, 7]);
|
||||
$even_array = [2, 4, 6, 8];
|
||||
$expected = [1, 3, 5, 7, 2, 4, 6, 8];
|
||||
|
||||
$actual = $obj->merge($even_array);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testShuffle()
|
||||
{
|
||||
$original = [1, 2, 3, 4];
|
||||
$test = [1, 2, 3, 4];
|
||||
$obj = $this->arr($test);
|
||||
$actual = $obj->shuffle();
|
||||
|
||||
$this->assertNotEquals($actual, $original);
|
||||
$this->assertTrue(is_array($actual));
|
||||
}
|
||||
|
||||
public function testHas()
|
||||
{
|
||||
$obj = $this->arr([1, 2, 6, 8, 11]);
|
||||
$this->assertTrue($obj->has(8));
|
||||
$this->assertFalse($obj->has(8745));
|
||||
}
|
||||
|
||||
public function testSearch()
|
||||
{
|
||||
$obj = $this->arr([1, 2, 5, 7, 47]);
|
||||
$actual = $obj->search(47);
|
||||
$this->assertEquals(4, $actual);
|
||||
}
|
||||
|
||||
public function testFill()
|
||||
{
|
||||
$obj = $this->arr([]);
|
||||
$expected = ['?', '?', '?'];
|
||||
$actual = $obj->fill(0, 3, '?');
|
||||
$this->assertEquals($actual, $expected);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user