Compare commits
123 Commits
Author | SHA1 | Date |
---|---|---|
Timothy Warren | c59288b5f9 | |
Timothy Warren | 6ff4ee2746 | |
Timothy Warren | 9b03f102f3 | |
Timothy Warren | 816a309f18 | |
Timothy Warren | b85ddb9464 | |
Timothy Warren | 3918ce4eb7 | |
Timothy Warren | 3bd5c7d218 | |
Timothy Warren | 9c73ab928c | |
Timothy Warren | 016e0988e9 | |
Timothy Warren | 97dfa89b6d | |
Timothy Warren | f5549934fe | |
Timothy Warren | 27ac7e8063 | |
Timothy Warren | 275b0eea40 | |
Timothy Warren | fa4940f22d | |
Timothy Warren | bfe46fbbd1 | |
Timothy Warren | 38faaebb5f | |
Timothy Warren | 3c124456d0 | |
Timothy Warren | b4489311c9 | |
Timothy Warren | 7a9fe42d83 | |
Scrutinizer Auto-Fixer | dd0e15137a | |
Timothy Warren | 205c7ac76d | |
Timothy Warren | 6455541210 | |
Scrutinizer Auto-Fixer | 7cdd79a116 | |
Timothy Warren | 6e4e8edd9d | |
Timothy Warren | 1251486aa3 | |
Timothy Warren | 1b8ed53afb | |
Timothy Warren | 5e048977a3 | |
Scrutinizer Auto-Fixer | 355aff2951 | |
Timothy Warren | 3c41682d73 | |
Timothy Warren | 77709c068a | |
Timothy Warren | d48cafa54d | |
Timothy Warren | f386766841 | |
Timothy Warren | 52397bbd61 | |
Timothy Warren | 99b429433c | |
Timothy Warren | c23c63cb15 | |
Timothy Warren | fff46421bf | |
Timothy Warren | 61f1963db8 | |
Timothy Warren | 253f191113 | |
Timothy Warren | 6622014bd1 | |
Timothy Warren | bd6b5e2b54 | |
Timothy Warren | 4ed6200d9f | |
Timothy Warren | c009a96a15 | |
Timothy Warren | ae88282b13 | |
Scrutinizer Auto-Fixer | 38b2f34527 | |
Timothy Warren | afced2339a | |
Timothy Warren | 83cd815750 | |
Timothy Warren | ca2e72d3f0 | |
Timothy Warren | 3c4ba096c6 | |
Timothy Warren | 1891aafef5 | |
Timothy Warren | aee2fa7120 | |
Timothy Warren | c55f91a79f | |
Timothy Warren | 8f95bfe7e0 | |
Scrutinizer Auto-Fixer | db33f46547 | |
Timothy Warren | 68cb36b193 | |
Timothy Warren | ff28b40c9e | |
Timothy Warren | e6b4fe59a3 | |
Timothy Warren | 0cd30e811d | |
Timothy Warren | d3541da789 | |
Timothy Warren | cdb4406e14 | |
Timothy Warren | 6ef8caca00 | |
Timothy Warren | beb127c06c | |
Timothy Warren | 9b38242f9d | |
Scrutinizer Auto-Fixer | d63d33d245 | |
Timothy Warren | 30f18106cb | |
Timothy Warren | fdd0da8d93 | |
Timothy Warren | 4ad6178bf0 | |
Timothy Warren | 32d20a9234 | |
Timothy Warren | def424da72 | |
Timothy Warren | c99d4ee53d | |
Timothy Warren | 935076cc63 | |
Timothy Warren | 766fad6bb2 | |
Timothy Warren | 672b781425 | |
Timothy Warren | 95ecc9e9b8 | |
Timothy Warren | 8625f20b74 | |
Timothy Warren | 944a0c9c2a | |
Timothy Warren | f22015635e | |
Timothy Warren | 23122964d2 | |
Timothy Warren | 67080a098f | |
Timothy Warren | 5bf46e0840 | |
Timothy Warren | 454679626c | |
Timothy Warren | e2e27c2311 | |
Timothy Warren | 3cf753a707 | |
Timothy Warren | 9ed18ce131 | |
Timothy Warren | 6b1f39525d | |
Timothy Warren | 725915060b | |
Scrutinizer Auto-Fixer | 2b6b8bff43 | |
Timothy Warren | f49e4fe3d8 | |
Timothy Warren | 9cf958b1ed | |
Scrutinizer Auto-Fixer | 25981afeef | |
Timothy Warren | 3de32f52af | |
Timothy Warren | 15707167f1 | |
Timothy Warren | c86d72f3e2 | |
Scrutinizer Auto-Fixer | edd393793a | |
Timothy Warren | f9b3d64eca | |
Timothy Warren | 83f1a9afd9 | |
Timothy Warren | d53524ed86 | |
Timothy Warren | aedabd6eda | |
Timothy Warren | af95ac941f | |
Scrutinizer Auto-Fixer | 7c5a73e73b | |
Timothy Warren | 5e5434d057 | |
Timothy Warren | 9f123822b1 | |
Timothy Warren | 651b9c4483 | |
Timothy Warren | e71e76dbc6 | |
Timothy Warren | 7917c39065 | |
Timothy Warren | e8a9982f9a | |
Timothy Warren | e634a22134 | |
Timothy Warren | a7ae1ac3a6 | |
Timothy Warren | 5624d9b44e | |
Timothy Warren | 92d9124bb7 | |
Timothy Warren | 8fb6dae119 | |
Timothy Warren | 5f6119c86b | |
Timothy Warren | 602759b471 | |
Timothy Warren | c788cf5d87 | |
Timothy Warren | 9193938dee | |
Timothy Warren | b1c6039630 | |
Timothy Warren | 67799fcdfa | |
Timothy Warren | dfe0b3a6cf | |
Timothy Warren | cee211621c | |
Timothy Warren | 8904054212 | |
Timothy Warren | 6450a76351 | |
Timothy Warren | 54371bf76a | |
Timothy Warren | c33575e3d8 | |
Timothy Warren | ef4e9b3e85 |
|
@ -0,0 +1,20 @@
|
|||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = false
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{cpp,c,h,hpp,cxx}]
|
||||
insert_final_newline = true
|
||||
|
||||
# Yaml files
|
||||
[*.{yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
|
@ -1,10 +1,21 @@
|
|||
vendor
|
||||
app/cache/*
|
||||
public/images/*
|
||||
public/js/cache/*
|
||||
composer.lock
|
||||
*.sqlite
|
||||
*.db
|
||||
*.sqlite3
|
||||
docs/*
|
||||
coverage/*
|
||||
.codelite
|
||||
*.phprj
|
||||
*.workspace
|
||||
vendor
|
||||
app/cache/*
|
||||
app/logs/*
|
||||
public/images/*
|
||||
public/js/cache/*
|
||||
composer.lock
|
||||
*.sqlite
|
||||
*.db
|
||||
*.sqlite3
|
||||
docs/*
|
||||
coverage/*
|
||||
tests/test_data/sessions/*
|
||||
build/coverage/*
|
||||
build/logs/*
|
||||
build/pdepend/*
|
||||
build/phpdox/*
|
||||
cache.properties
|
||||
tests/test_data/cache/*
|
|
@ -4,7 +4,6 @@ install:
|
|||
- composer install
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7
|
||||
|
@ -13,8 +12,12 @@ php:
|
|||
|
||||
script:
|
||||
- mkdir -p build/logs
|
||||
- phpunit --coverage-clover=coverage.clover
|
||||
- phpunit -c build
|
||||
|
||||
after_script:
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
|
||||
- php ocular.phar code-coverage:upload --format=php-clover build/logs/coverage.clover
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: nightly
|
32
README.md
32
README.md
|
@ -2,9 +2,11 @@
|
|||
|
||||
A self-hosted client that allows custom formatting of data from the hummingbird api
|
||||
|
||||
[![Build Status](https://travis-ci.org/timw4mail/HummingBirdAnimeClient.svg)](https://travis-ci.org/timw4mail/HummingBirdAnimeClient)
|
||||
[![Build Status](https://jenkins.timshomepage.net/buildStatus/icon?job=animeclient)](https://jenkins.timshomepage.net/job/animeclient/)
|
||||
[![Build Status](https://travis-ci.org/timw4mail/HummingBirdAnimeClient.svg?branch=master)](https://travis-ci.org/timw4mail/HummingBirdAnimeClient)
|
||||
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/timw4mail/HummingBirdAnimeClient/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/timw4mail/HummingBirdAnimeClient/?branch=master)
|
||||
|
||||
[[Hosted Example](https://anime.timshomepage.net)]
|
||||
[[Hosted Example](https://list.timshomepage.net)]
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -13,33 +15,32 @@ A self-hosted client that allows custom formatting of data from the hummingbird
|
|||
* Plan to Watch
|
||||
* On Hold
|
||||
* Dropped
|
||||
* Completed
|
||||
* All of the above
|
||||
|
||||
* Completed
|
||||
* Combined View
|
||||
|
||||
* Manga List views (Each with list and cover views):
|
||||
* Reading
|
||||
* Plan to Read
|
||||
* On Hold
|
||||
* Dropped
|
||||
* Completed
|
||||
* All of the above
|
||||
|
||||
* Combined View
|
||||
|
||||
* Anime collection view (segmented by media type):
|
||||
* Cover Images
|
||||
* Table List
|
||||
|
||||
|
||||
### Requirements
|
||||
|
||||
* PHP 5.4+
|
||||
* PHP 5.5+
|
||||
* PDO SQLite (For collection tab)
|
||||
* GD
|
||||
|
||||
### Installation
|
||||
|
||||
1. Install dependencies via composer: `composer install`
|
||||
2. Change the `WHOSE` constant declaration in `index.php` to your name
|
||||
3. Configure settings in `app/config/config.php` to your liking
|
||||
4. Create the following directories if they don't exist, and make sure they are world writable
|
||||
1. Install via composer: `composer create-project timw4mail/hummingbird-anime-client`
|
||||
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
|
||||
* public/images/anime
|
||||
|
@ -48,8 +49,11 @@ A self-hosted client that allows custom formatting of data from the hummingbird
|
|||
#### Anime Collection Additional Installation
|
||||
* Run `php /vendor/bin/phinx migrate -e development` to create the database tables
|
||||
* For importing anime:
|
||||
1. Login
|
||||
2. Use the form to select your media
|
||||
3. Save & Repeat as needed
|
||||
* For bulk importing anime:
|
||||
1. Find the anime you are looking for on the hummingbird search api page: `https://hummingbird.me/api/v1/search/anime?query=`
|
||||
2. Create an `import.json` file in the root of the app, with an array of objects from the search page that you want to import
|
||||
3. Go to the anime collection tab, and the import will be run
|
||||
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Base API Model
|
||||
*/
|
||||
namespace AnimeClient;
|
||||
|
||||
use \GuzzleHttp\Client;
|
||||
use \GuzzleHttp\Cookie\CookieJar;
|
||||
|
||||
/**
|
||||
* Base model for api interaction
|
||||
*/
|
||||
class BaseApiModel extends BaseModel {
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$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,254 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Base Controller
|
||||
*/
|
||||
namespace AnimeClient;
|
||||
|
||||
use Aura\Web\WebFactory;
|
||||
|
||||
/**
|
||||
* Base class for controllers, defines output methods
|
||||
*/
|
||||
class BaseController {
|
||||
|
||||
/**
|
||||
* 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 = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(Config $config, Array $web)
|
||||
{
|
||||
$this->config = $config;
|
||||
|
||||
list($request, $response) = $web;
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string output of a partial template
|
||||
*
|
||||
* @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 Exception("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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @param string $url
|
||||
* @param int $code
|
||||
* @return void
|
||||
*/
|
||||
public function redirect($url, $code, $type="anime")
|
||||
{
|
||||
$url = full_url($url, $type);
|
||||
|
||||
$codes = [
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other'
|
||||
];
|
||||
|
||||
header("HTTP/1.1 {$code} {$codes[$code]}");
|
||||
header("Location: {$url}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message box to the page
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
session_destroy();
|
||||
$this->response->redirect->seeOther(full_url(''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the login form
|
||||
*
|
||||
* @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(full_url('', $this->base_data['url_type']));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->login("Invalid username or password.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the appropriate response
|
||||
*
|
||||
* @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 model for database interaction
|
||||
*/
|
||||
class BaseDBModel extends BaseModel {
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->db_config = $this->config->database;
|
||||
}
|
||||
}
|
||||
// End of BaseDBModel.php
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace AnimeClient;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
// End of config.php
|
|
@ -1,175 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Routing logic
|
||||
*/
|
||||
|
||||
namespace AnimeClient;
|
||||
|
||||
/**
|
||||
* Basic routing/ dispatch
|
||||
*/
|
||||
class Router {
|
||||
|
||||
/**
|
||||
* The route-matching object
|
||||
* @var object $router
|
||||
*/
|
||||
protected $router;
|
||||
|
||||
/**
|
||||
* The global configuration object
|
||||
* @var object $config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Array containing request and response objects
|
||||
* @var array $web
|
||||
*/
|
||||
protected $web;
|
||||
|
||||
/**
|
||||
* 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->web = [$request, $response];
|
||||
|
||||
$this->_setup_routes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current route object, if one matches
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function get_route()
|
||||
{
|
||||
global $defaultHandler;
|
||||
|
||||
$raw_route = $_SERVER['REQUEST_URI'];
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the current route
|
||||
*
|
||||
* @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);
|
||||
|
||||
$controller_name = 'BaseController';
|
||||
$action_method = 'outputHTML';
|
||||
$params = [
|
||||
'template' => '404',
|
||||
'data' => [
|
||||
'title' => 'Page Not Found'
|
||||
]
|
||||
];
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select controller based on the current url, and apply its relevent routes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function _setup_routes()
|
||||
{
|
||||
$route_map = [
|
||||
'anime' => '\\AnimeClient\\AnimeController',
|
||||
'manga' => '\\AnimeClient\\MangaController',
|
||||
];
|
||||
$route_type = "anime";
|
||||
|
||||
if ($this->config->manga_host !== "" && strpos($_SERVER['HTTP_HOST'], $this->config->manga_host) !== FALSE)
|
||||
{
|
||||
$route_type = "manga";
|
||||
}
|
||||
else if ($this->config->manga_path !== "" && strpos($_SERVER['REQUEST_URI'], $this->config->manga_path) !== FALSE)
|
||||
{
|
||||
$route_type = "manga";
|
||||
}
|
||||
|
||||
$routes = $this->config->routes;
|
||||
|
||||
// Add routes
|
||||
foreach(['common', $route_type] as $key)
|
||||
{
|
||||
foreach($routes[$key] 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";
|
||||
|
||||
if ( ! array_key_exists('tokens', $route))
|
||||
{
|
||||
$this->router->$add($name, $path)->addValues($route);
|
||||
}
|
||||
else
|
||||
{
|
||||
$tokens = $route['tokens'];
|
||||
unset($route['tokens']);
|
||||
|
||||
$this->router->$add($name, $path)
|
||||
->addValues($route)
|
||||
->addTokens($tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// End of Router.php
|
|
@ -1,134 +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 base url for css/js/images
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function asset_url(/*...*/)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$args = func_get_args();
|
||||
$base_url = rtrim($config->asset_path, '/');
|
||||
|
||||
array_unshift($args, $base_url);
|
||||
|
||||
return implode("/", $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base url from the config
|
||||
*
|
||||
* @param string $type - (optional) The controller
|
||||
# @param object $config - (optional) Config
|
||||
* @return string
|
||||
*/
|
||||
function base_url($type="anime", $config=NULL)
|
||||
{
|
||||
if (is_null($config)) global $config;
|
||||
|
||||
|
||||
$config_path = trim($config->{"{$type}_path"}, "/");
|
||||
$config_host = $config->{"{$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
|
||||
# @param object $config - (optional) Config
|
||||
* @return string
|
||||
*/
|
||||
function full_url($path="", $type="anime", $config=NULL)
|
||||
{
|
||||
if (is_null($config)) global $config;
|
||||
|
||||
$config_path = trim($config->{"{$type}_path"}, "/");
|
||||
$config_host = $config->{"{$type}_host"};
|
||||
$config_default_route = $config->{"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 ($config->default_to_list_view) $path .= '/list';
|
||||
}
|
||||
|
||||
// Set an leading folder
|
||||
if ($config_path !== '')
|
||||
{
|
||||
$path = "{$config_path}/{$path}";
|
||||
}
|
||||
|
||||
return "//{$host}/{$path}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// End of functions.php
|
|
@ -1,43 +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);
|
||||
$class = end($class_parts);
|
||||
|
||||
$dirs = ["base", "controllers", "models"];
|
||||
|
||||
foreach($dirs as $dir)
|
||||
{
|
||||
$file = _dir(APP_DIR, $dir, "{$class}.php");
|
||||
if (file_exists($file))
|
||||
{
|
||||
require_once $file;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,57 +1,94 @@
|
|||
<?php
|
||||
|
||||
namespace AnimeClient;
|
||||
/**
|
||||
* Bootstrap / Dependency Injection
|
||||
*/
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use \Whoops\Handler\PrettyPageHandler;
|
||||
use \Whoops\Handler\JsonResponseHandler;
|
||||
use \Aura\Web\WebFactory;
|
||||
use \Aura\Router\RouterFactory;
|
||||
use \GuzzleHttp\Client;
|
||||
use \GuzzleHttp\Cookie\CookieJar;
|
||||
use Aura\Html\HelperLocatorFactory;
|
||||
use Aura\Web\WebFactory;
|
||||
use Aura\Router\RouterFactory;
|
||||
use Aura\Session\SessionFactory;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
use Monolog\Handler\BrowserConsoleHandler;
|
||||
|
||||
use Aviat\Ion\Di\Container;
|
||||
use Aviat\AnimeClient\Auth\HummingbirdAuth;
|
||||
use Aviat\AnimeClient\Model;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Setup error handling
|
||||
// Setup DI container
|
||||
// -----------------------------------------------------------------------------
|
||||
$whoops = new \Whoops\Run();
|
||||
return function(array $config_array = []) {
|
||||
$container = new Container();
|
||||
|
||||
// Set up default handler for general errors
|
||||
$defaultHandler = new PrettyPageHandler();
|
||||
$whoops->pushHandler($defaultHandler);
|
||||
// -------------------------------------------------------------------------
|
||||
// Logging
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Set up json handler for ajax errors
|
||||
$jsonHandler = new JsonResponseHandler();
|
||||
$jsonHandler->onlyForAjaxRequests(true);
|
||||
$whoops->pushHandler($jsonHandler);
|
||||
$app_logger = new Logger('animeclient');
|
||||
$app_logger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/app.log', Logger::NOTICE));
|
||||
$container->setLogger($app_logger, 'default');
|
||||
|
||||
$whoops->register();
|
||||
// -------------------------------------------------------------------------
|
||||
// Injected Objects
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Injected Objects
|
||||
// -----------------------------------------------------------------------------
|
||||
// Create Config Object
|
||||
$config = new Config($config_array);
|
||||
$container->set('config', $config);
|
||||
|
||||
// Create Config Object
|
||||
$config = new Config();
|
||||
require _dir(BASE_DIR, '/functions.php');
|
||||
// Create Aura Router Object
|
||||
$aura_router = (new RouterFactory())->newInstance();
|
||||
$container->set('aura-router', $aura_router);
|
||||
|
||||
// Create Aura Router Object
|
||||
$router_factory = new RouterFactory();
|
||||
$aura_router = $router_factory->newInstance();
|
||||
// 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,
|
||||
'_POST' => $_POST,
|
||||
'_COOKIE' => $_COOKIE,
|
||||
'_SERVER' => $_SERVER,
|
||||
'_FILES' => $_FILES
|
||||
]);
|
||||
$request = $web_factory->newRequest();
|
||||
$response = $web_factory->newResponse();
|
||||
// Create Request/Response Objects
|
||||
$web_factory = new WebFactory([
|
||||
'_GET' => $_GET,
|
||||
'_POST' => $_POST,
|
||||
'_COOKIE' => $_COOKIE,
|
||||
'_SERVER' => $_SERVER,
|
||||
'_FILES' => $_FILES
|
||||
]);
|
||||
$container->set('request', $web_factory->newRequest());
|
||||
$container->set('response', $web_factory->newResponse());
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Router
|
||||
// -----------------------------------------------------------------------------
|
||||
$router = new Router($config, $aura_router, $request, $response);
|
||||
$router->dispatch();
|
||||
// Create session Object
|
||||
$session = (new SessionFactory())->newInstance($_COOKIE);
|
||||
$container->set('session', $session);
|
||||
|
||||
$container->set('url-generator', new UrlGenerator($container));
|
||||
|
||||
|
||||
// Miscellaneous helper methods
|
||||
$anime_client = new AnimeClient();
|
||||
$anime_client->setContainer($container);
|
||||
$container->set('anime-client', $anime_client);
|
||||
|
||||
// Models
|
||||
$container->set('api-model', new Model\API($container));
|
||||
$container->set('anime-model', new Model\Anime($container));
|
||||
$container->set('manga-model', new Model\Manga($container));
|
||||
$container->set('anime-collection-model', new Model\AnimeCollection($container));
|
||||
|
||||
$container->set('auth', new HummingbirdAuth($container));
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Dispatcher
|
||||
// -------------------------------------------------------------------------
|
||||
$container->set('dispatcher', new Dispatcher($container));
|
||||
|
||||
return $container;
|
||||
};
|
||||
|
||||
// End of bootstrap.php
|
|
@ -1,15 +1,34 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Lower level configuration
|
||||
//
|
||||
// You shouldn't generally need to change anything below this line
|
||||
// ----------------------------------------------------------------------------
|
||||
$APP_DIR = realpath(__DIR__ . '/../');
|
||||
$ROOT_DIR = realpath("{$APP_DIR}/../");
|
||||
|
||||
$base_config = [
|
||||
// Template file path
|
||||
'view_path' => "{$APP_DIR}/views",
|
||||
|
||||
// Cache paths
|
||||
'data_cache_path' => _dir(APP_DIR, 'cache'),
|
||||
'img_cache_path' => _dir(ROOT_DIR, 'public/images'),
|
||||
'data_cache_path' => "{$APP_DIR}/cache",
|
||||
'img_cache_path' => "{$ROOT_DIR}/public/images",
|
||||
|
||||
// Included config files
|
||||
'routes' => require _dir(CONF_DIR, 'routes.php'),
|
||||
'database' => require _dir(CONF_DIR, 'database.php'),
|
||||
'database' => require 'database.php',
|
||||
'menus' => require 'menus.php',
|
||||
'routes' => require 'routes.php',
|
||||
];
|
|
@ -1,40 +1,37 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
$config = [
|
||||
// ----------------------------------------------------------------------------
|
||||
// Username for anime and manga lists
|
||||
// ----------------------------------------------------------------------------
|
||||
'hummingbird_username' => 'timw4mail',
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Whose list is it?
|
||||
// ----------------------------------------------------------------------------
|
||||
'whose_list' => 'Tim',
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// General config
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// do you wish to show the anime collection tab?
|
||||
// do you wish to show the anime collection?
|
||||
'show_anime_collection' => TRUE,
|
||||
|
||||
// path to public directory
|
||||
'asset_path' => '//' . $_SERVER['HTTP_HOST'] . '/public',
|
||||
// do you wish to show the manga collection?
|
||||
'show_manga_collection' => FALSE,
|
||||
|
||||
// path to public directory on the server
|
||||
'asset_dir' => __DIR__ . '/../../public',
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Routing
|
||||
//
|
||||
// Route by path, or route by domain. To route by path, set the _host suffixed
|
||||
// options to an empty string. To route by host, set the _path suffixed options
|
||||
// to an empty string
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
'anime_host' => 'anime.timshomepage.net',
|
||||
'manga_host' => 'manga.timshomepage.net',
|
||||
'anime_path' => '',
|
||||
'manga_path' => '',
|
||||
|
||||
// Default pages for anime/manga
|
||||
'default_anime_path' => '/watching',
|
||||
'default_manga_path' => '/all',
|
||||
|
||||
// Default to list view?
|
||||
'default_to_list_view' => FALSE,
|
||||
'asset_dir' => realpath(__DIR__ . '/../../public'),
|
||||
];
|
|
@ -1,4 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
return [
|
||||
'collection' => [
|
||||
'type' => 'sqlite',
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
return [
|
||||
'anime_list' => [
|
||||
'route_prefix' => '/anime',
|
||||
'items' => [
|
||||
'watching' => '/watching',
|
||||
'plan_to_watch' => '/plan_to_watch',
|
||||
'on_hold' => '/on_hold',
|
||||
'dropped' => '/dropped',
|
||||
'completed' => '/completed',
|
||||
'all' => '/all'
|
||||
]
|
||||
],
|
||||
'manga_list' => [
|
||||
'route_prefix' => '/manga',
|
||||
'items' => [
|
||||
'reading' => '/reading',
|
||||
'plan_to_read' => '/plan_to_read',
|
||||
'on_hold' => '/on_hold',
|
||||
'dropped' => '/dropped',
|
||||
'completed' => '/completed',
|
||||
'all' => '/all'
|
||||
]
|
||||
]
|
||||
];
|
|
@ -1,21 +1,20 @@
|
|||
<?php
|
||||
/**
|
||||
* Easy Min
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* Simple minification for better website performance
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2012
|
||||
* @link https://github.com/aviat4ion/Easy-Min
|
||||
* @license http://philsturgeon.co.uk/code/dbad-license
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/* $config = */require 'config.php';
|
||||
|
||||
$config = (object)$config;
|
||||
|
||||
// Should we use myth to preprocess?
|
||||
$use_myth = FALSE;
|
||||
|
||||
|
@ -27,7 +26,7 @@ $use_myth = FALSE;
|
|||
| The folder where css files exist, in relation to the document root
|
||||
|
|
||||
*/
|
||||
$css_root = $config->asset_dir. '/css/';
|
||||
$css_root = $config['asset_dir'] . '/css/';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -57,4 +56,4 @@ $path_to = '';
|
|||
| The folder where javascript files exist, in relation to the document root
|
||||
|
|
||||
*/
|
||||
$js_root = $config->asset_dir. '/js/';
|
||||
$js_root = $config['asset_dir'] . '/js/';
|
|
@ -1,13 +1,14 @@
|
|||
<?php
|
||||
/**
|
||||
* Easy Min
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* Simple minification for better website performance
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2012
|
||||
* @link https://github.com/aviat4ion/Easy-Min
|
||||
* @license http://philsturgeon.co.uk/code/dbad-license
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<?php
|
||||
/**
|
||||
* Easy Min
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* Simple minification for better website performance
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2012
|
||||
* @link https://github.com/aviat4ion/Easy-Min
|
||||
* @license http://philsturgeon.co.uk/code/dbad-license
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
@ -34,6 +35,26 @@ return [
|
|||
'show_message.js',
|
||||
'anime_edit.js',
|
||||
'manga_edit.js'
|
||||
],
|
||||
'table_edit' => [
|
||||
'lib/jquery.min.js',
|
||||
'lib/table_sorter/jquery.tablesorter.min.js',
|
||||
'sort_tables.js',
|
||||
'show_message.js',
|
||||
'anime_edit.js',
|
||||
'manga_edit.js'
|
||||
],
|
||||
'anime_collection' => [
|
||||
'lib/jquery.min.js',
|
||||
'lib/jquery.throttle-debounce.js',
|
||||
'lib/jsrender.js',
|
||||
'anime_collection.js'
|
||||
],
|
||||
'manga_collection' => [
|
||||
'lib/jquery.min.js',
|
||||
'lib/jquery.throttle-debounce.js',
|
||||
'lib/jsrender.js',
|
||||
'manga_collection.js'
|
||||
]
|
||||
];
|
||||
|
||||
|
|
|
@ -1,188 +1,150 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
use Aviat\AnimeClient\AnimeClient;
|
||||
|
||||
return [
|
||||
// Routes on all controllers
|
||||
'common' => [
|
||||
'update' => [
|
||||
'path' => '/update',
|
||||
'action' => ['update'],
|
||||
'verb' => 'post'
|
||||
],
|
||||
'login_form' => [
|
||||
'path' => '/login',
|
||||
'action' => ['login'],
|
||||
// -------------------------------------------------------------------------
|
||||
// Routing options
|
||||
//
|
||||
// Specify default paths and views
|
||||
// -------------------------------------------------------------------------
|
||||
'route_config' => [
|
||||
// Subfolder prefix for url, if in a subdirectory of the web root
|
||||
'subfolder_prefix' => '',
|
||||
|
||||
// Path to public directory, where images/css/javascript are located,
|
||||
// appended to the url
|
||||
'asset_path' => '/public',
|
||||
|
||||
// Which list should be the default?
|
||||
'default_list' => 'anime', // anime or manga
|
||||
|
||||
// Default pages for anime/manga
|
||||
'default_anime_list_path' => "watching", // watching|plan_to_watch|on_hold|dropped|completed|all
|
||||
'default_manga_list_path' => "reading", // reading|plan_to_read|on_hold|dropped|completed|all
|
||||
|
||||
// Default view type (cover_view/list_view)
|
||||
'default_view_type' => 'cover_view',
|
||||
],
|
||||
// -------------------------------------------------------------------------
|
||||
// Routing Config
|
||||
//
|
||||
// Maps paths to controlers and methods
|
||||
// -------------------------------------------------------------------------
|
||||
'routes' => [
|
||||
// ---------------------------------------------------------------------
|
||||
// Anime List Routes
|
||||
// ---------------------------------------------------------------------
|
||||
'anime_add_form' => [
|
||||
'path' => '/anime/add',
|
||||
'action' => 'add_form',
|
||||
'verb' => 'get'
|
||||
],
|
||||
'login_action' => [
|
||||
'path' => '/login',
|
||||
'action' => ['login_action'],
|
||||
'anime_add' => [
|
||||
'path' => '/anime/add',
|
||||
'action' => 'add',
|
||||
'verb' => 'post'
|
||||
],
|
||||
'logout' => [
|
||||
'path' => '/logout',
|
||||
'action' => ['logout']
|
||||
// ---------------------------------------------------------------------
|
||||
// Anime Collection Routes
|
||||
// ---------------------------------------------------------------------
|
||||
'collection_search' => [
|
||||
'path' => '/collection/search',
|
||||
'action' => 'search'
|
||||
],
|
||||
],
|
||||
// Routes on anime controller
|
||||
'anime' => [
|
||||
'index' => [
|
||||
'path' => '/',
|
||||
'action' => ['redirect'],
|
||||
'params' => [
|
||||
'url' => '', // Determined by config
|
||||
'code' => '301'
|
||||
]
|
||||
'collection_add_form' => [
|
||||
'path' => '/collection/add',
|
||||
'action' => 'form',
|
||||
'params' => [],
|
||||
],
|
||||
'all' => [
|
||||
'path' => '/all{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'all',
|
||||
'title' => WHOSE . " Anime List · All"
|
||||
],
|
||||
'collection_edit_form' => [
|
||||
'path' => '/collection/edit/{id}',
|
||||
'action' => 'form',
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
'id' => '[0-9]+'
|
||||
]
|
||||
],
|
||||
'watching' => [
|
||||
'path' => '/watching{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'currently-watching',
|
||||
'title' => WHOSE . " Anime List · Watching"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
'collection_add' => [
|
||||
'path' => '/collection/add',
|
||||
'action' => 'add',
|
||||
'verb' => 'post'
|
||||
],
|
||||
'plan_to_watch' => [
|
||||
'path' => '/plan_to_watch{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'plan-to-watch',
|
||||
'title' => WHOSE . " Anime List · Plan to Watch"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'on_hold' => [
|
||||
'path' => '/on_hold{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'on-hold',
|
||||
'title' => WHOSE . " Anime List · On Hold"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'dropped' => [
|
||||
'path' => '/dropped{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'dropped',
|
||||
'title' => WHOSE . " Anime List · Dropped"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'completed' => [
|
||||
'path' => '/completed{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'completed',
|
||||
'title' => WHOSE . " Anime List · Completed"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
'collection_edit' => [
|
||||
'path' => '/collection/edit',
|
||||
'action' => 'edit',
|
||||
'verb' => 'post'
|
||||
],
|
||||
'collection' => [
|
||||
'path' => '/collection{/view}',
|
||||
'action' => ['collection'],
|
||||
'path' => '/collection/view{/view}',
|
||||
'action' => 'index',
|
||||
'params' => [],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
]
|
||||
],
|
||||
'manga' => [
|
||||
'index' => [
|
||||
],
|
||||
// ---------------------------------------------------------------------
|
||||
// Default / Shared routes
|
||||
// ---------------------------------------------------------------------
|
||||
'login' => [
|
||||
'path' => '/{controller}/login',
|
||||
'action' => 'login',
|
||||
],
|
||||
'login_post' => [
|
||||
'path' => '/{controller}/login',
|
||||
'action' => 'login_action',
|
||||
'verb' => 'post'
|
||||
],
|
||||
'logout' => [
|
||||
'path' => '/{controller}/logout',
|
||||
'action' => 'logout'
|
||||
],
|
||||
'update' => [
|
||||
'path' => '/{controller}/update',
|
||||
'action' => 'update',
|
||||
'verb' => 'post',
|
||||
'tokens' => [
|
||||
'controller' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'update_form' => [
|
||||
'path' => '/{controller}/update_form',
|
||||
'action' => 'form_update',
|
||||
'verb' => 'post',
|
||||
'tokens' => [
|
||||
'controller' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'edit' => [
|
||||
'path' => '/{controller}/edit/{id}/{status}',
|
||||
'action' => 'edit',
|
||||
'tokens' => [
|
||||
'id' => '[0-9a-z_]+',
|
||||
'status' => '[a-zA-z\- ]+',
|
||||
]
|
||||
],
|
||||
'list' => [
|
||||
'path' => '/{controller}/{type}{/view}',
|
||||
'action' => AnimeClient::DEFAULT_CONTROLLER_METHOD,
|
||||
'tokens' => [
|
||||
'type' => '[a-z_]+',
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'index_redirect' => [
|
||||
'path' => '/',
|
||||
'action' => ['redirect'],
|
||||
'params' => [
|
||||
'url' => '', // Determined by config
|
||||
'code' => '301',
|
||||
'type' => 'manga'
|
||||
]
|
||||
'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE,
|
||||
'action' => 'redirect_to_default'
|
||||
],
|
||||
'all' => [
|
||||
'path' => '/all{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'all',
|
||||
'title' => WHOSE . " Manga List · All"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'reading' => [
|
||||
'path' => '/reading{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'Reading',
|
||||
'title' => WHOSE . " Manga List · Reading"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'plan_to_read' => [
|
||||
'path' => '/plan_to_read{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'Plan to Read',
|
||||
'title' => WHOSE . " Manga List · Plan to Read"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'on_hold' => [
|
||||
'path' => '/on_hold{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'On Hold',
|
||||
'title' => WHOSE . " Manga List · On Hold"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'dropped' => [
|
||||
'path' => '/dropped{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'Dropped',
|
||||
'title' => WHOSE . " Manga List · Dropped"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'completed' => [
|
||||
'path' => '/completed{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'Completed',
|
||||
'title' => WHOSE . " Manga List · Completed"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
|
@ -1,121 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Anime Controller
|
||||
*/
|
||||
|
||||
namespace AnimeClient;
|
||||
|
||||
/**
|
||||
* Controller for Anime-related pages
|
||||
*/
|
||||
class AnimeController 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}',
|
||||
'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();
|
||||
$this->collection_model = new AnimeCollectionModel();
|
||||
$this->base_data = [
|
||||
'message' => '',
|
||||
'url_type' => 'anime',
|
||||
'other_type' => 'manga',
|
||||
'nav_routes' => $this->nav_routes,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an anime item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
print_r($this->model->update($this->request->post->get()));
|
||||
}
|
||||
}
|
||||
// End of AnimeController.php
|
|
@ -1,87 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Manga Controller
|
||||
*/
|
||||
namespace AnimeClient;
|
||||
|
||||
/**
|
||||
* Controller for manga list
|
||||
*/
|
||||
class MangaController extends BaseController {
|
||||
|
||||
/**
|
||||
* 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();
|
||||
$this->base_data = [
|
||||
'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,206 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Anime Collection DB Model
|
||||
*/
|
||||
|
||||
namespace AnimeClient;
|
||||
|
||||
/**
|
||||
* Model for getting anime collection data
|
||||
*/
|
||||
class AnimeCollectionModel extends BaseDBModel {
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->db = \Query($this->db_config['collection']);
|
||||
$this->anime_model = new AnimeModel();
|
||||
|
||||
// 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 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 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' => $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
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function update_genres()
|
||||
{
|
||||
$genres = [];
|
||||
$flipped_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']];
|
||||
}
|
||||
}
|
||||
|
||||
// Get the anime collection
|
||||
$collection = $this->_get_collection();
|
||||
foreach($collection as $anime)
|
||||
{
|
||||
// Get api information
|
||||
$api = $this->anime_model->get_anime($anime['hummingbird_id']);
|
||||
|
||||
|
||||
foreach($api['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['hummingbird_id'],
|
||||
'genre_id' => $flipped_genres[$genre['name']]
|
||||
];
|
||||
|
||||
if (array_key_exists($anime['hummingbird_id'], $links))
|
||||
{
|
||||
if ( ! in_array($flipped_genres[$genre['name']], $links[$anime['hummingbird_id']]))
|
||||
{
|
||||
$this->db->set($insert_array)->insert('genre_anime_set_link');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->db->set($insert_array)->insert('genre_anime_set_link');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// End of AnimeCollectionModel.php
|
|
@ -1,245 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Anime API Model
|
||||
*/
|
||||
|
||||
namespace AnimeClient;
|
||||
|
||||
/**
|
||||
* Model for handling requests dealing with the anime list
|
||||
*/
|
||||
class AnimeModel extends BaseApiModel {
|
||||
/**
|
||||
* The base url for api requests
|
||||
* @var string $base_url
|
||||
*/
|
||||
protected $base_url = "https://hummingbird.me/api/v1/";
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Exception($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 Exception($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,193 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Manga API Model
|
||||
*/
|
||||
namespace AnimeClient;
|
||||
|
||||
/**
|
||||
* Model for handling requests dealing with the manga list
|
||||
*/
|
||||
class MangaModel extends BaseApiModel {
|
||||
|
||||
/**
|
||||
* The base url for api requests
|
||||
* @var string
|
||||
*/
|
||||
protected $base_url = "https://hummingbird.me/";
|
||||
|
||||
|
||||
/**
|
||||
* 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 Exception($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 (empty($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;
|
||||
}
|
||||
}
|
||||
|
||||
//file_put_contents(_dir($this->config->data_cache_path, "manga-processed.json"), json_encode($data, JSON_PRETTY_PRINT));
|
||||
|
||||
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
|
|
@ -1,7 +1,4 @@
|
|||
<body>
|
||||
<main>
|
||||
<h1>404</h1>
|
||||
<h2>Page Not Found</h2>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
<main>
|
||||
<h1>404</h1>
|
||||
<h2>Page Not Found</h2>
|
||||
</main>
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<?php if ($auth->is_authenticated()): ?>
|
||||
<main>
|
||||
<h2>Add Anime to your List</h2>
|
||||
<form action="<?= $action_url ?>" method="post">
|
||||
<section>
|
||||
<label for="search">Search for anime by name: <input type="search" id="search" /></label>
|
||||
<section id="series_list" class="media-wrap">
|
||||
</section>
|
||||
</section>
|
||||
<br />
|
||||
<table class="form">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="status">Watching Status</label></td>
|
||||
<td>
|
||||
<select name="status" id="status">
|
||||
<?php foreach($status_list as $status_key => $status_title): ?>
|
||||
<option value="<?= $status_key ?>"><?= $status_title ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<button type="submit">Save</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</main>
|
||||
<template id="show_list">
|
||||
<article class="media">
|
||||
<div class="name"><label><input type="radio" name="id" value="{{:slug}}" /> <span>{{:title}}<br />{{:alternate_title}}</span></label></div>
|
||||
<img src="{{:cover_image}}" alt="{{:title}}" />
|
||||
</article>
|
||||
</template>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=anime_collection') ?>"></script>
|
||||
<?php endif ?>
|
|
@ -1,28 +0,0 @@
|
|||
<main>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<section class="status">
|
||||
<h2><?= $name ?></h2>
|
||||
<section class="media-wrap">
|
||||
<?php foreach($items as $item): ?>
|
||||
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
|
||||
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
|
||||
<img src="<?= $item['cover_image'] ?>" />
|
||||
<div class="name">
|
||||
<?= $item['title'] ?>
|
||||
<?= ($item['alternate_title'] != "") ? "<br />({$item['alternate_title']})" : ""; ?>
|
||||
</div>
|
||||
<div class="table">
|
||||
<div class="row">
|
||||
<div class="completion">Episodes: <?= $item['episode_count'] ?></div>
|
||||
<div class="media_type"><?= $item['show_type'] ?></div>
|
||||
<div class="age_rating"><?= $item['age_rating'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</a>
|
||||
<?php endforeach ?>
|
||||
</section>
|
||||
</section>
|
||||
<?php endforeach ?>
|
||||
</main>
|
|
@ -1,43 +0,0 @@
|
|||
<main>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<h2><?= $name ?></h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Alternate Title</th>
|
||||
<th>Episode Count</th>
|
||||
<th>Episode Length</th>
|
||||
<th>Show Type</th>
|
||||
<th>Age Rating</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($items as $item): ?>
|
||||
<tr>
|
||||
<td class="align_left">
|
||||
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
|
||||
<?= $item['title'] ?>
|
||||
</a>
|
||||
</td>
|
||||
<td class="align_left"><?= $item['alternate_title'] ?></td>
|
||||
<td><?= $item['episode_count'] ?></td>
|
||||
<td><?= $item['episode_length'] ?></td>
|
||||
<td><?= $item['show_type'] ?></td>
|
||||
<td><?= $item['age_rating'] ?></td>
|
||||
<td class="align_left"><?= $item['notes'] ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
<?php endforeach ?>
|
||||
</main>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="/public/js/table_sorter/jquery.tablesorter.min.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
$('table').tablesorter();
|
||||
});
|
||||
</script>
|
|
@ -1,32 +1,60 @@
|
|||
<main>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<a class="bracketed" href="<?= $urlGenerator->url('anime/add', 'anime') ?>">Add Item</a>
|
||||
<?php endif ?>
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<section class="status">
|
||||
<h2><?= $name ?></h2>
|
||||
<h2><?= $escape->html($name) ?></h2>
|
||||
<section class="media-wrap">
|
||||
<?php foreach($items as $item): ?>
|
||||
<article class="media" id="a-<?= $item['anime']['id'] ?>">
|
||||
<?php if (is_logged_in()): ?>
|
||||
<button class="plus_one" hidden>+1 Episode</button>
|
||||
<?php if ($item['private'] && ! $auth->is_authenticated()) continue; ?>
|
||||
<article class="media" id="<?= $item['anime']['slug'] ?>">
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<button title="Increment episode count" class="plus_one" hidden>+1 Episode</button>
|
||||
<?php endif ?>
|
||||
<img src="<?= $item['anime']['cover_image'] ?>" />
|
||||
<?= $helper->img($item['anime']['image']); ?>
|
||||
<div class="name">
|
||||
<a href="<?= $item['anime']['url'] ?>">
|
||||
<?= $item['anime']['title'] ?>
|
||||
<a href="<?= $escape->attr($item['anime']['url']) ?>" target="_blank">
|
||||
<?= $escape->html($item['anime']['title']) ?>
|
||||
<?= ($item['anime']['alternate_title'] != "") ? "<br />({$item['anime']['alternate_title']})" : ""; ?>
|
||||
</a>
|
||||
</div>
|
||||
<div class="table">
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<div class="row">
|
||||
<div class="user_rating">Rating: <?= ($item['rating']['value'] > 0) ? (int)($item['rating']['value'] * 2) : " - " ?> / 10</div>
|
||||
<span class="edit">
|
||||
<a class="bracketed" title="Edit information about this anime" href="<?= $urlGenerator->url("anime/edit/{$item['id']}/{$item['watching_status']}") ?>">Edit</a>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php if ($item['private'] || $item['rewatching']): ?>
|
||||
<div class="row">
|
||||
<?php foreach(['private', 'rewatching'] as $attr): ?>
|
||||
<?php if($item[$attr]): ?>
|
||||
<span class="item-<?= $attr ?>"><?= ucfirst($attr) ?></span>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php if ($item['rewatched'] > 0): ?>
|
||||
<div class="row">
|
||||
<div>Rewatched <?= $item['rewatched'] ?> time(s)</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="row">
|
||||
<div class="user_rating">Rating: <?= $item['user_rating'] ?> / 10</div>
|
||||
<div class="completion">Episodes:
|
||||
<span class="completed_number"><?= $item['episodes_watched'] ?></span> /
|
||||
<span class="total_number"><?= ($item['anime']['episode_count'] != 0) ? $item['anime']['episode_count'] : "-" ?></span>
|
||||
<span class="completed_number"><?= $item['episodes']['watched'] ?></span> /
|
||||
<span class="total_number"><?= $item['episodes']['total'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="media_type"><?= $item['anime']['show_type'] ?></div>
|
||||
<div class="airing_status"><?= $item['anime']['status'] ?></div>
|
||||
<div class="age_rating"><?= $item['anime']['age_rating'] ?></div>
|
||||
<div class="media_type"><?= $escape->html($item['anime']['type']) ?></div>
|
||||
<div class="airing_status"><?= $escape->html($item['airing']['status']) ?></div>
|
||||
<div class="age_rating"><?= $escape->html($item['anime']['age_rating']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
@ -34,7 +62,8 @@
|
|||
</section>
|
||||
</section>
|
||||
<?php endforeach ?>
|
||||
</main>
|
||||
<?php if (is_logged_in()): ?>
|
||||
<script src="<?= asset_url('js.php?g=edit') ?>"></script>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=edit') ?>"></script>
|
||||
<?php endif ?>
|
||||
|
|
|
@ -1,8 +1,89 @@
|
|||
<body>
|
||||
<?php include 'nav.php' ?>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<main>
|
||||
<h2>Edit Anime List Item</h2>
|
||||
<form action="<?= $action ?>" method="post">
|
||||
<table class="form">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<h3><?= $escape->html($item['anime']['title']) ?></h3>
|
||||
<?php if($item['anime']['alternate_title'] != ""): ?>
|
||||
<h4><?= $escape->html($item['anime']['alternate_title']) ?></h4>
|
||||
<?php endif ?>
|
||||
</th>
|
||||
<th>
|
||||
<article class="media">
|
||||
<?= $helper->img($item['anime']['image']); ?>
|
||||
</article>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="private">Is Private?</label></td>
|
||||
<td>
|
||||
<input type="checkbox" name="private" id="private"
|
||||
<?php if($item['private']): ?>checked="checked"<?php endif ?>
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="watching_status">Watching Status</label></td>
|
||||
<td>
|
||||
<select name="watching_status" id="watching_status">
|
||||
<?php foreach($statuses as $status_key => $status_title): ?>
|
||||
<option <?php if($item['watching_status'] === $status_key): ?>selected="selected"<?php endif ?>
|
||||
value="<?= $status_key ?>"><?= $status_title ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="series_rating">Rating</label></td>
|
||||
<td>
|
||||
<input type="number" min="0" max="10" maxlength="2" name="user_rating" id="series_rating" value="<?= $item['user_rating'] ?>" id="series_rating" size="2" /> / 10
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="episodes_watched">Episodes Watched</label></td>
|
||||
<td>
|
||||
<input type="number" min="0" size="4" maxlength="4" value="<?= $item['episodes']['watched'] ?>" name="episodes_watched" id="episodes_watched" />
|
||||
<?php if($item['episodes']['total'] > 0): ?>
|
||||
/ <?= $item['episodes']['total'] ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="rewatching_flag">Rewatching?</label></td>
|
||||
<td>
|
||||
<input type="checkbox" name="rewatching" id="rewatching_flag"
|
||||
<?php if($item['rewatching'] === TRUE): ?>checked="checked"<?php endif ?>
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="rewatched">Rewatch Count</label></td>
|
||||
<td>
|
||||
<input type="number" min="0" id="rewatched" name="rewatched" value="<?= $item['rewatched'] ?>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="notes">Notes</label></td>
|
||||
<td>
|
||||
<textarea name="notes" id="notes"><?= $escape->html($item['notes']) ?></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<input type="hidden" value="<?= $item['anime']['slug'] ?>" name="id" />
|
||||
<input type="hidden" value="true" name="edit" />
|
||||
<button type="submit">Submit</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=edit') ?>"></script>
|
||||
<?php endif ?>
|
|
@ -1,36 +1,77 @@
|
|||
<main>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<a class="bracketed" href="<?= $urlGenerator->url('anime/add', 'anime') ?>">Add Item</a>
|
||||
<?php endif ?>
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<h2><?= $name ?></h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<?php if($auth->is_authenticated()): ?>
|
||||
<th> </th>
|
||||
<?php endif ?>
|
||||
<th>Title</th>
|
||||
<th>Alternate Title</th>
|
||||
<th>Airing Status</th>
|
||||
<th>Score</th>
|
||||
<th>Type</th>
|
||||
<th>Progress</th>
|
||||
<th>Rated</th>
|
||||
<th>Attributes</th>
|
||||
<th>Notes</th>
|
||||
<th>Genres</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($items as $item): ?>
|
||||
<tr id="a-<?= $item['anime']['id'] ?>">
|
||||
<?php if ($item['private'] && ! $auth->is_authenticated()) continue; ?>
|
||||
<tr id="a-<?= $item['id'] ?>">
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<td>
|
||||
<a class="bracketed" href="<?= $urlGenerator->url("/anime/edit/{$item['id']}/{$item['watching_status']}") ?>">Edit</a>
|
||||
</td>
|
||||
<?php endif ?>
|
||||
<td class="align_left">
|
||||
<a href="<?= $item['anime']['url'] ?>">
|
||||
<a href="<?= $item['anime']['url'] ?>" target="_blank">
|
||||
<?= $item['anime']['title'] ?>
|
||||
</a>
|
||||
<?= ( ! empty($item['anime']['alternate_title'])) ? " <br /> " . $item['anime']['alternate_title'] : "" ?>
|
||||
</td>
|
||||
<td class="align_left"><?= $item['airing']['status'] ?></td>
|
||||
<td><?= $item['user_rating'] ?> / 10 </td>
|
||||
<td><?= $item['anime']['type'] ?></td>
|
||||
<td id="<?= $item['anime']['slug'] ?>">
|
||||
Episodes: <br />
|
||||
<span class="completed_number"><?= $item['episodes']['watched'] ?></span> / <span class="total_number"><?= $item['episodes']['total'] ?></span>
|
||||
</td>
|
||||
<td class="align_left"><?= $item['anime']['alternate_title'] ?></td>
|
||||
<td class="align_left"><?= $item['anime']['status'] ?></td>
|
||||
<td><?= (int)($item['rating']['value'] * 2) ?> / 10 </td>
|
||||
<td><?= $item['anime']['show_type'] ?></td>
|
||||
<td>Episodes: <?= $item['episodes_watched'] ?> / <?= $item['anime']['episode_count'] ?></td>
|
||||
<td><?= $item['anime']['age_rating'] ?></td>
|
||||
<td>
|
||||
<?php if ($item['rewatched'] > 0): ?>
|
||||
Rewatched <?= $item['rewatched'] ?> time(s)<br />
|
||||
<?php endif ?>
|
||||
<?php $attr_list = []; ?>
|
||||
<?php foreach(['private','rewatching'] as $attr): ?>
|
||||
<?php if($item[$attr]): ?>
|
||||
<?php $attr_list[] = ucfirst($attr); ?>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
<?= implode(', ', $attr_list); ?>
|
||||
</td>
|
||||
<td>
|
||||
<p><?= $escape->html($item['notes']) ?></p>
|
||||
</td>
|
||||
<td class="align_left">
|
||||
<?php sort($item['anime']['genres']) ?>
|
||||
<?= join(', ', $item['anime']['genres']) ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
<script src="<?= asset_url('js.php?g=table') ?>"></script>
|
||||
<?php $group = ($auth->is_authenticated()) ? 'table_edit' : 'table' ?>
|
||||
<script src="<?= $urlGenerator->asset_url("js.php?g={$group}") ?>"></script>
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?php if ($auth->is_authenticated()): ?>
|
||||
<main>
|
||||
<h2>Add Anime to your Collection</h2>
|
||||
<form action="<?= $action_url ?>" method="post">
|
||||
<section>
|
||||
<label for="search">Search for anime by name: <input type="search" id="search" name="search" /></label>
|
||||
<section id="series_list" class="media-wrap">
|
||||
</section>
|
||||
</section>
|
||||
<br />
|
||||
<table class="form">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="media_id">Media</label></td>
|
||||
<td>
|
||||
<select name="media_id" id="media_id">
|
||||
<?php foreach($media_items as $id => $name): ?>
|
||||
<option value="<?= $id ?>"><?= $name ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="notes">Notes</label></td>
|
||||
<td><textarea id="notes" name="notes"></textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<button type="submit">Save</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</main>
|
||||
<template id="show_list">
|
||||
<article class="media">
|
||||
<div class="name"><label><input type="radio" name="id" value="{{:id}}" /> <span>{{:title}}<br />{{:alternate_title}}</span></label></div>
|
||||
<img src="{{:cover_image}}" alt="{{:title}}" />
|
||||
</article>
|
||||
</template>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=anime_collection') ?>"></script>
|
||||
<?php endif ?>
|
|
@ -0,0 +1,40 @@
|
|||
<main>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<a class="bracketed" href="<?= $urlGenerator->url('collection/add', 'anime') ?>">Add Item</a>
|
||||
<?php endif ?>
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<section class="status">
|
||||
<h2><?= $name ?></h2>
|
||||
<section class="media-wrap">
|
||||
<?php foreach($items as $item): ?>
|
||||
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
|
||||
<img src="<?= $urlGenerator->asset_url('images', 'anime', basename($item['cover_image'])) ?>" />
|
||||
<div class="name">
|
||||
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
|
||||
<?= $item['title'] ?>
|
||||
<?= ($item['alternate_title'] != "") ? "<br />({$item['alternate_title']})" : ""; ?>
|
||||
</a>
|
||||
</div>
|
||||
<div class="table">
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<div class="row">
|
||||
<span class="edit"><a class="bracketed" href="<?= $urlGenerator->url("collection/edit/{$item['hummingbird_id']}") ?>">Edit</a></span>
|
||||
<?php /*<span class="delete"><a class="bracketed" href="<?= $urlGenerator->url("collection/delete/{$item['hummingbird_id']}") ?>">Delete</a></span> */ ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="row">
|
||||
<div class="completion">Episodes: <?= $item['episode_count'] ?></div>
|
||||
<div class="media_type"><?= $item['show_type'] ?></div>
|
||||
<div class="age_rating"><?= $item['age_rating'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach ?>
|
||||
</section>
|
||||
</section>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</main>
|
|
@ -0,0 +1,56 @@
|
|||
<?php if ($auth->is_authenticated()): ?>
|
||||
<main>
|
||||
<h2>Edit Anime Collection Item</h2>
|
||||
<form action="<?= $action_url ?>" method="post">
|
||||
<table class="form">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<h3><?= $escape->html($item['title']) ?></h3>
|
||||
<?php if($item['alternate_title'] != ""): ?>
|
||||
<h4><?= $escape->html($item['alternate_title']) ?></h4>
|
||||
<?php endif ?>
|
||||
</th>
|
||||
<th>
|
||||
<article class="media">
|
||||
<?= $helper->img($item['cover_image']); ?>
|
||||
</article>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="media_id">Media</label></td>
|
||||
<td>
|
||||
<select name="media_id" id="media_id">
|
||||
<?php foreach($media_items as $id => $name): ?>
|
||||
<option <?= $item['media_id'] == $id ? 'selected="selected"' : '' ?> value="<?= $id ?>"><?= $name ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="notes">Notes</label></td>
|
||||
<td><textarea id="notes" name="notes"><?= $escape->html($item['notes']) ?></textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<?php if($action === 'Edit'): ?>
|
||||
<input type="hidden" name="hummingbird_id" value="<?= $item['hummingbird_id'] ?>" />
|
||||
<?php endif ?>
|
||||
<button type="submit">Save</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</main>
|
||||
<template id="show_list">
|
||||
<article class="media">
|
||||
<div class="name"><label><input type="radio" name="id" value="{{:id}}" /> <span>{{:title}}<br />{{:alternate_title}}</span></label></div>
|
||||
<img src="{{:cover_image}}" alt="{{:title}}" />
|
||||
</article>
|
||||
</template>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=anime_collection') ?>"></script>
|
||||
<?php endif ?>
|
|
@ -0,0 +1,52 @@
|
|||
<main>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<a class="bracketed" href="<?= $urlGenerator->full_url('collection/add', 'anime') ?>">Add Item</a>
|
||||
<?php endif ?>
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<h2><?= $name ?></h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<?php if($auth->is_authenticated()): ?>
|
||||
<th>Actions</th>
|
||||
<?php endif ?>
|
||||
<th>Title</th>
|
||||
<th>Episode Count</th>
|
||||
<th>Episode Length</th>
|
||||
<th>Show Type</th>
|
||||
<th>Age Rating</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($items as $item): ?>
|
||||
<tr>
|
||||
<?php if($auth->is_authenticated()): ?>
|
||||
<td>
|
||||
<a class="bracketed" href="<?= $urlGenerator->full_url("collection/edit/{$item['hummingbird_id']}") ?>">Edit</a>
|
||||
<?php /*<a class="bracketed" href="<?= $urlGenerator->full_url("collection/delete/{$item['hummingbird_id']}") ?>">Delete</a>*/ ?>
|
||||
</td>
|
||||
<?php endif ?>
|
||||
<td class="align_left">
|
||||
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
|
||||
<?= $item['title'] ?>
|
||||
</a>
|
||||
<?= ( ! empty($item['alternate_title'])) ? " · " . $item['alternate_title'] : "" ?>
|
||||
</td>
|
||||
<td><?= $item['episode_count'] ?></td>
|
||||
<td><?= $item['episode_length'] ?></td>
|
||||
<td><?= $item['show_type'] ?></td>
|
||||
<td><?= $item['age_rating'] ?></td>
|
||||
<td class="align_left"><?= $item['notes'] ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=table') ?>"></script>
|
|
@ -0,0 +1,5 @@
|
|||
<main>
|
||||
<h1><?= $title ?></h1>
|
||||
<h2><?= $message ?></h2>
|
||||
<div><?= $long_message ?></div>
|
||||
</main>
|
|
@ -1,35 +1,58 @@
|
|||
<?php namespace Aviat\AnimeClient ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title><?= $title ?></title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="<?= asset_url('css.php?g=base') ?>" />
|
||||
<link rel="stylesheet" href="<?= $urlGenerator->asset_url('css.php?g=base') ?>" />
|
||||
<script>
|
||||
var BASE_URL = "<?= base_url($url_type) ?>";
|
||||
var BASE_URL = "<?= $urlGenerator->base_url($url_type) ?>";
|
||||
var CONTROLLER = "<?= $url_type ?>";
|
||||
</script>
|
||||
</head>
|
||||
<body class="<?= $url_type ?> list">
|
||||
<h1 class="flex flex-align-end flex-wrap">
|
||||
<span class="flex-no-wrap grow-1"><?= WHOSE ?> <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?> [<a href="<?= full_url("", $other_type) ?>"><?= ucfirst($other_type) ?> List</a>]</span>
|
||||
<span class="flex-no-wrap small-font">
|
||||
<?php if (is_logged_in()): ?>
|
||||
[<a href="<?= full_url("/logout", $url_type) ?>">Logout</a>]
|
||||
<?php else: ?>
|
||||
[<a href="<?= full_url("/login", $url_type) ?>"><?= WHOSE ?> Login</a>]
|
||||
<body class="<?= $escape->attr($url_type) ?> list">
|
||||
<header>
|
||||
<h1 class="flex flex-align-end flex-wrap">
|
||||
<span class="flex-no-wrap grow-1">
|
||||
<?php if(strpos($route_path, 'collection') === FALSE): ?>
|
||||
<a href="<?= $escape->attr($urlGenerator->default_url($url_type)) ?>">
|
||||
<?= $config->get('whose_list') ?>'s <?= ucfirst($url_type) ?> List
|
||||
</a>
|
||||
<?php if($config->get("show_{$url_type}_collection")): ?>
|
||||
[<a href="<?= $urlGenerator->url('collection/view') ?>"><?= ucfirst($url_type) ?> Collection</a>]
|
||||
<?php endif ?>
|
||||
[<a href="<?= $urlGenerator->default_url($other_type) ?>"><?= ucfirst($other_type) ?> List</a>]
|
||||
<?php else: ?>
|
||||
<a href="<?= $urlGenerator->url('collection/view') ?>">
|
||||
<?= $config->get('whose_list') ?>'s <?= ucfirst($url_type) ?> Collection
|
||||
</a>
|
||||
[<a href="<?= $urlGenerator->default_url('anime') ?>">Anime List</a>]
|
||||
[<a href="<?= $urlGenerator->default_url('manga') ?>">Manga List</a>]
|
||||
<?php endif ?>
|
||||
</span>
|
||||
<span class="flex-no-wrap small-font">
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<a class="bracketed" href="<?= $urlGenerator->url("/{$url_type}/logout", $url_type) ?>">Logout</a>
|
||||
<?php else: ?>
|
||||
[<a href="<?= $urlGenerator->url("/{$url_type}/login", $url_type) ?>"><?= $config->get('whose_list') ?>'s Login</a>]
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</h1>
|
||||
<nav>
|
||||
<?php if ($container->get('anime-client')->is_view_page()): ?>
|
||||
<?= $helper->menu($menu_name) ?>
|
||||
<br />
|
||||
<ul>
|
||||
<li class="<?= AnimeClient::is_not_selected('list', $urlGenerator->last_segment()) ?>"><a href="<?= $urlGenerator->url($route_path) ?>">Cover View</a></li>
|
||||
<li class="<?= AnimeClient::is_selected('list', $urlGenerator->last_segment()) ?>"><a href="<?= $urlGenerator->url("{$route_path}/list") ?>">List View</a></li>
|
||||
</ul>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<?php foreach($nav_routes as $title => $nav_path): ?>
|
||||
<li class="<?= is_selected($nav_path, $route_path) ?>"><a href="<?= full_url($nav_path, $url_type) ?>"><?= $title ?></a></li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<br />
|
||||
<ul>
|
||||
<li class="<?= is_not_selected('list', last_segment()) ?>"><a href="<?= full_url($route_path, $url_type) ?>">Cover View</a></li>
|
||||
<li class="<?= is_selected('list', last_segment()) ?>"><a href="<?= full_url("{$route_path}/list", $url_type) ?>">List View</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<br />
|
||||
</nav>
|
||||
</header>
|
||||
<?php if(isset($message) && is_array($message)): ?>
|
||||
<div class="message <?= $escape->attr($message['message_type']) ?>">
|
||||
<span class="icon"></span>
|
||||
<?= $escape->html($message['message']) ?>
|
||||
<span class="close" onclick="this.parentElement.style.display='none'">x</span>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
<main>
|
||||
<h2><?= $config->get('whose_list'); ?>'s Login</h2>
|
||||
<?= $message ?>
|
||||
<aside>
|
||||
<form method="post" action="<?= full_url('/login', $url_type) ?>">
|
||||
<dl>
|
||||
<dt><label for="username">Username: </label></dt>
|
||||
<dd><input type="text" id="username" name="username" required="required" /></dd>
|
||||
|
||||
<dt><label for="password">Password: </label></dt>
|
||||
<dd><input type="password" id="password" name="password" required="required" /></dd>
|
||||
|
||||
<dt> </dt>
|
||||
<dd><input type="submit" value="Login" /></dd>
|
||||
</dl>
|
||||
</form>
|
||||
</aside>
|
||||
<form method="post" action="<?= $urlGenerator->full_url($urlGenerator->path(), $url_type) ?>">
|
||||
<table class="form invisible">
|
||||
<tr>
|
||||
<td><label for="password">Password: </label></td>
|
||||
<td><input type="password" id="password" name="password" required="required" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td><button type="submit">Login</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</main>
|
|
@ -1,49 +1,57 @@
|
|||
<main>
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<section class="status">
|
||||
<h2><?= $name ?></h2>
|
||||
<h2><?= $escape->html($name) ?></h2>
|
||||
<section class="media-wrap">
|
||||
<?php foreach($items as $item): ?>
|
||||
<article class="media" id="manga-<?= $item['id'] ?>">
|
||||
<?php if (is_logged_in()): ?>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<div class="edit_buttons" hidden>
|
||||
<button class="plus_one_chapter">+1 Chapter</button>
|
||||
<button class="plus_one_volume">+1 Volume</button>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<img src="<?= $item['manga']['poster_image'] ?>" />
|
||||
<img src="<?= $escape->attr($item['manga']['image']) ?>" />
|
||||
<div class="name">
|
||||
<a href="https://hummingbird.me/manga/<?= $item['manga_id'] ?>">
|
||||
<?= $item['manga']['romaji_title'] ?>
|
||||
<?= (isset($item['manga']['english_title'])) ? "<br />({$item['manga']['english_title']})" : ""; ?>
|
||||
<a href="<?= $item['manga']['url'] ?>">
|
||||
<?= $escape->html($item['manga']['title']) ?>
|
||||
<?= (isset($item['manga']['alternate_title'])) ? "<br />({$item['manga']['alternate_title']})" : ""; ?>
|
||||
</a>
|
||||
</div>
|
||||
<div class="table">
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<div class="row">
|
||||
<div class="user_rating">Rating: <?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</div>
|
||||
<span class="edit">
|
||||
<a class="bracketed" title="Edit information about this manga" href="<?= $urlGenerator->url("manga/edit/{$item['id']}/{$name}") ?>">Edit</a>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="row">
|
||||
<div class="user_rating">Rating: <?= $item['user_rating'] ?> / 10</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="chapter_completion">
|
||||
Chapters: <span class="chapters_read"><?= $item['chapters_read'] ?></span> /
|
||||
<span class="chapter_count"><?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?></span>
|
||||
Chapters: <span class="chapters_read"><?= $item['chapters']['read'] ?></span> /
|
||||
<span class="chapter_count"><?= $item['chapters']['total'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="volume_completion">
|
||||
Volumes: <span class="volumes_read"><?= $item['volumes_read'] ?></span> /
|
||||
<span class="volume_count"><?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?></span>
|
||||
Volumes: <span class="volumes_read"><?= $item['volumes']['read'] ?></span> /
|
||||
<span class="volume_count"><?= $item['volumes']['total'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php /*<div class="medium_metadata">
|
||||
<div class="media_type"><?= $item['manga']['manga_type'] ?></div>
|
||||
</div> */ ?>
|
||||
</article>
|
||||
<?php endforeach ?>
|
||||
</section>
|
||||
</section>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
<?php if (is_logged_in()): ?>
|
||||
<script src="<?= asset_url('js.php?g=edit') ?>"></script>
|
||||
<?php endif ?>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=edit') ?>"></script>
|
||||
<?php endif ?>
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
<?php if ($auth->is_authenticated()): ?>
|
||||
<main>
|
||||
<h1>
|
||||
Edit <?= $item['manga']['title'] ?>
|
||||
<?= ($item['manga']['alternate_title'] != "") ? "({$item['manga']['alternate_title']})" : ""; ?>
|
||||
</h1>
|
||||
<form action="<?= $action ?>" method="post">
|
||||
<table class="form">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<h3><?= $escape->html($item['manga']['title']) ?></h3>
|
||||
<?php if($item['manga']['alternate_title'] != ""): ?>
|
||||
<h4><?= $escape->html($item['manga']['alternate_title']) ?></h4>
|
||||
<?php endif ?>
|
||||
</th>
|
||||
<th>
|
||||
<article class="media">
|
||||
<?= $helper->img($item['manga']['image']); ?>
|
||||
</article>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="status">Reading Status</label></td>
|
||||
<td>
|
||||
<select name="status" id="status">
|
||||
<?php foreach($status_list as $status): ?>
|
||||
<option <?php if($item['reading_status'] === $status): ?>selected="selected"<?php endif ?>
|
||||
value="<?= $status ?>"><?= $status ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="series_rating">Rating</label></td>
|
||||
<td>
|
||||
<input type="number" min="0" max="10" maxlength="2" name="new_rating" value="<?= $item['user_rating'] ?>" id="series_rating" size="2" /> / 10
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="chapters_read">Chapters Read</label></td>
|
||||
<td>
|
||||
<input type="number" min="0" name="chapters_read" id="chapters_read" value="<?= $item['chapters']['read'] ?>" /> / <?= $item['chapters']['total'] ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="volumes_read">Volumes Read</label></td>
|
||||
<td>
|
||||
<input type="number" min="0" name="volumes_read" id="volumes_read" value="<?= $item['volumes']['read'] ?>" /> / <?= $item['volumes']['total'] ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="rereading_flag">Rereading?</label></td>
|
||||
<td>
|
||||
<input type="checkbox" name="reareading" id="rereading_flag"
|
||||
<?php if($item['rereading'] === TRUE): ?>checked="checked"<?php endif ?>
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="reread_count">Reread Count</label></td>
|
||||
<td>
|
||||
<input type="number" min="0" id="reread_count" name="reread_count" value="<?= $item['reread'] ?>" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="notes">Notes</label></td>
|
||||
<td>
|
||||
<textarea name="notes" id="notes"><?= $escape->html($item['notes']) ?></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
|
||||
<input type="hidden" value="<?= $item['manga']['slug'] ?>" name="manga_id" />
|
||||
<input type="hidden" value="<?= $item['user_rating'] ?>" name="old_rating" />
|
||||
<input type="hidden" value="true" name="edit" />
|
||||
<button type="submit">Submit</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</main>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=edit') ?>"></script>
|
||||
<?php endif ?>
|
|
@ -1,9 +1,15 @@
|
|||
<main>
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<h2><?= $name ?></h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<th> </th>
|
||||
<?php endif ?>
|
||||
<th>Title</th>
|
||||
<th>Rating</th>
|
||||
<th>Chapters</th>
|
||||
|
@ -13,21 +19,27 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($items as $item): ?>
|
||||
<tr id="manga-<?= $item['manga']['id'] ?>">
|
||||
<td class="align_left">
|
||||
<a href="https://hummingbird.me/manga/<?= $item['manga']['id'] ?>">
|
||||
<?= $item['manga']['romaji_title'] ?>
|
||||
</a>
|
||||
<?= (array_key_exists('english_title', $item['manga'])) ? " · " . $item['manga']['english_title'] : "" ?>
|
||||
<tr id="manga-<?= $item['id'] ?>">
|
||||
<?php if($auth->is_authenticated()): ?>
|
||||
<td>
|
||||
<a class="bracketed" href="<?= $urlGenerator->url("manga/edit/{$item['id']}/{$name}") ?>">Edit</a>
|
||||
</td>
|
||||
<td><?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</td>
|
||||
<td><?= $item['chapters_read'] ?> / <?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?></td>
|
||||
<td><?= $item['volumes_read'] ?> / <?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?></td>
|
||||
<td><?= $item['manga']['manga_type'] ?></td>
|
||||
<?php endif ?>
|
||||
<td class="align_left">
|
||||
<a href="<?= $item['manga']['url'] ?>">
|
||||
<?= $item['manga']['title'] ?>
|
||||
</a>
|
||||
<?= ( ! is_null($item['manga']['alternate_title'])) ? " · " . $item['manga']['alternate_title'] : "" ?>
|
||||
</td>
|
||||
<td><?= $item['user_rating'] ?> / 10</td>
|
||||
<td><?= $item['chapters']['read'] ?> / <?= $item['chapters']['total'] ?></td>
|
||||
<td><?= $item['volumes']['read'] ?> / <?= $item['volumes']['total'] ?></td>
|
||||
<td><?= $item['manga']['type'] ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
<script src="<?= asset_url('js.php?g=table') ?>"></script>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=table') ?>"></script>
|
|
@ -1,5 +1,5 @@
|
|||
<div class="message <?= $stat_class ?>">
|
||||
<div class="message <?= $escape->attr($message_type) ?>">
|
||||
<span class="icon"></span>
|
||||
<?= $message ?>
|
||||
<?= $escape->html($message) ?>
|
||||
<span class="close" onclick="this.parentElement.style.display='none'">x</span>
|
||||
</div>
|
|
@ -0,0 +1,158 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project default="full-build" name="animeclient" basedir=".">
|
||||
<!-- By default, we assume all tools to be on the $PATH -->
|
||||
<property name="pdepend" value="pdepend" />
|
||||
<property name="phpcpd" value="phpcpd" />
|
||||
<property name="phpdox" value="phpdox" />
|
||||
<property name="phploc" value="phploc" />
|
||||
<property name="phpmd" value="phpmd" />
|
||||
<property name="phpunit" value="phpunit" />
|
||||
<property name="sonar" value="sonar-runner" />
|
||||
|
||||
<target name="full-build"
|
||||
depends="prepare,static-analysis,phpunit,phpdox,sonar"
|
||||
description="Performs static analysis, runs the tests, and generates project documentation"
|
||||
/>
|
||||
<target name="full-build-parallel"
|
||||
depends="prepare,static-analysis-parallel,phpunit,phpdox"
|
||||
description="Performs static analysis (executing the tools in parallel), runs the tests, and generates project documentation"
|
||||
/>
|
||||
<target name="quick-build"
|
||||
depends="prepare,lint,phpunit-no-coverage"
|
||||
description="Performs a lint check and runs the tests (without generating code coverage reports)"
|
||||
/>
|
||||
<target name="static-analysis"
|
||||
depends="lint,phploc-ci,pdepend,phpcpd-ci"
|
||||
description="Performs static analysis"
|
||||
/>
|
||||
|
||||
<!-- Adjust the threadCount attribute's value to the number of CPUs -->
|
||||
<target name="static-analysis-parallel" description="Performs static analysis (executing the tools in parallel)">
|
||||
<parallel threadCount="6">
|
||||
<sequential>
|
||||
<antcall target="pdepend" />
|
||||
</sequential>
|
||||
<antcall target="lint" />
|
||||
<antcall target="phpcpd-ci" />
|
||||
<antcall target="phploc-ci" />
|
||||
</parallel>
|
||||
</target>
|
||||
|
||||
<target name="clean" unless="clean.done" description="Cleanup build artifacts">
|
||||
<delete dir="build/api" />
|
||||
<delete dir="build/coverage" />
|
||||
<delete dir="build/logs" />
|
||||
<delete dir="build/pdepend" />
|
||||
<delete dir="build/phpdox" />
|
||||
<property name="clean.done" value="true" />
|
||||
</target>
|
||||
|
||||
<target name="prepare" depends="clean" unless="prepare.done" description="Prepare for build">
|
||||
<mkdir dir="build/api" />
|
||||
<mkdir dir="build/coverage" />
|
||||
<mkdir dir="build/logs" />
|
||||
<mkdir dir="build/pdepend" />
|
||||
<mkdir dir="build/phpdox" />
|
||||
<property name="prepare.done" value="true" />
|
||||
</target>
|
||||
|
||||
<target name="lint" unless="lint.done" description="Perform syntax check of sourcecode files">
|
||||
<apply executable="php" taskname="lint">
|
||||
<arg value="-l" />
|
||||
|
||||
<fileset dir="src">
|
||||
<include name="**/*.php" />
|
||||
</fileset>
|
||||
|
||||
<fileset dir="tests">
|
||||
<include name="**/*.php" />
|
||||
</fileset>
|
||||
</apply>
|
||||
|
||||
<property name="lint.done" value="true" />
|
||||
</target>
|
||||
|
||||
<target name="phploc" unless="phploc.done" description="Measure project size using PHPLOC and print human readable output. Intended for usage on the command line.">
|
||||
<exec executable="${phploc}" taskname="phploc">
|
||||
<arg value="--count-tests" />
|
||||
<arg path="src" />
|
||||
<arg path="tests" />
|
||||
</exec>
|
||||
|
||||
<property name="phploc.done" value="true" />
|
||||
</target>
|
||||
|
||||
<target name="phploc-ci" depends="prepare" unless="phploc.done" description="Measure project size using PHPLOC and log result in CSV and XML format. Intended for usage within a continuous integration environment.">
|
||||
<exec executable="${phploc}" taskname="phploc">
|
||||
<arg value="--count-tests" />
|
||||
<arg value="--log-csv" />
|
||||
<arg path="build/logs/phploc.csv" />
|
||||
<arg value="--log-xml" />
|
||||
<arg path="build/logs/phploc.xml" />
|
||||
<arg path="src" />
|
||||
<arg path="tests" />
|
||||
</exec>
|
||||
|
||||
<property name="phploc.done" value="true" />
|
||||
</target>
|
||||
|
||||
<target name="pdepend" depends="prepare" unless="pdepend.done" description="Calculate software metrics using PHP_Depend and log result in XML format. Intended for usage within a continuous integration environment.">
|
||||
<exec executable="${pdepend}" taskname="pdepend">
|
||||
<arg value="--jdepend-xml=build/logs/jdepend.xml" />
|
||||
<arg value="--jdepend-chart=build/pdepend/dependencies.svg" />
|
||||
<arg value="--overview-pyramid=build/pdepend/overview-pyramid.svg" />
|
||||
<arg path="src" />
|
||||
</exec>
|
||||
|
||||
<property name="pdepend.done" value="true" />
|
||||
</target>
|
||||
|
||||
<target name="phpcpd" unless="phpcpd.done" description="Find duplicate code using PHPCPD and print human readable output. Intended for usage on the command line before committing.">
|
||||
<exec executable="${phpcpd}" taskname="phpcpd">
|
||||
<arg path="src" />
|
||||
</exec>
|
||||
|
||||
<property name="phpcpd.done" value="true" />
|
||||
</target>
|
||||
|
||||
<target name="phpcpd-ci" depends="prepare" unless="phpcpd.done" description="Find duplicate code using PHPCPD and log result in XML format. Intended for usage within a continuous integration environment.">
|
||||
<exec executable="${phpcpd}" taskname="phpcpd">
|
||||
<arg value="--log-pmd" />
|
||||
<arg path="build/logs/pmd-cpd.xml" />
|
||||
<arg path="src" />
|
||||
</exec>
|
||||
|
||||
<property name="phpcpd.done" value="true" />
|
||||
</target>
|
||||
|
||||
<target name="phpunit" unless="phpunit.done" depends="prepare" description="Run unit tests with PHPUnit">
|
||||
<exec executable="${phpunit}" taskname="phpunit">
|
||||
<arg value="--configuration" />
|
||||
<arg path="build/phpunit.xml" />
|
||||
</exec>
|
||||
|
||||
<property name="phpunit.done" value="true" />
|
||||
</target>
|
||||
|
||||
<target name="phpunit-no-coverage" depends="prepare" unless="phpunit.done" description="Run unit tests with PHPUnit (without generating code coverage reports)">
|
||||
<exec executable="${phpunit}" failonerror="true" taskname="phpunit">
|
||||
<arg value="--configuration" />
|
||||
<arg path="build/phpunit.xml" />
|
||||
<arg value="--no-coverage" />
|
||||
</exec>
|
||||
|
||||
<property name="phpunit.done" value="true" />
|
||||
</target>
|
||||
|
||||
<target name="phpdox" depends="phploc-ci,phpunit" unless="phpdox.done" description="Generate project documentation using phpDox">
|
||||
<exec dir="build" executable="${phpdox}" taskname="phpdox" />
|
||||
|
||||
<property name="phpdox.done" value="true" />
|
||||
</target>
|
||||
|
||||
<target name="sonar" depends="phpunit" unless="sonar.done" description="Generate code analysis with sonarqube">
|
||||
<exec executable="${sonar}" taskname="sonar" />
|
||||
|
||||
<property name="sonar.done" value="true" />
|
||||
</target>
|
||||
</project>
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Ion
|
||||
*
|
||||
* Building blocks for web development
|
||||
*
|
||||
* @package Ion
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @license MIT
|
||||
*/
|
|
@ -0,0 +1,131 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- This is a skeleton phpDox config file - Check http://phpDox.de for latest version and more info -->
|
||||
<phpdox xmlns="http://xml.phpdox.net/config" silent="false">
|
||||
<!-- @silent: true | false to enable or disable visual output of progress -->
|
||||
|
||||
<!-- Additional bootstrap files to load for additional parsers, enrichers and/or engines -->
|
||||
<!-- Place as many require nodes as you feel like in this container -->
|
||||
<!-- syntax: <require file="/path/to/file.php" /> -->
|
||||
<bootstrap />
|
||||
|
||||
<!-- A phpDox project to process, you can have multiple projects in one config file -->
|
||||
<project name="Hummingbird Anime Client" source="../src" workdir="phpdox/xml">
|
||||
<!-- @name - The name of the project -->
|
||||
<!-- @source - The source directory of the application to process -->
|
||||
<!-- @workdir - The directory to store the xml data files in -->
|
||||
|
||||
<!-- A phpDox config file can define additional variables (properties) per project -->
|
||||
<!-- <property name="some.name" value="the.value" /> -->
|
||||
|
||||
<!-- Values can make use of previously defined properties -->
|
||||
<!-- The following are defined by default:
|
||||
|
||||
${basedir} Directory the loaded config file is in
|
||||
|
||||
${phpDox.home} Directory of the phpDox installation
|
||||
${phpDox.file} The current config file
|
||||
${phpDox.version} phpDox' version number
|
||||
|
||||
${phpDox.project.name} The value of project/@name if set, otherwise 'unnamed'
|
||||
${phpDox.project.source} The value of project/@source if set, otherwise '${basedir}/src'
|
||||
${phpDox.project.workdir} The value of project/@workdir if set, otherwise '${basedir}/build/phpdox/xml'
|
||||
|
||||
${phpDox.php.version} The PHP Version of the interpreter in use
|
||||
|
||||
-->
|
||||
|
||||
<!-- Additional configuration for the collecting process (parsing of php code, generation of xml data) -->
|
||||
<collector publiconly="false" backend="parser" encoding="auto">
|
||||
<!-- @publiconly - Flag to disable/enable processing of non public methods and members -->
|
||||
<!-- @backend - The collector backend to use, currently only shipping with 'parser' -->
|
||||
<!-- @encoding - Charset encoding of source files (overwrite default 'auto' if detection fails) -->
|
||||
|
||||
<!-- <include / exclude filter for filelist generator, mask must follow fnmatch() requirements -->
|
||||
<include mask="*.php" />
|
||||
<exclude mask="" />
|
||||
|
||||
<!-- How to handle inheritance -->
|
||||
<inheritance resolve="true">
|
||||
<!-- @resolve - Flag to enable/disable resolving of inheritance -->
|
||||
|
||||
<!-- You can define multiple (external) dependencies to be included -->
|
||||
<!-- <dependency path="" -->
|
||||
<!-- @path - path to a directory containing an index.xml for a dependency project -->
|
||||
</inheritance>
|
||||
|
||||
</collector>
|
||||
|
||||
<!-- Configuration of generation process -->
|
||||
<generator output="../docs">
|
||||
<!-- @output - (Base-)Directory to store output data in -->
|
||||
|
||||
<!-- A generation process consists of one or more build tasks and of (optional) enrich sources -->
|
||||
|
||||
<enrich base="logs">
|
||||
<!-- @base - (Base-)Directory of datafiles used for enrich process -->
|
||||
|
||||
<!--<source type="...">-->
|
||||
<!-- @type - the handler for the enrichment -->
|
||||
<!-- known types by default are: build, checkstyle, git, phpcs, phploc, phpunit, pmd -->
|
||||
|
||||
<!-- every enrichment source can have additional configuration nodes, most probably need a logfile -->
|
||||
<!-- <file name="path/to/log.xml" /> -->
|
||||
<!--</source> -->
|
||||
|
||||
<!-- add phploc output -->
|
||||
<source type="phploc">
|
||||
<file name="phploc.xml" />
|
||||
</source>
|
||||
|
||||
<!-- git vcs information -->
|
||||
<source type="git">
|
||||
<git binary="/usr/bin/git" />
|
||||
<history enabled="true" limit="15" cache="${phpDox.project.workdir}/gitlog.xml" />
|
||||
</source>
|
||||
|
||||
<!-- PHP Code Sniffer findings -->
|
||||
<!--
|
||||
<source type="phpcs">
|
||||
<file name="logs/phpcs.xml" />
|
||||
</source>
|
||||
-->
|
||||
|
||||
<!-- PHPMessDetector -->
|
||||
<!--
|
||||
<source type="pmd">
|
||||
<file name="pmd.xml" />
|
||||
</source>
|
||||
-->
|
||||
|
||||
<!-- PHPUnit Coverage XML -->
|
||||
<source type="phpunit">
|
||||
<!-- <coverage path="clover.xml" />-->
|
||||
<!-- @path - the directory where the xml code coverage report can be found -->
|
||||
<!--<filter directory="${phpDox.project.source}" />-->
|
||||
<!-- @directory - path of the phpunit config whitelist filter directory -->
|
||||
</source>
|
||||
<!--
|
||||
<source type="phpunit">
|
||||
<filter directory="${phpDox.project.source}" />
|
||||
</source>
|
||||
-->
|
||||
|
||||
</enrich>
|
||||
|
||||
<!-- <build engine="..." enabled="true" output="..." /> -->
|
||||
<!-- @engine - The name of the engine this build task uses, use ./phpDox - -engines to get a list of available engines -->
|
||||
<!-- @enabled - Flag to enable/disable this engine, default: enabled=true -->
|
||||
<!-- @output - (optional) Output directory; if relative (no / as first char) it is interpreted as relative to generator/@output -->
|
||||
|
||||
<!-- An engine and thus build node can have additional configuration child nodes, please check the documentation for the engine to find out more -->
|
||||
|
||||
<!-- default engine "html" -->
|
||||
<build engine="html" enabled="true">
|
||||
<template dir="${phpDox.home}/templates/html" />
|
||||
<file extension="html" />
|
||||
</build>
|
||||
|
||||
</generator>
|
||||
</project>
|
||||
|
||||
</phpdox>
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
colors="true"
|
||||
stopOnFailure="false"
|
||||
bootstrap="../tests/bootstrap.php"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||
checkForUnintentionallyCoveredCode="true"
|
||||
>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">../src/Aviat/Ion</directory>
|
||||
<directory suffix=".php">../src/Aviat/AnimeClient</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<testsuites>
|
||||
<testsuite name="Ion">
|
||||
<directory>../tests/Ion</directory>
|
||||
</testsuite>
|
||||
<testsuite name="AnimeClient">
|
||||
<directory>../tests/AnimeClient</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<logging>
|
||||
<log type="coverage-html" target="coverage"/>
|
||||
<log type="coverage-clover" target="logs/clover.xml"/>
|
||||
<log type="coverage-crap4j" target="logs/crap4j.xml"/>
|
||||
<log type="coverage-xml" target="logs/coverage" />
|
||||
<log type="junit" target="logs/junit.xml" logIncompleteSkipped="false"/>
|
||||
</logging>
|
||||
<php>
|
||||
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0" />
|
||||
<server name="HTTP_HOST" value="localhost" />
|
||||
<server name="SERVER_NAME" value="localhost" />
|
||||
<server name="REQUEST_URI" value="/" />
|
||||
<server name="REQUEST_METHOD" value="GET" />
|
||||
</php>
|
||||
</phpunit>
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
$animeclient_file_patterns = [
|
||||
'app/config/*.php',
|
||||
'app/booststrap.php',
|
||||
'src/functions.php',
|
||||
'src/Aviat/AnimeClient/*.php'
|
||||
];
|
||||
|
||||
$ion_file_patterns = [
|
||||
'src/Aviat/Ion/*.php'
|
||||
];
|
||||
|
||||
if ( ! function_exists('glob_recursive'))
|
||||
{
|
||||
// Does not support flag GLOB_BRACE
|
||||
|
||||
function glob_recursive($pattern, $flags = 0)
|
||||
{
|
||||
$files = glob($pattern, $flags);
|
||||
|
||||
foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir)
|
||||
{
|
||||
$files = array_merge($files, glob_recursive($dir . '/' . basename($pattern), $flags));
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
|
||||
function get_text_to_replace($tokens)
|
||||
{
|
||||
if ($tokens[0][0] !== T_OPEN_TAG)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// If there is already a docblock, as the second token after the
|
||||
// open tag, get the contents of that token to replace
|
||||
if ($tokens[1][0] === T_DOC_COMMENT)
|
||||
{
|
||||
return "<?php\n" . $tokens[1][1];
|
||||
}
|
||||
else if ($tokens[1][0] !== T_DOC_COMMENT)
|
||||
{
|
||||
return "<?php";
|
||||
}
|
||||
}
|
||||
|
||||
function get_tokens($source)
|
||||
{
|
||||
return token_get_all($source);
|
||||
}
|
||||
|
||||
function replace_files(array $files, $template)
|
||||
{
|
||||
foreach ($files as $file)
|
||||
{
|
||||
$source = file_get_contents($file);
|
||||
$tokens = get_tokens($source);
|
||||
$text_to_replace = get_text_to_replace($tokens);
|
||||
|
||||
$header = file_get_contents(__DIR__ . $template);
|
||||
$new_text = "<?php\n{$header}";
|
||||
|
||||
$new_source = str_replace($text_to_replace, $new_text, $source);
|
||||
file_put_contents($file, $new_source);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($animeclient_file_patterns as $glob)
|
||||
{
|
||||
$files = glob_recursive($glob);
|
||||
replace_files($files, '/animeclient_header_comment.txt');
|
||||
}
|
||||
$loose_files = [
|
||||
__DIR__ . '/../index.php',
|
||||
__DIR__ . '/../public/css.php',
|
||||
__DIR__ . '/../public/js.php'
|
||||
];
|
||||
replace_files($loose_files, '/animeclient_header_comment.txt');
|
||||
|
||||
foreach ($ion_file_patterns as $glob)
|
||||
{
|
||||
$files = glob_recursive($glob);
|
||||
replace_files($files, '/ion_header_comment.txt');
|
||||
}
|
||||
|
||||
echo "Successfully updated headers \n";
|
|
@ -1,11 +1,21 @@
|
|||
{
|
||||
"name": "timw4mail/hummingbird-anime-client",
|
||||
"description": "A self-hosted anime/manga client for hummingbird.",
|
||||
"license":"MIT",
|
||||
"require": {
|
||||
"guzzlehttp/guzzle": "5.3.*",
|
||||
"filp/whoops": "1.1.*",
|
||||
"abeautifulsite/simpleimage": "2.5.*",
|
||||
"aura/html": "2.*",
|
||||
"aura/router": "2.2.*",
|
||||
"aura/web": "2.0.*",
|
||||
"aviat4ion/query": "2.0.*",
|
||||
"robmorgan/phinx": "*",
|
||||
"abeautifulsite/simpleimage": "*"
|
||||
"aura/session": "2.*",
|
||||
"aura/web": "2.*",
|
||||
"aviat4ion/query": "2.5.*",
|
||||
"container-interop/container-interop": "1.*",
|
||||
"danielstjules/stringy": "~2.1",
|
||||
"filp/whoops": "2.0.*",
|
||||
"guzzlehttp/guzzle": "6.*",
|
||||
"monolog/monolog": "1.*",
|
||||
"psr/log": "~1.0",
|
||||
"robmorgan/phinx": "0.4.*",
|
||||
"yosymfony/toml": "0.3.*"
|
||||
}
|
||||
}
|
105
index.php
105
index.php
|
@ -1,24 +1,17 @@
|
|||
<?php
|
||||
/**
|
||||
* Here begins everything!
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace AnimeClient;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ! Start config
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Well, whose list is it?
|
||||
*/
|
||||
define('WHOSE', "Tim's");
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// ! End config
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
\session_start();
|
||||
use Whoops\Handler\PrettyPageHandler;
|
||||
use Whoops\Handler\JsonResponseHandler;
|
||||
|
||||
// Work around the silly timezone error
|
||||
$timezone = ini_get('date.timezone');
|
||||
|
@ -27,17 +20,75 @@ if ($timezone === '' || $timezone === FALSE)
|
|||
ini_set('date.timezone', 'GMT');
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins paths together. Variadic to take an
|
||||
* arbitrary number of arguments
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function _dir()
|
||||
{
|
||||
return implode(DIRECTORY_SEPARATOR, func_get_args());
|
||||
}
|
||||
|
||||
// Define base directories
|
||||
define('ROOT_DIR', __DIR__);
|
||||
define('APP_DIR', ROOT_DIR . DIRECTORY_SEPARATOR . 'app');
|
||||
define('CONF_DIR', APP_DIR . DIRECTORY_SEPARATOR . 'config');
|
||||
define('BASE_DIR', APP_DIR . DIRECTORY_SEPARATOR . 'base');
|
||||
require BASE_DIR . DIRECTORY_SEPARATOR . 'pre_conf_functions.php';
|
||||
$APP_DIR = _dir(__DIR__, 'app');
|
||||
$SRC_DIR = _dir(__DIR__, 'src');
|
||||
$CONF_DIR = _dir($APP_DIR, 'config');
|
||||
|
||||
// Setup autoloaders
|
||||
_setup_autoloaders();
|
||||
/**
|
||||
* Set up autoloaders
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return void
|
||||
*/
|
||||
spl_autoload_register(function($class) use ($SRC_DIR) {
|
||||
$class_parts = explode('\\', $class);
|
||||
$ns_path = $SRC_DIR . '/' . implode('/', $class_parts) . ".php";
|
||||
|
||||
// Do dependency injection, and go!
|
||||
require _dir(APP_DIR, 'bootstrap.php');
|
||||
if (file_exists($ns_path))
|
||||
{
|
||||
require_once($ns_path);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
require _dir(__DIR__, '/vendor/autoload.php');
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Setup error handling
|
||||
// -------------------------------------------------------------------------
|
||||
$whoops = new \Whoops\Run();
|
||||
|
||||
// Set up default handler for general errors
|
||||
$defaultHandler = new PrettyPageHandler();
|
||||
$whoops->pushHandler($defaultHandler);
|
||||
|
||||
// Set up json handler for ajax errors
|
||||
$jsonHandler = new JsonResponseHandler();
|
||||
$whoops->pushHandler($jsonHandler);
|
||||
|
||||
// Register as the error handler
|
||||
$whoops->register();
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Dependency Injection setup
|
||||
// -----------------------------------------------------------------------------
|
||||
require _dir($CONF_DIR, 'base_config.php'); // $base_config
|
||||
require _dir($CONF_DIR, 'config.php'); // $config
|
||||
$config_array = array_merge($base_config, $config);
|
||||
$di = require _dir($APP_DIR, 'bootstrap.php');
|
||||
|
||||
// Unset 'constants'
|
||||
unset($APP_DIR);
|
||||
unset($SRC_DIR);
|
||||
unset($CONF_DIR);
|
||||
|
||||
$container = $di($config_array);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Dispatch to the current route
|
||||
// -----------------------------------------------------------------------------
|
||||
$container->get('dispatcher')->__invoke();
|
||||
|
||||
// End of index.php
|
|
@ -1,23 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<phpdoc>
|
||||
<title>Hummingbird Anime Client</title>
|
||||
<parser>
|
||||
<target>docs</target>
|
||||
</parser>
|
||||
<transformer>
|
||||
<target>docs</target>
|
||||
</transformer>
|
||||
<transformations>
|
||||
<template name="clean" />
|
||||
</transformations>
|
||||
<files>
|
||||
<directory>.</directory>
|
||||
<directory>app</directory>
|
||||
<ignore>public/*</ignore>
|
||||
<ignore>app/views/*</ignore>
|
||||
<ignore>app/config/*</ignore>
|
||||
<ignore>migrations/*</ignore>
|
||||
<ignore>tests/*</ignore>
|
||||
<ignore>vendor/*</ignore>
|
||||
</files>
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<phpdoc>
|
||||
<title>Hummingbird Anime Client</title>
|
||||
<parser>
|
||||
<target>docs</target>
|
||||
</parser>
|
||||
<transformer>
|
||||
<target>docs</target>
|
||||
</transformer>
|
||||
<transformations>
|
||||
<template name="clean" />
|
||||
</transformations>
|
||||
<files>
|
||||
<directory>src/Aviat</directory>
|
||||
</files>
|
||||
</phpdoc>
|
22
phpunit.xml
22
phpunit.xml
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
colors="true"
|
||||
stopOnFailure="false"
|
||||
bootstrap="tests/bootstrap.php">
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">app/base</directory>
|
||||
<directory suffix=".php">app/controllers</directory>
|
||||
<directory suffix=".php">app/models</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<testsuites>
|
||||
<testsuite name="BaseTests">
|
||||
<directory>tests/base</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<php>
|
||||
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0" />
|
||||
<server name="HTTP_HOST" value="localhost" />
|
||||
</php>
|
||||
</phpunit>
|
|
@ -1,13 +1,14 @@
|
|||
<?php
|
||||
/**
|
||||
* Easy Min
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* Simple minification for better website performance
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2012
|
||||
* @link https://github.com/aviat4ion/Easy-Min
|
||||
* @license http://philsturgeon.co.uk/code/dbad-license
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:active {
|
||||
color: #7d12db;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 85%;
|
||||
margin: 0 auto;
|
||||
|
@ -11,7 +20,65 @@ tbody > tr:nth-child(odd) {
|
|||
background: #ddd;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form tr > td:nth-child(odd) {
|
||||
text-align: right;
|
||||
min-width: 25px;
|
||||
max-width: 30%;
|
||||
}
|
||||
|
||||
.form tr > td:nth-child(even) {
|
||||
text-align: left;
|
||||
min-width: 70%;
|
||||
}
|
||||
|
||||
.form thead th,
|
||||
.form thead tr {
|
||||
background: inherit;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.form.invisible tr:nth-child(odd) {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.form.invisible tr,
|
||||
.form.invisible td,
|
||||
.form.invisible th {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.bracketed,
|
||||
h1 a {
|
||||
text-shadow: 1px 1px 1px #000;
|
||||
}
|
||||
|
||||
.bracketed:before {
|
||||
content: '[\00a0';
|
||||
}
|
||||
|
||||
.bracketed:after {
|
||||
content: '\00a0]';
|
||||
}
|
||||
|
||||
.bracketed {
|
||||
color: #12db18;
|
||||
}
|
||||
|
||||
.bracketed:hover,
|
||||
.bracketed:active {
|
||||
color: #db7d12;
|
||||
}
|
||||
|
||||
.grow-1 {
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex-grow: 1;
|
||||
-ms-flex-positive: 1;
|
||||
flex-grow: 1;
|
||||
|
@ -30,16 +97,32 @@ tbody > tr:nth-child(odd) {
|
|||
}
|
||||
|
||||
.flex-align-end {
|
||||
-webkit-box-align: end;
|
||||
-webkit-align-items: flex-end;
|
||||
-ms-flex-align: end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.flex-align-space-around {
|
||||
-webkit-align-content: space-around;
|
||||
-ms-flex-line-pack: distribute;
|
||||
align-content: space-around;
|
||||
}
|
||||
|
||||
.flex-justify-space-around {
|
||||
jusify-content: space-around;
|
||||
-webkit-justify-content: space-around;
|
||||
-ms-flex-pack: distribute;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.flex-self-center {
|
||||
-webkit-align-self: center;
|
||||
-ms-flex-item-align: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
|
@ -49,6 +132,10 @@ tbody > tr:nth-child(odd) {
|
|||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.align_center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align_left {
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -57,22 +144,6 @@ tbody > tr:nth-child(odd) {
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.round_all {
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.round_top {
|
||||
border-radius: 0;
|
||||
border-top-right-radius: 0.5em;
|
||||
border-top-left-radius: 0.5em;
|
||||
}
|
||||
|
||||
.round_bottom {
|
||||
border-radius: 0;
|
||||
border-bottom-right-radius: 0.5em;
|
||||
border-bottom-left-radius: 0.5em;
|
||||
}
|
||||
|
||||
.media-wrap {
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
|
@ -134,11 +205,15 @@ button {
|
|||
.media:hover > .media_metadata > div,
|
||||
.media:hover > .medium_metadata > div,
|
||||
.media:hover > .table .row {
|
||||
-webkit-transition: .25s ease;
|
||||
transition: .25s ease;
|
||||
background: rgba(0,0,0,0.75);
|
||||
}
|
||||
|
||||
.media:hover > button[hidden],
|
||||
.media:hover > .edit_buttons[hidden] {
|
||||
-webkit-transition: .25s ease;
|
||||
transition: .25s ease;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
@ -217,7 +292,9 @@ button {
|
|||
.anime .airing_status,
|
||||
.anime .user_rating,
|
||||
.anime .completion,
|
||||
.anime .age_rating {
|
||||
.anime .age_rating,
|
||||
.anime .edit,
|
||||
.anime .delete {
|
||||
background: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -234,6 +311,7 @@ button {
|
|||
.manga .row {
|
||||
width: 100%;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
|
@ -247,6 +325,11 @@ button {
|
|||
padding: 0 inherit;
|
||||
}
|
||||
|
||||
.anime .row > span,
|
||||
.manga .row > span {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.anime .row > div,
|
||||
.manga .row > div {
|
||||
font-size: 0.8em;
|
||||
|
@ -260,7 +343,9 @@ button {
|
|||
|
||||
.anime .media > button.plus_one {
|
||||
position: absolute;
|
||||
top: 138px;
|
||||
top: calc(50% - 21.5px);
|
||||
left: 44px;
|
||||
left: calc(50% - 66.5px);
|
||||
}
|
||||
|
||||
|
@ -268,6 +353,10 @@ button {
|
|||
Manga-list-specific styles
|
||||
------------------------------------------------------------------------------*/
|
||||
|
||||
.manga .row {
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.manga .media {
|
||||
border: 1px solid #ddd;
|
||||
width: 200px;
|
||||
|
@ -277,6 +366,8 @@ button {
|
|||
|
||||
.manga .media > .edit_buttons {
|
||||
position: absolute;
|
||||
top: 86px;
|
||||
top: calc(50% - 58.5px);
|
||||
left: 5px;
|
||||
left: calc(50% - 95px);
|
||||
}
|
|
@ -1,13 +1,20 @@
|
|||
:root {
|
||||
--link-shadow: 1px 1px 1px #000;
|
||||
--shadow: 1px 2px 1px rgba(0, 0, 0, 0.85);
|
||||
--title-overlay: rgba(0, 0, 0, 0.45);
|
||||
--text-color: #ffffff;
|
||||
--normal-padding: 0.25em;
|
||||
--radius: 0.5em;
|
||||
--link-hover-color: #7d12db;
|
||||
--edit-link-hover-color: #db7d12;
|
||||
--edit-link-color: #12db18;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0.5em;
|
||||
template {display:none}
|
||||
|
||||
body {margin: 0.5em;}
|
||||
|
||||
a:hover, a:active {
|
||||
color: var(--link-hover-color)
|
||||
}
|
||||
|
||||
table {
|
||||
|
@ -19,40 +26,61 @@ tbody > tr:nth-child(odd) {
|
|||
background: #ddd;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.form { width:100%; }
|
||||
.form tr > td:nth-child(odd) {
|
||||
text-align:right;
|
||||
min-width:25px;
|
||||
max-width:30%;
|
||||
}
|
||||
.form tr > td:nth-child(even) {
|
||||
text-align:left;
|
||||
min-width:70%;
|
||||
}
|
||||
|
||||
.form thead th, .form thead tr {
|
||||
background: inherit;
|
||||
border:0;
|
||||
}
|
||||
|
||||
.form.invisible tr:nth-child(odd) {
|
||||
background: inherit;
|
||||
}
|
||||
.form.invisible tr, .form.invisible td, .form.invisible th {
|
||||
border:0;
|
||||
}
|
||||
|
||||
.bracketed, h1 a {
|
||||
text-shadow: var(--link-shadow);
|
||||
}
|
||||
.bracketed:before {content: '[\00a0'}
|
||||
.bracketed:after {content: '\00a0]'}
|
||||
.bracketed {
|
||||
color: var(--edit-link-color);
|
||||
}
|
||||
.bracketed:hover, .bracketed:active {
|
||||
color: var(--edit-link-hover-color)
|
||||
}
|
||||
|
||||
.grow-1 {flex-grow: 1}
|
||||
.flex-wrap {flex-wrap: wrap}
|
||||
.flex-no-wrap {flex-wrap: nowrap}
|
||||
.flex-align-end {align-items: flex-end}
|
||||
.flex-justify-space-around {jusify-content: space-around}
|
||||
.flex-align-space-around {align-content: space-around}
|
||||
.flex-justify-space-around {justify-content: space-around}
|
||||
.flex-self-center {align-self:center}
|
||||
.flex {display: flex}
|
||||
|
||||
.small-font {
|
||||
font-size:1.6rem;
|
||||
}
|
||||
|
||||
.align_left {
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.align_right {
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
.round_all {
|
||||
border-radius:var(--radius);
|
||||
}
|
||||
|
||||
.round_top {
|
||||
border-radius: 0;
|
||||
border-top-right-radius:var(--radius);
|
||||
border-top-left-radius:var(--radius);
|
||||
}
|
||||
|
||||
.round_bottom {
|
||||
border-radius: 0;
|
||||
border-bottom-right-radius:var(--radius);
|
||||
border-bottom-left-radius:var(--radius);
|
||||
}
|
||||
.align_center {text-align:center}
|
||||
.align_left {text-align:left;}
|
||||
.align_right {text-align:right;}
|
||||
|
||||
.media-wrap {
|
||||
text-align:center;
|
||||
|
@ -115,12 +143,14 @@ button {
|
|||
.media:hover > .medium_metadata > div,
|
||||
.media:hover > .table .row
|
||||
{
|
||||
transition: .25s ease;
|
||||
background:rgba(0,0,0,0.75);
|
||||
}
|
||||
|
||||
.media:hover > button[hidden],
|
||||
.media:hover > .edit_buttons[hidden]
|
||||
{
|
||||
transition: .25s ease;
|
||||
display:block;
|
||||
}
|
||||
|
||||
|
@ -197,7 +227,9 @@ button {
|
|||
.anime .airing_status,
|
||||
.anime .user_rating,
|
||||
.anime .completion,
|
||||
.anime .age_rating {
|
||||
.anime .age_rating,
|
||||
.anime .edit,
|
||||
.anime .delete {
|
||||
background: none;
|
||||
text-align:center;
|
||||
}
|
||||
|
@ -220,6 +252,10 @@ button {
|
|||
padding:0 inherit;
|
||||
}
|
||||
|
||||
.anime .row > span, .manga .row > span {
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.anime .row > div, .manga .row > div {
|
||||
font-size:0.8em;
|
||||
display:flex-item;
|
||||
|
@ -230,13 +266,19 @@ button {
|
|||
|
||||
.anime .media > button.plus_one {
|
||||
position:absolute;
|
||||
top: calc(50% - (43px / 2));
|
||||
left: calc(50% - (97px / 2 + 18));
|
||||
top: 138px;
|
||||
top: calc(50% - 21.5px);
|
||||
left: 44px;
|
||||
left: calc(50% - 66.5px);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Manga-list-specific styles
|
||||
------------------------------------------------------------------------------*/
|
||||
.manga .row {
|
||||
padding:1px;
|
||||
}
|
||||
|
||||
.manga .media {
|
||||
border:1px solid #ddd;
|
||||
width:200px;
|
||||
|
@ -246,6 +288,8 @@ button {
|
|||
|
||||
.manga .media > .edit_buttons {
|
||||
position:absolute;
|
||||
top: calc(50% - (117px / 2));
|
||||
left: calc(50% - (190px / 2));
|
||||
top: 86px;
|
||||
top: calc(50% - 58.5px);
|
||||
left: 5px;
|
||||
left: calc(50% - 95px);
|
||||
}
|
||||
|
|
|
@ -1,167 +1,229 @@
|
|||
:root {
|
||||
box-sizing: border-box;
|
||||
cursor: default;
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, 'Lucida Grande', sans-serif;
|
||||
font-family: 'Open Sans', 'Nimbus Sans L', 'Helvetica Neue', Helvetica, 'Lucida Grande', sans-serif;
|
||||
line-height: 1.4;
|
||||
overflow-y: scroll;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%; }
|
||||
-ms-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none; }
|
||||
display: none;
|
||||
}
|
||||
|
||||
details {
|
||||
display: block; }
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*input[type="number"] {
|
||||
width: auto; }*/
|
||||
|
||||
input[type="number"] {
|
||||
width: auto; }
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; }
|
||||
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none; }
|
||||
-webkit-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
main {
|
||||
display: block; }
|
||||
display: block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: block; }
|
||||
display: block;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow: auto; }
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
progress {
|
||||
display: inline-block; }
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 75%; }
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
big {
|
||||
font-size: 125%; }
|
||||
font-size: 125%;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none; }
|
||||
display: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical; }
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none; }
|
||||
display: none;
|
||||
}
|
||||
|
||||
[unselectable] {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none; }
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
*, ::before, ::after {
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
box-sizing: inherit; }
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
* {
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::before, ::after {
|
||||
::before,
|
||||
::after {
|
||||
text-decoration: inherit;
|
||||
vertical-align: inherit; }
|
||||
vertical-align: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none; }
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
audio, canvas, iframe, img, svg, video {
|
||||
vertical-align: middle; }
|
||||
audio,
|
||||
canvas,
|
||||
iframe,
|
||||
img,
|
||||
svg,
|
||||
video {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
button, input, select, textarea {
|
||||
background-color: transparent;
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
/*background-color: transparent;*/
|
||||
border: .1rem solid #ccc;
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
font-style: inherit;
|
||||
font-weight: inherit;
|
||||
min-height: 1.4em; }
|
||||
min-height: 1.4em;
|
||||
}
|
||||
|
||||
code, kbd, pre, samp {
|
||||
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace, monospace; }
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace, monospace;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0; }
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background-color: #b3d4fc;
|
||||
text-shadow: none; }
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: #b3d4fc;
|
||||
text-shadow: none; }
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner {
|
||||
border: 0; }
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media screen {
|
||||
[hidden~="screen"] {
|
||||
display: inherit; }
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
[hidden~="screen"]:not(:active):not(:focus):not(:target) {
|
||||
clip: rect(0 0 0 0) !important;
|
||||
position: absolute !important; } }
|
||||
position: absolute !important;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: #444;
|
||||
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, 'Lucida Grande', sans-serif;
|
||||
font-family: 'Open Sans', 'Nimbus Sans L', 'Helvetica Neue', Helvetica, 'Lucida Grande', sans-serif;
|
||||
font-size: 1.6rem;
|
||||
font-style: normal;
|
||||
font-weight: 400; }
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 1.6rem; }
|
||||
margin: 0 0 1.6rem;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Lato', 'Open Sans', 'Helvetica Neue', Helvetica, 'Lucida Grande', sans-serif;
|
||||
margin: 2rem 0 1.6rem; }
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: 'Lato', 'Open Sans', 'Nimbus Sans L', 'Helvetica Neue', Helvetica, 'Lucida Grande', sans-serif;
|
||||
margin: 2rem 0 1.6rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
border-bottom: .1rem solid rgba(0, 0, 0, 0.2);
|
||||
font-size: 3.6rem;
|
||||
font-style: normal;
|
||||
font-weight: 500; }
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 3rem;
|
||||
font-style: normal;
|
||||
font-weight: 500; }
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 2.4rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
margin: 1.6rem 0 0.4rem; }
|
||||
margin: 1.6rem 0 0.4rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.8rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
margin: 1.6rem 0 0.4rem; }
|
||||
margin: 1.6rem 0 0.4rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.6rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
margin: 1.6rem 0 0.4rem; }
|
||||
margin: 1.6rem 0 0.4rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
color: #777;
|
||||
font-size: 1.4rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
margin: 1.6rem 0 0.4rem; }
|
||||
margin: 1.6rem 0 0.4rem;
|
||||
}
|
||||
|
||||
small {
|
||||
color: #777; }
|
||||
color: #777;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #efefef;
|
||||
|
@ -172,7 +234,8 @@ pre {
|
|||
margin: 1.6rem 0;
|
||||
padding: 1.6rem;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word; }
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #efefef;
|
||||
|
@ -180,51 +243,73 @@ code {
|
|||
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
font-size: 1.4rem;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word; }
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1271db;
|
||||
-webkit-transition: .25s ease;
|
||||
transition: .25s ease; }
|
||||
a:hover, a:focus {
|
||||
text-decoration: none; }
|
||||
transition: .25s ease;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: 1.6rem; }
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-left: 4rem; }
|
||||
margin-left: 4rem;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
ul,
|
||||
ol {
|
||||
margin-bottom: 0.8rem;
|
||||
padding-left: 2rem; }
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: .2rem solid #1271db;
|
||||
font-family: Georgia, Times, 'Times New Roman', serif;
|
||||
font-style: italic;
|
||||
margin: 1.6rem 0;
|
||||
padding-left: 1.6rem; }
|
||||
padding-left: 1.6rem;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
font-family: Georgia, Times, 'Times New Roman', serif; }
|
||||
font-family: Georgia, Times, 'Times New Roman', serif;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 62.5%; }
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0; }
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
main, header, footer, article, section, aside, details, summary {
|
||||
main,
|
||||
header,
|
||||
footer,
|
||||
article,
|
||||
section,
|
||||
aside,
|
||||
details,
|
||||
summary {
|
||||
display: block;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
width: 100%; }
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
main {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
padding: 0 1.6rem 1.6rem; }
|
||||
padding: 0 1.6rem 1.6rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
border-top: .1rem solid rgba(0, 0, 0, 0.2);
|
||||
|
@ -233,33 +318,57 @@ footer {
|
|||
float: left;
|
||||
max-width: 100%;
|
||||
padding: 1rem 0;
|
||||
text-align: center; }
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: .1rem solid rgba(0, 0, 0, 0.2);
|
||||
display: block;
|
||||
margin-bottom: 1.6rem;
|
||||
width: 100%; }
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
vertical-align: baseline; }
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 40rem) {
|
||||
article, section, aside {
|
||||
article,
|
||||
section,
|
||||
aside {
|
||||
clear: both;
|
||||
display: block;
|
||||
max-width: 100%; }
|
||||
img {
|
||||
margin-right: 1.6rem; } }
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"], select {
|
||||
img {
|
||||
margin-right: 1.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="email"],
|
||||
input[type="url"],
|
||||
input[type="date"],
|
||||
input[type="month"],
|
||||
input[type="time"],
|
||||
input[type="datetime"],
|
||||
input[type="datetime-local"],
|
||||
input[type="week"],
|
||||
input[type="number"],
|
||||
input[type="search"],
|
||||
input[type="tel"],
|
||||
input[type="color"],
|
||||
select {
|
||||
border: .1rem solid #ccc;
|
||||
border-radius: 0;
|
||||
display: inline-block;
|
||||
padding: 0.8rem;
|
||||
vertical-align: middle; }
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
input:not([type]) {
|
||||
-webkit-appearance: none;
|
||||
|
@ -270,59 +379,113 @@ input:not([type]) {
|
|||
color: #444;
|
||||
display: inline-block;
|
||||
padding: 0.8rem;
|
||||
text-align: left; }
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
input[type="color"] {
|
||||
padding: 0.8rem 1.6rem; }
|
||||
padding: 0.8rem 1.6rem;
|
||||
}
|
||||
|
||||
input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus, select:focus, textarea:focus {
|
||||
border-color: #b3d4fc; }
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus,
|
||||
input[type="email"]:focus,
|
||||
input[type="url"]:focus,
|
||||
input[type="date"]:focus,
|
||||
input[type="month"]:focus,
|
||||
input[type="time"]:focus,
|
||||
input[type="datetime"]:focus,
|
||||
input[type="datetime-local"]:focus,
|
||||
input[type="week"]:focus,
|
||||
input[type="number"]:focus,
|
||||
input[type="search"]:focus,
|
||||
input[type="tel"]:focus,
|
||||
input[type="color"]:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
border-color: #b3d4fc;
|
||||
}
|
||||
|
||||
input:not([type]):focus {
|
||||
border-color: #b3d4fc; }
|
||||
border-color: #b3d4fc;
|
||||
}
|
||||
|
||||
input[type="radio"], input[type="checkbox"] {
|
||||
vertical-align: middle; }
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus {
|
||||
outline: .1rem solid thin #444; }
|
||||
input[type="file"]:focus,
|
||||
input[type="radio"]:focus,
|
||||
input[type="checkbox"]:focus {
|
||||
outline: .1rem solid thin #444;
|
||||
}
|
||||
|
||||
input[type="text"][disabled], input[type="password"][disabled], input[type="email"][disabled], input[type="url"][disabled], input[type="date"][disabled], input[type="month"][disabled], input[type="time"][disabled], input[type="datetime"][disabled], input[type="datetime-local"][disabled], input[type="week"][disabled], input[type="number"][disabled], input[type="search"][disabled], input[type="tel"][disabled], input[type="color"][disabled], select[disabled], textarea[disabled] {
|
||||
input[type="text"][disabled],
|
||||
input[type="password"][disabled],
|
||||
input[type="email"][disabled],
|
||||
input[type="url"][disabled],
|
||||
input[type="date"][disabled],
|
||||
input[type="month"][disabled],
|
||||
input[type="time"][disabled],
|
||||
input[type="datetime"][disabled],
|
||||
input[type="datetime-local"][disabled],
|
||||
input[type="week"][disabled],
|
||||
input[type="number"][disabled],
|
||||
input[type="search"][disabled],
|
||||
input[type="tel"][disabled],
|
||||
input[type="color"][disabled],
|
||||
select[disabled],
|
||||
textarea[disabled] {
|
||||
background-color: #efefef;
|
||||
color: #777;
|
||||
cursor: not-allowed; }
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input:not([type])[disabled] {
|
||||
background-color: #efefef;
|
||||
color: #777;
|
||||
cursor: not-allowed; }
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input[readonly], select[readonly], textarea[readonly] {
|
||||
input[readonly],
|
||||
select[readonly],
|
||||
textarea[readonly] {
|
||||
background-color: #efefef;
|
||||
border-color: #ccc;
|
||||
color: #777; }
|
||||
color: #777;
|
||||
}
|
||||
|
||||
input:focus:invalid, textarea:focus:invalid, select:focus:invalid {
|
||||
input:focus:invalid,
|
||||
textarea:focus:invalid,
|
||||
select:focus:invalid {
|
||||
border-color: #e9322d;
|
||||
color: #b94a48; }
|
||||
color: #b94a48;
|
||||
}
|
||||
|
||||
input[type="file"]:focus:invalid:focus, input[type="radio"]:focus:invalid:focus, input[type="checkbox"]:focus:invalid:focus {
|
||||
outline-color: #ff4136; }
|
||||
input[type="file"]:focus:invalid:focus,
|
||||
input[type="radio"]:focus:invalid:focus,
|
||||
input[type="checkbox"]:focus:invalid:focus {
|
||||
outline-color: #ff4136;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: #fff;
|
||||
border: .1rem solid #ccc; }
|
||||
border: .1rem solid #ccc;
|
||||
}
|
||||
|
||||
select[multiple] {
|
||||
height: auto; }
|
||||
height: auto;
|
||||
}
|
||||
|
||||
label {
|
||||
line-height: 2; }
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0.8rem 0; }
|
||||
padding: 0.8rem 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
border-bottom: .1rem solid #ccc;
|
||||
|
@ -330,7 +493,8 @@ legend {
|
|||
display: block;
|
||||
margin-bottom: 0.8rem;
|
||||
padding: 0.8rem 0;
|
||||
width: 100%; }
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
border: .1rem solid #ccc;
|
||||
|
@ -338,9 +502,11 @@ textarea {
|
|||
display: block;
|
||||
margin-bottom: 0.8rem;
|
||||
padding: 0.8rem;
|
||||
vertical-align: middle; }
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
input[type=submit], button {
|
||||
input[type=submit],
|
||||
button {
|
||||
background-color: transparent;
|
||||
border: .2rem solid #444;
|
||||
border-radius: 0;
|
||||
|
@ -354,83 +520,119 @@ input[type=submit], button {
|
|||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
-webkit-transition: .25s ease;
|
||||
transition: .25s ease;
|
||||
transition: .25s ease;
|
||||
-webkit-user-drag: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
vertical-align: baseline; }
|
||||
input[type=submit] a, button a {
|
||||
color: #444; }
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
input[type=submit]::-moz-focus-inner, button::-moz-focus-inner {
|
||||
padding: 0; }
|
||||
input[type=submit] a,
|
||||
button a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
input[type=submit]:hover, button:hover {
|
||||
input[type=submit]::-moz-focus-inner,
|
||||
button::-moz-focus-inner {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type=submit]:hover,
|
||||
button:hover {
|
||||
background: #444;
|
||||
border-color: #444;
|
||||
color: #fff; }
|
||||
input[type=submit]:hover a, button:hover a {
|
||||
color: #fff; }
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
input[type=submit]:active, button:active {
|
||||
input[type=submit]:hover a,
|
||||
button:hover a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
input[type=submit]:active,
|
||||
button:active {
|
||||
background: #6a6a6a;
|
||||
border-color: #6a6a6a;
|
||||
color: #fff; }
|
||||
input[type=submit]:active a, button:active a {
|
||||
color: #fff; }
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
input[type=submit]:disabled, button:disabled {
|
||||
input[type=submit]:active a,
|
||||
button:active a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
input[type=submit]:disabled,
|
||||
button:disabled {
|
||||
box-shadow: none;
|
||||
cursor: not-allowed;
|
||||
opacity: .40; }
|
||||
opacity: .40;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-top: 1.6rem;
|
||||
text-align: center; }
|
||||
nav ul li {
|
||||
display: inline; }
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
nav a {
|
||||
border-bottom: .2rem solid transparent;
|
||||
color: #444;
|
||||
padding: 0.8rem 1.6rem;
|
||||
text-decoration: none;
|
||||
-webkit-transition: .25s ease;
|
||||
transition: .25s ease; }
|
||||
nav a:hover, nav li.selected a {
|
||||
border-color: rgba(0, 0, 0, 0.2); }
|
||||
nav a:active {
|
||||
border-color: rgba(0, 0, 0, 0.56); }
|
||||
transition: .25s ease;
|
||||
}
|
||||
|
||||
nav a:hover,
|
||||
nav li.selected a {
|
||||
border-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
nav a:active {
|
||||
border-color: rgba(0, 0, 0, 0.56);
|
||||
}
|
||||
|
||||
table {
|
||||
margin-bottom: 1.6rem; }
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding: 0.8rem 0; }
|
||||
padding: 0.8rem 0;
|
||||
}
|
||||
|
||||
thead th {
|
||||
background: #efefef;
|
||||
color: #444; }
|
||||
color: #444;
|
||||
}
|
||||
|
||||
tr {
|
||||
background: #fff;
|
||||
margin-bottom: 0.8rem; }
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
th, td {
|
||||
th,
|
||||
td {
|
||||
border: .1rem solid #ccc;
|
||||
padding: 0.8rem 1.6rem;
|
||||
text-align: center;
|
||||
vertical-align: inherit; }
|
||||
vertical-align: inherit;
|
||||
}
|
||||
|
||||
tfoot tr {
|
||||
background: none; }
|
||||
background: none;
|
||||
}
|
||||
|
||||
tfoot td {
|
||||
color: #efefef;
|
||||
font-size: 0.8rem;
|
||||
font-style: italic;
|
||||
padding: 1.6rem 0.4rem; }
|
||||
padding: 1.6rem 0.4rem;
|
||||
}
|
|
@ -0,0 +1,437 @@
|
|||
:root {
|
||||
--default-font-list: 'Open Sans', 'Nimbus Sans L', 'Helvetica Neue', Helvetica, 'Lucida Grande', sans-serif;
|
||||
box-sizing: border-box;
|
||||
cursor: default;
|
||||
font-family: var(--default-font-list);
|
||||
line-height: 1.4;
|
||||
overflow-y: scroll;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%; }
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none; }
|
||||
|
||||
details {
|
||||
display: block; }
|
||||
|
||||
/*input[type="number"] {
|
||||
width: auto; }*/
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; }
|
||||
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none; }
|
||||
|
||||
main {
|
||||
display: block; }
|
||||
|
||||
summary {
|
||||
display: block; }
|
||||
|
||||
pre {
|
||||
overflow: auto; }
|
||||
|
||||
progress {
|
||||
display: inline-block; }
|
||||
|
||||
small {
|
||||
font-size: 75%; }
|
||||
|
||||
big {
|
||||
font-size: 125%; }
|
||||
|
||||
template {
|
||||
display: none; }
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical; }
|
||||
|
||||
[hidden] {
|
||||
display: none; }
|
||||
|
||||
[unselectable] {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none; }
|
||||
|
||||
*, ::before, ::after {
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
box-sizing: inherit; }
|
||||
|
||||
* {
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
|
||||
::before, ::after {
|
||||
text-decoration: inherit;
|
||||
vertical-align: inherit; }
|
||||
|
||||
a {
|
||||
text-decoration: none; }
|
||||
|
||||
audio, canvas, iframe, img, svg, video {
|
||||
vertical-align: middle; }
|
||||
|
||||
button, input, select, textarea {
|
||||
/*background-color: transparent;*/
|
||||
border: .1rem solid #ccc;
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
font-style: inherit;
|
||||
font-weight: inherit;
|
||||
min-height: 1.4em; }
|
||||
|
||||
code, kbd, pre, samp {
|
||||
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace, monospace; }
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0; }
|
||||
|
||||
::-moz-selection {
|
||||
background-color: #b3d4fc;
|
||||
text-shadow: none; }
|
||||
|
||||
::selection {
|
||||
background-color: #b3d4fc;
|
||||
text-shadow: none; }
|
||||
|
||||
button::-moz-focus-inner {
|
||||
border: 0; }
|
||||
|
||||
@media screen {
|
||||
[hidden~="screen"] {
|
||||
display: inherit; }
|
||||
[hidden~="screen"]:not(:active):not(:focus):not(:target) {
|
||||
clip: rect(0 0 0 0) !important;
|
||||
position: absolute !important; } }
|
||||
|
||||
body {
|
||||
color: #444;
|
||||
font-family: var(--default-font-list);
|
||||
font-size: 1.6rem;
|
||||
font-style: normal;
|
||||
font-weight: 400; }
|
||||
|
||||
p {
|
||||
margin: 0 0 1.6rem; }
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Lato', var(--default-font-list);
|
||||
margin: 2rem 0 1.6rem; }
|
||||
|
||||
h1 {
|
||||
border-bottom: .1rem solid rgba(0, 0, 0, 0.2);
|
||||
font-size: 3.6rem;
|
||||
font-style: normal;
|
||||
font-weight: 500; }
|
||||
|
||||
h2 {
|
||||
font-size: 3rem;
|
||||
font-style: normal;
|
||||
font-weight: 500; }
|
||||
|
||||
h3 {
|
||||
font-size: 2.4rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
margin: 1.6rem 0 0.4rem; }
|
||||
|
||||
h4 {
|
||||
font-size: 1.8rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
margin: 1.6rem 0 0.4rem; }
|
||||
|
||||
h5 {
|
||||
font-size: 1.6rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
margin: 1.6rem 0 0.4rem; }
|
||||
|
||||
h6 {
|
||||
color: #777;
|
||||
font-size: 1.4rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
margin: 1.6rem 0 0.4rem; }
|
||||
|
||||
small {
|
||||
color: #777; }
|
||||
|
||||
pre {
|
||||
background: #efefef;
|
||||
color: #444;
|
||||
display: block;
|
||||
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
font-size: 1.4rem;
|
||||
margin: 1.6rem 0;
|
||||
padding: 1.6rem;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word; }
|
||||
|
||||
code {
|
||||
background: #efefef;
|
||||
color: #444;
|
||||
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
font-size: 1.4rem;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word; }
|
||||
|
||||
a {
|
||||
color: #1271db;
|
||||
-webkit-transition: .25s ease;
|
||||
transition: .25s ease; }
|
||||
a:hover, a:focus {
|
||||
text-decoration: none; }
|
||||
|
||||
dl {
|
||||
margin-bottom: 1.6rem; }
|
||||
|
||||
dd {
|
||||
margin-left: 4rem; }
|
||||
|
||||
ul, ol {
|
||||
margin-bottom: 0.8rem;
|
||||
padding-left: 2rem; }
|
||||
|
||||
blockquote {
|
||||
border-left: .2rem solid #1271db;
|
||||
font-family: Georgia, Times, 'Times New Roman', serif;
|
||||
font-style: italic;
|
||||
margin: 1.6rem 0;
|
||||
padding-left: 1.6rem; }
|
||||
|
||||
figcaption {
|
||||
font-family: Georgia, Times, 'Times New Roman', serif; }
|
||||
|
||||
html {
|
||||
font-size: 62.5%; }
|
||||
|
||||
body {
|
||||
padding: 0; }
|
||||
|
||||
main, header, footer, article, section, aside, details, summary {
|
||||
display: block;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
width: 100%; }
|
||||
|
||||
main {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
padding: 0 1.6rem 1.6rem; }
|
||||
|
||||
footer {
|
||||
border-top: .1rem solid rgba(0, 0, 0, 0.2);
|
||||
clear: both;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
max-width: 100%;
|
||||
padding: 1rem 0;
|
||||
text-align: center; }
|
||||
|
||||
hr {
|
||||
border-top: .1rem solid rgba(0, 0, 0, 0.2);
|
||||
display: block;
|
||||
margin-bottom: 1.6rem;
|
||||
width: 100%; }
|
||||
|
||||
img {
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
vertical-align: baseline; }
|
||||
|
||||
@media screen and (max-width: 40rem) {
|
||||
article, section, aside {
|
||||
clear: both;
|
||||
display: block;
|
||||
max-width: 100%; }
|
||||
img {
|
||||
margin-right: 1.6rem; } }
|
||||
|
||||
input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"], select {
|
||||
border: .1rem solid #ccc;
|
||||
border-radius: 0;
|
||||
display: inline-block;
|
||||
padding: 0.8rem;
|
||||
vertical-align: middle; }
|
||||
|
||||
input:not([type]) {
|
||||
-webkit-appearance: none;
|
||||
background-clip: padding-box;
|
||||
background-color: #fff;
|
||||
border: .1rem solid #ccc;
|
||||
border-radius: 0;
|
||||
color: #444;
|
||||
display: inline-block;
|
||||
padding: 0.8rem;
|
||||
text-align: left; }
|
||||
|
||||
input[type="color"] {
|
||||
padding: 0.8rem 1.6rem; }
|
||||
|
||||
input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus, select:focus, textarea:focus {
|
||||
border-color: #b3d4fc; }
|
||||
|
||||
input:not([type]):focus {
|
||||
border-color: #b3d4fc; }
|
||||
|
||||
input[type="radio"], input[type="checkbox"] {
|
||||
vertical-align: middle; }
|
||||
|
||||
input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus {
|
||||
outline: .1rem solid thin #444; }
|
||||
|
||||
input[type="text"][disabled], input[type="password"][disabled], input[type="email"][disabled], input[type="url"][disabled], input[type="date"][disabled], input[type="month"][disabled], input[type="time"][disabled], input[type="datetime"][disabled], input[type="datetime-local"][disabled], input[type="week"][disabled], input[type="number"][disabled], input[type="search"][disabled], input[type="tel"][disabled], input[type="color"][disabled], select[disabled], textarea[disabled] {
|
||||
background-color: #efefef;
|
||||
color: #777;
|
||||
cursor: not-allowed; }
|
||||
|
||||
input:not([type])[disabled] {
|
||||
background-color: #efefef;
|
||||
color: #777;
|
||||
cursor: not-allowed; }
|
||||
|
||||
input[readonly], select[readonly], textarea[readonly] {
|
||||
background-color: #efefef;
|
||||
border-color: #ccc;
|
||||
color: #777; }
|
||||
|
||||
input:focus:invalid, textarea:focus:invalid, select:focus:invalid {
|
||||
border-color: #e9322d;
|
||||
color: #b94a48; }
|
||||
|
||||
input[type="file"]:focus:invalid:focus, input[type="radio"]:focus:invalid:focus, input[type="checkbox"]:focus:invalid:focus {
|
||||
outline-color: #ff4136; }
|
||||
|
||||
select {
|
||||
background-color: #fff;
|
||||
border: .1rem solid #ccc; }
|
||||
|
||||
select[multiple] {
|
||||
height: auto; }
|
||||
|
||||
label {
|
||||
line-height: 2; }
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0.8rem 0; }
|
||||
|
||||
legend {
|
||||
border-bottom: .1rem solid #ccc;
|
||||
color: #444;
|
||||
display: block;
|
||||
margin-bottom: 0.8rem;
|
||||
padding: 0.8rem 0;
|
||||
width: 100%; }
|
||||
|
||||
textarea {
|
||||
border: .1rem solid #ccc;
|
||||
border-radius: 0;
|
||||
display: block;
|
||||
margin-bottom: 0.8rem;
|
||||
padding: 0.8rem;
|
||||
vertical-align: middle; }
|
||||
|
||||
input[type=submit], button {
|
||||
background-color: transparent;
|
||||
border: .2rem solid #444;
|
||||
border-radius: 0;
|
||||
color: #444;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin-bottom: 0.8rem;
|
||||
margin-right: 0.4rem;
|
||||
padding: 0.8rem 1.6rem;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
-webkit-transition: .25s ease;
|
||||
transition: .25s ease;
|
||||
-webkit-user-drag: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
vertical-align: baseline; }
|
||||
input[type=submit] a, button a {
|
||||
color: #444; }
|
||||
|
||||
input[type=submit]::-moz-focus-inner, button::-moz-focus-inner {
|
||||
padding: 0; }
|
||||
|
||||
input[type=submit]:hover, button:hover {
|
||||
background: #444;
|
||||
border-color: #444;
|
||||
color: #fff; }
|
||||
input[type=submit]:hover a, button:hover a {
|
||||
color: #fff; }
|
||||
|
||||
input[type=submit]:active, button:active {
|
||||
background: #6a6a6a;
|
||||
border-color: #6a6a6a;
|
||||
color: #fff; }
|
||||
input[type=submit]:active a, button:active a {
|
||||
color: #fff; }
|
||||
|
||||
input[type=submit]:disabled, button:disabled {
|
||||
box-shadow: none;
|
||||
cursor: not-allowed;
|
||||
opacity: .40; }
|
||||
|
||||
nav ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-top: 1.6rem;
|
||||
text-align: center; }
|
||||
nav ul li {
|
||||
display: inline; }
|
||||
nav a {
|
||||
border-bottom: .2rem solid transparent;
|
||||
color: #444;
|
||||
padding: 0.8rem 1.6rem;
|
||||
text-decoration: none;
|
||||
-webkit-transition: .25s ease;
|
||||
transition: .25s ease; }
|
||||
nav a:hover, nav li.selected a {
|
||||
border-color: rgba(0, 0, 0, 0.2); }
|
||||
nav a:active {
|
||||
border-color: rgba(0, 0, 0, 0.56); }
|
||||
|
||||
table {
|
||||
margin-bottom: 1.6rem; }
|
||||
|
||||
caption {
|
||||
padding: 0.8rem 0; }
|
||||
|
||||
thead th {
|
||||
background: #efefef;
|
||||
color: #444; }
|
||||
|
||||
tr {
|
||||
background: #fff;
|
||||
margin-bottom: 0.8rem; }
|
||||
|
||||
th, td {
|
||||
border: .1rem solid #ccc;
|
||||
padding: 0.8rem 1.6rem;
|
||||
text-align: center;
|
||||
vertical-align: inherit; }
|
||||
|
||||
tfoot tr {
|
||||
background: none; }
|
||||
|
||||
tfoot td {
|
||||
color: #efefef;
|
||||
font-size: 0.8rem;
|
||||
font-style: italic;
|
||||
padding: 1.6rem 0.4rem; }
|
|
@ -1,23 +1,29 @@
|
|||
<?php
|
||||
/**
|
||||
* Easy Min
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* Simple minification for better website performance
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2012
|
||||
* @link https://github.com/aviat4ion/Easy-Min
|
||||
* @license http://philsturgeon.co.uk/code/dbad-license
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
|
||||
//Get config files
|
||||
require('../app/config/minify_config.php');
|
||||
require_once('../app/config/minify_config.php');
|
||||
|
||||
//Include the js groups
|
||||
$groups_file = "../app/config/minify_js_groups.php";
|
||||
$groups = require($groups_file);
|
||||
$groups = require_once($groups_file);
|
||||
|
||||
// Include guzzle
|
||||
require_once('../vendor/autoload.php');
|
||||
|
||||
//The name of this file
|
||||
$this_file = __FILE__;
|
||||
|
@ -40,7 +46,7 @@ function get_files()
|
|||
foreach($groups[$_GET['g']] as $file)
|
||||
{
|
||||
$new_file = realpath($js_root.$file);
|
||||
$js .= file_get_contents($new_file);
|
||||
$js .= file_get_contents($new_file) . "\n\n";
|
||||
}
|
||||
|
||||
return $js;
|
||||
|
@ -57,14 +63,49 @@ function get_files()
|
|||
*/
|
||||
function google_min($new_file)
|
||||
{
|
||||
//Get a much-minified version from Google's closure compiler
|
||||
$ch = curl_init('http://closure-compiler.appspot.com/compile');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, 'output_info=compiled_code&output_format=text&compilation_level=SIMPLE_OPTIMIZATIONS&js_code=' . urlencode($new_file));
|
||||
$output = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return $output;
|
||||
$options = [
|
||||
'output_info' => 'errors',
|
||||
'output_format' => 'json',
|
||||
'compilation_level' => 'SIMPLE_OPTIMIZATIONS',
|
||||
'js_code' => $new_file,
|
||||
'language' => 'ECMASCRIPT5',
|
||||
'language_out' => 'ECMASCRIPT5_STRICT'
|
||||
];
|
||||
|
||||
// First check for errors
|
||||
$error_client = new Client();
|
||||
$error_res = $error_client->post('http://closure-compiler.appspot.com/compile', [
|
||||
'headers' => [
|
||||
'Accept-Encoding' => 'gzip',
|
||||
"Content-type" => "application/x-www-form-urlencoded"
|
||||
],
|
||||
'form_params' => $options
|
||||
]);
|
||||
|
||||
$error_json = $error_res->getBody();
|
||||
$error_obj = json_decode($error_json);
|
||||
|
||||
if ( ! empty($error_obj->errors))
|
||||
{
|
||||
?><pre><?= json_encode($err_obj, JSON_PRETTY_PRINT) ?></pre><?php
|
||||
die();
|
||||
}
|
||||
|
||||
// Now actually retrieve the compiled code
|
||||
$options['output_info'] = 'compiled_code';
|
||||
$client = new Client();
|
||||
$res = $client->post('http://closure-compiler.appspot.com/compile', [
|
||||
'headers' => [
|
||||
'Accept-Encoding' => 'gzip',
|
||||
"Content-type" => "application/x-www-form-urlencoded"
|
||||
],
|
||||
'form_params' => $options
|
||||
]);
|
||||
|
||||
$json = $res->getBody();
|
||||
$obj = json_decode($json);
|
||||
|
||||
return $obj->compiledCode;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
@ -146,7 +187,12 @@ if($last_modified === $requested_time)
|
|||
// --------------------------------------------------------------------------
|
||||
|
||||
//Determine what to do: rebuild cache, send files as is, or send cache.
|
||||
if($cache_modified < $last_modified)
|
||||
// If debug is set, just concatenate
|
||||
if(isset($_GET['debug']))
|
||||
{
|
||||
$js = get_files();
|
||||
}
|
||||
else if($cache_modified < $last_modified)
|
||||
{
|
||||
$js = google_min(get_files());
|
||||
|
||||
|
@ -156,11 +202,6 @@ if($cache_modified < $last_modified)
|
|||
die("Cache file was not created. Make sure you have the correct folder permissions.");
|
||||
}
|
||||
}
|
||||
// If debug is set, just concatenate
|
||||
else if(isset($_GET['debug']))
|
||||
{
|
||||
$js = get_files();
|
||||
}
|
||||
// Otherwise, send the cached file
|
||||
else
|
||||
{
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
(function($, undefined) {
|
||||
|
||||
function search(query, callback)
|
||||
{
|
||||
$.get(BASE_URL + 'collection/search', {'query':query}, callback);
|
||||
}
|
||||
|
||||
$("#search").on('keypress', $.throttle(750, function(e) {
|
||||
var query = encodeURIComponent($(this).val());
|
||||
search(query, function(res) {
|
||||
var template = $.templates("#show_list");
|
||||
var html = template.render(res);
|
||||
$('#series_list').html(html);
|
||||
});
|
||||
}));
|
||||
|
||||
}(jQuery));
|
|
@ -1,17 +1,19 @@
|
|||
/**
|
||||
* Javascript for editing anime, if logged in
|
||||
*/
|
||||
(function($, undefined){
|
||||
(function($){
|
||||
|
||||
"use strict";
|
||||
|
||||
if (CONTROLLER !== "anime") return;
|
||||
|
||||
// Action to increment episode count
|
||||
$(".media button.plus_one").on("click", function(e) {
|
||||
$(".plus_one").on("click", function(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
var this_sel = $(this);
|
||||
var parent_sel = $(this).closest("article");
|
||||
var self = this;
|
||||
var this_sel = $(this);
|
||||
var parent_sel = $(this).closest("article, td");
|
||||
|
||||
var watched_count = parseInt(parent_sel.find('.completed_number').text(), 10);
|
||||
var total_count = parseInt(parent_sel.find('.total_number').text(), 10);
|
||||
|
@ -19,7 +21,7 @@
|
|||
|
||||
// Setup the update data
|
||||
var data = {
|
||||
id: this_sel.parent('article').attr('id').replace('a-', ''),
|
||||
id: this_sel.parent('article, td').attr('id'),
|
||||
increment_episodes: true
|
||||
};
|
||||
|
||||
|
@ -38,14 +40,22 @@
|
|||
}
|
||||
|
||||
// okay, lets actually make some changes!
|
||||
$.post(BASE_URL + 'update', data, function(res) {
|
||||
$.ajax({
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
method: 'POST',
|
||||
mimeType: 'application/json',
|
||||
url: BASE_URL + CONTROLLER + '/update'
|
||||
}).done(function(res) {
|
||||
if (res.status === 'completed')
|
||||
{
|
||||
this_sel.parent('article').hide();
|
||||
$(self).closest('article, tr').hide();
|
||||
}
|
||||
|
||||
add_message('success', "Sucessfully updated " + title);
|
||||
parent_sel.find('.completed_number').text(++watched_count);
|
||||
}).fail(function() {
|
||||
add_message('error', "Failed to updated " + title);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,252 @@
|
|||
/*!
|
||||
* jQuery throttle / debounce - v1.1 - 3/7/2010
|
||||
* http://benalman.com/projects/jquery-throttle-debounce-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
|
||||
// Script: jQuery throttle / debounce: Sometimes, less is more!
|
||||
//
|
||||
// *Version: 1.1, Last updated: 3/7/2010*
|
||||
//
|
||||
// Project Home - http://benalman.com/projects/jquery-throttle-debounce-plugin/
|
||||
// GitHub - http://github.com/cowboy/jquery-throttle-debounce/
|
||||
// Source - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.js
|
||||
// (Minified) - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.min.js (0.7kb)
|
||||
//
|
||||
// About: License
|
||||
//
|
||||
// Copyright (c) 2010 "Cowboy" Ben Alman,
|
||||
// Dual licensed under the MIT and GPL licenses.
|
||||
// http://benalman.com/about/license/
|
||||
//
|
||||
// About: Examples
|
||||
//
|
||||
// These working examples, complete with fully commented code, illustrate a few
|
||||
// ways in which this plugin can be used.
|
||||
//
|
||||
// Throttle - http://benalman.com/code/projects/jquery-throttle-debounce/examples/throttle/
|
||||
// Debounce - http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/
|
||||
//
|
||||
// About: Support and Testing
|
||||
//
|
||||
// Information about what version or versions of jQuery this plugin has been
|
||||
// tested with, what browsers it has been tested in, and where the unit tests
|
||||
// reside (so you can test it yourself).
|
||||
//
|
||||
// jQuery Versions - none, 1.3.2, 1.4.2
|
||||
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome 4-5, Opera 9.6-10.1.
|
||||
// Unit Tests - http://benalman.com/code/projects/jquery-throttle-debounce/unit/
|
||||
//
|
||||
// About: Release History
|
||||
//
|
||||
// 1.1 - (3/7/2010) Fixed a bug in <jQuery.throttle> where trailing callbacks
|
||||
// executed later than they should. Reworked a fair amount of internal
|
||||
// logic as well.
|
||||
// 1.0 - (3/6/2010) Initial release as a stand-alone project. Migrated over
|
||||
// from jquery-misc repo v0.4 to jquery-throttle repo v1.0, added the
|
||||
// no_trailing throttle parameter and debounce functionality.
|
||||
//
|
||||
// Topic: Note for non-jQuery users
|
||||
//
|
||||
// jQuery isn't actually required for this plugin, because nothing internal
|
||||
// uses any jQuery methods or properties. jQuery is just used as a namespace
|
||||
// under which these methods can exist.
|
||||
//
|
||||
// Since jQuery isn't actually required for this plugin, if jQuery doesn't exist
|
||||
// when this plugin is loaded, the method described below will be created in
|
||||
// the `Cowboy` namespace. Usage will be exactly the same, but instead of
|
||||
// $.method() or jQuery.method(), you'll need to use Cowboy.method().
|
||||
|
||||
(function(window,undefined){
|
||||
'$:nomunge'; // Used by YUI compressor.
|
||||
|
||||
// Since jQuery really isn't required for this plugin, use `jQuery` as the
|
||||
// namespace only if it already exists, otherwise use the `Cowboy` namespace,
|
||||
// creating it if necessary.
|
||||
var $ = window.jQuery || window.Cowboy || ( window.Cowboy = {} ),
|
||||
|
||||
// Internal method reference.
|
||||
jq_throttle;
|
||||
|
||||
// Method: jQuery.throttle
|
||||
//
|
||||
// Throttle execution of a function. Especially useful for rate limiting
|
||||
// execution of handlers on events like resize and scroll. If you want to
|
||||
// rate-limit execution of a function to a single time, see the
|
||||
// <jQuery.debounce> method.
|
||||
//
|
||||
// In this visualization, | is a throttled-function call and X is the actual
|
||||
// callback execution:
|
||||
//
|
||||
// > Throttled with `no_trailing` specified as false or unspecified:
|
||||
// > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
|
||||
// > X X X X X X X X X X X X
|
||||
// >
|
||||
// > Throttled with `no_trailing` specified as true:
|
||||
// > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
|
||||
// > X X X X X X X X X X
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// > var throttled = jQuery.throttle( delay, [ no_trailing, ] callback );
|
||||
// >
|
||||
// > jQuery('selector').bind( 'someevent', throttled );
|
||||
// > jQuery('selector').unbind( 'someevent', throttled );
|
||||
//
|
||||
// This also works in jQuery 1.4+:
|
||||
//
|
||||
// > jQuery('selector').bind( 'someevent', jQuery.throttle( delay, [ no_trailing, ] callback ) );
|
||||
// > jQuery('selector').unbind( 'someevent', callback );
|
||||
//
|
||||
// Arguments:
|
||||
//
|
||||
// delay - (Number) A zero-or-greater delay in milliseconds. For event
|
||||
// callbacks, values around 100 or 250 (or even higher) are most useful.
|
||||
// no_trailing - (Boolean) Optional, defaults to false. If no_trailing is
|
||||
// true, callback will only execute every `delay` milliseconds while the
|
||||
// throttled-function is being called. If no_trailing is false or
|
||||
// unspecified, callback will be executed one final time after the last
|
||||
// throttled-function call. (After the throttled-function has not been
|
||||
// called for `delay` milliseconds, the internal counter is reset)
|
||||
// callback - (Function) A function to be executed after delay milliseconds.
|
||||
// The `this` context and all arguments are passed through, as-is, to
|
||||
// `callback` when the throttled-function is executed.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// (Function) A new, throttled, function.
|
||||
|
||||
$.throttle = jq_throttle = function( delay, no_trailing, callback, debounce_mode ) {
|
||||
// After wrapper has stopped being called, this timeout ensures that
|
||||
// `callback` is executed at the proper times in `throttle` and `end`
|
||||
// debounce modes.
|
||||
var timeout_id,
|
||||
|
||||
// Keep track of the last time `callback` was executed.
|
||||
last_exec = 0;
|
||||
|
||||
// `no_trailing` defaults to falsy.
|
||||
if ( typeof no_trailing !== 'boolean' ) {
|
||||
debounce_mode = callback;
|
||||
callback = no_trailing;
|
||||
no_trailing = undefined;
|
||||
}
|
||||
|
||||
// The `wrapper` function encapsulates all of the throttling / debouncing
|
||||
// functionality and when executed will limit the rate at which `callback`
|
||||
// is executed.
|
||||
function wrapper() {
|
||||
var that = this,
|
||||
elapsed = +new Date() - last_exec,
|
||||
args = arguments;
|
||||
|
||||
// Execute `callback` and update the `last_exec` timestamp.
|
||||
function exec() {
|
||||
last_exec = +new Date();
|
||||
callback.apply( that, args );
|
||||
};
|
||||
|
||||
// If `debounce_mode` is true (at_begin) this is used to clear the flag
|
||||
// to allow future `callback` executions.
|
||||
function clear() {
|
||||
timeout_id = undefined;
|
||||
};
|
||||
|
||||
if ( debounce_mode && !timeout_id ) {
|
||||
// Since `wrapper` is being called for the first time and
|
||||
// `debounce_mode` is true (at_begin), execute `callback`.
|
||||
exec();
|
||||
}
|
||||
|
||||
// Clear any existing timeout.
|
||||
timeout_id && clearTimeout( timeout_id );
|
||||
|
||||
if ( debounce_mode === undefined && elapsed > delay ) {
|
||||
// In throttle mode, if `delay` time has been exceeded, execute
|
||||
// `callback`.
|
||||
exec();
|
||||
|
||||
} else if ( no_trailing !== true ) {
|
||||
// In trailing throttle mode, since `delay` time has not been
|
||||
// exceeded, schedule `callback` to execute `delay` ms after most
|
||||
// recent execution.
|
||||
//
|
||||
// If `debounce_mode` is true (at_begin), schedule `clear` to execute
|
||||
// after `delay` ms.
|
||||
//
|
||||
// If `debounce_mode` is false (at end), schedule `callback` to
|
||||
// execute after `delay` ms.
|
||||
timeout_id = setTimeout( debounce_mode ? clear : exec, debounce_mode === undefined ? delay - elapsed : delay );
|
||||
}
|
||||
};
|
||||
|
||||
// Set the guid of `wrapper` function to the same of original callback, so
|
||||
// it can be removed in jQuery 1.4+ .unbind or .die by using the original
|
||||
// callback as a reference.
|
||||
if ( $.guid ) {
|
||||
wrapper.guid = callback.guid = callback.guid || $.guid++;
|
||||
}
|
||||
|
||||
// Return the wrapper function.
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
// Method: jQuery.debounce
|
||||
//
|
||||
// Debounce execution of a function. Debouncing, unlike throttling,
|
||||
// guarantees that a function is only executed a single time, either at the
|
||||
// very beginning of a series of calls, or at the very end. If you want to
|
||||
// simply rate-limit execution of a function, see the <jQuery.throttle>
|
||||
// method.
|
||||
//
|
||||
// In this visualization, | is a debounced-function call and X is the actual
|
||||
// callback execution:
|
||||
//
|
||||
// > Debounced with `at_begin` specified as false or unspecified:
|
||||
// > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
|
||||
// > X X
|
||||
// >
|
||||
// > Debounced with `at_begin` specified as true:
|
||||
// > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
|
||||
// > X X
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// > var debounced = jQuery.debounce( delay, [ at_begin, ] callback );
|
||||
// >
|
||||
// > jQuery('selector').bind( 'someevent', debounced );
|
||||
// > jQuery('selector').unbind( 'someevent', debounced );
|
||||
//
|
||||
// This also works in jQuery 1.4+:
|
||||
//
|
||||
// > jQuery('selector').bind( 'someevent', jQuery.debounce( delay, [ at_begin, ] callback ) );
|
||||
// > jQuery('selector').unbind( 'someevent', callback );
|
||||
//
|
||||
// Arguments:
|
||||
//
|
||||
// delay - (Number) A zero-or-greater delay in milliseconds. For event
|
||||
// callbacks, values around 100 or 250 (or even higher) are most useful.
|
||||
// at_begin - (Boolean) Optional, defaults to false. If at_begin is false or
|
||||
// unspecified, callback will only be executed `delay` milliseconds after
|
||||
// the last debounced-function call. If at_begin is true, callback will be
|
||||
// executed only at the first debounced-function call. (After the
|
||||
// throttled-function has not been called for `delay` milliseconds, the
|
||||
// internal counter is reset)
|
||||
// callback - (Function) A function to be executed after delay milliseconds.
|
||||
// The `this` context and all arguments are passed through, as-is, to
|
||||
// `callback` when the debounced-function is executed.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// (Function) A new, debounced, function.
|
||||
|
||||
$.debounce = function( delay, at_begin, callback ) {
|
||||
return callback === undefined
|
||||
? jq_throttle( delay, at_begin, false )
|
||||
: jq_throttle( delay, callback, at_begin !== false );
|
||||
};
|
||||
|
||||
})(this);
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,9 @@
|
|||
/**
|
||||
* Javascript for editing manga, if logged in
|
||||
*/
|
||||
(function ($, undefined) {
|
||||
(function ($) {
|
||||
|
||||
"use strict";
|
||||
|
||||
if (CONTROLLER !== "manga") return;
|
||||
|
||||
|
@ -28,10 +30,18 @@
|
|||
// Update the total count
|
||||
data[type + "s_read"] = ++completed;
|
||||
|
||||
$.post(BASE_URL + 'update', data, function(res) {
|
||||
$.ajax({
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
method: 'POST',
|
||||
mimeType: 'application/json',
|
||||
url: BASE_URL + CONTROLLER + '/update'
|
||||
}).done(function(res) {
|
||||
console.table(res);
|
||||
parent_sel.find("."+type+"s_read").text(completed);
|
||||
add_message('success', "Sucessfully updated " + res.manga[0].romaji_title);
|
||||
}).fail(function() {
|
||||
add_message('error', "Failed to updated " + res.manga[0].romaji_title);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
sonar.projectKey=animeclient
|
||||
sonar.projectName=Anime Client
|
||||
sonar.projectVersion=2.1.0
|
||||
sonar.sources=src
|
||||
sonar.php.coverage.reportPath=build/logs/clover.xml
|
||||
sonar.php.tests.reportPath=build/logs/junit.xml
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
define('SRC_DIR', realpath(__DIR__ . '/../../'));
|
||||
|
||||
/**
|
||||
* Odds and Ends class
|
||||
*/
|
||||
class AnimeClient {
|
||||
|
||||
use \Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth';
|
||||
const DEFAULT_CONTROLLER_NAMESPACE = 'Aviat\AnimeClient\Controller';
|
||||
const DEFAULT_CONTROLLER = 'Aviat\AnimeClient\Controller\Anime';
|
||||
const DEFAULT_CONTROLLER_METHOD = 'index';
|
||||
const NOT_FOUND_METHOD = 'not_found';
|
||||
const ERROR_MESSAGE_METHOD = 'error_page';
|
||||
const SRC_DIR = SRC_DIR;
|
||||
|
||||
private static $form_pages = [
|
||||
'edit',
|
||||
'add',
|
||||
'update',
|
||||
'update_form',
|
||||
'login',
|
||||
'logout'
|
||||
];
|
||||
|
||||
/**
|
||||
* HTML selection helper function
|
||||
*
|
||||
* @param string $a - First item to compare
|
||||
* @param string $b - Second item to compare
|
||||
* @return string
|
||||
*/
|
||||
public static 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
|
||||
*/
|
||||
public static function is_not_selected($a, $b)
|
||||
{
|
||||
return ($a !== $b) ? 'selected' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether to show the sub-menu
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_view_page()
|
||||
{
|
||||
$url = $this->container->get('request')
|
||||
->url->get();
|
||||
$page_segments = explode("/", $url);
|
||||
|
||||
$intersect = array_intersect($page_segments, self::$form_pages);
|
||||
|
||||
return empty($intersect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the page is a page with a form, and
|
||||
* not suitable for redirection
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_form_page()
|
||||
{
|
||||
return ! $this->is_view_page();
|
||||
}
|
||||
|
||||
}
|
||||
// End of anime_client.php
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Auth;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\AnimeClient;
|
||||
use Aviat\AnimeClient\Model\API;
|
||||
|
||||
/**
|
||||
* Hummingbird API Authentication
|
||||
*/
|
||||
class HummingbirdAuth {
|
||||
|
||||
use \Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
/**
|
||||
* Anime API Model
|
||||
*
|
||||
* @var \Aviat\AnimeClient\Model\API
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Session object
|
||||
*
|
||||
* @var Aura\Session\Segment
|
||||
*/
|
||||
protected $segment;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->setContainer($container);
|
||||
$this->segment = $container->get('session')
|
||||
->getSegment(AnimeClient::SESSION_SEGMENT);
|
||||
$this->model = $container->get('api-model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the appropriate authentication call,
|
||||
* and save the resulting auth token if successful
|
||||
*
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($password)
|
||||
{
|
||||
$username = $this->container->get('config')
|
||||
->get('hummingbird_username');
|
||||
$auth_token = $this->model->authenticate($username, $password);
|
||||
|
||||
if (FALSE !== $auth_token)
|
||||
{
|
||||
$this->segment->set('auth_token', $auth_token);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current user is authenticated
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_authenticated()
|
||||
{
|
||||
return ($this->get_auth_token() !== FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear authentication values
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
$this->segment->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the authentication token from the session
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function get_auth_token()
|
||||
{
|
||||
return $this->segment->get('auth_token', FALSE);
|
||||
}
|
||||
|
||||
}
|
||||
// End of HummingbirdAuth.php
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Wrapper for configuration values
|
||||
*/
|
||||
class Config {
|
||||
|
||||
use \Aviat\Ion\ArrayWrapper;
|
||||
|
||||
/**
|
||||
* Config object
|
||||
*
|
||||
* @var \Aviat\Ion\Type\ArrayType
|
||||
*/
|
||||
protected $map = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $config_array
|
||||
*/
|
||||
public function __construct(array $config_array = [])
|
||||
{
|
||||
$this->map = $this->arr($config_array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a config value
|
||||
*
|
||||
* @param array|string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
if (is_array($key))
|
||||
{
|
||||
return $this->map->get_deep_key($key);
|
||||
}
|
||||
|
||||
return $this->map->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a config value
|
||||
*
|
||||
* @param string|array $key
|
||||
* @return void
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
if (is_array($key))
|
||||
{
|
||||
$this->map->set_deep_key($key, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
$pos =& $this->map->get($key);
|
||||
$pos = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a config value
|
||||
*
|
||||
* @param integer|string|array $key
|
||||
* @param mixed $value
|
||||
* @throws InvalidArgumentException
|
||||
* @return Config
|
||||
*/
|
||||
public function set($key, $value)
|
||||
{
|
||||
if (is_array($key))
|
||||
{
|
||||
$this->map->set_deep_key($key, $value);
|
||||
}
|
||||
else if (is_scalar($key) && ! empty($key))
|
||||
{
|
||||
$this->map->set($key, $value);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidArgumentException("Key must be integer, string, or array, and cannot be empty");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
// End of config.php
|
|
@ -0,0 +1,406 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\View\HttpView;
|
||||
use Aviat\Ion\View\HtmlView;
|
||||
use Aviat\Ion\View\JsonView;
|
||||
use Aviat\AnimeClient\AnimeClient;
|
||||
|
||||
/**
|
||||
* Controller base, defines output methods
|
||||
*
|
||||
* @property Response object $response
|
||||
* @property Config object $config
|
||||
*/
|
||||
class Controller {
|
||||
|
||||
use \Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Url generatation class
|
||||
* @var UrlGenerator
|
||||
*/
|
||||
protected $urlGenerator;
|
||||
|
||||
/**
|
||||
* Session segment
|
||||
* @var [type]
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* Common data to be sent to views
|
||||
* @var array
|
||||
*/
|
||||
protected $base_data = [
|
||||
'url_type' => 'anime',
|
||||
'other_type' => 'manga',
|
||||
'menu_name' => ''
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->setContainer($container);
|
||||
$urlGenerator = $container->get('url-generator');
|
||||
$this->config = $container->get('config');
|
||||
$this->request = $container->get('request');
|
||||
$this->response = $container->get('response');
|
||||
$this->base_data['urlGenerator'] = $urlGenerator;
|
||||
$this->base_data['auth'] = $container->get('auth');
|
||||
$this->base_data['config'] = $this->config;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
|
||||
$session = $container->get('session');
|
||||
$this->session = $session->getSegment(AnimeClient::SESSION_SEGMENT);
|
||||
|
||||
// Set a 'previous' flash value for better redirects
|
||||
$this->session->setFlash('previous', $this->request->server->get('HTTP_REFERER'));
|
||||
|
||||
// Set a message box if available
|
||||
$this->base_data['message'] = $this->session->getFlash('message');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the default controller/url from an empty path
|
||||
*/
|
||||
public function redirect_to_default()
|
||||
{
|
||||
$default_type = $this->config->get(['routes', 'route_config', 'default_list']);
|
||||
$this->redirect($this->urlGenerator->default_url($default_type), 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the previous page
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function redirect_to_previous()
|
||||
{
|
||||
$previous = $this->session->getFlash('previous');
|
||||
$this->redirect($previous, 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current url in the session as the target of a future redirect
|
||||
*
|
||||
* @param string|null $url
|
||||
* @return void
|
||||
*/
|
||||
public function set_session_redirect($url = NULL)
|
||||
{
|
||||
$anime_client = $this->container->get('anime-client');
|
||||
$double_form_page = $this->request->server->get('HTTP_REFERER') == $this->request->url->get();
|
||||
|
||||
// Don't attempt to set the redirect url if
|
||||
// the page is one of the form type pages,
|
||||
// and the previous page is also a form type page_segments
|
||||
if ($double_form_page)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($url))
|
||||
{
|
||||
$url = ($anime_client->is_view_page())
|
||||
? $this->request->url->get()
|
||||
: $this->request->server->get('HTTP_REFERER');
|
||||
}
|
||||
|
||||
$this->session->set('redirect_url', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the url previously set in the session
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function session_redirect()
|
||||
{
|
||||
$target = $this->session->get('redirect_url');
|
||||
if (empty($target))
|
||||
{
|
||||
$this->not_found();
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->redirect($target, 303);
|
||||
$this->session->set('redirect_url', NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a class member
|
||||
*
|
||||
* @param string $key
|
||||
* @return object
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
$allowed = ['response', 'config'];
|
||||
|
||||
if (in_array($key, $allowed))
|
||||
{
|
||||
return $this->$key;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string output of a partial template
|
||||
*
|
||||
* @param HtmlView $view
|
||||
* @param string $template
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
protected function load_partial($view, $template, array $data = [])
|
||||
{
|
||||
$router = $this->container->get('dispatcher');
|
||||
|
||||
if (isset($this->base_data))
|
||||
{
|
||||
$data = array_merge($this->base_data, $data);
|
||||
}
|
||||
|
||||
$route = $router->get_route();
|
||||
$data['route_path'] = ($route) ? $router->get_route()->path : "";
|
||||
|
||||
|
||||
$template_path = _dir($this->config->get('view_path'), "{$template}.php");
|
||||
|
||||
if ( ! is_file($template_path))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid template : {$template}");
|
||||
}
|
||||
|
||||
return $view->render_template($template_path, (array)$data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a template with header and footer
|
||||
*
|
||||
* @param HtmlView $view
|
||||
* @param string $template
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function render_full_page($view, $template, array $data)
|
||||
{
|
||||
$view->appendOutput($this->load_partial($view, 'header', $data));
|
||||
|
||||
if (array_key_exists('message', $data) && is_array($data['message']))
|
||||
{
|
||||
$view->appendOutput($this->load_partial($view, 'message', $data['message']));
|
||||
}
|
||||
|
||||
$view->appendOutput($this->load_partial($view, $template, $data));
|
||||
$view->appendOutput($this->load_partial($view, 'footer', $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the login form
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $status
|
||||
* @return void
|
||||
*/
|
||||
public function login($status = "")
|
||||
{
|
||||
$message = "";
|
||||
|
||||
$view = new HtmlView($this->container);
|
||||
|
||||
if ($status != "")
|
||||
{
|
||||
$message = $this->show_message($view, 'error', $status);
|
||||
}
|
||||
|
||||
// Set the redirect url
|
||||
$this->set_session_redirect();
|
||||
|
||||
$this->outputHTML('login', [
|
||||
'title' => 'Api login',
|
||||
'message' => $message
|
||||
], $view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt login authentication
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function login_action()
|
||||
{
|
||||
$auth = $this->container->get('auth');
|
||||
if ($auth->authenticate($this->request->post->get('password')))
|
||||
{
|
||||
return $this->session_redirect();
|
||||
}
|
||||
|
||||
$this->login("Invalid username or password.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deauthorize the current user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
$auth = $this->container->get('auth');
|
||||
$auth->logout();
|
||||
|
||||
$this->redirect_to_default();
|
||||
}
|
||||
|
||||
/**
|
||||
* 404 action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function not_found()
|
||||
{
|
||||
$this->outputHTML('404', [
|
||||
'title' => 'Sorry, page not found'
|
||||
], NULL, 404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a generic error page
|
||||
*
|
||||
* @param int $http_code
|
||||
* @param string $title
|
||||
* @param string $message
|
||||
* @param string $long_message
|
||||
* @return void
|
||||
*/
|
||||
public function error_page($http_code, $title, $message, $long_message = "")
|
||||
{
|
||||
$this->outputHTML('error', [
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'long_message' => $long_message
|
||||
], NULL, $http_code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a session flash variable to display a message on
|
||||
* next page load
|
||||
*
|
||||
* @param string $message
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public function set_flash_message($message, $type = "info")
|
||||
{
|
||||
$this->session->setFlash('message', [
|
||||
'message_type' => $type,
|
||||
'message' => $message
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message box to the page
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param HtmlView $view
|
||||
* @param string $type
|
||||
* @param string $message
|
||||
* @return string
|
||||
*/
|
||||
protected function show_message($view, $type, $message)
|
||||
{
|
||||
return $this->load_partial($view, 'message', [
|
||||
'message_type' => $type,
|
||||
'message' => $message
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a template to HTML, using the provided data
|
||||
*
|
||||
* @param string $template
|
||||
* @param array $data
|
||||
* @param HtmlView|null $view
|
||||
* @param int $code
|
||||
* @return void
|
||||
*/
|
||||
protected function outputHTML($template, array $data = [], $view = NULL, $code = 200)
|
||||
{
|
||||
if (is_null($view))
|
||||
{
|
||||
$view = new HtmlView($this->container);
|
||||
}
|
||||
|
||||
$view->setStatusCode($code);
|
||||
$this->render_full_page($view, $template, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a JSON Response
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return void
|
||||
*/
|
||||
protected function outputJSON($data = [])
|
||||
{
|
||||
$view = new JsonView($this->container);
|
||||
$view->setOutput($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the selected page
|
||||
*
|
||||
* @param string $url
|
||||
* @param int $code
|
||||
* @return void
|
||||
*/
|
||||
protected function redirect($url, $code)
|
||||
{
|
||||
$http = new HttpView($this->container);
|
||||
$http->redirect($url, $code);
|
||||
}
|
||||
}
|
||||
// End of BaseController.php
|
|
@ -0,0 +1,259 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\AnimeClient\Hummingbird\Enum\AnimeWatchingStatus;
|
||||
use Aviat\AnimeClient\Model\Anime as AnimeModel;
|
||||
use Aviat\AnimeClient\Hummingbird\Transformer\AnimeListTransformer;
|
||||
|
||||
/**
|
||||
* Controller for Anime-related pages
|
||||
*/
|
||||
class Anime extends BaseController {
|
||||
|
||||
use \Aviat\Ion\StringWrapper;
|
||||
|
||||
/**
|
||||
* The anime list model
|
||||
* @var object $model
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Data to ve sent to all routes in this controller
|
||||
* @var array $base_data
|
||||
*/
|
||||
protected $base_data;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
|
||||
$this->model = $container->get('anime-model');
|
||||
|
||||
$this->base_data = array_merge($this->base_data, [
|
||||
'menu_name' => 'anime_list',
|
||||
'url_type' => 'anime',
|
||||
'other_type' => 'manga',
|
||||
'config' => $this->config,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a portion, or all of the anime list
|
||||
*
|
||||
* @param string $type - The section of the list
|
||||
* @param string $view - List or cover view
|
||||
* @return void
|
||||
*/
|
||||
public function index($type = "watching", $view = '')
|
||||
{
|
||||
$type_title_map = [
|
||||
'all' => 'All',
|
||||
'watching' => 'Currently Watching',
|
||||
'plan_to_watch' => 'Plan to Watch',
|
||||
'on_hold' => 'On Hold',
|
||||
'dropped' => 'Dropped',
|
||||
'completed' => 'Completed'
|
||||
];
|
||||
|
||||
$model_map = [
|
||||
'watching' => AnimeWatchingStatus::WATCHING,
|
||||
'plan_to_watch' => AnimeWatchingStatus::PLAN_TO_WATCH,
|
||||
'on_hold' => AnimeWatchingStatus::ON_HOLD,
|
||||
'all' => 'all',
|
||||
'dropped' => AnimeWatchingStatus::DROPPED,
|
||||
'completed' => AnimeWatchingStatus::COMPLETED
|
||||
];
|
||||
|
||||
if (array_key_exists($type, $type_title_map))
|
||||
{
|
||||
$title = $this->config->get('whose_list') .
|
||||
"'s Anime List · {$type_title_map[$type]}";
|
||||
}
|
||||
else
|
||||
{
|
||||
$title = '';
|
||||
}
|
||||
|
||||
$view_map = [
|
||||
'' => 'cover',
|
||||
'list' => 'list'
|
||||
];
|
||||
|
||||
$data = ($type != 'all')
|
||||
? $this->model->get_list($model_map[$type])
|
||||
: $this->model->get_all_lists();
|
||||
|
||||
$this->outputHTML('anime/' . $view_map[$view], [
|
||||
'title' => $title,
|
||||
'sections' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to add an anime
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_form()
|
||||
{
|
||||
$raw_status_list = AnimeWatchingStatus::getConstList();
|
||||
|
||||
$statuses = [];
|
||||
|
||||
foreach ($raw_status_list as $status_item)
|
||||
{
|
||||
$statuses[$status_item] = (string)$this->string($status_item)
|
||||
->underscored()
|
||||
->humanize()
|
||||
->titleize();
|
||||
}
|
||||
|
||||
$this->set_session_redirect();
|
||||
$this->outputHTML('anime/add', [
|
||||
'title' => $this->config->get('whose_list') .
|
||||
"'s Anime List · Add",
|
||||
'action_url' => $this->urlGenerator->url('anime/add'),
|
||||
'status_list' => $statuses
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an anime to the list
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$data = $this->request->post->get();
|
||||
if ( ! array_key_exists('id', $data))
|
||||
{
|
||||
$this->redirect("anime/add", 303);
|
||||
}
|
||||
|
||||
$result = $this->model->update($data);
|
||||
|
||||
if ($result['statusCode'] == 201)
|
||||
{
|
||||
$this->set_flash_message('Added new anime to list', 'success');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->set_flash_message('Failed to add new anime to list', 'error');
|
||||
}
|
||||
|
||||
$this->session_redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to edit details about a series
|
||||
*
|
||||
* @param int $id
|
||||
* @param string $status
|
||||
* @return void
|
||||
*/
|
||||
public function edit($id, $status = "all")
|
||||
{
|
||||
$item = $this->model->get_library_item($id, $status);
|
||||
$raw_status_list = AnimeWatchingStatus::getConstList();
|
||||
|
||||
$statuses = [];
|
||||
|
||||
foreach ($raw_status_list as $status_item)
|
||||
{
|
||||
$statuses[$status_item] = (string)$this->string($status_item)
|
||||
->underscored()
|
||||
->humanize()
|
||||
->titleize();
|
||||
}
|
||||
|
||||
$this->set_session_redirect($this->request->server->get('HTTP_REFERRER'));
|
||||
|
||||
$this->outputHTML('anime/edit', [
|
||||
'title' => $this->config->get('whose_list') .
|
||||
"'s Anime List · Edit",
|
||||
'item' => $item,
|
||||
'statuses' => $statuses,
|
||||
'action' => $this->container->get('url-generator')
|
||||
->url('/anime/update_form'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for anime
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function search()
|
||||
{
|
||||
$query = $this->request->query->get('query');
|
||||
$this->outputJSON($this->model->search($query));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an anime item via a form submission
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function form_update()
|
||||
{
|
||||
$post_data = $this->request->post->get();
|
||||
|
||||
// Do some minor data manipulation for
|
||||
// large form-based updates
|
||||
$transformer = new AnimeListTransformer();
|
||||
$post_data = $transformer->untransform($post_data);
|
||||
|
||||
$full_result = $this->model->update($post_data);
|
||||
$result = $full_result['body'];
|
||||
|
||||
if (array_key_exists('anime', $result))
|
||||
{
|
||||
$title = ( ! empty($result['anime']['alternate_title']))
|
||||
? "{$result['anime']['title']} ({$result['anime']['alternate_title']})"
|
||||
: "{$result['anime']['title']}";
|
||||
|
||||
$this->set_flash_message("Successfully updated {$title}.", 'success');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->set_flash_message('Failed to update anime.', 'error');
|
||||
}
|
||||
|
||||
$this->session_redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an anime item
|
||||
*
|
||||
* @return boolean|null
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$this->outputJSON(
|
||||
$this->model->update(
|
||||
$this->request->post->get()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
// End of AnimeController.php
|
|
@ -0,0 +1,186 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\AnimeClient\Config;
|
||||
use Aviat\AnimeClient\UrlGenerator;
|
||||
use Aviat\AnimeClient\Model\Anime as AnimeModel;
|
||||
use Aviat\AnimeClient\Model\AnimeCollection as AnimeCollectionModel;
|
||||
|
||||
/**
|
||||
* Controller for Anime collection pages
|
||||
*/
|
||||
class Collection extends BaseController {
|
||||
|
||||
/**
|
||||
* The anime collection model
|
||||
* @var AnimeCollectionModel $anime_collection_model
|
||||
*/
|
||||
private $anime_collection_model;
|
||||
|
||||
/**
|
||||
* The anime API model
|
||||
* @var AnimeModel $anime_model
|
||||
*/
|
||||
private $anime_model;
|
||||
|
||||
/**
|
||||
* Data to ve sent to all routes in this controller
|
||||
* @var array $base_data
|
||||
*/
|
||||
protected $base_data;
|
||||
|
||||
/**
|
||||
* Url Generator class
|
||||
* @var UrlGenerator
|
||||
*/
|
||||
protected $urlGenerator;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
|
||||
$this->urlGenerator = $container->get('url-generator');
|
||||
$this->anime_model = $container->get('anime-model');
|
||||
$this->anime_collection_model = $container->get('anime-collection-model');
|
||||
$this->base_data = array_merge($this->base_data, [
|
||||
'menu_name' => 'collection',
|
||||
'url_type' => 'anime',
|
||||
'other_type' => 'manga',
|
||||
'config' => $this->config,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for anime
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function search()
|
||||
{
|
||||
$query = $this->request->query->get('query');
|
||||
$this->outputJSON($this->anime_model->search($query));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the anime collection page
|
||||
*
|
||||
* @param string $view
|
||||
* @return void
|
||||
*/
|
||||
public function index($view)
|
||||
{
|
||||
$view_map = [
|
||||
'' => 'cover',
|
||||
'list' => 'list'
|
||||
];
|
||||
|
||||
$data = $this->anime_collection_model->get_collection();
|
||||
|
||||
$this->outputHTML('collection/' . $view_map[$view], [
|
||||
'title' => $this->config->get('whose_list') . "'s Anime Collection",
|
||||
'sections' => $data,
|
||||
'genres' => $this->anime_collection_model->get_genre_list()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the anime collection add/edit form
|
||||
*
|
||||
* @param integer|null $id
|
||||
* @return void
|
||||
*/
|
||||
public function form($id = NULL)
|
||||
{
|
||||
$this->set_session_redirect();
|
||||
|
||||
$action = (is_null($id)) ? "Add" : "Edit";
|
||||
|
||||
$this->outputHTML('collection/' . strtolower($action), [
|
||||
'action' => $action,
|
||||
'action_url' => $this->urlGenerator->full_url('collection/' . strtolower($action)),
|
||||
'title' => $this->config->get('whose_list') . " Anime Collection · {$action}",
|
||||
'media_items' => $this->anime_collection_model->get_media_type_list(),
|
||||
'item' => ($action === "Edit") ? $this->anime_collection_model->get($id) : []
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a collection item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
$data = $this->request->post->get();
|
||||
if (array_key_exists('hummingbird_id', $data))
|
||||
{
|
||||
$this->anime_collection_model->update($data);
|
||||
$this->set_flash_message('Successfully updated collection item.', 'success');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->set_flash_message('Failed to update collection item', 'error');
|
||||
}
|
||||
|
||||
$this->session_redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a collection item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$data = $this->request->post->get();
|
||||
if (array_key_exists('id', $data))
|
||||
{
|
||||
$this->anime_collection_model->add($data);
|
||||
$this->set_flash_message('Successfully added collection item', 'success');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->set_flash_message('Failed to add collection item.', 'error');
|
||||
}
|
||||
|
||||
$this->session_redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a collection item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$data = $this->request->post->get();
|
||||
if ( ! array_key_exists('id', $data))
|
||||
{
|
||||
$this->redirect("collection/view", 303);
|
||||
}
|
||||
|
||||
$this->anime_collection_model->delete($data);
|
||||
|
||||
$this->redirect("collection/view", 303);
|
||||
}
|
||||
}
|
||||
// End of CollectionController.php
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Controller;
|
||||
use Aviat\AnimeClient\Config;
|
||||
use Aviat\AnimeClient\Model\Manga as MangaModel;
|
||||
use Aviat\AnimeClient\Hummingbird\Enum\MangaReadingStatus;
|
||||
use Aviat\AnimeClient\Hummingbird\Transformer\MangaListTransformer;
|
||||
|
||||
/**
|
||||
* Controller for manga list
|
||||
*/
|
||||
class Manga extends Controller {
|
||||
|
||||
use \Aviat\Ion\StringWrapper;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
|
||||
$this->model = $container->get('manga-model');
|
||||
$this->base_data = array_merge($this->base_data, [
|
||||
'menu_name' => 'manga_list',
|
||||
'config' => $this->config,
|
||||
'url_type' => 'manga',
|
||||
'other_type' => 'anime'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a section of the manga list
|
||||
*
|
||||
* @param string $status
|
||||
* @param string $view
|
||||
* @return void
|
||||
*/
|
||||
public function index($status = "all", $view = "")
|
||||
{
|
||||
$map = [
|
||||
'all' => 'All',
|
||||
'plan_to_read' => MangaModel::PLAN_TO_READ,
|
||||
'reading' => MangaModel::READING,
|
||||
'completed' => MangaModel::COMPLETED,
|
||||
'dropped' => MangaModel::DROPPED,
|
||||
'on_hold' => MangaModel::ON_HOLD
|
||||
];
|
||||
|
||||
$title = $this->config->get('whose_list') . "'s Manga List · {$map[$status]}";
|
||||
|
||||
$view_map = [
|
||||
'' => 'cover',
|
||||
'list' => 'list'
|
||||
];
|
||||
|
||||
$data = ($status !== 'all')
|
||||
? [$map[$status] => $this->model->get_list($map[$status])]
|
||||
: $this->model->get_all_lists();
|
||||
|
||||
$this->outputHTML('manga/' . $view_map[$view], [
|
||||
'title' => $title,
|
||||
'sections' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the manga edit form
|
||||
*
|
||||
* @param string $id
|
||||
* @param string $status
|
||||
* @return void
|
||||
*/
|
||||
public function edit($id, $status = "All")
|
||||
{
|
||||
$this->set_session_redirect();
|
||||
$item = $this->model->get_library_item($id, $status);
|
||||
$title = $this->config->get('whose_list') . "'s Manga List · Edit";
|
||||
|
||||
$this->outputHTML('manga/edit', [
|
||||
'title' => $title,
|
||||
'status_list' => MangaReadingStatus::getConstList(),
|
||||
'item' => $item,
|
||||
'action' => $this->container->get('url-generator')
|
||||
->url('/manga/update_form'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an anime item via a form submission
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function form_update()
|
||||
{
|
||||
$post_data = $this->request->post->get();
|
||||
|
||||
// Do some minor data manipulation for
|
||||
// large form-based updates
|
||||
$transformer = new MangaListTransformer();
|
||||
$post_data = $transformer->untransform($post_data);
|
||||
$full_result = $this->model->update($post_data);
|
||||
|
||||
$result = $full_result['body'];
|
||||
|
||||
if (array_key_exists('manga', $result))
|
||||
{
|
||||
$m =& $result['manga'][0];
|
||||
$title = ( ! empty($m['english_title']))
|
||||
? "{$m['romaji_title']} ({$m['english_title']})"
|
||||
: "{$m['romaji_title']}";
|
||||
|
||||
$this->set_flash_message("Successfully updated {$title}.", 'success');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->set_flash_message('Failed to update anime.', 'error');
|
||||
}
|
||||
|
||||
$this->session_redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an anime item
|
||||
*
|
||||
* @return boolean|null
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$this->outputJSON($this->model->update($this->request->post->get()));
|
||||
}
|
||||
}
|
||||
// End of MangaController.php
|
|
@ -0,0 +1,348 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aura\Web\Request;
|
||||
use Aura\Web\Response;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\AnimeClient;
|
||||
|
||||
/**
|
||||
* Basic routing/ dispatch
|
||||
*/
|
||||
class Dispatcher extends RoutingBase {
|
||||
|
||||
/**
|
||||
* The route-matching object
|
||||
* @var object $router
|
||||
*/
|
||||
protected $router;
|
||||
|
||||
/**
|
||||
* Class wrapper for input superglobals
|
||||
* @var object
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Routes added to router
|
||||
* @var array $output_routes
|
||||
*/
|
||||
protected $output_routes;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->router = $container->get('aura-router');
|
||||
$this->request = $container->get('request');
|
||||
|
||||
$this->output_routes = $this->_setup_routes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current route object, if one matches
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function get_route()
|
||||
{
|
||||
$logger = $this->container->getLogger('default');
|
||||
|
||||
$raw_route = $this->request->url->get(PHP_URL_PATH);
|
||||
$route_path = "/" . trim($raw_route, '/');
|
||||
|
||||
$logger->debug('Dispatcher - Routing data from get_route method');
|
||||
$logger->debug(print_r([
|
||||
'route_path' => $route_path
|
||||
], TRUE));
|
||||
|
||||
return $this->router->match($route_path, $_SERVER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of routes applied
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_output_routes()
|
||||
{
|
||||
return $this->output_routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the current route
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param object|null $route
|
||||
* @return void
|
||||
*/
|
||||
public function __invoke($route = NULL)
|
||||
{
|
||||
$logger = $this->container->getLogger('default');
|
||||
|
||||
if (is_null($route))
|
||||
{
|
||||
$route = $this->get_route();
|
||||
|
||||
$logger->debug('Dispatcher - Route invoke arguments');
|
||||
$logger->debug(print_r($route, TRUE));
|
||||
}
|
||||
|
||||
if($route)
|
||||
{
|
||||
$parsed = $this->process_route($route);
|
||||
$controller_name = $parsed['controller_name'];
|
||||
$action_method = $parsed['action_method'];
|
||||
$params = $parsed['params'];
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not route was matched, return an appropriate http
|
||||
// error message
|
||||
$error_route = $this->get_error_params();
|
||||
$controller_name = AnimeClient::DEFAULT_CONTROLLER;
|
||||
$action_method = $error_route['action_method'];
|
||||
$params = $error_route['params'];
|
||||
}
|
||||
|
||||
// Actually instantiate the controller
|
||||
$this->call($controller_name, $action_method, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse out the arguments for the appropriate controller for
|
||||
* the current route
|
||||
*
|
||||
* @param \Aura\Router\Route $route
|
||||
* @return array
|
||||
*/
|
||||
protected function process_route($route)
|
||||
{
|
||||
if (array_key_exists('controller', $route->params))
|
||||
{
|
||||
$controller_name = $route->params['controller'];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \LogicException("Missing controller");
|
||||
}
|
||||
|
||||
// Get the full namespace for a controller if a short name is given
|
||||
if (strpos($controller_name, '\\') === FALSE)
|
||||
{
|
||||
$map = $this->get_controller_list();
|
||||
$controller_name = $map[$controller_name];
|
||||
}
|
||||
|
||||
$action_method = (array_key_exists('action', $route->params))
|
||||
? $route->params['action']
|
||||
: AnimeClient::NOT_FOUND_METHOD;
|
||||
|
||||
$params = (array_key_exists('params', $route->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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'controller_name' => $controller_name,
|
||||
'action_method' => $action_method,
|
||||
'params' => $params
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of route, to select the current controller
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_controller()
|
||||
{
|
||||
$route_type = $this->__get('default_list');
|
||||
$request_uri = $this->request->url->get(PHP_URL_PATH);
|
||||
$path = trim($request_uri, '/');
|
||||
|
||||
$segments = explode('/', $path);
|
||||
$controller = reset($segments);
|
||||
|
||||
if (empty($controller))
|
||||
{
|
||||
$controller = $route_type;
|
||||
}
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of controllers in the default namespace
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_controller_list()
|
||||
{
|
||||
$default_namespace = AnimeClient::DEFAULT_CONTROLLER_NAMESPACE;
|
||||
$path = str_replace('\\', '/', $default_namespace);
|
||||
$path = trim($path, '/');
|
||||
$actual_path = realpath(\_dir(AnimeClient::SRC_DIR, $path));
|
||||
$class_files = glob("{$actual_path}/*.php");
|
||||
|
||||
$controllers = [];
|
||||
|
||||
foreach ($class_files as $file)
|
||||
{
|
||||
$raw_class_name = basename(str_replace(".php", "", $file));
|
||||
$path = strtolower(basename($raw_class_name));
|
||||
$class_name = trim($default_namespace . '\\' . $raw_class_name, '\\');
|
||||
|
||||
$controllers[$path] = $class_name;
|
||||
}
|
||||
|
||||
return $controllers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the controller object and call the appropriate
|
||||
* method
|
||||
*
|
||||
* @param string $controller_name - The full namespace of the controller class
|
||||
* @param string $method
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
protected function call($controller_name, $method, array $params)
|
||||
{
|
||||
$logger = $this->container->getLogger('default');
|
||||
|
||||
$controller = new $controller_name($this->container);
|
||||
|
||||
// Run the appropriate controller method
|
||||
$logger->debug('Dispatcher - controller arguments');
|
||||
$logger->debug(print_r($params, TRUE));
|
||||
call_user_func_array([$controller, $method], $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate params for the error page
|
||||
* pased on the failed route
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
protected function get_error_params()
|
||||
{
|
||||
$logger = $this->container->getLogger('default');
|
||||
$failure = $this->router->getFailedRoute();
|
||||
|
||||
$logger->info('Dispatcher - failed route');
|
||||
$logger->info(print_r($failure, TRUE));
|
||||
|
||||
$action_method = AnimeClient::ERROR_MESSAGE_METHOD;
|
||||
|
||||
$params = [];
|
||||
|
||||
if ($failure->failedMethod())
|
||||
{
|
||||
$params = [
|
||||
'http_code' => 405,
|
||||
'title' => '405 Method Not Allowed',
|
||||
'message' => 'Invalid HTTP Verb'
|
||||
];
|
||||
}
|
||||
else if($failure->failedAccept())
|
||||
{
|
||||
$params = [
|
||||
'http_code' => 406,
|
||||
'title' => '406 Not Acceptable',
|
||||
'message' => 'Unacceptable content type'
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fall back to a 404 message
|
||||
$action_method = AnimeClient::NOT_FOUND_METHOD;
|
||||
}
|
||||
|
||||
return [
|
||||
'params' => $params,
|
||||
'action_method' => $action_method
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Select controller based on the current url, and apply its relevent routes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _setup_routes()
|
||||
{
|
||||
$route_type = $this->get_controller();
|
||||
|
||||
// Add routes
|
||||
$routes = [];
|
||||
foreach ($this->routes as $name => &$route)
|
||||
{
|
||||
$path = $route['path'];
|
||||
unset($route['path']);
|
||||
|
||||
$controller_map = $this->get_controller_list();
|
||||
$controller_class = (array_key_exists($route_type, $controller_map))
|
||||
? $controller_map[$route_type]
|
||||
: AnimeClient::DEFAULT_CONTROLLER;
|
||||
|
||||
if (array_key_exists($route_type, $controller_map))
|
||||
{
|
||||
$controller_class = $controller_map[$route_type];
|
||||
}
|
||||
|
||||
// Prepend the controller to the route parameters
|
||||
$route['controller'] = $controller_class;
|
||||
|
||||
// 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))
|
||||
{
|
||||
$routes[] = $this->router->$add($name, $path)->addValues($route);
|
||||
}
|
||||
else
|
||||
{
|
||||
$tokens = $route['tokens'];
|
||||
unset($route['tokens']);
|
||||
|
||||
$routes[] = $this->router->$add($name, $path)
|
||||
->addValues($route)
|
||||
->addTokens($tokens);
|
||||
}
|
||||
}
|
||||
|
||||
return $routes;
|
||||
}
|
||||
}
|
||||
// End of Dispatcher.php
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Helper;
|
||||
|
||||
use Aviat\AnimeClient\MenuGenerator;
|
||||
|
||||
/**
|
||||
* MenuGenerator helper wrapper
|
||||
*/
|
||||
class Menu {
|
||||
|
||||
use \Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
/**
|
||||
* Create the html for the selected menu
|
||||
*
|
||||
* @param string $menu_name
|
||||
* @return string
|
||||
*/
|
||||
public function __invoke($menu_name)
|
||||
{
|
||||
$generator = new MenuGenerator($this->container);
|
||||
return $generator->generate($menu_name);
|
||||
}
|
||||
|
||||
}
|
||||
// End of Menu.php
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Hummingbird\Enum;
|
||||
|
||||
use Aviat\Ion\Enum as BaseEnum;
|
||||
|
||||
/**
|
||||
* Status of when anime is being/was/will be aired
|
||||
*/
|
||||
class AnimeAiringStatus extends BaseEnum {
|
||||
const NOT_YET_AIRED = 'Not Yet Aired';
|
||||
const AIRING = 'Currently Airing';
|
||||
const FINISHED_AIRING = 'Finished Airing';
|
||||
}
|
||||
// End of AnimeAiringStatus.php
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Hummingbird\Enum;
|
||||
|
||||
use Aviat\Ion\Enum as BaseEnum;
|
||||
|
||||
/**
|
||||
* Type of Anime
|
||||
*/
|
||||
class AnimeShowType extends BaseEnum {
|
||||
const TV = 'TV';
|
||||
const MOVIE = 'Movie';
|
||||
const OVA = 'OVA';
|
||||
const ONA = 'ONA';
|
||||
const SPECIAL = 'Special';
|
||||
const MUSIC = 'Music';
|
||||
}
|
||||
// End of AnimeShowType.php
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Hummingbird\Enum;
|
||||
|
||||
use Aviat\Ion\Enum as BaseEnum;
|
||||
|
||||
/**
|
||||
* Possible values for watching status for the current anime
|
||||
*/
|
||||
class AnimeWatchingStatus extends BaseEnum {
|
||||
const WATCHING = 'currently-watching';
|
||||
const PLAN_TO_WATCH = 'plan-to-watch';
|
||||
const COMPLETED = 'completed';
|
||||
const ON_HOLD = 'on-hold';
|
||||
const DROPPED = 'dropped';
|
||||
}
|
||||
// End of AnimeWatchingStatus.php
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Hummingbird\Enum;
|
||||
|
||||
use Aviat\Ion\Enum as BaseEnum;
|
||||
|
||||
/**
|
||||
* Possible values for current reading status of manga
|
||||
*/
|
||||
class MangaReadingStatus extends BaseEnum {
|
||||
const READING = 'Currently Reading';
|
||||
const PLAN_TO_READ = 'Plan to Read';
|
||||
const DROPPED = 'Dropped';
|
||||
const ON_HOLD = 'On Hold';
|
||||
const COMPLETED = 'Completed';
|
||||
}
|
||||
// End of MangaReadingStatus.php
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Hummingbird\Transformer;
|
||||
|
||||
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||
|
||||
/**
|
||||
* Transformer for anime list
|
||||
*/
|
||||
class AnimeListTransformer extends AbstractTransformer {
|
||||
|
||||
/**
|
||||
* Convert raw api response to a more
|
||||
* logical and workable structure
|
||||
*
|
||||
* @param array $item API library item
|
||||
* @return array
|
||||
*/
|
||||
public function transform($item)
|
||||
{
|
||||
$anime =& $item['anime'];
|
||||
$genres = $this->linearize_genres($item['anime']['genres']);
|
||||
|
||||
$rating = NULL;
|
||||
if ($item['rating']['type'] === 'advanced')
|
||||
{
|
||||
$rating = (is_numeric($item['rating']['value']))
|
||||
? intval(2 * $item['rating']['value'])
|
||||
: '-';
|
||||
}
|
||||
|
||||
$total_episodes = (is_numeric($anime['episode_count']))
|
||||
? $anime['episode_count']
|
||||
: '-';
|
||||
|
||||
$alternate_title = NULL;
|
||||
if (array_key_exists('alternate_title', $anime))
|
||||
{
|
||||
// If the alternate title is very similar, or
|
||||
// a subset of the main title, don't list the
|
||||
// alternate title
|
||||
$not_subset = stripos($anime['title'], $anime['alternate_title']) === FALSE;
|
||||
$diff = levenshtein($anime['title'], $anime['alternate_title']);
|
||||
if ($not_subset && $diff >= 5)
|
||||
{
|
||||
$alternate_title = $anime['alternate_title'];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $item['id'],
|
||||
'episodes' => [
|
||||
'watched' => $item['episodes_watched'],
|
||||
'total' => $total_episodes,
|
||||
'length' => $anime['episode_length'],
|
||||
],
|
||||
'airing' => [
|
||||
'status' => $anime['status'],
|
||||
'started' => $anime['started_airing'],
|
||||
'ended' => $anime['finished_airing']
|
||||
],
|
||||
'anime' => [
|
||||
'age_rating' => $anime['age_rating'],
|
||||
'title' => $anime['title'],
|
||||
'alternate_title' => $alternate_title,
|
||||
'slug' => $anime['slug'],
|
||||
'url' => $anime['url'],
|
||||
'type' => $anime['show_type'],
|
||||
'image' => $anime['cover_image'],
|
||||
'genres' => $genres,
|
||||
],
|
||||
'watching_status' => $item['status'],
|
||||
'notes' => $item['notes'],
|
||||
'rewatching' => (bool) $item['rewatching'],
|
||||
'rewatched' => $item['rewatched_times'],
|
||||
'user_rating' => $rating,
|
||||
'private' => (bool) $item['private'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert transformed data to
|
||||
* api response format
|
||||
*
|
||||
* @param array $item Transformed library item
|
||||
* @return array API library item
|
||||
*/
|
||||
public function untransform($item)
|
||||
{
|
||||
// Messy mapping of boolean values to their API string equivalents
|
||||
$privacy = 'public';
|
||||
if (array_key_exists('private', $item) && $item['private'])
|
||||
{
|
||||
$privacy = 'private';
|
||||
}
|
||||
|
||||
$rewatching = 'false';
|
||||
if (array_key_exists('rewatching', $item) && $item['rewatching'])
|
||||
{
|
||||
$rewatching = 'true';
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $item['id'],
|
||||
'status' => $item['watching_status'],
|
||||
'sane_rating_update' => $item['user_rating'] / 2,
|
||||
'rewatching' => $rewatching,
|
||||
'rewatched_times' => $item['rewatched'],
|
||||
'notes' => $item['notes'],
|
||||
'episodes_watched' => $item['episodes_watched'],
|
||||
'privacy' => $privacy
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify structure of genre list
|
||||
*
|
||||
* @param array $raw_genres
|
||||
* @return array
|
||||
*/
|
||||
protected function linearize_genres(array $raw_genres)
|
||||
{
|
||||
$genres = [];
|
||||
|
||||
foreach ($raw_genres as $genre)
|
||||
{
|
||||
$genres[] = $genre['name'];
|
||||
}
|
||||
|
||||
return $genres;
|
||||
}
|
||||
}
|
||||
// End of AnimeListTransformer.php
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Hummingbird\Transformer;
|
||||
|
||||
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||
|
||||
/**
|
||||
* Data transformation class for zippered Hummingbird manga
|
||||
*/
|
||||
class MangaListTransformer extends AbstractTransformer {
|
||||
|
||||
use \Aviat\Ion\StringWrapper;
|
||||
|
||||
/**
|
||||
* Remap zipped anime data to a more logical form
|
||||
*
|
||||
* @param array $item manga entry item
|
||||
* @return array
|
||||
*/
|
||||
public function transform($item)
|
||||
{
|
||||
$manga =& $item['manga'];
|
||||
|
||||
$rating = (is_numeric($item['rating']))
|
||||
? intval(2 * $item['rating'])
|
||||
: '-';
|
||||
|
||||
$total_chapters = ($manga['chapter_count'] > 0)
|
||||
? $manga['chapter_count']
|
||||
: '-';
|
||||
|
||||
$total_volumes = ($manga['volume_count'] > 0)
|
||||
? $manga['volume_count']
|
||||
: '-';
|
||||
|
||||
$map = [
|
||||
'id' => $item['id'],
|
||||
'chapters' => [
|
||||
'read' => $item['chapters_read'],
|
||||
'total' => $total_chapters
|
||||
],
|
||||
'volumes' => [
|
||||
'read' => $item['volumes_read'],
|
||||
'total' => $total_volumes
|
||||
],
|
||||
'manga' => [
|
||||
'title' => $manga['romaji_title'],
|
||||
'alternate_title' => NULL,
|
||||
'slug' => $manga['id'],
|
||||
'url' => 'https://hummingbird.me/manga/' . $manga['id'],
|
||||
'type' => $manga['manga_type'],
|
||||
'image' => $manga['poster_image_thumb'],
|
||||
'genres' => $manga['genres'],
|
||||
],
|
||||
'reading_status' => $item['status'],
|
||||
'notes' => $item['notes'],
|
||||
'rereading' => (bool)$item['rereading'],
|
||||
'reread' => $item['reread_count'],
|
||||
'user_rating' => $rating,
|
||||
];
|
||||
|
||||
if (array_key_exists('english_title', $manga))
|
||||
{
|
||||
$diff = levenshtein($manga['romaji_title'], $manga['english_title']);
|
||||
|
||||
// If the titles are REALLY similar, don't bother showing both
|
||||
if ($diff >= 5)
|
||||
{
|
||||
$map['manga']['alternate_title'] = $manga['english_title'];
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Untransform data to update the api
|
||||
*
|
||||
* @param array $item
|
||||
* @return array
|
||||
*/
|
||||
public function untransform($item)
|
||||
{
|
||||
$rereading = (array_key_exists('rereading', $item)) && (bool)$item['rereading'];
|
||||
|
||||
$map = [
|
||||
'id' => $item['id'],
|
||||
'manga_id' => $item['manga_id'],
|
||||
'status' => $item['status'],
|
||||
'chapters_read' => (int)$item['chapters_read'],
|
||||
'volumes_read' => (int)$item['volumes_read'],
|
||||
'rereading' => $rereading,
|
||||
'reread_count' => (int)$item['reread_count'],
|
||||
'notes' => $item['notes'],
|
||||
];
|
||||
|
||||
if ($item['new_rating'] !== $item['old_rating'])
|
||||
{
|
||||
$map['rating'] = ($item['new_rating'] > 0)
|
||||
? $item['new_rating'] / 2
|
||||
: $item['old_rating'] / 2;
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
// End of MangaListTransformer.php
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Hummingbird\Transformer;
|
||||
|
||||
/**
|
||||
* Merges the two separate manga lists together
|
||||
*/
|
||||
class MangaListsZipper {
|
||||
|
||||
/**
|
||||
* List of manga information
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $manga_series_list = [];
|
||||
|
||||
/**
|
||||
* List of manga tracking information
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $manga_tracking_list = [];
|
||||
|
||||
/**
|
||||
* Create the transformer
|
||||
*
|
||||
* @param array $merge_lists The raw manga data
|
||||
*/
|
||||
public function __construct(array $merge_lists)
|
||||
{
|
||||
$this->manga_series_list = $merge_lists['manga'];
|
||||
$this->manga_tracking_list = $merge_lists['manga_library_entries'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the transformation, and return the output
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function transform()
|
||||
{
|
||||
$this->index_manga_entries();
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($this->manga_tracking_list as &$entry)
|
||||
{
|
||||
$id = $entry['manga_id'];
|
||||
$entry['manga'] = $this->manga_series_list[$id];
|
||||
unset($entry['manga_id']);
|
||||
|
||||
$output[] = $entry;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Index manga series by the id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function index_manga_entries()
|
||||
{
|
||||
$orig_list = $this->manga_series_list;
|
||||
$indexed_list = [];
|
||||
|
||||
foreach ($orig_list as $manga)
|
||||
{
|
||||
$id = $manga['id'];
|
||||
$indexed_list[$id] = $manga;
|
||||
}
|
||||
|
||||
$this->manga_series_list = $indexed_list;
|
||||
}
|
||||
|
||||
}
|
||||
// End of ManagListsZipper.php
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Helper object to manage menu creation and selection
|
||||
*/
|
||||
class MenuGenerator extends UrlGenerator {
|
||||
|
||||
use \Aviat\Ion\StringWrapper;
|
||||
use \Aviat\Ion\ArrayWrapper;
|
||||
|
||||
/**
|
||||
* Html generation helper
|
||||
*
|
||||
* @var Aura\Html\HelperLocator
|
||||
*/
|
||||
protected $helper;
|
||||
|
||||
/**
|
||||
* Request object
|
||||
*
|
||||
* @var Aura\Web\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Create menu generator
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->helper = $container->get('html-helper');
|
||||
$this->request = $container->get('request');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the full menu structure from the config files
|
||||
*
|
||||
* @param array $menus
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_config(array $menus)
|
||||
{
|
||||
$parsed = [];
|
||||
|
||||
foreach ($menus as $name => $menu)
|
||||
{
|
||||
$parsed[$name] = [];
|
||||
foreach ($menu['items'] as $path_name => $partial_path)
|
||||
{
|
||||
$title = (string)$this->string($path_name)->humanize()->titleize();
|
||||
$parsed[$name][$title] = (string)$this->string($menu['route_prefix'])->append($partial_path);
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the html structure of the menu selected
|
||||
*
|
||||
* @param string $menu
|
||||
* @return string
|
||||
*/
|
||||
public function generate($menu)
|
||||
{
|
||||
$menus = $this->config->get('menus');
|
||||
$parsed_config = $this->parse_config($menus);
|
||||
|
||||
// Bail out early on invalid menu
|
||||
if ( ! $this->arr($parsed_config)->has_key($menu))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
$menu_config = $parsed_config[$menu];
|
||||
|
||||
foreach ($menu_config as $title => $path)
|
||||
{
|
||||
$has = $this->string($path)->contains($this->path());
|
||||
$selected = ($has && strlen($path) >= strlen($this->path()));
|
||||
|
||||
$link = $this->helper->a($this->url($path), $title);
|
||||
|
||||
$attrs = ($selected)
|
||||
? ['class' => 'selected']
|
||||
: [];
|
||||
|
||||
$this->helper->ul()->rawItem($link, $attrs);
|
||||
}
|
||||
|
||||
// Create the menu html
|
||||
return $this->helper->ul();
|
||||
}
|
||||
}
|
||||
// End of MenuGenerator.php
|
|
@ -1,29 +1,48 @@
|
|||
<?php
|
||||
/**
|
||||
* Base for base models
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
namespace AnimeClient;
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use abeautifulsite\SimpleImage;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Common base for all Models
|
||||
*/
|
||||
class BaseModel {
|
||||
class Model {
|
||||
|
||||
use \Aviat\Ion\StringWrapper;
|
||||
|
||||
/**
|
||||
* The global configuration object
|
||||
* @var object $config
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* The container object
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
public function __construct()
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
global $config;
|
||||
$this->config = $config;
|
||||
$this->container = $container;
|
||||
$this->config = $container->get('config');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,7 +55,7 @@ class BaseModel {
|
|||
* @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")
|
||||
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));
|
||||
|
@ -45,22 +64,25 @@ class BaseModel {
|
|||
$ext = end($ext_parts);
|
||||
|
||||
// Workaround for some broken extensions
|
||||
if ($ext == "jjpg") $ext = "jpg";
|
||||
if ($ext == "jjpg")
|
||||
{
|
||||
$ext = "jpg";
|
||||
}
|
||||
|
||||
// Failsafe for weird urls
|
||||
if (strlen($ext) > 3) return $api_path;
|
||||
if (strlen($ext) > 3)
|
||||
{
|
||||
return $api_path;
|
||||
}
|
||||
|
||||
$img_cache_path = $this->config->get('img_cache_path');
|
||||
$cached_image = "{$series_slug}.{$ext}";
|
||||
$cached_path = "{$this->config->img_cache_path}/{$type}/{$cached_image}";
|
||||
$cached_path = "{$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'))
|
||||
if (function_exists('curl_init'))
|
||||
{
|
||||
$ch = curl_init($api_path);
|
||||
$fp = fopen($cached_path, 'wb');
|
||||
|
@ -70,11 +92,15 @@ class BaseModel {
|
|||
]);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
fclose($ch);
|
||||
fclose($fp);
|
||||
}
|
||||
else if (ini_get('allow_url_fopen'))
|
||||
{
|
||||
copy($api_path, $cached_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Couldn't cache images because they couldn't be downloaded.");
|
||||
throw new DomainException("Couldn't cache images because they couldn't be downloaded.");
|
||||
}
|
||||
|
||||
// Resize the image
|
||||
|
@ -100,7 +126,7 @@ class BaseModel {
|
|||
private function _resize($path, $width, $height)
|
||||
{
|
||||
$img = new SimpleImage($path);
|
||||
$img->resize($width,$height)->save();
|
||||
$img->resize($width, $height)->save();
|
||||
}
|
||||
}
|
||||
// End of BaseModel.php
|
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use GuzzleHttp\Psr7\ResponseInterface;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Model as BaseModel;
|
||||
|
||||
/**
|
||||
* Base model for api interaction
|
||||
*
|
||||
* @method ResponseInterface get(string $uri, array $options);
|
||||
* @method ResponseInterface delete(string $uri, array $options);
|
||||
* @method ResponseInterface head(string $uri, array $options);
|
||||
* @method ResponseInterface options(string $uri, array $options);
|
||||
* @method ResponseInterface patch(string $uri, array $options);
|
||||
* @method ResponseInterface post(string $uri, array $options);
|
||||
* @method ResponseInterface put(string $uri, array $options);
|
||||
*/
|
||||
class API extends BaseModel {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the class properties
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
$this->cookieJar = new CookieJar();
|
||||
$this->client = new Client([
|
||||
'base_uri' => $this->base_url,
|
||||
'cookies' => TRUE,
|
||||
'http_errors' => FALSE,
|
||||
'defaults' => [
|
||||
'cookies' => $this->cookieJar,
|
||||
'headers' => [
|
||||
'User-Agent' => "Tim's Anime Client/2.0",
|
||||
'Accept-Encoding' => 'application/json'
|
||||
],
|
||||
'timeout' => 25,
|
||||
'connect_timeout' => 25
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic methods to call guzzle api client
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return ResponseInterface|null
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
$valid_methods = [
|
||||
'get',
|
||||
'delete',
|
||||
'head',
|
||||
'options',
|
||||
'patch',
|
||||
'post',
|
||||
'put'
|
||||
];
|
||||
|
||||
if ( ! in_array($method, $valid_methods))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
array_unshift($args, strtoupper($method));
|
||||
return call_user_func_array([$this->client, 'request'], $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data for the specified library entry
|
||||
*
|
||||
* @param string $id
|
||||
* @param string $status
|
||||
* @return array
|
||||
*/
|
||||
public function get_library_item($id, $status)
|
||||
{
|
||||
$data = $this->_get_list_from_api($status);
|
||||
$index_array = array_column($data, 'id');
|
||||
|
||||
$key = array_search($id, $index_array);
|
||||
|
||||
return $key !== FALSE
|
||||
? $data[$key]
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the manga entries by their title
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param array $array
|
||||
* @param string $sort_key
|
||||
* @return void
|
||||
*/
|
||||
protected function sort_by_name(&$array, $sort_key)
|
||||
{
|
||||
$sort = array();
|
||||
|
||||
foreach ($array as $key => $item)
|
||||
{
|
||||
$sort[$key] = $item[$sort_key]['title'];
|
||||
}
|
||||
|
||||
array_multisort($sort, SORT_ASC, $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt login via the api
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return string|false
|
||||
*/
|
||||
public function authenticate($username, $password)
|
||||
{
|
||||
$response = $this->post('https://hummingbird.me/api/v1/users/authenticate', [
|
||||
'form_params' => [
|
||||
'username' => $username,
|
||||
'password' => $password
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() === 201)
|
||||
{
|
||||
return json_decode($response->getBody(), TRUE);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
// End of BaseApiModel.php
|
|
@ -0,0 +1,241 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use Aviat\Ion\Json;
|
||||
use Aviat\AnimeClient\Hummingbird\Enum\AnimeWatchingStatus;
|
||||
use Aviat\AnimeClient\Hummingbird\Transformer\AnimeListTransformer;
|
||||
|
||||
/**
|
||||
* Model for handling requests dealing with the anime list
|
||||
*/
|
||||
class Anime extends API {
|
||||
|
||||
// Display constants
|
||||
const WATCHING = 'Watching';
|
||||
const PLAN_TO_WATCH = 'Plan to Watch';
|
||||
const DROPPED = 'Dropped';
|
||||
const ON_HOLD = 'On Hold';
|
||||
const COMPLETED = 'Completed';
|
||||
|
||||
/**
|
||||
* The base url for api requests
|
||||
* @var string $base_url
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param array $data
|
||||
* @return array|false
|
||||
*/
|
||||
public function update($data)
|
||||
{
|
||||
$auth = $this->container->get('auth');
|
||||
if ( ! $auth->is_authenticated() || ! array_key_exists('id', $data))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$id = $data['id'];
|
||||
$data['auth_token'] = $auth->get_auth_token();
|
||||
|
||||
$response = $this->client->post("libraries/{$id}", [
|
||||
'form_params' => $data
|
||||
]);
|
||||
|
||||
return [
|
||||
'statusCode' => $response->getStatusCode(),
|
||||
'body' => Json::decode($response->getBody(), TRUE)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full set of anime lists
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_lists()
|
||||
{
|
||||
$output = [
|
||||
self::WATCHING => [],
|
||||
self::PLAN_TO_WATCH => [],
|
||||
self::ON_HOLD => [],
|
||||
self::DROPPED => [],
|
||||
self::COMPLETED => [],
|
||||
];
|
||||
|
||||
$data = $this->_get_list_from_api();
|
||||
|
||||
foreach ($data as $datum)
|
||||
{
|
||||
$output[$this->const_map[$datum['watching_status']]][] = $datum;
|
||||
}
|
||||
|
||||
// Sort anime by name
|
||||
foreach ($output as &$status_list)
|
||||
{
|
||||
$this->sort_by_name($status_list, 'anime');
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a category out of the full list
|
||||
*
|
||||
* @param string $status
|
||||
* @return array
|
||||
*/
|
||||
public function get_list($status)
|
||||
{
|
||||
$data = $this->_get_list_from_api($status);
|
||||
$this->sort_by_name($data, 'anime');
|
||||
|
||||
$output = [];
|
||||
$output[$this->const_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 Json::decode($response->getBody(), TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for anime by name
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public function search($name)
|
||||
{
|
||||
$logger = $this->container->getLogger('default');
|
||||
|
||||
$config = [
|
||||
'query' => [
|
||||
'query' => $name
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->get('search/anime', $config);
|
||||
|
||||
if ($response->getStatusCode() != 200)
|
||||
{
|
||||
$logger->warning("Non 200 response for search api call");
|
||||
$logger->warning($response->getBody());
|
||||
|
||||
throw new RuntimeException($response->getEffectiveUrl());
|
||||
}
|
||||
|
||||
return Json::decode($response->getBody(), TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve data from the api
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $status
|
||||
* @return array
|
||||
*/
|
||||
protected function _get_list_from_api($status = "all")
|
||||
{
|
||||
$config = [
|
||||
'allow_redirects' => FALSE
|
||||
];
|
||||
|
||||
if ($status != "all")
|
||||
{
|
||||
$config['query']['status'] = $status;
|
||||
}
|
||||
|
||||
$username = $this->config->get('hummingbird_username');
|
||||
$auth = $this->container->get('auth');
|
||||
if ($auth->is_authenticated())
|
||||
{
|
||||
$config['query']['auth_token'] = $auth->get_auth_token();
|
||||
}
|
||||
|
||||
$response = $this->get("users/{$username}/library", $config);
|
||||
$output = $this->_check_cache($status, $response);
|
||||
|
||||
foreach ($output as &$row)
|
||||
{
|
||||
$row['anime']['image'] = $this->get_cached_image($row['anime']['image'], $row['anime']['slug'], 'anime');
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle caching of transformed api data
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $status
|
||||
* @param \GuzzleHttp\Message\Response
|
||||
* @return array
|
||||
*/
|
||||
protected function _check_cache($status, $response)
|
||||
{
|
||||
$cache_file = _dir($this->config->get('data_cache_path'), "anime-{$status}.json");
|
||||
$transformed_cache_file = _dir($this->config->get('data_cache_path'), "anime-{$status}-transformed.json");
|
||||
|
||||
$cached = (file_exists($cache_file))
|
||||
? Json::decodeFile($cache_file)
|
||||
: [];
|
||||
$api_data = Json::decode($response->getBody(), TRUE);
|
||||
|
||||
if ($api_data === $cached && file_exists($transformed_cache_file))
|
||||
{
|
||||
return Json::decodeFile($transformed_cache_file);
|
||||
}
|
||||
else
|
||||
{
|
||||
Json::encodeFile($cache_file, $api_data);
|
||||
$transformer = new AnimeListTransformer();
|
||||
$transformed = $transformer->transform_collection($api_data);
|
||||
Json::encodeFile($transformed_cache_file, $transformed);
|
||||
return $transformed;
|
||||
}
|
||||
}
|
||||
}
|
||||
// End of AnimeModel.php
|
|
@ -0,0 +1,441 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use Aviat\Ion\Json;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\AnimeClient;
|
||||
use Aviat\AnimeClient\Model\Anime as AnimeModel;
|
||||
|
||||
/**
|
||||
* Model for getting anime collection data
|
||||
*/
|
||||
class AnimeCollection extends DB {
|
||||
|
||||
/**
|
||||
* Anime API Model
|
||||
* @var object $anime_model
|
||||
*/
|
||||
private $anime_model;
|
||||
|
||||
/**
|
||||
* Whether the database is valid for querying
|
||||
* @var bool
|
||||
*/
|
||||
private $valid_database = FALSE;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
|
||||
try
|
||||
{
|
||||
$this->db = \Query($this->db_config['collection']);
|
||||
}
|
||||
catch (\PDOException $e)
|
||||
{
|
||||
$this->valid_database = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
$this->anime_model = $container->get('anime-model');
|
||||
|
||||
// Is database valid? If not, set a flag so the
|
||||
// app can be run without a valid database
|
||||
$db_file_name = $this->db_config['collection']['file'];
|
||||
if ($db_file_name !== ':memory:')
|
||||
{
|
||||
if (file_exists($db_file_name))
|
||||
{
|
||||
$db_file = file_get_contents($db_file_name);
|
||||
$this->valid_database = (strpos($db_file, 'SQLite format 3') === 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->valid_database = FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->valid_database = TRUE;
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a colleciton 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('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') || ! $this->valid_database)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$anime = Json::decodeFile("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
|
||||
*
|
||||
* @param int $anime_id The current 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
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Model as BaseModel;
|
||||
|
||||
/**
|
||||
* Base model for database interaction
|
||||
*/
|
||||
class DB extends BaseModel {
|
||||
/**
|
||||
* The query builder object
|
||||
* @var object $db
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* The database connection information array
|
||||
* @var array $db_config
|
||||
*/
|
||||
protected $db_config;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->db_config = (array)$this->config->get('database');
|
||||
}
|
||||
}
|
||||
// End of BaseDBModel.php
|
|
@ -0,0 +1,224 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use GuzzleHttp\Cookie\Cookiejar;
|
||||
use GuzzleHttp\Cookie\SetCookie;
|
||||
|
||||
use Aviat\Ion\Json;
|
||||
use Aviat\AnimeClient\Model\API;
|
||||
use Aviat\AnimeClient\Hummingbird\Transformer;
|
||||
use Aviat\AnimeClient\Hummingbird\Enum\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
|
||||
*/
|
||||
protected $base_url = "https://hummingbird.me/";
|
||||
|
||||
/**
|
||||
* Update the selected manga
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function update($data)
|
||||
{
|
||||
$id = $data['id'];
|
||||
|
||||
$token = $this->container->get('auth')
|
||||
->get_auth_token();
|
||||
|
||||
// Set the token cookie, with the authentication token
|
||||
// from the auth class.
|
||||
$cookieJar = $this->cookieJar;
|
||||
$cookie_data = new SetCookie([
|
||||
'Name' => 'token',
|
||||
'Value' => $token,
|
||||
'Domain' => 'hummingbird.me'
|
||||
]);
|
||||
$cookieJar->setCookie($cookie_data);
|
||||
|
||||
$result = $this->put("manga_library_entries/{$id}", [
|
||||
'cookies' => $cookieJar,
|
||||
'json' => ['manga_library_entry' => $data]
|
||||
]);
|
||||
|
||||
return [
|
||||
'statusCode' => $result->getStatusCode(),
|
||||
'body' => Json::decode($result->getBody(), TRUE)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full set of anime lists
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_lists()
|
||||
{
|
||||
$data = $this->_get_list_from_api();
|
||||
|
||||
foreach ($data as &$val)
|
||||
{
|
||||
$this->sort_by_name($val, 'manga');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a category out of the full list
|
||||
*
|
||||
* @param string $status
|
||||
* @return array
|
||||
*/
|
||||
public function get_list($status)
|
||||
{
|
||||
$data = $this->_get_list_from_api($status);
|
||||
$this->sort_by_name($data, 'manga');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the list from the hummingbird api
|
||||
*
|
||||
* @param string $status
|
||||
* @return array
|
||||
*/
|
||||
protected function _get_list_from_api($status = "All")
|
||||
{
|
||||
$config = [
|
||||
'query' => [
|
||||
'user_id' => $this->config->get('hummingbird_username')
|
||||
],
|
||||
'allow_redirects' => FALSE
|
||||
];
|
||||
|
||||
$response = $this->get('manga_library_entries', $config);
|
||||
$data = $this->_check_cache($response);
|
||||
$output = $this->map_by_status($data);
|
||||
|
||||
return (array_key_exists($status, $output))
|
||||
? $output[$status]
|
||||
: $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the status of the cache and return the appropriate response
|
||||
*
|
||||
* @param \GuzzleHttp\Message\Response $response
|
||||
* @codeCoverageIgnore
|
||||
* @return array
|
||||
*/
|
||||
private function _check_cache($response)
|
||||
{
|
||||
// Bail out early if there isn't any manga data
|
||||
$api_data = Json::decode($response->getBody(), TRUE);
|
||||
if ( ! array_key_exists('manga', $api_data))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
$cache_file = _dir($this->config->get('data_cache_path'), 'manga.json');
|
||||
$transformed_cache_file = _dir(
|
||||
$this->config->get('data_cache_path'),
|
||||
'manga-transformed.json'
|
||||
);
|
||||
|
||||
$cached_data = file_exists($cache_file)
|
||||
? Json::decodeFile($cache_file)
|
||||
: [];
|
||||
|
||||
if ($cached_data === $api_data && file_exists($transformed_cache_file))
|
||||
{
|
||||
return Json::decodeFile($transformed_cache_file);
|
||||
}
|
||||
else
|
||||
{
|
||||
Json::encodeFile($cache_file, $api_data);
|
||||
|
||||
$zippered_data = $this->zipper_lists($api_data);
|
||||
$transformer = new Transformer\MangaListTransformer();
|
||||
$transformed_data = $transformer->transform_collection($zippered_data);
|
||||
Json::encodeFile($transformed_cache_file, $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 => [],
|
||||
];
|
||||
|
||||
foreach ($data as &$entry)
|
||||
{
|
||||
$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 $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the two manga lists into one
|
||||
* @param array $raw_data
|
||||
* @return array
|
||||
*/
|
||||
private function zipper_lists($raw_data)
|
||||
{
|
||||
return (new Transformer\MangaListsZipper($raw_data))->transform();
|
||||
}
|
||||
}
|
||||
// End of MangaModel.php
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Base for routing/url classes
|
||||
*/
|
||||
class RoutingBase {
|
||||
|
||||
use \Aviat\Ion\StringWrapper;
|
||||
|
||||
/**
|
||||
* Injection Container
|
||||
* @var ContainerInterface $container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Config Object
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Routing array
|
||||
* @var array
|
||||
*/
|
||||
protected $routes;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->config = $container->get('config');
|
||||
$this->base_routes = $this->config->get('routes');
|
||||
$this->routes = $this->base_routes['routes'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreive the appropriate value for the routing key
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
$routing_config = $this->base_routes['route_config'];
|
||||
|
||||
if (array_key_exists($key, $routing_config))
|
||||
{
|
||||
return $routing_config[$key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current url path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function path()
|
||||
{
|
||||
$request = $this->container->get('request');
|
||||
$path = $request->url->get(PHP_URL_PATH);
|
||||
$cleaned_path = $this->string($path)
|
||||
->trim()
|
||||
->trimRight('/')
|
||||
->ensureLeft('/');
|
||||
|
||||
return (string)$cleaned_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url segments
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function segments()
|
||||
{
|
||||
$path = $this->path();
|
||||
return explode('/', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a segment of the current url
|
||||
*
|
||||
* @param int $num
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_segment($num)
|
||||
{
|
||||
$segments = $this->segments();
|
||||
return (array_key_exists($num, $segments)) ? $segments[$num] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last url segment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function last_segment()
|
||||
{
|
||||
$segments = $this->segments();
|
||||
return end($segments);
|
||||
}
|
||||
}
|
||||
// End of RoutingBase.php
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
/**
|
||||
* Hummingbird Anime Client
|
||||
*
|
||||
* An API client for Hummingbird to manage anime and manga watch lists
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
* @license MIT
|
||||
*/
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
/**
|
||||
* UrlGenerator class.
|
||||
*/
|
||||
class UrlGenerator extends RoutingBase {
|
||||
|
||||
/**
|
||||
* The current HTTP host
|
||||
*/
|
||||
protected $host;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->host = $container->get('request')->server->get('HTTP_HOST');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base url for css/js/images
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function asset_url(/*...*/)
|
||||
{
|
||||
$args = func_get_args();
|
||||
$base_url = rtrim($this->url(""), '/');
|
||||
|
||||
$base_url = "{$base_url}" . $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"), "/");
|
||||
|
||||
$path = ($config_path !== '') ? $config_path : "";
|
||||
|
||||
return implode("/", ['/', $this->host, $path]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a proper url from the path
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function url($path)
|
||||
{
|
||||
$path = trim($path, '/');
|
||||
|
||||
$path = preg_replace('`{/.*?}`i', '', $path);
|
||||
|
||||
// Remove any optional parameters from the route
|
||||
// and replace them with existing route parameters, if they exist
|
||||
$path_segments = explode('/', $path);
|
||||
$segment_count = count($path_segments);
|
||||
$segments = $this->segments();
|
||||
|
||||
for ($i = 0; $i < $segment_count; $i++)
|
||||
{
|
||||
if ( ! array_key_exists($i + 1, $segments))
|
||||
{
|
||||
$segments[$i + 1] = "";
|
||||
}
|
||||
|
||||
$path_segments[$i] = preg_replace('`{.*?}`i', $segments[$i + 1], $path_segments[$i]);
|
||||
}
|
||||
$path = implode('/', $path_segments);
|
||||
|
||||
return "//{$this->host}/{$path}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Full default path for the list pages
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public function default_url($type)
|
||||
{
|
||||
$type = trim($type);
|
||||
$default_path = $this->__get("default_{$type}_list_path");
|
||||
|
||||
if ( ! is_null($default_path))
|
||||
{
|
||||
return $this->url("{$type}/{$default_path}");
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Invalid default type: '{$type}'");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_default_route = $this->__get("default_{$type}_path");
|
||||
|
||||
// Remove beginning/trailing slashes
|
||||
$path = trim($path, '/');
|
||||
|
||||
// Set the default view
|
||||
if ($path === '')
|
||||
{
|
||||
$path .= trim($config_default_route, '/');
|
||||
if ($this->__get('default_to_list_view'))
|
||||
{
|
||||
$path .= '/list';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->url($path);
|
||||
}
|
||||
}
|
||||
// End of UrlGenerator.php
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
/**
|
||||
* Ion
|
||||
*
|
||||
* Building blocks for web development
|
||||
*
|
||||
* @package Ion
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\Ion;
|
||||
|
||||
use Aviat\Ion\Type\ArrayType;
|
||||
|
||||
/**
|
||||
* Wrapper to shortcut creating ArrayType objects
|
||||
*/
|
||||
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
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
/**
|
||||
* Ion
|
||||
*
|
||||
* Building blocks for web development
|
||||
*
|
||||
* @package Ion
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\Ion\Di;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Dependency container
|
||||
*/
|
||||
class Container implements ContainerInterface {
|
||||
|
||||
/**
|
||||
* Array with class instances
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $container = [];
|
||||
|
||||
/**
|
||||
* Map of logger instances
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $loggers = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $values (optional)
|
||||
*/
|
||||
public function __construct(array $values = [])
|
||||
{
|
||||
$this->container = $values;
|
||||
$this->loggers = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an entry of the container by its identifier and returns it.
|
||||
*
|
||||
* @param string $id Identifier of the entry to look for.
|
||||
*
|
||||
* @throws NotFoundException No entry was found for this identifier.
|
||||
* @throws ContainerException Error while retrieving the entry.
|
||||
*
|
||||
* @return mixed Entry.
|
||||
*/
|
||||
public function get($id)
|
||||
{
|
||||
if ( ! is_string($id))
|
||||
{
|
||||
throw new Exception\ContainerException("Id must be a string");
|
||||
}
|
||||
|
||||
if ($this->has($id))
|
||||
{
|
||||
return $this->container[$id];
|
||||
}
|
||||
|
||||
throw new Exception\NotFoundException("Item '{$id}' does not exist in container.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value to the container
|
||||
*
|
||||
* @param string $id
|
||||
* @param mixed $value
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public function set($id, $value)
|
||||
{
|
||||
$this->container[$id] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the container can return an entry for the given identifier.
|
||||
* Returns false otherwise.
|
||||
*
|
||||
* @param string $id Identifier of the entry to look for.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function has($id)
|
||||
{
|
||||
return array_key_exists($id, $this->container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a logger channel is registered
|
||||
* @param string $key The logger channel
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasLogger($key = 'default')
|
||||
{
|
||||
return array_key_exists($key, $this->loggers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a logger to the Container
|
||||
*
|
||||
* @param LoggerInterface $logger
|
||||
* @param string $key The logger 'channel'
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger, $key = 'default')
|
||||
{
|
||||
$this->loggers[$key] = $logger;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a logger for the selected channel
|
||||
*
|
||||
* @param string $key The logger to retreive
|
||||
* @return LoggerInterface|null
|
||||
*/
|
||||
public function getLogger($key = 'default')
|
||||
{
|
||||
return ($this->hasLogger($key))
|
||||
? $this->loggers[$key]
|
||||
: NULL;
|
||||
}
|
||||
}
|
||||
// End of Container.php
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
/**
|
||||
* Ion
|
||||
*
|
||||
* Building blocks for web development
|
||||
*
|
||||
* @package Ion
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\Ion\Di;
|
||||
|
||||
/**
|
||||
* Trait implementation of ContainerAwareInterface
|
||||
*/
|
||||
trait ContainerAware {
|
||||
|
||||
/**
|
||||
* Di Container
|
||||
*
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Set the container for the current object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @return $this
|
||||
*/
|
||||
public function setContainer(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the container object
|
||||
*
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public function getContainer()
|
||||
{
|
||||
return $this->container;
|
||||
}
|
||||
}
|
||||
// End of ContainerAware.php
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
/**
|
||||
* Ion
|
||||
*
|
||||
* Building blocks for web development
|
||||
*
|
||||
* @package Ion
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\Ion\Di;
|
||||
|
||||
/**
|
||||
* Interface for a class that is aware of the Di Container
|
||||
*/
|
||||
interface ContainerAwareInterface {
|
||||
|
||||
/**
|
||||
* Set the container for the current object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @return void
|
||||
*/
|
||||
public function setContainer(ContainerInterface $container);
|
||||
|
||||
/**
|
||||
* Get the container object
|
||||
*
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public function getContainer();
|
||||
|
||||
}
|
||||
// End of ContainerAwareInterface.php
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
/**
|
||||
* Ion
|
||||
*
|
||||
* Building blocks for web development
|
||||
*
|
||||
* @package Ion
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\Ion\Di;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Interface for the Dependency Injection Container
|
||||
*/
|
||||
interface ContainerInterface extends \Interop\Container\ContainerInterface {
|
||||
|
||||
/**
|
||||
* Add a value to the container
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public function set($key, $value);
|
||||
|
||||
/**
|
||||
* Add a logger to the Container
|
||||
*
|
||||
* @param LoggerInterface $logger
|
||||
* @param string $key The logger 'channel'
|
||||
* @return Container
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger, $key = 'default');
|
||||
|
||||
/**
|
||||
* Retrieve a logger for the selected channel
|
||||
*
|
||||
* @param string $key The logger to retreive
|
||||
* @return LoggerInterface|null
|
||||
*/
|
||||
public function getLogger($key = 'default');
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
/**
|
||||
* Ion
|
||||
*
|
||||
* Building blocks for web development
|
||||
*
|
||||
* @package Ion
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\Ion\Di\Exception;
|
||||
|
||||
/**
|
||||
* Generic exception for Di Container
|
||||
*/
|
||||
class ContainerException extends \Exception implements \Interop\Container\Exception\ContainerException {
|
||||
|
||||
}
|
||||
// End of ContainerException.php
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
/**
|
||||
* Ion
|
||||
*
|
||||
* Building blocks for web development
|
||||
*
|
||||
* @package Ion
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\Ion\Di\Exception;
|
||||
|
||||
/**
|
||||
* Exception for Di Container when trying to access a
|
||||
* key that doesn't exist in the container
|
||||
*/
|
||||
class NotFoundException extends ContainerException implements \Interop\Container\Exception\NotFoundException {
|
||||
|
||||
}
|
||||
// End of NotFoundException.php
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
/**
|
||||
* Ion
|
||||
*
|
||||
* Building blocks for web development
|
||||
*
|
||||
* @package Ion
|
||||
* @author Timothy J. Warren
|
||||
* @copyright Copyright (c) 2015 - 2016
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Aviat\Ion;
|
||||
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Class emulating an enumeration type
|
||||
*
|
||||
* @method bool isValid(mixed $key)
|
||||
* @method array getConstList()
|
||||
*/
|
||||
abstract class Enum {
|
||||
|
||||
use StaticInstance;
|
||||
|
||||
/**
|
||||
* Return the list of constant values for the Enum
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getConstList()
|
||||
{
|
||||
$reflect = new ReflectionClass($this);
|
||||
return $reflect->getConstants();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a constant value is valid
|
||||
* @param mixed $key
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isValid($key)
|
||||
{
|
||||
$values = array_values($this->getConstList());
|
||||
return in_array($key, $values);
|
||||
}
|
||||
}
|
||||
// End of Enum.php
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue