Ugly Progress Commit

* Cache and resize images - not just cache them
* Convert to webp on cache
* Show webp images if available
* Settings Form Generation (doesn't yet save)
This commit is contained in:
Timothy Warren 2018-10-05 21:32:15 -04:00
parent ae283cd898
commit d6800dbc46
18 changed files with 663 additions and 91 deletions

1
.gitignore vendored
View File

@ -31,3 +31,4 @@ public/images/avatars/**
public/images/manga/** public/images/manga/**
public/images/characters/** public/images/characters/**
public/images/people/** public/images/people/**
public/mal_mappings.json

View File

@ -200,7 +200,7 @@ return [
'verb' => 'get', 'verb' => 'get',
'tokens' => [ 'tokens' => [
'type' => '[a-z0-9\-]+', 'type' => '[a-z0-9\-]+',
'file' => '[a-z0-9\-]+\.[a-z]{3}' 'file' => '[a-z0-9\-]+\.[a-z]{3,4}'
] ]
], ],
'cache_purge' => [ 'cache_purge' => [

View File

@ -81,6 +81,11 @@ return function ($configArray = []) {
$menuHelper->setContainer($container); $menuHelper->setContainer($container);
return $menuHelper; return $menuHelper;
}); });
$htmlHelper->set('field', function() use ($container) {
$formHelper = new Helper\Form();
$formHelper->setContainer($container);
return $formHelper;
});
return $htmlHelper; return $htmlHelper;
}); });
@ -156,6 +161,11 @@ return function ($configArray = []) {
$container->set('manga-collection-model', function($container) { $container->set('manga-collection-model', function($container) {
return new Model\MangaCollection($container); return new Model\MangaCollection($container);
}); });
$container->set('settings-model', function($container) {
$model = new Model\Settings($container->get('config'));
$model->setContainer($container);
return $model;
});
// Miscellaneous Classes // Miscellaneous Classes
$container->set('auth', function($container) { $container->set('auth', function($container) {

32
app/views/_form.php Normal file
View File

@ -0,0 +1,32 @@
<?php
// Higher scoped variables:
// $fields
// $hiddenFields
// $nestedPrefix
if ( ! function_exists('subfieldRender'))
{
function subfieldRender ($nestedPrefix, $fields, &$hiddenFields, $helper, $section)
{
include '_form.php';
}
}
?>
<?php foreach ($fields as $name => $field): ?>
<?php $fieldname = ($section === 'config' || $nestedPrefix !== 'config') ? "{$nestedPrefix}[{$name}]" : "{$nestedPrefix}[{$section}][{$name}]"; ?>
<?php if ($field['type'] === 'subfield'): ?>
<section>
<h4><?= $field['title'] ?></h4>
<?php subfieldRender($fieldname, $field['fields'], $hiddenFields, $helper, $section); ?>
</section>
<?php elseif ( ! empty($field['display'])): ?>
<article>
<label for="<?= $fieldname ?>"><?= $field['title'] ?></label><br />
<small><?= $field['description'] ?></small><br />
<?= $helper->field($fieldname, $field); ?>
</article>
<?php else: ?>
<?php $hiddenFields[] = $helper->field($fieldname, $field); ?>
<?php endif ?>
<?php endforeach ?>

View File

@ -6,7 +6,12 @@
<?php if ($auth->isAuthenticated()): ?> <?php if ($auth->isAuthenticated()): ?>
<button title="Increment episode count" class="plus_one" hidden>+1 Episode</button> <button title="Increment episode count" class="plus_one" hidden>+1 Episode</button>
<?php endif ?> <?php endif ?>
<img src="<?= $urlGenerator->assetUrl("images/anime/{$item['anime']['id']}.jpg") ?>" alt=""/> <picture>
<source srcset="<?= $urlGenerator->assetUrl("images/anime/{$item['anime']['id']}.webp") ?>" type="image/webp">
<source srcset="<?= $urlGenerator->assetUrl("images/anime/{$item['anime']['id']}.jpg") ?>" type="image/jpeg">
<img src="<?= $urlGenerator->assetUrl("images/anime/{$item['anime']['id']}.jpg") ?>" alt="" />
</picture>
<div class="name"> <div class="name">
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]); ?>"> <a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]); ?>">
<span class="canonical"><?= $item['anime']['title'] ?></span> <span class="canonical"><?= $item['anime']['title'] ?></span>

View File

@ -1,7 +1,11 @@
<main class="details fixed"> <main class="details fixed">
<section class="flex flex-no-wrap"> <section class="flex flex-no-wrap">
<div> <div>
<img class="cover" width="402" height="284" src="<?= $urlGenerator->assetUrl("images/anime/{$show_data['id']}.jpg") ?>" alt="" /> <picture class="cover">
<source srcset="<?= $urlGenerator->assetUrl("images/anime/{$show_data['id']}-original.webp") ?>" type="image/webp">
<source srcset="<?= $urlGenerator->assetUrl("images/anime/{$show_data['id']}-original.jpg") ?>" type="image/jpeg">
<img src="<?= $urlGenerator->assetUrl("images/anime/{$show_data['id']}-original.jpg") ?>" alt="" />
</picture>
<br /> <br />
<br /> <br />
<table class="media_details"> <table class="media_details">
@ -96,9 +100,11 @@
<?= $helper->a($link, $char['name']); ?> <?= $helper->a($link, $char['name']); ?>
</div> </div>
<a href="<?= $link ?>"> <a href="<?= $link ?>">
<?= $helper->img($urlGenerator->assetUrl("images/characters/{$id}.jpg"), [ <picture>
'width' => '225' <source srcset="<?= $urlGenerator->assetUrl("images/characters/{$id}.webp") ?>" type="image/webp">
]) ?> <source srcset="<?= $urlGenerator->assetUrl("images/characters/{$id}.jpg") ?>" type="image/jpeg">
<img src="<?= $urlGenerator->assetUrl("images/characters/{$id}.jpg") ?>" alt="" />
</picture>
</a> </a>
</article> </article>
<?php endif ?> <?php endif ?>

View File

@ -2,7 +2,11 @@
<main class="details fixed"> <main class="details fixed">
<section class="flex flex-no-wrap"> <section class="flex flex-no-wrap">
<div> <div>
<img class="cover" width="284" src="<?= $urlGenerator->assetUrl("images/characters/{$data[0]['id']}.jpg") ?>" alt="" /> <picture class="cover">
<source srcset="<?= $urlGenerator->assetUrl("images/characters/{$data[0]['id']}-original.webp") ?>" type="image/webp">
<source srcset="<?= $urlGenerator->assetUrl("images/characters/{$data[0]['id']}-original.jpg") ?>" type="image/jpeg">
<img src="<?= $urlGenerator->assetUrl("images/characters/{$data[0]['id']}-original.jpg") ?>" alt="" />
</picture>
</div> </div>
<div> <div>
<h2><?= $data[0]['attributes']['name'] ?></h2> <h2><?= $data[0]['attributes']['name'] ?></h2>

View File

@ -1,6 +1,10 @@
<article class="media" id="a-<?= $item['hummingbird_id'] ?>"> <article class="media" id="a-<?= $item['hummingbird_id'] ?>">
<img src="<?= $urlGenerator->assetUrl("images/anime/{$item['hummingbird_id']}.jpg") ?>" <picture>
alt="<?= $item['title'] ?> cover image"/> <source srcset="<?= $urlGenerator->assetUrl("images/anime/{$item['hummingbird_id']}.webp") ?>" type="image/webp">
<source srcset="<?= $urlGenerator->assetUrl("images/anime/{$item['hummingbird_id']}.jpg") ?>" type="image/jpeg">
<img src="<?= $urlGenerator->assetUrl("images/anime/{$item['hummingbird_id']}.jpg") ?>" alt="<?= $item['title'] ?> cover image" />
</picture>
<div class="name"> <div class="name">
<a href="<?= $url->generate('anime.details', ['id' => $item['slug']]) ?>"> <a href="<?= $url->generate('anime.details', ['id' => $item['slug']]) ?>">
<?= $item['title'] ?> <?= $item['title'] ?>

View File

@ -23,7 +23,11 @@
<?php /* <button class="plus_one_volume">+1 Volume</button> */ ?> <?php /* <button class="plus_one_volume">+1 Volume</button> */ ?>
</div> </div>
<?php endif ?> <?php endif ?>
<img src="<?= $urlGenerator->assetUrl('images/manga', "{$item['manga']['id']}.jpg") ?>" /> <picture>
<source srcset="<?= $urlGenerator->assetUrl("images/manga/{$item['manga']['id']}.webp") ?>" type="image/webp">
<source srcset="<?= $urlGenerator->assetUrl("images/manga/{$item['manga']['id']}.jpg") ?>" type="image/jpeg">
<img src="<?= $urlGenerator->assetUrl("images/manga/{$item['manga']['id']}.jpg") ?>" alt="" />
</picture>
<div class="name"> <div class="name">
<a href="<?= $url->generate('manga.details', ['id' => $item['manga']['slug']]) ?>"> <a href="<?= $url->generate('manga.details', ['id' => $item['manga']['slug']]) ?>">
<?= $escape->html($item['manga']['title']) ?> <?= $escape->html($item['manga']['title']) ?>

View File

@ -1,7 +1,11 @@
<main class="details fixed"> <main class="details fixed">
<section class="flex flex-no-wrap"> <section class="flex flex-no-wrap">
<div> <div>
<img class="cover" src="<?= $urlGenerator->assetUrl('images/manga', "{$data['id']}.jpg") ?>" alt="<?= $data['title'] ?> cover image" /> <picture class="cover">
<source srcset="<?= $urlGenerator->assetUrl("images/manga/{$data['id']}-original.webp") ?>" type="image/webp">
<source srcset="<?= $urlGenerator->assetUrl("images/manga/{$data['id']}-original.jpg") ?>" type="image/jpeg">
<img src="<?= $urlGenerator->assetUrl("images/manga/{$data['id']}-original.jpg") ?>" alt="" />
</picture>
<br /> <br />
<br /> <br />
<table> <table>
@ -47,9 +51,14 @@
<?= $helper->a($link, $char['name']); ?> <?= $helper->a($link, $char['name']); ?>
</div> </div>
<a href="<?= $link ?>"> <a href="<?= $link ?>">
<?= $helper->img($urlGenerator->assetUrl('images/characters', "{$id}.jpg"), [ <picture>
<source srcset="<?= $urlGenerator->assetUrl("images/characters/{$id}.webp") ?>" type="image/webp">
<source srcset="<?= $urlGenerator->assetUrl("images/characters/{$id}.jpg") ?>" type="image/jpeg">
<img src="<?= $urlGenerator->assetUrl("images/characters/{$id}.jpg") ?>" alt="" />
</picture>
<?php /*<?= $helper->img($urlGenerator->assetUrl('images/characters', "{$id}.jpg"), [
'width' => '225' 'width' => '225'
]) ?> ]) ?> */ ?>
</a> </a>
</article> </article>
<?php endif ?> <?php endif ?>

View File

@ -1,60 +1,42 @@
<?php <?php
use function Aviat\AnimeClient\loadTomlByFile;
$settings = loadTomlByFile($config->get('config_dir'));
if ( ! $auth->isAuthenticated()) if ( ! $auth->isAuthenticated())
{ {
echo '<h1>Not Authorized</h1>'; echo '<h1>Not Authorized</h1>';
return; return;
} }
$sectionMapping = [
'config' => 'General Settings',
'cache' => 'Caching',
'database' => 'Collection Database Settings',
];
function render_settings_form ($data, $file) $hiddenFields = [];
{ $nestedPrefix = 'config';
ob_start();
foreach ($data as $key => $value)
{
?>
<tr>
<td><label for="<?= $key ?>"><?= $key ?></label></td>
<td>
<?php if (is_scalar($value)): ?>
<input
type="text"
id="<?= $key ?>"
name="config[<?= $file ?>][<?= $key ?>]"
value="<?= $value ?>"
/>
<?php else: ?>
<table><?= render_settings_form($value, $file); ?></table>
<?php endif ?>
</td>
</tr>
<?php
}
$buffer = ob_get_contents();
ob_end_clean();
return $buffer;
}
?> ?>
<pre><?= print_r($_POST, TRUE) ?></pre> <pre><?= print_r($_POST, TRUE) ?></pre>
<?php foreach($settings as $file => $properties): ?>
<form action="<?= $_SERVER['REQUEST_URI'] ?>" method="POST"> <form action="<?= $_SERVER['REQUEST_URI'] ?>" method="POST">
<table class="form"> <main class='form'>
<caption><?= $file ?></caption> <button type="submit">Save Changes</button>
<tbody> <br />
<?= render_settings_form($properties, $file); ?> <?php foreach ($form as $section => $fields): ?>
</tbody> <fieldset class="box">
</table> <legend><?= $sectionMapping[$section] ?></legend>
<button type="submit">Save Changes</button> <section class='form'>
<?php require __DIR__ . '/_form.php' ?>
</section>
</fieldset>
<?php endforeach ?>
<hr />
<?php foreach ($hiddenFields as $field): ?>
<?= $field ?>
<?php endforeach ?>
<button type="submit">Save Changes</button>
</main>
</form> </form>
<?php endforeach ?>

File diff suppressed because one or more lines are too long

View File

@ -384,6 +384,12 @@ a:hover, a:active {
z-index: 0; z-index: 0;
} }
.details picture.cover,
picture.cover {
display: initial;
width: 100%;
}
.media > img, .media > img,
.character > img, .character > img,
.small_character > img { .small_character > img {
@ -915,3 +921,23 @@ CSS Tabs
} }
} }
/* ----------------------------------------------------------------------------
Settings page forms
-----------------------------------------------------------------------------*/
fieldset.box {
display: inline-block;
vertical-align:top;
width:40%;
margin: 2em;
}
fieldset.box section {
margin: 0 auto;
text-align:center;
}
fieldset.box article {
margin: auto;
text-align:left;
}

