Version 5.1 - All the GraphQL #32
@ -1,81 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Base API Model
|
||||
*/
|
||||
namespace AnimeClient\Base;
|
||||
|
||||
use \GuzzleHttp\Client;
|
||||
use \GuzzleHttp\Cookie\CookieJar;
|
||||
|
||||
/**
|
||||
* Base model for api interaction
|
||||
*/
|
||||
class ApiModel extends Model {
|
||||
|
||||
/**
|
||||
* Base url for making api requests
|
||||
* @var string
|
||||
*/
|
||||
protected $base_url = '';
|
||||
|
||||
/**
|
||||
* The Guzzle http client object
|
||||
* @var object
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* Cookie jar object for api requests
|
||||
* @var object
|
||||
*/
|
||||
protected $cookieJar;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
$this->cookieJar = new CookieJar();
|
||||
$this->client = new Client([
|
||||
'base_url' => $this->base_url,
|
||||
'defaults' => [
|
||||
'cookies' => $this->cookieJar,
|
||||
'headers' => [
|
||||
'User-Agent' => $_SERVER['HTTP_USER_AGENT'],
|
||||
'Accept-Encoding' => 'application/json'
|
||||
],
|
||||
'timeout' => 5,
|
||||
'connect_timeout' => 5
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt login via the api
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticate($username, $password)
|
||||
{
|
||||
$result = $this->client->post('https://hummingbird.me/api/v1/users/authenticate', [
|
||||
'body' => [
|
||||
'username' => $username,
|
||||
'password' => $password
|
||||
]
|
||||
]);
|
||||
|
||||
if ($result->getStatusCode() === 201)
|
||||
{
|
||||
$_SESSION['hummingbird_anime_token'] = $result->json();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
// End of BaseApiModel.php
|
@ -1,131 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Base Configuration class
|
||||
*/
|
||||
|
||||
namespace AnimeClient\Base;
|
||||
|
||||
/**
|
||||
* Wrapper for configuration values
|
||||
*/
|
||||
class Config {
|
||||
|
||||
/**
|
||||
* Config object
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $config_files
|
||||
*/
|
||||
public function __construct(Array $config_files=[])
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if (empty($config_files))
|
||||
{
|
||||
require_once _dir(CONF_DIR, 'config.php'); // $config
|
||||
require_once _dir(CONF_DIR, 'base_config.php'); // $base_config
|
||||
}
|
||||
else // @codeCoverageIgnoreEnd
|
||||
{
|
||||
$config = $config_files['config'];
|
||||
$base_config = $config_files['base_config'];
|
||||
}
|
||||
|
||||
$this->config = array_merge($config, $base_config);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for config values
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
if (isset($this->config[$key]))
|
||||
{
|
||||
return $this->config[$key];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base url for css/js/images
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function asset_url(/*...*/)
|
||||
{
|
||||
$args = func_get_args();
|
||||
$base_url = rtrim($this->__get('asset_path'), '/');
|
||||
|
||||
array_unshift($args, $base_url);
|
||||
|
||||
return implode("/", $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base url from the config
|
||||
*
|
||||
* @param string $type - (optional) The controller
|
||||
* @return string
|
||||
*/
|
||||
public function base_url($type="anime")
|
||||
{
|
||||
$config_path = trim($this->__get("{$type}_path"), "/");
|
||||
$config_host = $this->__get("{$type}_host");
|
||||
|
||||
// Set the appropriate HTTP host
|
||||
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
|
||||
$path = ($config_path !== '') ? $config_path : "";
|
||||
|
||||
return implode("/", ['/', $host, $path]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate full url path from the route path based on config
|
||||
*
|
||||
* @param string $path - (optional) The route path
|
||||
* @param string $type - (optional) The controller (anime or manga), defaults to anime
|
||||
* @return string
|
||||
*/
|
||||
public function full_url($path="", $type="anime")
|
||||
{
|
||||
$config_path = trim($this->__get("{$type}_path"), "/");
|
||||
$config_host = $this->__get("{$type}_host");
|
||||
$config_default_route = $this->__get("default_{$type}_path");
|
||||
|
||||
// Remove beginning/trailing slashes
|
||||
$config_path = trim($config_path, '/');
|
||||
$path = trim($path, '/');
|
||||
|
||||
// Remove any optional parameters from the route
|
||||
$path = preg_replace('`{/.*?}`i', '', $path);
|
||||
|
||||
// Set the appropriate HTTP host
|
||||
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
|
||||
|
||||
// Set the default view
|
||||
if ($path === '')
|
||||
{
|
||||
$path .= trim($config_default_route, '/');
|
||||
if ($this->__get('default_to_list_view')) $path .= '/list';
|
||||
}
|
||||
|
||||
// Set an leading folder
|
||||
if ($config_path !== '')
|
||||
{
|
||||
$path = "{$config_path}/{$path}";
|
||||
}
|
||||
|
||||
return "//{$host}/{$path}";
|
||||
}
|
||||
}
|
||||
// End of config.php
|
@ -1,285 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Base Controller
|
||||
*/
|
||||
namespace AnimeClient\Base;
|
||||
|
||||
use Aura\Web\WebFactory;
|
||||
|
||||
/**
|
||||
* Base class for controllers, defines output methods
|
||||
*/
|
||||
class Controller {
|
||||
|
||||
/**
|
||||
* The global configuration object
|
||||
* @var object $config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Request object
|
||||
* @var object $request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Response object
|
||||
* @var object $response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* The api model for the current controller
|
||||
* @var object
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Common data to be sent to views
|
||||
* @var array
|
||||
*/
|
||||
protected $base_data = [
|
||||
'url_type' => 'anime',
|
||||
'other_type' => 'manga',
|
||||
'nav_routes' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \AnimeClient\Client $config
|
||||
* @param array $web
|
||||
*/
|
||||
public function __construct(Config &$config, Array $web)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->base_data['config'] = $config;
|
||||
|
||||
list($request, $response) = $web;
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a class member
|
||||
*
|
||||
* @param string $key
|
||||
* @return object
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
$allowed = ['request', 'response', 'config'];
|
||||
|
||||
if (in_array($key, $allowed))
|
||||
{
|
||||
return $this->$key;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string output of a partial template
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $template
|
||||
* @param array|object $data
|
||||
* @return string
|
||||
*/
|
||||
public function load_partial($template, $data=[])
|
||||
{
|
||||
if (isset($this->base_data))
|
||||
{
|
||||
$data = array_merge($this->base_data, $data);
|
||||
}
|
||||
|
||||
global $router, $defaultHandler;
|
||||
$route = $router->get_route();
|
||||
$data['route_path'] = ($route) ? $router->get_route()->path : "";
|
||||
|
||||
$defaultHandler->addDataTable('Template Data', $data);
|
||||
|
||||
$template_path = _dir(APP_DIR, 'views', "{$template}.php");
|
||||
|
||||
if ( ! is_file($template_path))
|
||||
{
|
||||
throw new InvalidArgumentException("Invalid template : {$path}");
|
||||
}
|
||||
|
||||
ob_start();
|
||||
extract($data);
|
||||
include _dir(APP_DIR, 'views', 'header.php');
|
||||
include $template_path;
|
||||
include _dir(APP_DIR, 'views', 'footer.php');
|
||||
$buffer = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a template to HTML, using the provided data
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $template
|
||||
* @param array|object $data
|
||||
* @return void
|
||||
*/
|
||||
public function outputHTML($template, $data=[])
|
||||
{
|
||||
$buffer = $this->load_partial($template, $data);
|
||||
|
||||
$this->response->content->setType('text/html');
|
||||
$this->response->content->set($buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output json with the proper content type
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return void
|
||||
*/
|
||||
public function outputJSON($data)
|
||||
{
|
||||
if ( ! is_string($data))
|
||||
{
|
||||
$data = json_encode($data);
|
||||
}
|
||||
|
||||
$this->response->content->setType('application/json');
|
||||
$this->response->content->set($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the selected page
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $url
|
||||
* @param int $code
|
||||
* @return void
|
||||
*/
|
||||
public function redirect($url, $code, $type="anime")
|
||||
{
|
||||
$url = $this->config->full_url($url, $type);
|
||||
|
||||
$this->response->redirect->to($url, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message box to the page
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $type
|
||||
* @param string $message
|
||||
* @return string
|
||||
*/
|
||||
public function show_message($type, $message)
|
||||
{
|
||||
return $this->load_partial('message', [
|
||||
'stat_class' => $type,
|
||||
'message' => $message
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the api session
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return void
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
session_destroy();
|
||||
$this->response->redirect->seeOther($this->config->full_url(''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the login form
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $status
|
||||
* @return void
|
||||
*/
|
||||
public function login($status="")
|
||||
{
|
||||
$message = "";
|
||||
|
||||
if ($status != "")
|
||||
{
|
||||
$message = $this->show_message('error', $status);
|
||||
}
|
||||
|
||||
$this->outputHTML('login', [
|
||||
'title' => 'Api login',
|
||||
'message' => $message
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to log in with the api
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function login_action()
|
||||
{
|
||||
if (
|
||||
$this->model->authenticate(
|
||||
$this->config->hummingbird_username,
|
||||
$this->request->post->get('password')
|
||||
)
|
||||
)
|
||||
{
|
||||
$this->response->redirect->afterPost($this->config->full_url('', $this->base_data['url_type']));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->login("Invalid username or password.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the appropriate response
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return void
|
||||
*/
|
||||
private function output()
|
||||
{
|
||||
// send status
|
||||
@header($this->response->status->get(), true, $this->response->status->getCode());
|
||||
|
||||
// headers
|
||||
foreach($this->response->headers->get() as $label => $value)
|
||||
{
|
||||
@header("{$label}: {$value}");
|
||||
}
|
||||
|
||||
// cookies
|
||||
foreach($this->response->cookies->get() as $name => $cookie)
|
||||
{
|
||||
@setcookie(
|
||||
$name,
|
||||
$cookie['value'],
|
||||
$cookie['expire'],
|
||||
$cookie['path'],
|
||||
$cookie['domain'],
|
||||
$cookie['secure'],
|
||||
$cookie['httponly']
|
||||
);
|
||||
}
|
||||
|
||||
// send the actual response
|
||||
echo $this->response->content->get();
|
||||
}
|
||||
}
|
||||
// End of BaseController.php
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Base DB model
|
||||
*/
|
||||
namespace AnimeClient\Base;
|
||||
|
||||
/**
|
||||
* Base model for database interaction
|
||||
*/
|
||||
class DBModel extends Model {
|
||||
/**
|
||||
* The query builder object
|
||||
* @var object $db
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* The database connection information array
|
||||
* @var array $db_config
|
||||
*/
|
||||
protected $db_config;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
parent::__construct($config);
|
||||
$this->db_config = $this->config->database;
|
||||
}
|
||||
}
|
||||
// End of BaseDBModel.php
|
@ -1,105 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Base for base models
|
||||
*/
|
||||
namespace AnimeClient\Base;
|
||||
|
||||
use abeautifulsite\SimpleImage;
|
||||
|
||||
/**
|
||||
* Common base for all Models
|
||||
*/
|
||||
class Model {
|
||||
|
||||
/**
|
||||
* The global configuration object
|
||||
* @var object $config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(Config &$config)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of the cached version of the image. Create the cached image
|
||||
* if the file does not already exist
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $api_path - The original image url
|
||||
* @param string $series_slug - The part of the url with the series name, becomes the image name
|
||||
* @param string $type - Anime or Manga, controls cache path
|
||||
* @return string - the frontend path for the cached image
|
||||
*/
|
||||
public function get_cached_image($api_path, $series_slug, $type="anime")
|
||||
{
|
||||
$api_path = str_replace("jjpg", "jpg", $api_path);
|
||||
$path_parts = explode('?', basename($api_path));
|
||||
$path = current($path_parts);
|
||||
$ext_parts = explode('.', $path);
|
||||
$ext = end($ext_parts);
|
||||
|
||||
// Workaround for some broken extensions
|
||||
if ($ext == "jjpg") $ext = "jpg";
|
||||
|
||||
// Failsafe for weird urls
|
||||
if (strlen($ext) > 3) return $api_path;
|
||||
|
||||
$cached_image = "{$series_slug}.{$ext}";
|
||||
$cached_path = "{$this->config->img_cache_path}/{$type}/{$cached_image}";
|
||||
|
||||
// Cache the file if it doesn't already exist
|
||||
if ( ! file_exists($cached_path))
|
||||
{
|
||||
if (ini_get('allow_url_fopen'))
|
||||
{
|
||||
copy($api_path, $cached_path);
|
||||
}
|
||||
elseif (function_exists('curl_init'))
|
||||
{
|
||||
$ch = curl_init($api_path);
|
||||
$fp = fopen($cached_path, 'wb');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_FILE => $fp,
|
||||
CURLOPT_HEADER => 0
|
||||
]);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
fclose($ch);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DomainException("Couldn't cache images because they couldn't be downloaded.");
|
||||
}
|
||||
|
||||
// Resize the image
|
||||
if ($type == 'anime')
|
||||
{
|
||||
$resize_width = 220;
|
||||
$resize_height = 319;
|
||||
$this->_resize($cached_path, $resize_width, $resize_height);
|
||||
}
|
||||
}
|
||||
|
||||
return "/public/images/{$type}/{$cached_image}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize an image
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $path
|
||||
* @param string $width
|
||||
* @param string $height
|
||||
*/
|
||||
private function _resize($path, $width, $height)
|
||||
{
|
||||
$img = new SimpleImage($path);
|
||||
$img->resize($width,$height)->save();
|
||||
}
|
||||
}
|
||||
// End of BaseModel.php
|
@ -1,232 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Routing logic
|
||||
*/
|
||||
|
||||
namespace AnimeClient\Base;
|
||||
|
||||
/**
|
||||
* Basic routing/ dispatch
|
||||
*/
|
||||
class Router {
|
||||
|
||||
/**
|
||||
* The route-matching object
|
||||
* @var object $router
|
||||
*/
|
||||
protected $router;
|
||||
|
||||
/**
|
||||
* The global configuration object
|
||||
* @var object $config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Class wrapper for input superglobals
|
||||
* @var object
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Array containing request and response objects
|
||||
* @var array $web
|
||||
*/
|
||||
protected $web;
|
||||
|
||||
/**
|
||||
* Routes added to router
|
||||
* @var array $output_routes
|
||||
*/
|
||||
protected $output_routes;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param
|
||||
*/
|
||||
public function __construct(Config $config, \Aura\Router\Router $router, \Aura\Web\Request $request, \Aura\Web\Response $response)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->router = $router;
|
||||
$this->request = $request;
|
||||
$this->web = [$request, $response];
|
||||
|
||||
$this->output_routes = $this->_setup_routes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current route object, if one matches
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function get_route()
|
||||
{
|
||||
global $defaultHandler;
|
||||
|
||||
$raw_route = parse_url($this->request->server->get('REQUEST_URI'), \PHP_URL_PATH);
|
||||
$route_path = str_replace([$this->config->anime_path, $this->config->manga_path], '', $raw_route);
|
||||
$route_path = "/" . trim($route_path, '/');
|
||||
|
||||
/*$defaultHandler->addDataTable('Route Info', [
|
||||
'route_path' => $route_path
|
||||
]);*/
|
||||
|
||||
$route = $this->router->match($route_path, $_SERVER);
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of routes applied
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_output_routes()
|
||||
{
|
||||
return $this->output_routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the current route
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param [object] $route
|
||||
* @return void
|
||||
*/
|
||||
public function dispatch($route = NULL)
|
||||
{
|
||||
global $defaultHandler;
|
||||
|
||||
if (is_null($route))
|
||||
{
|
||||
$route = $this->get_route();
|
||||
}
|
||||
|
||||
if ( ! $route)
|
||||
{
|
||||
$failure = $this->router->getFailedRoute();
|
||||
$defaultHandler->addDataTable('failed_route', (array)$failure);
|
||||
}
|
||||
else
|
||||
{
|
||||
list($controller_name, $action_method) = $route->params['action'];
|
||||
$params = (isset($route->params['params'])) ? $route->params['params'] : [];
|
||||
|
||||
if ( ! empty($route->tokens))
|
||||
{
|
||||
foreach($route->tokens as $key => $v)
|
||||
{
|
||||
if (array_key_exists($key, $route->params))
|
||||
{
|
||||
$params[$key] = $route->params[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$controller = new $controller_name($this->config, $this->web);
|
||||
|
||||
// Run the appropriate controller method
|
||||
$defaultHandler->addDataTable('controller_args', $params);
|
||||
call_user_func_array([$controller, $action_method], $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of route, to select the current controller
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_route_type()
|
||||
{
|
||||
$route_type = $this->config->default_list;
|
||||
|
||||
$host = $this->request->server->get("HTTP_HOST");
|
||||
$request_uri = $this->request->server->get('REQUEST_URI');
|
||||
|
||||
// Host-based controller selection
|
||||
if ($this->config->route_by === "host")
|
||||
{
|
||||
if (strtolower($host) === strtolower($this->config->anime_host))
|
||||
{
|
||||
$route_type = "anime";
|
||||
}
|
||||
|
||||
if (strtolower($host) === strtolower($this->config->manga_host))
|
||||
{
|
||||
$route_type = "manga";
|
||||
}
|
||||
}
|
||||
|
||||
// Path-based controller selection
|
||||
if ($this->config->route_by === "path")
|
||||
{
|
||||
$path = trim($request_uri, '/');
|
||||
|
||||
if (stripos($path, trim($this->config->anime_path, '/')) === 0)
|
||||
{
|
||||
$route_type = "anime";
|
||||
}
|
||||
|
||||
if (stripos($path, trim($this->config->manga_path, '/')) === 0)
|
||||
{
|
||||
$route_type = "manga";
|
||||
}
|
||||
}
|
||||
|
||||
return $route_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select controller based on the current url, and apply its relevent routes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function _setup_routes()
|
||||
{
|
||||
$route_map = [
|
||||
'anime' => '\\AnimeClient\\Controller\\Anime',
|
||||
'manga' => '\\AnimeClient\\Controller\\Manga',
|
||||
];
|
||||
|
||||
$output_routes = [];
|
||||
|
||||
$route_type = $this->get_route_type();
|
||||
|
||||
// Return early if invalid route array
|
||||
if ( ! array_key_exists($route_type, $this->config->routes)) return [];
|
||||
|
||||
$applied_routes = array_merge($this->config->routes['common'], $this->config->routes[$route_type]);
|
||||
|
||||
// Add routes
|
||||
foreach($applied_routes as $name => &$route)
|
||||
{
|
||||
$path = $route['path'];
|
||||
unset($route['path']);
|
||||
|
||||
// Prepend the controller to the route parameters
|
||||
array_unshift($route['action'], $route_map[$route_type]);
|
||||
|
||||
// Select the appropriate router method based on the http verb
|
||||
$add = (array_key_exists('verb', $route)) ? "add" . ucfirst(strtolower($route['verb'])) : "addGet";
|
||||
|
||||
// Add the route to the router object
|
||||
if ( ! array_key_exists('tokens', $route))
|
||||
{
|
||||
$output_routes[] = $this->router->$add($name, $path)->addValues($route);
|
||||
}
|
||||
else
|
||||
{
|
||||
$tokens = $route['tokens'];
|
||||
unset($route['tokens']);
|
||||
|
||||
$output_routes[] = $this->router->$add($name, $path)
|
||||
->addValues($route)
|
||||
->addTokens($tokens);
|
||||
}
|
||||
}
|
||||
|
||||
return $output_routes;
|
||||
}
|
||||
}
|
||||
// End of Router.php
|
@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Global functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if the user is currently logged in
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_logged_in()
|
||||
{
|
||||
return array_key_exists('hummingbird_anime_token', $_SESSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML selection helper function
|
||||
*
|
||||
* @param string $a - First item to compare
|
||||
* @param string $b - Second item to compare
|
||||
* @return string
|
||||
*/
|
||||
function is_selected($a, $b)
|
||||
{
|
||||
return ($a === $b) ? 'selected' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse of selected helper function
|
||||
*
|
||||
* @param string $a - First item to compare
|
||||
* @param string $b - Second item to compare
|
||||
* @return string
|
||||
*/
|
||||
function is_not_selected($a, $b)
|
||||
{
|
||||
return ($a !== $b) ? 'selected' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last segment of the current url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function last_segment()
|
||||
{
|
||||
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
$segments = explode('/', $path);
|
||||
return end($segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether to show the sub-menu
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_view_page()
|
||||
{
|
||||
$blacklist = ['edit', 'add', 'update', 'login', 'logout'];
|
||||
$page_segments = explode("/", $_SERVER['REQUEST_URI']);
|
||||
|
||||
$intersect = array_intersect($page_segments, $blacklist);
|
||||
|
||||
return empty($intersect);
|
||||
}
|
||||
|
||||
// End of functions.php
|
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Functions that need to be included before config
|
||||
*/
|
||||
|
||||
/**
|
||||
* Joins paths together. Variadic to take an
|
||||
* arbitrary number of arguments
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function _dir()
|
||||
{
|
||||
return implode(DIRECTORY_SEPARATOR, func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up autoloaders
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return void
|
||||
*/
|
||||
function _setup_autoloaders()
|
||||
{
|
||||
require _dir(ROOT_DIR, '/vendor/autoload.php');
|
||||
spl_autoload_register(function ($class) {
|
||||
$class_parts = explode('\\', $class);
|
||||
array_shift($class_parts);
|
||||
$ns_path = APP_DIR . '/' . implode('/', $class_parts) . ".php";
|
||||
|
||||
if (file_exists($ns_path))
|
||||
{
|
||||
require_once($ns_path);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Anime Controller
|
||||
*/
|
||||
|
||||
namespace AnimeClient\Controller;
|
||||
|
||||
use AnimeClient\Base\Controller as BaseController;
|
||||
use AnimeClient\Base\Config;
|
||||
use AnimeClient\Model\Anime as AnimeModel;
|
||||
use AnimeClient\Model\AnimeCollection as AnimeCollectionModel;
|
||||
|
||||
/**
|
||||
* Controller for Anime-related pages
|
||||
*/
|
||||
class Anime extends BaseController {
|
||||
|
||||
/**
|
||||
* The anime list model
|
||||
* @var object $model
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* The anime collection model
|
||||
* @var object $collection_model
|
||||
*/
|
||||
private $collection_model;
|
||||
|
||||
/**
|
||||
* Data to ve sent to all routes in this controller
|
||||
* @var array $base_data
|
||||
*/
|
||||
protected $base_data;
|
||||
|
||||
/**
|
||||
* Route mapping for main navigation
|
||||
* @var array $nav_routes
|
||||
*/
|
||||
private $nav_routes = [
|
||||
'Watching' => '/watching{/view}',
|
||||
'Plan to Watch' => '/plan_to_watch{/view}',
|
||||
'On Hold' => '/on_hold{/view}',
|
||||
'Dropped' => '/dropped{/view}',
|
||||
'Completed' => '/completed{/view}',
|
||||
'Collection' => '/collection/view{/view}',
|
||||
'All' => '/all{/view}'
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(Config $config, Array $web)
|
||||
{
|
||||
parent::__construct($config, $web);
|
||||
|
||||
if ($this->config->show_anime_collection === FALSE)
|
||||
{
|
||||
unset($this->nav_routes['Collection']);
|
||||
}
|
||||
|
||||
$this->model = new AnimeModel($config);
|
||||
$this->collection_model = new AnimeCollectionModel($config);
|
||||
$this->base_data = [
|
||||
'message' => '',
|
||||
'url_type' => 'anime',
|
||||
'other_type' => 'manga',
|
||||
'nav_routes' => $this->nav_routes,
|
||||
'config' => $this->config,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for anime
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function search()
|
||||
{
|
||||
$query = $this->request->query->get('query');
|
||||
$this->outputJSON($this->model->search($query));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return void
|
||||
*/
|
||||
public function anime_list($type, $title, $view)
|
||||
{
|
||||
$view_map = [
|
||||
'' => 'cover',
|
||||
'list' => 'list'
|
||||
];
|
||||
|
||||
$data = ($type != 'all')
|
||||
? $this->model->get_list($type)
|
||||
: $this->model->get_all_lists();
|
||||
|
||||
$this->outputHTML('anime/' . $view_map[$view], [
|
||||
'title' => $title,
|
||||
'sections' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the anime collection page
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collection($view)
|
||||
{
|
||||
$view_map = [
|
||||
'' => 'collection',
|
||||
'list' => 'collection_list'
|
||||
];
|
||||
|
||||
$data = $this->collection_model->get_collection();
|
||||
|
||||
$this->outputHTML('anime/' . $view_map[$view], [
|
||||
'title' => WHOSE . " Anime Collection",
|
||||
'sections' => $data,
|
||||
'genres' => $this->collection_model->get_genre_list()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the anime collection add/edit form
|
||||
*
|
||||
* @param int $id
|
||||
* @return void
|
||||
*/
|
||||
public function collection_form($id=NULL)
|
||||
{
|
||||
$action = (is_null($id)) ? "Add" : "Edit";
|
||||
|
||||
$this->outputHTML('anime/collection_' . strtolower($action), [
|
||||
'action' => $action,
|
||||
'action_url' => $this->config->full_url("collection/" . strtolower($action)),
|
||||
'title' => WHOSE . " Anime Collection · {$action}",
|
||||
'media_items' => $this->collection_model->get_media_type_list(),
|
||||
'item' => ($action === "Edit") ? $this->collection_model->get($id) : []
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a collection item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collection_edit()
|
||||
{
|
||||
$data = $this->request->post->get();
|
||||
if ( ! array_key_exists('hummingbird_id', $data))
|
||||
{
|
||||
$this->redirect("collection/view", 303, "anime");
|
||||
}
|
||||
|
||||
$this->collection_model->update($data);
|
||||
|
||||
$this->redirect("collection/view", 303, "anime");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a collection item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collection_add()
|
||||
{
|
||||
$data = $this->request->post->get();
|
||||
if ( ! array_key_exists('id', $data))
|
||||
{
|
||||
$this->redirect("collection/view", 303, "anime");
|
||||
}
|
||||
|
||||
$this->collection_model->add($data);
|
||||
|
||||
$this->redirect("collection/view", 303, "anime");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an anime item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$this->outputJSON($this->model->update($this->request->post->get()));
|
||||
}
|
||||
}
|
||||
// End of AnimeController.php
|
@ -1,92 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Manga Controller
|
||||
*/
|
||||
namespace AnimeClient\Controller;
|
||||
|
||||
use AnimeClient\Base\Controller;
|
||||
use AnimeClient\Base\Config;
|
||||
use AnimeClient\Model\Manga as MangaModel;
|
||||
|
||||
/**
|
||||
* Controller for manga list
|
||||
*/
|
||||
class Manga extends Controller {
|
||||
|
||||
/**
|
||||
* The manga model
|
||||
* @var object $model
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Data to ve sent to all routes in this controller
|
||||
* @var array $base_data
|
||||
*/
|
||||
protected $base_data;
|
||||
|
||||
|
||||
/**
|
||||
* Route mapping for main navigation
|
||||
* @var array $nav_routes
|
||||
*/
|
||||
private $nav_routes = [
|
||||
'Reading' => '/reading{/view}',
|
||||
'Plan to Read' => '/plan_to_read{/view}',
|
||||
'On Hold' => '/on_hold{/view}',
|
||||
'Dropped' => '/dropped{/view}',
|
||||
'Completed' => '/completed{/view}',
|
||||
'All' => '/all{/view}'
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(Config $config, Array $web)
|
||||
{
|
||||
parent::__construct($config, $web);
|
||||
$this->model = new MangaModel($config);
|
||||
$this->base_data = [
|
||||
'config' => $this->config,
|
||||
'url_type' => 'manga',
|
||||
'other_type' => 'anime',
|
||||
'nav_routes' => $this->nav_routes
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an anime item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$this->outputJSON($this->model->update($this->request->post->get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a section of the manga list
|
||||
*
|
||||
* @param string $status
|
||||
* @param string $title
|
||||
* @param string $view
|
||||
* @return void
|
||||
*/
|
||||
public function manga_list($status, $title, $view)
|
||||
{
|
||||
$view_map = [
|
||||
'' => 'cover',
|
||||
'list' => 'list'
|
||||
];
|
||||
|
||||
$data = ($status !== 'all')
|
||||
? [$status => $this->model->get_list($status)]
|
||||
: $this->model->get_all_lists();
|
||||
|
||||
$this->outputHTML('manga/' . $view_map[$view], [
|
||||
'title' => $title,
|
||||
'sections' => $data
|
||||
]);
|
||||
}
|
||||
}
|
||||
// End of MangaController.php
|
@ -1,248 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Anime API Model
|
||||
*/
|
||||
|
||||
namespace AnimeClient\Model;
|
||||
|
||||
use AnimeClient\Base\ApiModel;
|
||||
use AnimeClient\Base\Config;
|
||||
|
||||
/**
|
||||
* Model for handling requests dealing with the anime list
|
||||
*/
|
||||
class Anime extends ApiModel {
|
||||
/**
|
||||
* The base url for api requests
|
||||
* @var string $base_url
|
||||
*/
|
||||
protected $base_url = "https://hummingbird.me/api/v1/";
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the selected anime
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function update($data)
|
||||
{
|
||||
$data['auth_token'] = $_SESSION['hummingbird_anime_token'];
|
||||
|
||||
$result = $this->client->post("libraries/{$data['id']}", [
|
||||
'body' => $data
|
||||
]);
|
||||
|
||||
return $result->json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full set of anime lists
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_lists()
|
||||
{
|
||||
$output = [
|
||||
'Watching' => [],
|
||||
'Plan to Watch' => [],
|
||||
'On Hold' => [],
|
||||
'Dropped' => [],
|
||||
'Completed' => [],
|
||||
];
|
||||
|
||||
$data = $this->_get_list();
|
||||
|
||||
foreach($data as $datum)
|
||||
{
|
||||
switch($datum['status'])
|
||||
{
|
||||
case "completed":
|
||||
$output['Completed'][] = $datum;
|
||||
break;
|
||||
|
||||
case "plan-to-watch":
|
||||
$output['Plan to Watch'][] = $datum;
|
||||
break;
|
||||
|
||||
case "dropped":
|
||||
$output['Dropped'][] = $datum;
|
||||
break;
|
||||
|
||||
case "on-hold":
|
||||
$output['On Hold'][] = $datum;
|
||||
break;
|
||||
|
||||
case "currently-watching":
|
||||
$output['Watching'][] = $datum;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort anime by name
|
||||
foreach($output as &$status_list)
|
||||
{
|
||||
$this->sort_by_name($status_list);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a category out of the full list
|
||||
*
|
||||
* @param string $status
|
||||
* @return array
|
||||
*/
|
||||
public function get_list($status)
|
||||
{
|
||||
$map = [
|
||||
'currently-watching' => 'Watching',
|
||||
'plan-to-watch' => 'Plan to Watch',
|
||||
'on-hold' => 'On Hold',
|
||||
'dropped' => 'Dropped',
|
||||
'completed' => 'Completed',
|
||||
];
|
||||
|
||||
$data = $this->_get_list($status);
|
||||
$this->sort_by_name($data);
|
||||
|
||||
$output = [];
|
||||
$output[$map[$status]] = $data;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about an anime from its id
|
||||
*
|
||||
* @param string $anime_id
|
||||
* @return array
|
||||
*/
|
||||
public function get_anime($anime_id)
|
||||
{
|
||||
$config = [
|
||||
'query' => [
|
||||
'id' => $anime_id
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->client->get("anime/{$anime_id}", $config);
|
||||
|
||||
return $response->json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for anime by name
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public function search($name)
|
||||
{
|
||||
global $defaultHandler;
|
||||
|
||||
$config = [
|
||||
'query' => [
|
||||
'query' => $name
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->client->get('search/anime', $config);
|
||||
$defaultHandler->addDataTable('anime_search_response', (array)$response);
|
||||
|
||||
if ($response->getStatusCode() != 200)
|
||||
{
|
||||
throw new RuntimeException($response->getEffectiveUrl());
|
||||
}
|
||||
|
||||
return $response->json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually retreive the data from the api
|
||||
*
|
||||
* @param string $status - Status to filter by
|
||||
* @return array
|
||||
*/
|
||||
private function _get_list($status="all")
|
||||
{
|
||||
global $defaultHandler;
|
||||
|
||||
$cache_file = "{$this->config->data_cache_path}/anime-{$status}.json";
|
||||
|
||||
$config = [
|
||||
'allow_redirects' => FALSE
|
||||
];
|
||||
|
||||
if ($status != "all")
|
||||
{
|
||||
$config['query']['status'] = $status;
|
||||
}
|
||||
|
||||
$response = $this->client->get("users/{$this->config->hummingbird_username}/library", $config);
|
||||
|
||||
$defaultHandler->addDataTable('anime_list_response', (array)$response);
|
||||
|
||||
if ($response->getStatusCode() != 200)
|
||||
{
|
||||
if ( ! file_exists($cache_file))
|
||||
{
|
||||
throw new DomainException($response->getEffectiveUrl());
|
||||
}
|
||||
else
|
||||
{
|
||||
$output = json_decode(file_get_contents($cache_file), TRUE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$output = $response->json();
|
||||
$output_json = json_encode($output);
|
||||
|
||||
if (( ! file_exists($cache_file)) || file_get_contents($cache_file) !== $output_json)
|
||||
{
|
||||
// Attempt to create the cache folder if it doesn't exist
|
||||
if ( ! is_dir($this->config->data_cache_path))
|
||||
{
|
||||
mkdir($this->config->data_cache_path);
|
||||
}
|
||||
// Cache the call in case of downtime
|
||||
file_put_contents($cache_file, json_encode($output));
|
||||
}
|
||||
}
|
||||
|
||||
foreach($output as &$row)
|
||||
{
|
||||
$row['anime']['cover_image'] = $this->get_cached_image($row['anime']['cover_image'], $row['anime']['slug'], 'anime');
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the list by title
|
||||
*
|
||||
* @param array $array
|
||||
* @return void
|
||||
*/
|
||||
private function sort_by_name(&$array)
|
||||
{
|
||||
$sort = array();
|
||||
|
||||
foreach($array as $key => $item)
|
||||
{
|
||||
$sort[$key] = $item['anime']['title'];
|
||||
}
|
||||
|
||||
array_multisort($sort, SORT_ASC, $array);
|
||||
}
|
||||
}
|
||||
// End of AnimeModel.php
|
@ -1,372 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Anime Collection DB Model
|
||||
*/
|
||||
|
||||
namespace AnimeClient\Model;
|
||||
|
||||
use AnimeClient\Base\DBModel;
|
||||
use AnimeClient\Base\Config;
|
||||
use AnimeClient\Model\Anime as AnimeModel;
|
||||
|
||||
/**
|
||||
* Model for getting anime collection data
|
||||
*/
|
||||
class AnimeCollection extends DBModel {
|
||||
|
||||
/**
|
||||
* Anime API Model
|
||||
* @var object $anime_model
|
||||
*/
|
||||
private $anime_model;
|
||||
|
||||
/**
|
||||
* Whether the database is valid for querying
|
||||
* @var bool
|
||||
*/
|
||||
private $valid_database = FALSE;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
$this->db = \Query($this->db_config['collection']);
|
||||
$this->anime_model = new AnimeModel($config);
|
||||
|
||||
// Is database valid? If not, set a flag so the
|
||||
// app can be run without a valid database
|
||||
$db_file = file_get_contents($this->db_config['collection']['file']);
|
||||
$this->valid_database = (strpos($db_file, 'SQLite format 3') === 0);
|
||||
|
||||
|
||||
// Do an import if an import file exists
|
||||
$this->json_import();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get genres for anime collection items
|
||||
*
|
||||
* @param array $filter
|
||||
* @return array
|
||||
*/
|
||||
public function get_genre_list($filter=[])
|
||||
{
|
||||
$this->db->select('hummingbird_id, genre')
|
||||
->from('genre_anime_set_link gl')
|
||||
->join('genres g', 'g.id=gl.genre_id', 'left');
|
||||
|
||||
|
||||
if ( ! empty($filter)) $this->db->where_in('hummingbird_id', $filter);
|
||||
|
||||
$query = $this->db->order_by('hummingbird_id')
|
||||
->order_by('genre')
|
||||
->get();
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach($query->fetchAll(\PDO::FETCH_ASSOC) as $row)
|
||||
{
|
||||
$id = $row['hummingbird_id'];
|
||||
$genre = $row['genre'];
|
||||
|
||||
// Empty genre names aren't useful
|
||||
if (empty($genre)) continue;
|
||||
|
||||
|
||||
if (array_key_exists($id, $output))
|
||||
{
|
||||
array_push($output[$id], $genre);
|
||||
}
|
||||
else
|
||||
{
|
||||
$output[$id] = [$genre];
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collection from the database, and organize by media type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection()
|
||||
{
|
||||
$raw_collection = $this->_get_collection();
|
||||
|
||||
$collection = [];
|
||||
|
||||
foreach($raw_collection 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 get_media_type_list()
|
||||
{
|
||||
$output = array();
|
||||
|
||||
$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 get_collection_entry($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 _get_collection()
|
||||
{
|
||||
if ( ! $this->valid_database) 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('anime_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->anime_model->get_anime($data['id']);
|
||||
|
||||
$this->db->set([
|
||||
'hummingbird_id' => $data['id'],
|
||||
'slug' => $anime->slug,
|
||||
'title' => $anime->title,
|
||||
'alternate_title' => $anime->alternate_title,
|
||||
'show_type' => $anime->show_type,
|
||||
'age_rating' => $anime->age_rating,
|
||||
'cover_image' => basename($this->get_cached_image($anime->cover_image, $anime->slug, 'anime')),
|
||||
'episode_count' => $anime->episode_count,
|
||||
'episode_length' => $anime->episode_length,
|
||||
'media_id' => $data['media_id'],
|
||||
'notes' => $data['notes']
|
||||
])->insert('anime_set');
|
||||
|
||||
$this->update_genre($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('anime_set');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the details of a collection item
|
||||
*
|
||||
* @param int $hummingbird_id
|
||||
* @return array
|
||||
*/
|
||||
public function get($hummingbird_id)
|
||||
{
|
||||
$query = $this->db->from('anime_set')
|
||||
->where('hummingbird_id', $hummingbird_id)
|
||||
->get();
|
||||
|
||||
return $query->fetch(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import anime into collection from a json file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function json_import()
|
||||
{
|
||||
if ( ! file_exists('import.json')) return;
|
||||
if ( ! $this->valid_database) return;
|
||||
|
||||
$anime = json_decode(file_get_contents("import.json"));
|
||||
|
||||
foreach($anime as $item)
|
||||
{
|
||||
$this->db->set([
|
||||
'hummingbird_id' => $item->id,
|
||||
'slug' => $item->slug,
|
||||
'title' => $item->title,
|
||||
'alternate_title' => $item->alternate_title,
|
||||
'show_type' => $item->show_type,
|
||||
'age_rating' => $item->age_rating,
|
||||
'cover_image' => basename($this->get_cached_image($item->cover_image, $item->slug, 'anime')),
|
||||
'episode_count' => $item->episode_count,
|
||||
'episode_length' => $item->episode_length
|
||||
])->insert('anime_set');
|
||||
}
|
||||
|
||||
// Delete the import file
|
||||
unlink('import.json');
|
||||
|
||||
// Update genre info
|
||||
$this->update_genres();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update genre information for selected anime
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function update_genre($anime_id)
|
||||
{
|
||||
$genre_info = $this->get_genre_data();
|
||||
extract($genre_info);
|
||||
|
||||
// Get api information
|
||||
$anime = $this->anime_model->get_anime($anime_id);
|
||||
|
||||
foreach($anime['genres'] as $genre)
|
||||
{
|
||||
// Add genres that don't currently exist
|
||||
if ( ! in_array($genre['name'], $genres))
|
||||
{
|
||||
$this->db->set('genre', $genre['name'])
|
||||
->insert('genres');
|
||||
|
||||
$genres[] = $genre['name'];
|
||||
}
|
||||
|
||||
// Update link table
|
||||
// Get id of genre to put in link table
|
||||
$flipped_genres = array_flip($genres);
|
||||
|
||||
$insert_array = [
|
||||
'hummingbird_id' => $anime['id'],
|
||||
'genre_id' => $flipped_genres[$genre['name']]
|
||||
];
|
||||
|
||||
if (array_key_exists($anime['id'], $links))
|
||||
{
|
||||
if ( ! in_array($flipped_genres[$genre['name']], $links[$anime['id']]))
|
||||
{
|
||||
$this->db->set($insert_array)->insert('genre_anime_set_link');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->db->set($insert_array)->insert('genre_anime_set_link');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of existing genres
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_genre_data()
|
||||
{
|
||||
$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_anime_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
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update genre information for the entire collection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function update_genres()
|
||||
{
|
||||
// Get the anime collection
|
||||
$collection = $this->_get_collection();
|
||||
foreach($collection as $anime)
|
||||
{
|
||||
// Get api information
|
||||
$this->update_genre($anime['hummingbird_id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
// End of AnimeCollectionModel.php
|
@ -1,201 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Manga API Model
|
||||
*/
|
||||
namespace AnimeClient\Model;
|
||||
|
||||
use AnimeClient\Base\ApiModel;
|
||||
use AnimeClient\Base\Config;
|
||||
|
||||
/**
|
||||
* Model for handling requests dealing with the manga list
|
||||
*/
|
||||
class Manga extends ApiModel {
|
||||
|
||||
/**
|
||||
* The base url for api requests
|
||||
* @var string
|
||||
*/
|
||||
protected $base_url = "https://hummingbird.me/";
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the selected manga
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function update($data)
|
||||
{
|
||||
$id = $data['id'];
|
||||
unset($data['id']);
|
||||
|
||||
$result = $this->client->put("manga_library_entries/{$id}", [
|
||||
'cookies' => ['token' => $_SESSION['hummingbird_anime_token']],
|
||||
'json' => ['manga_library_entry' => $data]
|
||||
]);
|
||||
|
||||
return $result->json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full set of anime lists
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_lists()
|
||||
{
|
||||
$data = $this->_get_list();
|
||||
|
||||
foreach ($data as $key => &$val)
|
||||
{
|
||||
$this->sort_by_name($val);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a category out of the full list
|
||||
*
|
||||
* @param string $status
|
||||
* @return array
|
||||
*/
|
||||
public function get_list($status)
|
||||
{
|
||||
$data = $this->_get_list($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")
|
||||
{
|
||||
global $defaultHandler;
|
||||
|
||||
$cache_file = _dir($this->config->data_cache_path, 'manga.json');
|
||||
|
||||
$config = [
|
||||
'query' => [
|
||||
'user_id' => $this->config->hummingbird_username
|
||||
],
|
||||
'allow_redirects' => FALSE
|
||||
];
|
||||
|
||||
$response = $this->client->get('manga_library_entries', $config);
|
||||
|
||||
$defaultHandler->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));
|
||||
}
|
||||
|
||||
// Bail out early if there isn't any manga data
|
||||
if ( ! array_key_exists('manga', $raw_data)) return [];
|
||||
|
||||
$data = [
|
||||
'Reading' => [],
|
||||
'Plan to Read' => [],
|
||||
'On Hold' => [],
|
||||
'Dropped' => [],
|
||||
'Completed' => [],
|
||||
];
|
||||
$manga_data = [];
|
||||
|
||||
// Massage the two lists into one
|
||||
foreach($raw_data['manga'] as $manga)
|
||||
{
|
||||
$manga_data[$manga['id']] = $manga;
|
||||
}
|
||||
|
||||
// Filter data by status
|
||||
foreach($raw_data['manga_library_entries'] as &$entry)
|
||||
{
|
||||
$entry['manga'] = $manga_data[$entry['manga_id']];
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
return (array_key_exists($status, $data)) ? $data[$status] : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the manga entries by their title
|
||||
*
|
||||
* @param array $array
|
||||
* @return void
|
||||
*/
|
||||
private function sort_by_name(&$array)
|
||||
{
|
||||
$sort = array();
|
||||
|
||||
foreach($array as $key => $item)
|
||||
{
|
||||
$sort[$key] = $item['manga']['romaji_title'];
|
||||
}
|
||||
|
||||
array_multisort($sort, SORT_ASC, $array);
|
||||
}
|
||||
}
|
||||
// End of MangaModel.php
|
@ -70,6 +70,25 @@ return [
|
||||
'code' => '301'
|
||||
]
|
||||
],
|
||||
'login_form' => [
|
||||
'path' => '/anime/login',
|
||||
'action' => ['login'],
|
||||
'verb' => 'get'
|
||||
],
|
||||
'login_action' => [
|
||||
'path' => '/anime/login',
|
||||
'action' => ['login_action'],
|
||||
'verb' => 'post'
|
||||
],
|
||||
'logout' => [
|
||||
'path' => '/anime/logout',
|
||||
'action' => ['logout']
|
||||
],
|
||||
'update' => [
|
||||
'path' => '/anime/update',
|
||||
'action' => ['update'],
|
||||
'verb' => 'post'
|
||||
],
|
||||
'search' => [
|
||||
'path' => '/anime/search',
|
||||
'action' => ['search'],
|
||||
|
@ -149,7 +149,10 @@ class Router extends RoutingBase {
|
||||
$segments = explode('/', $path);
|
||||
$controller = reset($segments);
|
||||
|
||||
//$controller_class = '\\AnimeClient\\Controller\\' . ucfirst($controller);
|
||||
if (empty($controller))
|
||||
{
|
||||
$controller = $route_type;
|
||||
}
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
@ -44,4 +44,4 @@
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
<script src="<?= $config->asset_url('js.php?g=table') ?>"></script>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=table') ?>"></script>
|
@ -1,6 +1,6 @@
|
||||
<main>
|
||||
<?php if (is_logged_in()): ?>
|
||||
[<a href="<?= $config->full_url('collection/add', 'anime') ?>">Add Item</a>]
|
||||
[<a href="<?= $urlGenerator->full_url('collection/add', 'anime') ?>">Add Item</a>]
|
||||
<?php endif ?>
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
@ -43,7 +43,7 @@
|
||||
<td><?= $item['age_rating'] ?></td>
|
||||
<td class="align_left"><?= $item['notes'] ?></td>
|
||||
<?php if (is_logged_in()): ?>
|
||||
<td>[<a href="<?= $config->full_url("collection/edit/{$item['hummingbird_id']}", "anime") ?>">Edit</a>]</td>
|
||||
<td>[<a href="<?= $urlGenerator->full_url("collection/edit/{$item['hummingbird_id']}", "anime") ?>">Edit</a>]</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
@ -53,4 +53,4 @@
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
<script src="<?= $config->asset_url('js.php?g=table') ?>"></script>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=table') ?>"></script>
|
@ -18,9 +18,9 @@
|
||||
</span>
|
||||
<span class="flex-no-wrap small-font">
|
||||
<?php if (is_logged_in()): ?>
|
||||
[<a href="<?= $urlGenerator->full_url("/logout", $url_type) ?>">Logout</a>]
|
||||
[<a href="<?= $urlGenerator->url("/{$url_type}/logout", $url_type) ?>">Logout</a>]
|
||||
<?php else: ?>
|
||||
[<a href="<?= $urlGenerator->full_url("/login", $url_type) ?>"><?= WHOSE ?> Login</a>]
|
||||
[<a href="<?= $urlGenerator->url("/{$url_type}/login", $url_type) ?>"><?= WHOSE ?> Login</a>]
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</h1>
|
||||
|
@ -34,4 +34,4 @@
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
<script src="<?= $config->asset_url('js.php?g=table') ?>"></script>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=table') ?>"></script>
|
@ -205,5 +205,6 @@ class RouterTest extends AnimeClient_TestCase {
|
||||
$this->_set_up($config, "/", "localhost");
|
||||
$this->assertEquals('//localhost/manga/all', $this->urlGenerator->default_url('manga'), "Incorrect default url");
|
||||
$this->assertEquals('//localhost/anime/watching', $this->urlGenerator->default_url('anime'), "Incorrect default url");
|
||||
$this->assertEquals('', $this->urlGenerator->default_url('foo'), "Incorrect default url");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user