View File

@ -86,11 +86,11 @@ class Controller {
]; ];
/** /**
* Constructor * Controller constructor.
* *
* @throws \Aviat\Ion\Di\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException
* @param ContainerInterface $container * @param ContainerInterface $container
* @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\Exception\NotFoundException
*/ */
public function __construct(ContainerInterface $container) public function __construct(ContainerInterface $container)
{ {
@ -140,10 +140,9 @@ class Controller {
/** /**
* Set the current url in the session as the target of a future redirect * Set the current url in the session as the target of a future redirect
* *
* @throws \Aviat\Ion\Di\ContainerException * @param string|NULL $url
* @throws \Aviat\Ion\Di\NotFoundException * @throws \Aviat\Ion\Di\Exception\ContainerException
* @param string|null $url * @throws \Aviat\Ion\Di\Exception\NotFoundException
* @return void
*/ */
public function setSessionRedirect(string $url = NULL): void public function setSessionRedirect(string $url = NULL): void
{ {
@ -180,8 +179,8 @@ class Controller {
* Redirect to the url previously set in the session * Redirect to the url previously set in the session
* *
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws \Aviat\Ion\Di\ContainerException * @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException * @throws \Aviat\Ion\Di\Exception\NotFoundException
* @return void * @return void
*/ */
public function sessionRedirect() public function sessionRedirect()
@ -205,8 +204,8 @@ class Controller {
* @param string $template * @param string $template
* @param array $data * @param array $data
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws \Aviat\Ion\Di\ContainerException * @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException * @throws \Aviat\Ion\Di\Exception\NotFoundException
* @return string * @return string
*/ */
protected function loadPartial($view, string $template, array $data = []) protected function loadPartial($view, string $template, array $data = [])
@ -239,8 +238,8 @@ class Controller {
* @param string $template * @param string $template
* @param array $data * @param array $data
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws \Aviat\Ion\Di\ContainerException * @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException * @throws \Aviat\Ion\Di\Exception\NotFoundException
* @return void * @return void
*/ */
protected function renderFullPage($view, string $template, array $data) protected function renderFullPage($view, string $template, array $data)
@ -269,8 +268,8 @@ class Controller {
* @param string $title * @param string $title
* @param string $message * @param string $message
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws \Aviat\Ion\Di\ContainerException * @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException * @throws \Aviat\Ion\Di\Exception\NotFoundException
* @return void * @return void
*/ */
public function notFound( public function notFound(
@ -292,8 +291,8 @@ class Controller {
* @param string $message * @param string $message
* @param string $long_message * @param string $long_message
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws \Aviat\Ion\Di\ContainerException * @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException * @throws \Aviat\Ion\Di\Exception\NotFoundException
* @return void * @return void
*/ */
public function errorPage(int $httpCode, string $title, string $message, string $long_message = ''): void public function errorPage(int $httpCode, string $title, string $message, string $long_message = ''): void
@ -313,7 +312,7 @@ class Controller {
*/ */
public function redirectToDefaultRoute(): void public function redirectToDefaultRoute(): void
{ {
$defaultType = $this->config->get(['routes', 'route_config', 'default_list']) ?? 'anime'; $defaultType = $this->config->get('default_list') ?? 'anime';
$this->redirect($this->urlGenerator->defaultUrl($defaultType), 303); $this->redirect($this->urlGenerator->defaultUrl($defaultType), 303);
} }
@ -360,8 +359,8 @@ class Controller {
* @param string $type * @param string $type
* @param string $message * @param string $message
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws \Aviat\Ion\Di\ContainerException * @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException * @throws \Aviat\Ion\Di\Exception\NotFoundException
* @return string * @return string
*/ */
protected function showMessage($view, string $type, string $message): string protected function showMessage($view, string $type, string $message): string
@ -380,8 +379,8 @@ class Controller {
* @param HtmlView|null $view * @param HtmlView|null $view
* @param int $code * @param int $code
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws \Aviat\Ion\Di\ContainerException * @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException * @throws \Aviat\Ion\Di\Exception\NotFoundException
* @return void * @return void
*/ */
protected function outputHTML(string $template, array $data = [], $view = NULL, int $code = 200) protected function outputHTML(string $template, array $data = [], $view = NULL, int $code = 200)
@ -393,6 +392,7 @@ class Controller {
$view->setStatusCode($code); $view->setStatusCode($code);
$this->renderFullPage($view, $template, $data); $this->renderFullPage($view, $template, $data);
exit();
} }
/** /**
@ -407,8 +407,9 @@ class Controller {
{ {
(new JsonView($this->container)) (new JsonView($this->container))
->setStatusCode($code) ->setStatusCode($code)
->setOutput($data) ->setOutput($data);
->send(); // ->send();
exit();
} }
/** /**

View File

@ -20,12 +20,21 @@ use function Amp\Promise\wait;
use Aviat\AnimeClient\Controller as BaseController; use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\API\{HummingbirdClient, JsonAPI}; use Aviat\AnimeClient\API\{HummingbirdClient, JsonAPI};
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\View\HtmlView; use Aviat\Ion\View\HtmlView;
/** /**
* Controller for handling routes that don't fit elsewhere * Controller for handling routes that don't fit elsewhere
*/ */
final class Index extends BaseController { final class Index extends BaseController {
private $settingsModel;
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
$this->settingsModel = $container->get('settings-model');
}
/** /**
* Purges the API cache * Purges the API cache
@ -91,6 +100,7 @@ final class Index extends BaseController {
*/ */
public function anilistCallback() public function anilistCallback()
{ {
dump($_GET);
$this->outputHTML('blank', [ $this->outputHTML('blank', [
'title' => 'Oauth!' 'title' => 'Oauth!'
]); ]);
@ -169,8 +179,10 @@ final class Index extends BaseController {
public function settings() public function settings()
{ {
$auth = $this->container->get('auth'); $auth = $this->container->get('auth');
$form = $this->settingsModel->getSettingsForm();
$this->outputHTML('settings', [ $this->outputHTML('settings', [
'auth' => $auth, 'auth' => $auth,
'form' => $form,
'config' => $this->config, 'config' => $this->config,
'title' => $this->config->get('whose_list') . "'s Settings", 'title' => $this->config->get('whose_list') . "'s Settings",
]); ]);
@ -191,6 +203,7 @@ final class Index extends BaseController {
* *
* @param string $type The category of image * @param string $type The category of image
* @param string $file The filename to look for * @param string $file The filename to look for
* @param bool $display Whether to output the image to the server
* @throws \Aviat\Ion\Di\ContainerException * @throws \Aviat\Ion\Di\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException * @throws \Aviat\Ion\Di\NotFoundException
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
@ -199,26 +212,30 @@ final class Index extends BaseController {
* @throws \Throwable * @throws \Throwable
* @return void * @return void
*/ */
public function images(string $type, string $file): void public function images(string $type, string $file, $display = TRUE): void
{ {
$kitsuUrl = 'https://media.kitsu.io/'; $kitsuUrl = 'https://media.kitsu.io/';
[$id, $ext] = explode('.', basename($file)); $fileName = str_replace('-original', '', $file);
[$id, $ext] = explode('.', basename($fileName));
switch ($type) switch ($type)
{ {
case 'anime': case 'anime':
$kitsuUrl .= "anime/poster_images/{$id}/small.{$ext}"; $kitsuUrl .= "anime/poster_images/{$id}/small.jpg";
$width = 220;
break; break;
case 'avatars': case 'avatars':
$kitsuUrl .= "users/avatars/{$id}/original.{$ext}"; $kitsuUrl .= "users/avatars/{$id}/original.jpg";
break; break;
case 'manga': case 'manga':
$kitsuUrl .= "manga/poster_images/{$id}/small.{$ext}"; $kitsuUrl .= "manga/poster_images/{$id}/small.jpg";
$width = 220;
break; break;
case 'characters': case 'characters':
$kitsuUrl .= "characters/images/{$id}/original.{$ext}"; $kitsuUrl .= "characters/images/{$id}/original.jpg";
$width = 225;
break; break;
default: default:
@ -231,9 +248,38 @@ final class Index extends BaseController {
$data = wait($response->getBody()); $data = wait($response->getBody());
$baseSavePath = $this->config->get('img_cache_path'); $baseSavePath = $this->config->get('img_cache_path');
file_put_contents("{$baseSavePath}/{$type}/{$id}.{$ext}", $data); $filePrefix = "{$baseSavePath}/{$type}/{$id}";
header('Content-type: ' . $response->getHeader('content-type')[0]);
echo $data; [$origWidth, $origHeight] = getimagesizefromstring($data);
$gdImg = imagecreatefromstring($data);
$resizedImg = imagescale($gdImg, $width ?? $origWidth);
// save the webp versions
imagewebp($gdImg, "{$filePrefix}-original.webp");
imagewebp($resizedImg, "{$filePrefix}.webp");
// save the scaled jpeg file
imagejpeg($resizedImg, "{$filePrefix}.jpg");
imagedestroy($gdImg);
imagedestroy($resizedImg);
// And the original
file_put_contents("{$filePrefix}-original.jpg", $data);
if ($display)
{
$contentType = ($ext === 'webp')
? "image/webp"
: $response->getHeader('content-type')[0];
$outputFile = (strpos($file, '-original') !== FALSE)
? "{$filePrefix}-original.{$ext}"
: "{$filePrefix}.{$ext}";
header("Content-Type: {$contentType}");
echo file_get_contents($outputFile);
}
} }
/** /**

115
src/FormGenerator.php Normal file
View File

@ -0,0 +1,115 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient;
use Aviat\Ion\
{
ArrayWrapper, StringWrapper
};
use Aviat\Ion\Di\ContainerInterface;
/**
* Helper object to manage form generation, especially for config editing
*/
final class FormGenerator {
use ArrayWrapper;
use StringWrapper;
/**
* Injection Container
* @var ContainerInterface $container
*/
protected $container;
/**
* Html generation helper
*
* @var \Aura\Html\HelperLocator
*/
protected $helper;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->helper = $container->get('html-helper');
}
/**
* Generate the html structure of the form
*
* @param string $name
* @param array $form
* @return string
*/
public function generate(string $name, array $form)
{
$type = $form['type'];
if ($form['display'] === FALSE)
{
return $this->helper->input([
'type' => 'hidden',
'name' => $name,
'value' => $form['value'],
]);
}
$params = [
'name' => $name,
'value' => $form['value'],
'attribs' => [
'id' => $name,
],
];
switch($type)
{
case 'boolean':
/* $params['type'] = 'checkbox';
$params['attribs']['label'] = $form['description'];
$params['attribs']['value'] = TRUE;
$params['attribs']['value_unchecked'] = '0'; */
$params['type'] = 'radio';
$params['options'] = [
'1' => 'Yes',
'0' => 'No',
];
unset($params['attribs']['id']);
break;
case 'string':
$params['type'] = 'text';
break;
case 'select':
$params['type'] = 'select';
$params['options'] = array_flip($form['options']);
break;
}
foreach (['readonly', 'disabled'] as $key)
{
if ($form[$key] !== FALSE)
{
$params['attribs'][$key] = $form[$key];
}
}
return $this->helper->input($params);
}
}

41
src/Helper/Form.php Normal file
View File

@ -0,0 +1,41 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Helper;
use Aviat\AnimeClient\FormGenerator;
use Aviat\Ion\Di\ContainerAware;
/**
* MenuGenerator helper wrapper
*/
final class Form {
use ContainerAware;
/**
* Create the html for the selected menu
*
* @param string $name
* @param array $form
* @return string
*/
public function __invoke(string $name, array $form)
{
return (new FormGenerator($this->container))->generate($name, $form);
}
}
// End of Menu.php

286
src/Model/Settings.php Normal file
View File

@ -0,0 +1,286 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Model;
use Aviat\AnimeClient\Types\{Config, UndefinedPropertyException};
use Aviat\Ion\ConfigInterface;
use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\StringWrapper;
/**
* Model for handling settings control panel
*/
final class Settings {
use ContainerAware;
use StringWrapper;
private $config;
/**
* Map the config values to types and form fields
*/
private const SETTINGS_MAP = [
'anilist' => [
],
'config' => [
'kitsu_username' => [
'type' => 'string',
'title' => 'Kitsu Username',
'default' => '',
'readonly' => TRUE,
'description' => 'Username of the account to pull list data from.',
],
'whose_list' => [
'type' => 'string',
'title' => 'Whose List',
'default' => 'Somebody',
'readonly' => TRUE,
'description' => 'Name of the owner of the list data.',
],
'show_anime_collection' => [
'type' => 'boolean',
'title' => 'Show Anime Collection',
'default' => FALSE,
'description' => 'Should the anime collection be shown?',
],
'show_manga_collection' => [
'type' => 'boolean',
'title' => 'Show Manga Collection',
'default' => FALSE,
'description' => 'Should the manga collection be shown?',
],
'asset_path' => [
'type' => 'string',
'display' => FALSE,
'description' => 'Path to public directory, where images/css/javascript are located',
],
'default_list' => [
'type' => 'select',
'title' => 'Default List',
'description' => 'Which list to show by default.',
'options' => [
'Anime' => 'anime',
'Manga' => 'manga',
],
],
'default_anime_list_path' => [ //watching|plan_to_watch|on_hold|dropped|completed|all
'type' => 'select',
'title' => 'Default Anime List Section',
'description' => 'Which part of the anime list to show by default.',
'options' => [
'Watching' => 'watching',
'Plan to Watch' => 'plan_to_watch',
'On Hold' => 'on_hold',
'Dropped' => 'dropped',
'Completed' => 'completed',
'All' => 'all',
]
],
'default_manga_list_path' => [ //reading|plan_to_read|on_hold|dropped|completed|all
'type' => 'select',
'title' => 'Default Manga List Section',
'description' => 'Which part of the manga list to show by default.',
'options' => [
'Reading' => 'reading',
'Plan to Read' => 'plan_to_read',
'On Hold' => 'on_hold',
'Dropped' => 'dropped',
'Completed' => 'completed',
'All' => 'all',
]
]
],
'cache' => [
'driver' => [
'type' => 'select',
'title' => 'Cache Type',
'description' => 'The Cache backend',
'options' => [
'APCu' => 'apcu',
'Memcached' => 'memcached',
'Redis' => 'redis',
'No Cache' => 'null'
],
],
'connection' => [
'type' => 'subfield',
'title' => 'Connection',
'fields' => [
'host' => [
'type' => 'string',
'title' => 'Cache Host',
'description' => 'Host of the cache backend to connect to',
],
'port' => [
'type' => 'string',
'title' => 'Cache Port',
'description' => 'Port of the cache backend to connect to',
],
'password' => [
'type' => 'string',
'title' => 'Cache Password',
'description' => 'Password to connect to cache backend',
],
'database' => [
'type' => 'string',
'title' => 'Cache Database',
'description' => 'Cache database number for Redis',
],
],
],
],
'database' => [
'type' => [
'type' => 'select',
'title' => 'Database Type',
'options' => [
'MySQL' => 'mysql',
'PostgreSQL' => 'pgsql',
'SQLite' => 'sqlite',
],
'description' => 'Type of database to connect to',
],
'host' => [
'type' => 'string',
'title' => 'Host',
'description' => 'The host of the database server',
],
'user' => [
'type' => 'string',
'title' => 'User',
'description' => 'Database connection user',
],
'pass' => [
'type' => 'string',
'title' => 'Password',
'description' => 'Database connection password'
],
'port' => [
'type' => 'string',
'title' => 'Port',
'description' => 'Database connection port'
],
'database' => [
'type' => 'string',
'title' => 'Database Name',
'description' => 'Name of the database/schema to connect to',
],
'file' => [
'type' => 'string',
'title' => 'Database File',
'description' => 'Path to the database file, if required by the current database type.'
],
],
];
public function __construct(ConfigInterface $config)
{
$this->config = $config;
}
public function getSettings()
{
$settings = [
'config' => [],
];
foreach(static::SETTINGS_MAP as $file => $values)
{
if ($file === 'config')
{
$keys = array_keys($values);
foreach($keys as $key)
{
$settings['config'][$key] = $this->config->get($key);
}
}
else
{
$settings[$file] = $this->config->get($file);
}
}
return $settings;
}
public function getSettingsForm()
{
$output = [];
$settings = $this->getSettings();
foreach($settings as $file => $values)
{
foreach(static::SETTINGS_MAP[$file] as $key => $value)
{
if ($value['type'] === 'subfield')
{
foreach($value['fields'] as $k => $field)
{
$value['fields'][$k]['value'] = $values[$key][$k] ?? '';
$value['fields'][$k]['display'] = TRUE;
$value['fields'][$k]['readonly'] = FALSE;
$value['fields'][$k]['disabled'] = FALSE;
}
}
if (is_scalar($values[$key]))
{
$value['value'] = $values[$key];
}
foreach (['readonly', 'disabled'] as $flag)
{
if ( ! array_key_exists($flag, $value))
{
$value[$flag] = FALSE;
}
}
if ( ! array_key_exists('display', $value))
{
$value['display'] = TRUE;
}
$output[$file][$key] = $value;
}
}
return $output;
}
public function validateSettings(array $settings): bool
{
try
{
new Config($settings);
}
catch (UndefinedPropertyException $e)
{
return FALSE;
}
return TRUE;
}
public function saveSettingsFile()
{
}
}