Merge branch 'develop' of timw4mail/HummingBirdAnimeClient into master

This commit is contained in:
Timothy Warren 2019-05-08 12:40:32 -04:00 committed by GitLab
commit 82c8fa8661
42 changed files with 874 additions and 15597 deletions

View File

@ -35,7 +35,7 @@ use Zend\Diactoros\{Response, ServerRequestFactory};
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Setup DI container // Setup DI container
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
return function ($configArray = []) { return static function ($configArray = []) {
$container = new Container(); $container = new Container();
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -57,36 +57,38 @@ return function ($configArray = []) {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Create Config Object // Create Config Object
$container->set('config', function() use ($configArray) { $container->set('config', static function() use ($configArray) {
return new Config($configArray); return new Config($configArray);
}); });
// Create Cache Object // Create Cache Object
$container->set('cache', function($container) { $container->set('cache', static function($container) {
$logger = $container->getLogger(); $logger = $container->getLogger();
$config = $container->get('config')->get('cache'); $config = $container->get('config')->get('cache');
return new Pool($config, $logger); return new Pool($config, $logger);
}); });
// Create List Cache
// Create Aura Router Object // Create Aura Router Object
$container->set('aura-router', function() { $container->set('aura-router', static function() {
return new RouterContainer; return new RouterContainer;
}); });
// Create Html helper Object // Create Html helper Object
$container->set('html-helper', function($container) { $container->set('html-helper', static function($container) {
$htmlHelper = (new HelperLocatorFactory)->newInstance(); $htmlHelper = (new HelperLocatorFactory)->newInstance();
$htmlHelper->set('menu', function() use ($container) { $htmlHelper->set('menu', static function() use ($container) {
$menuHelper = new Helper\Menu(); $menuHelper = new Helper\Menu();
$menuHelper->setContainer($container); $menuHelper->setContainer($container);
return $menuHelper; return $menuHelper;
}); });
$htmlHelper->set('field', function() use ($container) { $htmlHelper->set('field', static function() use ($container) {
$formHelper = new Helper\Form(); $formHelper = new Helper\Form();
$formHelper->setContainer($container); $formHelper->setContainer($container);
return $formHelper; return $formHelper;
}); });
$htmlHelper->set('picture', function() use ($container) { $htmlHelper->set('picture', static function() use ($container) {
$pictureHelper = new Helper\Picture(); $pictureHelper = new Helper\Picture();
$pictureHelper->setContainer($container); $pictureHelper->setContainer($container);
return $pictureHelper; return $pictureHelper;
@ -96,7 +98,7 @@ return function ($configArray = []) {
}); });
// Create Request/Response Objects // Create Request/Response Objects
$container->set('request', function() { $container->set('request', static function() {
return ServerRequestFactory::fromGlobals( return ServerRequestFactory::fromGlobals(
$_SERVER, $_SERVER,
$_GET, $_GET,
@ -105,22 +107,22 @@ return function ($configArray = []) {
$_FILES $_FILES
); );
}); });
$container->set('response', function() { $container->set('response', static function() {
return new Response; return new Response;
}); });
// Create session Object // Create session Object
$container->set('session', function() { $container->set('session', static function() {
return (new SessionFactory())->newInstance($_COOKIE); return (new SessionFactory())->newInstance($_COOKIE);
}); });
// Miscellaneous helper methods // Miscellaneous helper methods
$container->set('util', function($container) { $container->set('util', static function($container) {
return new Util($container); return new Util($container);
}); });
// Models // Models
$container->set('kitsu-model', function($container) { $container->set('kitsu-model', static function($container) {
$requestBuilder = new KitsuRequestBuilder(); $requestBuilder = new KitsuRequestBuilder();
$requestBuilder->setLogger($container->getLogger('kitsu-request')); $requestBuilder->setLogger($container->getLogger('kitsu-request'));
@ -136,7 +138,7 @@ return function ($configArray = []) {
$model->setCache($cache); $model->setCache($cache);
return $model; return $model;
}); });
$container->set('anilist-model', function($container) { $container->set('anilist-model', static function($container) {
$requestBuilder = new Anilist\AnilistRequestBuilder(); $requestBuilder = new Anilist\AnilistRequestBuilder();
$requestBuilder->setLogger($container->getLogger('anilist-request')); $requestBuilder->setLogger($container->getLogger('anilist-request'));
@ -151,39 +153,39 @@ return function ($configArray = []) {
return $model; return $model;
}); });
$container->set('api-model', function($container) { $container->set('api-model', static function($container) {
return new Model\API($container); return new Model\API($container);
}); });
$container->set('anime-model', function($container) { $container->set('anime-model', static function($container) {
return new Model\Anime($container); return new Model\Anime($container);
}); });
$container->set('manga-model', function($container) { $container->set('manga-model', static function($container) {
return new Model\Manga($container); return new Model\Manga($container);
}); });
$container->set('anime-collection-model', function($container) { $container->set('anime-collection-model', static function($container) {
return new Model\AnimeCollection($container); return new Model\AnimeCollection($container);
}); });
$container->set('manga-collection-model', function($container) { $container->set('manga-collection-model', static function($container) {
return new Model\MangaCollection($container); return new Model\MangaCollection($container);
}); });
$container->set('settings-model', function($container) { $container->set('settings-model', static function($container) {
$model = new Model\Settings($container->get('config')); $model = new Model\Settings($container->get('config'));
$model->setContainer($container); $model->setContainer($container);
return $model; return $model;
}); });
// Miscellaneous Classes // Miscellaneous Classes
$container->set('auth', function($container) { $container->set('auth', static function($container) {
return new Kitsu\Auth($container); return new Kitsu\Auth($container);
}); });
$container->set('url-generator', function($container) { $container->set('url-generator', static function($container) {
return new UrlGenerator($container); return new UrlGenerator($container);
}); });
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Dispatcher // Dispatcher
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
$container->set('dispatcher', function($container) { $container->set('dispatcher', static function($container) {
return new Dispatcher($container); return new Dispatcher($container);
}); });

View File

@ -8,4 +8,4 @@ user = ""
pass = "" pass = ""
port = "" port = ""
database = "" database = ""
file = "anime_collection.sqlite" file = "anime_collection.sqlite3"

View File

@ -14,13 +14,13 @@
<tr> <tr>
<td class="align-right"><label for="title">Title</label></td> <td class="align-right"><label for="title">Title</label></td>
<td class="align-left"> <td class="align-left">
<input type="text" name="title" value="<?= $item['title'] ?>" /> <input type="text" id="title" name="title" value="<?= $item['title'] ?>" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="align-right"><label for="title">Alternate Title</label></td> <td class="align-right"><label for="alternate_title">Alternate Title</label></td>
<td class="align-left"> <td class="align-left">
<input type="text" name="alternate_title" value="<?= $item['alternate_title'] ?>"/> <input type="text" id="alternate_title" name="alternate_title" value="<?= $item['alternate_title'] ?>"/>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -28,7 +28,7 @@
<td class="align-left"> <td class="align-left">
<select name="media_id" id="media_id"> <select name="media_id" id="media_id">
<?php foreach($media_items as $id => $name): ?> <?php foreach($media_items as $id => $name): ?>
<option <?= $item['media_id'] === $id ? 'selected="selected"' : '' ?> value="<?= $id ?>"><?= $name ?></option> <option <?= $item['media_id'] === (string)$id ? 'selected="selected"' : '' ?> value="<?= $id ?>"><?= $name ?></option>
<?php endforeach ?> <?php endforeach ?>
</select> </select>
</td> </td>

View File

@ -10,6 +10,7 @@
</div> </div>
</div> </div>
</section> </section>
<script nomodule src="https://polyfill.io/v3/polyfill.min.js?features=es5%2CObject.assign"></script>
<?php if ($auth->isAuthenticated()): ?> <?php if ($auth->isAuthenticated()): ?>
<script nomodule async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/scripts-authed.min.js') ?>"></script> <script nomodule async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/scripts-authed.min.js') ?>"></script>
<script type="module" src="<?= $urlGenerator->assetUrl('js/src/index-authed.js') ?>"></script> <script type="module" src="<?= $urlGenerator->assetUrl('js/src/index-authed.js') ?>"></script>

View File

@ -32,7 +32,7 @@ use Aviat\AnimeClient\API\Kitsu;
<?php foreach ($casting as $sid => $series): ?> <?php foreach ($casting as $sid => $series): ?>
<article class="media"> <article class="media">
<?php <?php
$mediaType = (in_array($type, ['anime', 'manga'])) ? $type : 'anime'; $mediaType = in_array($type, ['anime', 'manga'], TRUE) ? $type : 'anime';
$link = $url->generate("{$mediaType}.details", ['id' => $series['slug']]); $link = $url->generate("{$mediaType}.details", ['id' => $series['slug']]);
$titles = Kitsu::filterTitles($series); $titles = Kitsu::filterTitles($series);
?> ?>

View File

@ -20,7 +20,7 @@
<log type="coverage-clover" target="logs/clover.xml"/> <log type="coverage-clover" target="logs/clover.xml"/>
<log type="coverage-crap4j" target="logs/crap4j.xml"/> <log type="coverage-crap4j" target="logs/crap4j.xml"/>
<log type="coverage-xml" target="logs/coverage" /> <log type="coverage-xml" target="logs/coverage" />
<log type="junit" target="logs/junit.xml" logIncompleteSkipped="false"/> <log type="junit" target="logs/junit.xml" />
</logging> </logging>
<php> <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_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0" />

View File

@ -1,9 +1,9 @@
paths: paths:
migrations: %%PHINX_CONFIG_DIR%%/migrations migrations: '%%PHINX_CONFIG_DIR%%/migrations'
environments: environments:
default_migration_table: phinxlog default_migration_table: phinxlog
default_database: development default_database: development
development: development:
adapter: sqlite adapter: sqlite
name: ./anime_collection.sqlite name: ./anime_collection # Phinx will add a .sqlite3 suffix

File diff suppressed because one or more lines are too long

View File

@ -52,8 +52,41 @@ small {
color: #fff; color: #fff;
} }
input, select, textarea { input, input[type], select, textarea {
color: #111; border-color: #eee;
color: #eee;
background: #666;
padding:.8em;
}
button {
background: #444;
background: linear-gradient(#666, #555, #444, #555, #666);
border-radius: 0.5em;
margin: 0;
text-transform: none;
border-color: #ddd;
color: #ddd;
}
button:hover {
background: #222;
background: linear-gradient(#444, #333, #222, #333, #444);
border-color: #ddd;
color: #ddd;
}
button:active {
background: #333;
background: linear-gradient(#333, #333);
}
.media:hover button {
background: linear-gradient(#666, #555, #444, #555, #666);
}
.media:hover button:hover {
background: linear-gradient(#444, #555, #666, #555, #444);
} }
.message, .static-message { .message, .static-message {
@ -131,5 +164,8 @@ input, select, textarea {
border-bottom: 1px solid #444; border-bottom: 1px solid #444;
} }
.streaming-logo {
-webkit-filter: drop-shadow(0 0 2px #fff);
filter: drop-shadow(0 0 2px #fff);
}

View File

@ -1 +1 @@
a{color:#1978e2;text-shadow:var(--link-shadow)}a:hover{color:#9e34fd}body,legend,nav ul li a{background:#333;color:#eee}nav a:hover,nav li.selected a{border-color:#fff}header button{background:transparent}table{-webkit-box-shadow:none;box-shadow:none}td,th{border-color:#111}thead td,thead th{background:#333;color:#eee}tbody>tr:nth-child(2n){background:#555;color:#eee}tbody>tr:nth-child(odd){background:#333}footer,hr,legend{border-color:#ddd}small{color:#fff}input,select,textarea{color:#111}.message,.static-message{text-shadow:var(--white-link-shadow)}.message.success,.static-message.success{background:#1f8454;border-color:#70dda9}.message.error,.static-message.error{border-color:#f3e6e6;background:#924949}.message.info,.static-message.info{border-color:#ffc;background:#bfbe3a}.invisible tbody>tr:nth-child(2n),.invisible tbody>tr:nth-child(odd),.invisible td,.invisible th,.invisible tr{background:transparent}#main-nav{border-bottom:.1rem solid #ddd}.tabs,.vertical-tabs{background:#333}.tabs>label,.vertical-tabs .tab label{background:#222;border:0;color:#eee}.vertical-tabs .tab label{width:100%}.tabs>label:hover,.vertical-tabs .tab>label:hover{background:#888}.tabs>label:active,.vertical-tabs .tab>label:active{background:#999}.tabs>[type=radio]:checked+label,.tabs>[type=radio]:checked+label+.content,.vertical-tabs [type=radio]:checked+label,.vertical-tabs [type=radio]:checked~.content{border:0;background:#666;color:#eee}.vertical-tabs{background:#222;border:1px solid #444}.vertical-tabs .tab{background:#666;border-bottom:1px solid #444} a{color:#1978e2;text-shadow:var(--link-shadow)}a:hover{color:#9e34fd}body,legend,nav ul li a{background:#333;color:#eee}nav a:hover,nav li.selected a{border-color:#fff}header button{background:transparent}table{-webkit-box-shadow:none;box-shadow:none}td,th{border-color:#111}thead td,thead th{background:#333;color:#eee}tbody>tr:nth-child(2n){background:#555;color:#eee}tbody>tr:nth-child(odd){background:#333}footer,hr,legend{border-color:#ddd}small{color:#fff}input,input[type],select,textarea{border-color:#eee;color:#eee;background:#666;padding:.8em}button{background:#444;background:-webkit-gradient(linear,left top,left bottom,from(#666),color-stop(#555),color-stop(#444),color-stop(#555),to(#666));background:linear-gradient(#666,#555,#444,#555,#666);border-radius:.5em;margin:0;text-transform:none}button,button:hover{border-color:#ddd;color:#ddd}button:hover{background:#222;background:-webkit-gradient(linear,left top,left bottom,from(#444),color-stop(#333),color-stop(#222),color-stop(#333),to(#444));background:linear-gradient(#444,#333,#222,#333,#444)}button:active{background:#333;background:-webkit-gradient(linear,left top,left bottom,from(#333),to(#333));background:linear-gradient(#333,#333)}.media:hover button{background:-webkit-gradient(linear,left top,left bottom,from(#666),color-stop(#555),color-stop(#444),color-stop(#555),to(#666));background:linear-gradient(#666,#555,#444,#555,#666)}.media:hover button:hover{background:-webkit-gradient(linear,left top,left bottom,from(#444),color-stop(#555),color-stop(#666),color-stop(#555),to(#444));background:linear-gradient(#444,#555,#666,#555,#444)}.message,.static-message{text-shadow:var(--white-link-shadow)}.message.success,.static-message.success{background:#1f8454;border-color:#70dda9}.message.error,.static-message.error{border-color:#f3e6e6;background:#924949}.message.info,.static-message.info{border-color:#ffc;background:#bfbe3a}.invisible tbody>tr:nth-child(2n),.invisible tbody>tr:nth-child(odd),.invisible td,.invisible th,.invisible tr{background:transparent}#main-nav{border-bottom:.1rem solid #ddd}.tabs,.vertical-tabs{background:#333}.tabs>label,.vertical-tabs .tab label{background:#222;border:0;color:#eee}.vertical-tabs .tab label{width:100%}.tabs>label:hover,.vertical-tabs .tab>label:hover{background:#888}.tabs>label:active,.vertical-tabs .tab>label:active{background:#999}.tabs>[type=radio]:checked+label,.tabs>[type=radio]:checked+label+.content,.vertical-tabs [type=radio]:checked+label,.vertical-tabs [type=radio]:checked~.content{border:0;background:#666;color:#eee}.vertical-tabs{background:#222;border:1px solid #444}.vertical-tabs .tab{background:#666;border-bottom:1px solid #444}.streaming-logo{-webkit-filter:drop-shadow(0 0 2px #fff);filter:url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg"><filter id="filter"><feGaussianBlur in="SourceAlpha" stdDeviation="2" /><feOffset dx="1" dy="1" result="offsetblur" /><feFlood flood-color="rgba(255,255,255,1)" /><feComposite in2="offsetblur" operator="in" /><feMerge><feMergeNode /><feMergeNode in="SourceGraphic" /></feMerge></filter></svg>#filter');filter:drop-shadow(0 0 2px #fff)}

View File

@ -22,8 +22,33 @@ body {
} }
button { button {
background: rgba(255, 255, 255, 0.65); background: #fff;
background: linear-gradient(#ddd, #eee, #fff, #eee, #ddd);
border-radius: 0.5em;
margin: 0; margin: 0;
text-transform: none;
border-color: #555;
color: #555;
}
button:hover {
background: #bbb;
background: linear-gradient(#cfcfcf, #dfdfdf, #efefef, #dfdfdf, #cfcfcf);
border-color: #555;
color: #555;
}
button:active {
background: #ddd;
background: linear-gradient(#ddd, #ddd);
}
.media:hover button {
background: linear-gradient(#bbb, #ccc, #ddd, #ccc, #bbb);
}
.media:hover button:hover {
background: linear-gradient(#afafaf, #bfbfbf, #cfcfcf, #bfbfbf, #afafaf);
} }
table { table {
@ -182,16 +207,19 @@ td .media-wrap-flex {
.danger { .danger {
background-color: #ff4136; background-color: #ff4136;
border-color: #924949; border-color: #924949;
color: #fff; color: #924949;
/* color: #fff; */
} }
.danger:hover, .danger:active { .danger:hover, .danger:active {
background-color: #924949; background-color: #924949;
border-color: #ff4136; border-color: #ff4136;
color: #fff; color: #ff4136;
/* color: #fff; */
} }
.user-btn { .user-btn {
background: transparent;
border-color: var(--edit-link-color); border-color: var(--edit-link-color);
color: var(--edit-link-color); color: var(--edit-link-color);
text-shadow: var(--link-shadow); text-shadow: var(--link-shadow);
@ -200,8 +228,14 @@ td .media-wrap-flex {
} }
.user-btn:hover, .user-btn:active { .user-btn:hover, .user-btn:active {
background: transparent;
border-color: var(--edit-link-hover-color); border-color: var(--edit-link-hover-color);
background-color: var(--edit-link-hover-color); color: var(--edit-link-hover-color);
}
.user-btn:active {
background: var(--edit-link-hover-color);
color: #fff;
} }
.full-width { .full-width {
@ -533,21 +567,12 @@ picture.cover {
border-color: hsla(0, 0%, 100%, .65); border-color: hsla(0, 0%, 100%, .65);
position: absolute; position: absolute;
top: 138px; top: 138px;
top: calc(50% - 21.5px); top: calc(50% - 21.2px);
left: 44px; left: 44px;
left: calc(50% - 66.5px); left: calc(50% - 57.8px);
z-index: 50; z-index: 50;
} }
.anime .media > button.plus-one:hover {
color: hsla(0, 0%, 100%, .65);
background: #888;
}
.anime .media > button.plus-one:active {
background: #444;
}
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
Manga-list-specific styles Manga-list-specific styles
------------------------------------------------------------------------------*/ ------------------------------------------------------------------------------*/
@ -565,9 +590,9 @@ picture.cover {
position: absolute; position: absolute;
top: 86px; top: 86px;
/* top: calc(50% - 58.5px); */ /* top: calc(50% - 58.5px); */
top: calc(50% - 22.4px); top: calc(50% - 21.2px);
left: 43.5px; left: 43.5px;
left: calc(50% - 66.5px); left: calc(50% - 57.8px);
z-index: 40; z-index: 40;
} }
@ -575,15 +600,6 @@ picture.cover {
border-color: hsla(0, 0%, 100%, .65); border-color: hsla(0, 0%, 100%, .65);
} }
.manga .media > .edit-buttons:hover button {
color: hsla(0, 0%, 100%, .65);
background: #888;
}
.manga .media > .edit-buttons button:active {
background: #444;
}
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
Search page styles Search page styles
------------------------------------------------------------------------------*/ ------------------------------------------------------------------------------*/
@ -617,7 +633,6 @@ picture.cover {
position: absolute; position: absolute;
top: 147px; top: 147px;
left: 0; left: 0;
height: 100%;
width: 100%; width: 100%;
z-index: 5; z-index: 5;
} }
@ -660,10 +675,6 @@ picture.cover {
margin: 0 auto; margin: 0 auto;
} }
.fixed .text {
max-width: 40em;
}
.details .cover { .details .cover {
display: block; display: block;
} }

View File

@ -96,8 +96,8 @@ textarea {
} }
*,::before,::after { *,::before,::after {
border-style:solid; /* border-style:solid;
border-width:0; border-width:0; */
box-sizing:inherit; box-sizing:inherit;
} }
@ -124,7 +124,7 @@ audio,canvas,iframe,img,svg,video {
vertical-align:middle; vertical-align:middle;
} }
button,input,select,textarea { input,/*select*/,textarea {
border:.1rem solid #ccc; border:.1rem solid #ccc;
color:inherit; color:inherit;
font-family:inherit; font-family:inherit;
@ -296,7 +296,7 @@ img {
vertical-align:baseline; vertical-align:baseline;
} }
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 { 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:.1rem solid #ccc;
border-radius:0; border-radius:0;
display:inline-block; display:inline-block;
@ -320,7 +320,7 @@ input[type=color] {
padding:.8rem 1.6rem; padding:.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 { 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; border-color:#b3d4fc;
} }
@ -336,7 +336,7 @@ input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus {
outline:.1rem solid thin #444; 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; background-color:#efefef;
color:#777; color:#777;
cursor:not-allowed; cursor:not-allowed;
@ -348,13 +348,13 @@ input:not([type])[disabled] {
cursor:not-allowed; cursor:not-allowed;
} }
input[readonly],select[readonly],textarea[readonly] { input[readonly],/*select[readonly]*/,textarea[readonly] {
background-color:#efefef; background-color:#efefef;
border-color:#ccc; 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; border-color:#e9322d;
color:#b94a48; color:#b94a48;
} }
@ -363,10 +363,10 @@ input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus,input
outline-color:#ff4136; outline-color:#ff4136;
} }
select { /* select {
background-color:#fff; background-color:#fff;
border:.1rem solid #ccc; border:.1rem solid #ccc;
} }*/
select[multiple] { select[multiple] {
height:auto; height:auto;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"file":"tables.min.js.map","sources":["src/base/sort_tables.js"],"sourcesContent":["const LightTableSorter = (() => {\n\tlet th = null;\n\tlet cellIndex = null;\n\tlet order = '';\n\tconst text = (row) => row.cells.item(cellIndex).textContent.toLowerCase();\n\tconst sort = (a, b) => {\n\t\tlet textA = text(a);\n\t\tlet textB = text(b);\n\t\tconst n = parseInt(textA, 10);\n\t\tif (n) {\n\t\t\ttextA = n;\n\t\t\ttextB = parseInt(textB, 10);\n\t\t}\n\t\tif (textA > textB) {\n\t\t\treturn 1;\n\t\t}\n\t\tif (textA < textB) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn 0;\n\t};\n\tconst toggle = () => {\n\t\tconst c = order !== 'sorting-asc' ? 'sorting-asc' : 'sorting-desc';\n\t\tth.className = (th.className.replace(order, '') + ' ' + c).trim();\n\t\treturn order = c;\n\t};\n\tconst reset = () => {\n\t\tth.classList.remove('sorting-asc', 'sorting-desc');\n\t\tth.classList.add('sorting');\n\t\treturn order = '';\n\t};\n\tconst onClickEvent = (e) => {\n\t\tif (th && (cellIndex !== e.target.cellIndex)) {\n\t\t\treset();\n\t\t}\n\t\tth = e.target;\n\t\tif (th.nodeName.toLowerCase() === 'th') {\n\t\t\tcellIndex = th.cellIndex;\n\t\t\tconst tbody = th.offsetParent.getElementsByTagName('tbody')[0];\n\t\t\tlet rows = Array.from(tbody.rows);\n\t\t\tif (rows) {\n\t\t\t\trows.sort(sort);\n\t\t\t\tif (order === 'sorting-asc') {\n\t\t\t\t\trows.reverse();\n\t\t\t\t}\n\t\t\t\ttoggle();\n\t\t\t\ttbody.innerHtml = '';\n\n\t\t\t\trows.forEach(row => {\n\t\t\t\t\ttbody.appendChild(row);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n\treturn {\n\t\tinit: () => {\n\t\t\tlet ths = document.getElementsByTagName('th');\n\t\t\tlet results = [];\n\t\t\tfor (let i = 0, len = ths.length; i < len; i++) {\n\t\t\t\tlet th = ths[i];\n\t\t\t\tth.classList.add('sorting');\n\t\t\t\tresults.push(th.onclick = onClickEvent);\n\t\t\t}\n\t\t\treturn results;\n\t\t}\n\t};\n})();\n\nLightTableSorter.init();"],"names":["LightTableSorter","th","cellIndex","order","text","row","cells","item","textContent","toLowerCase","sort","a","b","textA","textB","n","parseInt","toggle","c","className","trim","replace","reset","classList","remove","add","onClickEvent","e","target","nodeName","tbody","offsetParent","getElementsByTagName","rows","Array","from","reverse","innerHtml","forEach","appendChild","init","ths","document","results","i","len","length","push","onclick"],"mappings":"YAAA,IAAMA,iBAAoB,QAAA,EAAM,CAC/B,IAAIC,GAAK,IACT,KAAIC,UAAY,IAChB,KAAIC,MAAQ,EACZ,KAAMC,KAAOA,QAAA,CAACC,GAAD,CAAS,CAAA,MAAAA,IAAAC,MAAAC,KAAA,CAAeL,SAAf,CAAAM,YAAAC,YAAA,EAAA,CACtB,KAAMC,KAAOA,QAAA,CAACC,CAAD,CAAIC,CAAJ,CAAU,CACtB,IAAIC,MAAQT,IAAA,CAAKO,CAAL,CACZ,KAAIG,MAAQV,IAAA,CAAKQ,CAAL,CACZ,KAAMG,EAAIC,QAAA,CAASH,KAAT,CAAgB,EAAhB,CACV,IAAIE,CAAJ,CAAO,CACNF,KAAA,CAAQE,CACRD,MAAA,CAAQE,QAAA,CAASF,KAAT,CAAgB,EAAhB,CAFF,CAIP,GAAID,KAAJ,CAAYC,KAAZ,CACC,MAAO,EAER,IAAID,KAAJ,CAAYC,KAAZ,CACC,MAAQ,EAET,OAAO,EAde,CAgBvB,KAAMG,OAASA,QAAA,EAAM,CACpB,IAAMC,EAAIf,KAAA,GAAU,aAAV,CAA0B,aAA1B,CAA0C,cACpDF,GAAAkB,UAAA,CAAeC,CAACnB,EAAAkB,UAAAE,QAAA,CAAqBlB,KAArB,CAA4B,EAA5B,CAADiB,CAAmC,GAAnCA,CAAyCF,CAAzCE,MAAA,EACf,OAAOjB,MAAP;AAAee,CAHK,CAKrB,KAAMI,MAAQA,QAAA,EAAM,CACnBrB,EAAAsB,UAAAC,OAAA,CAAoB,aAApB,CAAmC,cAAnC,CACAvB,GAAAsB,UAAAE,IAAA,CAAiB,SAAjB,CACA,OAAOtB,MAAP,CAAe,EAHI,CAKpB,KAAMuB,aAAeA,QAAA,CAACC,CAAD,CAAO,CAC3B,GAAI1B,EAAJ,EAAWC,SAAX,GAAyByB,CAAAC,OAAA1B,UAAzB,CACCoB,KAAA,EAEDrB,GAAA,CAAK0B,CAAAC,OACL,IAAI3B,EAAA4B,SAAApB,YAAA,EAAJ,GAAkC,IAAlC,CAAwC,CACvCP,SAAA,CAAYD,EAAAC,UACZ,KAAM4B,MAAQ7B,EAAA8B,aAAAC,qBAAA,CAAqC,OAArC,CAAA,CAA8C,CAA9C,CACd,KAAIC,KAAOC,KAAAC,KAAA,CAAWL,KAAAG,KAAX,CACX,IAAIA,IAAJ,CAAU,CACTA,IAAAvB,KAAA,CAAUA,IAAV,CACA,IAAIP,KAAJ,GAAc,aAAd,CACC8B,IAAAG,QAAA,EAEDnB,OAAA,EACAa,MAAAO,UAAA,CAAkB,EAElBJ,KAAAK,QAAA,CAAa,QAAA,CAAAjC,GAAA,CAAO,CACnByB,KAAAS,YAAA,CAAkBlC,GAAlB,CADmB,CAApB,CARS,CAJ6B,CALb,CAuB5B;MAAO,CACNmC,KAAMA,QAAA,EAAM,CACX,IAAIC,IAAMC,QAAAV,qBAAA,CAA8B,IAA9B,CACV,KAAIW,QAAU,EACd,KAAK,IAAIC,EAAI,CAAR,CAAWC,IAAMJ,GAAAK,OAAtB,CAAkCF,CAAlC,CAAsCC,GAAtC,CAA2CD,CAAA,EAA3C,CAAgD,CAC/C,IAAI3C,KAAKwC,GAAA,CAAIG,CAAJ,CACT3C,KAAAsB,UAAAE,IAAA,CAAiB,SAAjB,CACAkB,QAAAI,KAAA,CAAa9C,IAAA+C,QAAb,CAA0BtB,YAA1B,CAH+C,CAKhD,MAAOiB,QARI,CADN,CAtDwB,CAAP,EAoEzB3C,iBAAAwC,KAAA;"} {"version":3,"file":"tables.min.js.map","sources":["src/base/sort_tables.js"],"sourcesContent":["const LightTableSorter = (() => {\n\tlet th = null;\n\tlet cellIndex = null;\n\tlet order = '';\n\tconst text = (row) => row.cells.item(cellIndex).textContent.toLowerCase();\n\tconst sort = (a, b) => {\n\t\tlet textA = text(a);\n\t\tlet textB = text(b);\n\t\tconst n = parseInt(textA, 10);\n\t\tif (n) {\n\t\t\ttextA = n;\n\t\t\ttextB = parseInt(textB, 10);\n\t\t}\n\t\tif (textA > textB) {\n\t\t\treturn 1;\n\t\t}\n\t\tif (textA < textB) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn 0;\n\t};\n\tconst toggle = () => {\n\t\tconst c = order !== 'sorting-asc' ? 'sorting-asc' : 'sorting-desc';\n\t\tth.className = (th.className.replace(order, '') + ' ' + c).trim();\n\t\treturn order = c;\n\t};\n\tconst reset = () => {\n\t\tth.classList.remove('sorting-asc', 'sorting-desc');\n\t\tth.classList.add('sorting');\n\t\treturn order = '';\n\t};\n\tconst onClickEvent = (e) => {\n\t\tif (th && (cellIndex !== e.target.cellIndex)) {\n\t\t\treset();\n\t\t}\n\t\tth = e.target;\n\t\tif (th.nodeName.toLowerCase() === 'th') {\n\t\t\tcellIndex = th.cellIndex;\n\t\t\tconst tbody = th.offsetParent.getElementsByTagName('tbody')[0];\n\t\t\tlet rows = Array.from(tbody.rows);\n\t\t\tif (rows) {\n\t\t\t\trows.sort(sort);\n\t\t\t\tif (order === 'sorting-asc') {\n\t\t\t\t\trows.reverse();\n\t\t\t\t}\n\t\t\t\ttoggle();\n\t\t\t\ttbody.innerHtml = '';\n\n\t\t\t\trows.forEach(row => {\n\t\t\t\t\ttbody.appendChild(row);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n\treturn {\n\t\tinit: () => {\n\t\t\tlet ths = document.getElementsByTagName('th');\n\t\t\tlet results = [];\n\t\t\tfor (let i = 0, len = ths.length; i < len; i++) {\n\t\t\t\tlet th = ths[i];\n\t\t\t\tth.classList.add('sorting');\n\t\t\t\tresults.push(th.onclick = onClickEvent);\n\t\t\t}\n\t\t\treturn results;\n\t\t}\n\t};\n})();\n\nLightTableSorter.init();"],"names":["th","cellIndex","order","text","row","cells","item","textContent","toLowerCase","sort","a","b","textA","textB","n","parseInt","toggle","c","className","trim","replace","reset","classList","remove","add","onClickEvent","e","target","nodeName","tbody","offsetParent","getElementsByTagName","rows","Array","from","reverse","innerHtml","forEach","appendChild","init","ths","document","results","i","len","length","push","onclick","LightTableSorter"],"mappings":"YAAA,gCACC,IAAIA,GAAK,IACT,KAAIC,UAAY,IAChB,KAAIC,MAAQ,EACZ,KAAMC,KAAOA,QAAA,CAACC,GAAD,CAAS,CAAA,MAAAA,IAAAC,MAAAC,KAAA,CAAeL,SAAf,CAAAM,YAAAC,YAAA,EAAA,CACtB,KAAMC,KAAOA,QAAA,CAACC,CAAD,CAAIC,CAAJ,CAAU,CACtB,IAAIC,MAAQT,IAAA,CAAKO,CAAL,CACZ,KAAIG,MAAQV,IAAA,CAAKQ,CAAL,CACZ,KAAMG,EAAIC,QAAA,CAASH,KAAT,CAAgB,EAAhB,CACV,IAAIE,CAAJ,CAAO,CACNF,KAAA,CAAQE,CACRD,MAAA,CAAQE,QAAA,CAASF,KAAT,CAAgB,EAAhB,CAFF,CAIP,GAAID,KAAJ,CAAYC,KAAZ,CACC,MAAO,EAER,IAAID,KAAJ,CAAYC,KAAZ,CACC,MAAQ,EAET,OAAO,EAde,CAgBvB,KAAMG,OAASA,QAAA,EAAM,CACpB,IAAMC,EAAIf,KAAA,GAAU,aAAV,CAA0B,aAA1B,CAA0C,cACpDF,GAAAkB,UAAA,CAAeC,CAACnB,EAAAkB,UAAAE,QAAA,CAAqBlB,KAArB,CAA4B,EAA5B,CAADiB,CAAmC,GAAnCA,CAAyCF,CAAzCE,MAAA,EACf,OAAOjB,MAAP;AAAee,CAHK,CAKrB,KAAMI,MAAQA,QAAA,EAAM,CACnBrB,EAAAsB,UAAAC,OAAA,CAAoB,aAApB,CAAmC,cAAnC,CACAvB,GAAAsB,UAAAE,IAAA,CAAiB,SAAjB,CACA,OAAOtB,MAAP,CAAe,EAHI,CAKpB,KAAMuB,aAAeA,QAAA,CAACC,CAAD,CAAO,CAC3B,GAAI1B,EAAJ,EAAWC,SAAX,GAAyByB,CAAAC,OAAA1B,UAAzB,CACCoB,KAAA,EAEDrB,GAAA,CAAK0B,CAAAC,OACL,IAAI3B,EAAA4B,SAAApB,YAAA,EAAJ,GAAkC,IAAlC,CAAwC,CACvCP,SAAA,CAAYD,EAAAC,UACZ,KAAM4B,MAAQ7B,EAAA8B,aAAAC,qBAAA,CAAqC,OAArC,CAAA,CAA8C,CAA9C,CACd,KAAIC,KAAOC,KAAAC,KAAA,CAAWL,KAAAG,KAAX,CACX,IAAIA,IAAJ,CAAU,CACTA,IAAAvB,KAAA,CAAUA,IAAV,CACA,IAAIP,KAAJ,GAAc,aAAd,CACC8B,IAAAG,QAAA,EAEDnB,OAAA,EACAa,MAAAO,UAAA,CAAkB,EAElBJ,KAAAK,QAAA,CAAa,QAAA,CAAAjC,GAAA,CAAO,CACnByB,KAAAS,YAAA,CAAkBlC,GAAlB,CADmB,CAApB,CARS,CAJ6B,CALb,CAuB5B;MAAO,CACNmC,KAAMA,QAAA,EAAM,CACX,IAAIC,IAAMC,QAAAV,qBAAA,CAA8B,IAA9B,CACV,KAAIW,QAAU,EACd,KAAK,IAAIC,EAAI,CAAR,CAAWC,IAAMJ,GAAAK,OAAtB,CAAkCF,CAAlC,CAAsCC,GAAtC,CAA2CD,CAAA,EAA3C,CAAgD,CAC/C,IAAI3C,KAAKwC,GAAA,CAAIG,CAAJ,CACT3C,KAAAsB,UAAAE,IAAA,CAAiB,SAAjB,CACAkB,QAAAI,KAAA,CAAa9C,IAAA+C,QAAb,CAA0BtB,YAA1B,CAH+C,CAKhD,MAAOiB,QARI,CADN,IAcRM,iBAAAT,KAAA;"}

View File

@ -9,13 +9,13 @@
"watch": "concurrently \"npm:watch:css\" \"npm:watch:js\" --kill-others" "watch": "concurrently \"npm:watch:css\" \"npm:watch:js\" --kill-others"
}, },
"devDependencies": { "devDependencies": {
"@ampproject/rollup-plugin-closure-compiler": "^0.8.3", "@ampproject/rollup-plugin-closure-compiler": "^0.9.0",
"concurrently": "^4.0.1", "concurrently": "^4.0.1",
"cssnano": "^4.0.5", "cssnano": "^4.0.5",
"postcss-cachify": "^1.3.1", "postcss-cachify": "^1.3.1",
"postcss-cssnext": "^3.0.0", "postcss-cssnext": "^3.0.0",
"postcss-import": "^12.0.0", "postcss-import": "^12.0.0",
"rollup": "^0.66.6", "rollup": "^1.11.3",
"rollup-plugin-closure-compiler-js": "^1.0.6", "rollup-plugin-closure-compiler-js": "^1.0.6",
"watch": "^1.0.2" "watch": "^1.0.2"
} }

File diff suppressed because it is too large Load Diff

View File

@ -422,8 +422,14 @@ final class Model {
$item['included'] = $included; $item['included'] = $included;
} }
$transformed = $this->animeListTransformer->transformCollection($data['data']); $transformed = $this->animeListTransformer->transformCollection($data['data']);
$keyed = [];
$cacheItem->set($transformed); foreach($transformed as $item)
{
$keyed[$item['id']] = $item;
}
$cacheItem->set($keyed);
$cacheItem->save(); $cacheItem->save();
} }

View File

@ -72,7 +72,7 @@ final class AnimeListTransformer extends AbstractTransformer {
} }
} }
$streamingLinks = (array_key_exists('streamingLinks', $anime['relationships'])) $streamingLinks = array_key_exists('streamingLinks', $anime['relationships'])
? Kitsu::parseListItemStreamingLinks($included, $animeId) ? Kitsu::parseListItemStreamingLinks($included, $animeId)
: []; : [];

View File

@ -1,280 +0,0 @@
<?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\API;
use DOMDocument, DOMNode, DOMNodeList, InvalidArgumentException;
/**
* XML <=> PHP Array codec
*/
final class XML {
/**
* XML representation of the data
*
* @var string
*/
private $xml;
/**
* PHP array version of the data
*
* @var array
*/
private $data;
/**
* XML constructor
*
* @param string $xml
* @param array $data
*/
public function __construct(string $xml = '', array $data = [])
{
$this->setXML($xml)->setData($data);
}
/**
* Serialize the data to an xml string
*
* @return string
*/
public function __toString(): string
{
return static::toXML($this->getData());
}
/**
* Get the data parsed from the XML
*
* @return array
*/
public function getData(): array
{
return $this->data;
}
/**
* Set the data to create xml from
*
* @param array $data
* @return self
*/
public function setData(array $data): self
{
$this->data = $data;
return $this;
}
/**
* Get the xml created from the data
*
* @return string
*/
public function getXML(): string
{
return $this->xml;
}
/**
* Set the xml to parse the data from
*
* @param string $xml
* @return self
*/
public function setXML(string $xml): self
{
$this->xml = $xml;
return $this;
}
/**
* Parse an xml document string to a php array
*
* @param string $xml
* @return array
*/
public static function toArray(string $xml): array
{
$data = [];
$xml = static::stripXMLWhitespace($xml);
$dom = new DOMDocument();
$hasLoaded = @$dom->loadXML($xml);
if ( ! $hasLoaded)
{
throw new InvalidArgumentException('Failed to load XML');
}
$root = $dom->documentElement;
$data[$root->tagName] = [];
if ($root->hasChildNodes())
{
static::childNodesToArray($data[$root->tagName], $root->childNodes);
}
return $data;
}
/**
* Transform the array into XML
*
* @param array $data
* @return string
*/
public static function toXML(array $data): string
{
$dom = new DOMDocument();
$dom->encoding = 'UTF-8';
static::arrayPropertiesToXmlNodes($dom, $dom, $data);
return $dom->saveXML();
}
/**
* Parse the xml document string to a php array
*
* @return array
*/
public function parse(): array
{
$xml = $this->getXML();
$data = static::toArray($xml);
return $this->setData($data)->getData();
}
/**
* Transform the array into XML
*
* @return string
*/
public function createXML(): string
{
return static::toXML($this->getData());
}
/**
* Strip whitespace from raw xml to remove irrelevant text nodes
*
* @param string $xml
* @return string
*/
private static function stripXMLWhitespace(string $xml): string
{
// Get rid of unimportant text nodes by removing
// whitespace characters from between xml tags,
// except for the xml declaration tag, Which looks
// something like:
/* <?xml version="1.0" encoding="UTF-8"?> */
return preg_replace('/([^?])>\s+</', '$1><', $xml);
}
/**
* Recursively create array structure based on xml structure
*
* @param array $root A reference to the current array location
* @param DOMNodeList $nodeList The current NodeList object
* @return void
*/
private static function childNodesToArray(array &$root, DOMNodelist $nodeList): void
{
$length = $nodeList->length;
for ($i = 0; $i < $length; $i++)
{
$el = $nodeList->item($i);
$current =& $root[$el->nodeName];
// It's a top level element!
if (( ! $el->hasChildNodes()) || is_a($el->childNodes->item(0), 'DomText'))
{
$current = $el->textContent;
continue;
}
// An empty value at the current root
if ($current === NULL)
{
$current = [];
static::childNodesToArray($current, $el->childNodes);
continue;
}
$keys = array_keys($current);
// Wrap the array in a containing array
// if there are only string keys
if ( ! is_numeric($keys[0]))
{
// But if there is only one key, don't wrap it in
// an array, just recurse to parse the child nodes
if (count($current) === 1)
{
static::childNodesToArray($current, $el->childNodes);
continue;
}
$current = [$current];
}
$current[] = [];
$index = count($current) - 1;
static::childNodesToArray($current[$index], $el->childNodes);
}
}
/**
* Recursively create xml nodes from array properties
*
* @param DOMDocument $dom The current DOM object
* @param DOMNode $parent The parent element to append children to
* @param array $data The data for the current node
* @return void
*/
private static function arrayPropertiesToXmlNodes(DOMDocument $dom, DOMNode $parent, array $data): void
{
foreach($data as $key => $props)
{
// 'Flatten' the array as you create the xml
if (is_numeric($key))
{
foreach($props as $key => $props)
{
break;
}
}
$node = $dom->createElement($key);
if (\is_array($props))
{
static::arrayPropertiesToXmlNodes($dom, $node, $props);
}
else
{
$tNode = $dom->createTextNode((string)$props);
$node->appendChild($tNode);
}
$parent->appendChild($node);
}
}
}

View File

@ -33,6 +33,12 @@ class Controller {
use ContainerAware; use ContainerAware;
/**
* The authentication object
* @var \Aviat\AnimeClient\API\Kitsu\Auth $auth ;
*/
protected $auth;
/** /**
* Cache manager * Cache manager
* @var \Psr\Cache\CacheItemPoolInterface * @var \Psr\Cache\CacheItemPoolInterface
@ -79,11 +85,7 @@ class Controller {
* Common data to be sent to views * Common data to be sent to views
* @var array * @var array
*/ */
protected $baseData = [ protected $baseData = [];
'url_type' => 'anime',
'other_type' => 'manga',
'menu_name' => ''
];
/** /**
* Controller constructor. * Controller constructor.
@ -95,46 +97,30 @@ class Controller {
public function __construct(ContainerInterface $container) public function __construct(ContainerInterface $container)
{ {
$this->setContainer($container); $this->setContainer($container);
$auraUrlGenerator = $container->get('aura-router')->getGenerator(); $auraUrlGenerator = $container->get('aura-router')->getGenerator();
$session = $container->get('session');
$urlGenerator = $container->get('url-generator'); $urlGenerator = $container->get('url-generator');
$this->auth = $container->get('auth');
$this->cache = $container->get('cache'); $this->cache = $container->get('cache');
$this->config = $container->get('config'); $this->config = $container->get('config');
$this->request = $container->get('request'); $this->request = $container->get('request');
$this->response = $container->get('response'); $this->response = $container->get('response');
$this->session = $session->getSegment(SESSION_SEGMENT);
$this->baseData = array_merge($this->baseData, [
'url' => $auraUrlGenerator,
'urlGenerator' => $urlGenerator,
'auth' => $container->get('auth'),
'config' => $this->config
]);
$this->url = $auraUrlGenerator; $this->url = $auraUrlGenerator;
$this->urlGenerator = $urlGenerator; $this->urlGenerator = $urlGenerator;
$session = $container->get('session'); $this->baseData = [
$this->session = $session->getSegment(SESSION_SEGMENT); 'auth' => $container->get('auth'),
'config' => $this->config,
// Set a 'previous' flash value for better redirects 'menu_name' => '',
$serverParams = $this->request->getServerParams(); 'message' => $this->session->getFlash('message'), // Get message box data if it exists
if (array_key_exists('HTTP_REFERER', $serverParams) && false === stripos($serverParams['HTTP_REFERER'], 'login')) 'other_type' => 'manga',
{ 'url' => $auraUrlGenerator,
$this->session->setFlash('previous', $serverParams['HTTP_REFERER']); 'url_type' => 'anime',
} 'urlGenerator' => $urlGenerator,
];
// Set a message box if available
$this->baseData['message'] = $this->session->getFlash('message');
}
/**
* Redirect to the previous page
*
* @return void
*/
public function redirectToPrevious(): void
{
$previous = $this->session->getFlash('previous');
$this->redirect($previous, 303);
} }
/** /**
@ -159,7 +145,7 @@ class Controller {
// Don't attempt to set the redirect url if // Don't attempt to set the redirect url if
// the page is one of the form type pages, // the page is one of the form type pages,
// and the previous page is also a form type page_segments // and the previous page is also a form type
if ($doubleFormPage || $isLoginPage) if ($doubleFormPage || $isLoginPage)
{ {
return; return;
@ -178,6 +164,8 @@ class Controller {
/** /**
* Redirect to the url previously set in the session * Redirect to the url previously set in the session
* *
* If one is not set, redirect to default url
*
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws \Aviat\Ion\Di\Exception\ContainerException * @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\Exception\NotFoundException * @throws \Aviat\Ion\Di\Exception\NotFoundException
@ -185,15 +173,25 @@ class Controller {
*/ */
public function sessionRedirect(): void public function sessionRedirect(): void
{ {
$target = $this->session->get('redirect_url'); $target = $this->session->get('redirect_url') ?? '/';
if (empty($target))
$this->redirect($target, 303);
$this->session->set('redirect_url', NULL);
}
/**
* Check if the current user is authenticated, else error and exit
*/
protected function checkAuth(): void
{
if ( ! $this->auth->isAuthenticated())
{ {
$this->notFound(); $this->errorPage(
} 403,
else 'Forbidden',
{ 'You must <a href="/login">log in</a> to perform this action.'
$this->redirect($target, 303); );
$this->session->set('redirect_url', NULL); die();
} }
} }
@ -218,7 +216,7 @@ class Controller {
} }
$route = $router->getRoute(); $route = $router->getRoute();
$data['route_path'] = $route ? $router->getRoute()->path : ''; $data['route_path'] = $route !== FALSE ? $route->path : '';
$templatePath = _dir($this->config->get('view_path'), "{$template}.php"); $templatePath = _dir($this->config->get('view_path'), "{$template}.php");
@ -275,7 +273,7 @@ class Controller {
public function notFound( public function notFound(
string $title = 'Sorry, page not found', string $title = 'Sorry, page not found',
string $message = 'Page Not Found' string $message = 'Page Not Found'
): void ): void
{ {
$this->outputHTML('404', [ $this->outputHTML('404', [
'title' => $title, 'title' => $title,
@ -408,7 +406,6 @@ class Controller {
(new JsonView($this->container)) (new JsonView($this->container))
->setStatusCode($code) ->setStatusCode($code)
->setOutput($data); ->setOutput($data);
// ->send();
exit(); exit();
} }
@ -421,8 +418,8 @@ class Controller {
*/ */
protected function redirect(string $url, int $code): void protected function redirect(string $url, int $code): void
{ {
$http = new HttpView($this->container); (new HttpView($this->container))->redirect($url, $code);
$http->redirect($url, $code); exit();
} }
} }
// End of BaseController.php // End of BaseController.php

View File

@ -28,6 +28,7 @@ use Aviat\Ion\Json;
* Controller for Anime-related pages * Controller for Anime-related pages
*/ */
final class Anime extends BaseController { final class Anime extends BaseController {
/** /**
* The anime list model * The anime list model
* @var \Aviat\AnimeClient\Model\Anime $model * @var \Aviat\AnimeClient\Model\Anime $model
@ -49,12 +50,9 @@ final class Anime extends BaseController {
$this->baseData = array_merge($this->baseData, [ $this->baseData = array_merge($this->baseData, [
'menu_name' => 'anime_list', 'menu_name' => 'anime_list',
'url_type' => 'anime',
'other_type' => 'manga', 'other_type' => 'manga',
'config' => $this->config, 'url_type' => 'anime',
]); ]);
$this->cache = $container->get('cache');
} }
/** /**
@ -69,6 +67,18 @@ final class Anime extends BaseController {
*/ */
public function index($type = KitsuWatchingStatus::WATCHING, string $view = NULL): void public function index($type = KitsuWatchingStatus::WATCHING, string $view = NULL): void
{ {
if ( ! in_array($type, [
'all',
'watching',
'plan_to_watch',
'on_hold',
'dropped',
'completed',
], TRUE))
{
$this->errorPage(404, 'Not Found', 'Page not found');
}
$title = array_key_exists($type, AnimeWatchingStatus::ROUTE_TO_TITLE) $title = array_key_exists($type, AnimeWatchingStatus::ROUTE_TO_TITLE)
? $this->formatTitle( ? $this->formatTitle(
$this->config->get('whose_list') . "'s Anime List", $this->config->get('whose_list') . "'s Anime List",
@ -102,6 +112,8 @@ final class Anime extends BaseController {
*/ */
public function addForm(): void public function addForm(): void
{ {
$this->checkAuth();
$this->setSessionRedirect(); $this->setSessionRedirect();
$this->outputHTML('anime/add', [ $this->outputHTML('anime/add', [
'title' => $this->formatTitle( 'title' => $this->formatTitle(
@ -122,6 +134,8 @@ final class Anime extends BaseController {
*/ */
public function add(): void public function add(): void
{ {
$this->checkAuth();
$data = $this->request->getParsedBody(); $data = $this->request->getParsedBody();
if (empty($data['mal_id'])) if (empty($data['mal_id']))
@ -157,6 +171,8 @@ final class Anime extends BaseController {
*/ */
public function edit(string $id, $status = 'all'): void public function edit(string $id, $status = 'all'): void
{ {
$this->checkAuth();
$item = $this->model->getLibraryItem($id); $item = $this->model->getLibraryItem($id);
$this->setSessionRedirect(); $this->setSessionRedirect();
@ -194,6 +210,8 @@ final class Anime extends BaseController {
*/ */
public function formUpdate(): void public function formUpdate(): void
{ {
$this->checkAuth();
$data = $this->request->getParsedBody(); $data = $this->request->getParsedBody();
// Do some minor data manipulation for // Do some minor data manipulation for
@ -222,6 +240,8 @@ final class Anime extends BaseController {
*/ */
public function increment(): void public function increment(): void
{ {
$this->checkAuth();
if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE) if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE)
{ {
$data = Json::decode((string)$this->request->getBody()); $data = Json::decode((string)$this->request->getBody());
@ -231,6 +251,12 @@ final class Anime extends BaseController {
$data = $this->request->getParsedBody(); $data = $this->request->getParsedBody();
} }
if (empty($data))
{
$this->errorPage(400, 'Bad Request', '');
die();
}
$response = $this->model->incrementLibraryItem(new FormItem($data)); $response = $this->model->incrementLibraryItem(new FormItem($data));
$this->cache->clear(); $this->cache->clear();
@ -244,6 +270,8 @@ final class Anime extends BaseController {
*/ */
public function delete(): void public function delete(): void
{ {
$this->checkAuth();
$body = $this->request->getParsedBody(); $body = $this->request->getParsedBody();
$response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']); $response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']);

View File

@ -56,9 +56,8 @@ final class AnimeCollection extends BaseController {
$this->baseData = array_merge($this->baseData, [ $this->baseData = array_merge($this->baseData, [
'collection_type' => 'anime', 'collection_type' => 'anime',
'menu_name' => 'collection', 'menu_name' => 'collection',
'url_type' => 'anime',
'other_type' => 'manga', 'other_type' => 'manga',
'config' => $this->config, 'url_type' => 'anime',
]); ]);
} }
@ -112,6 +111,8 @@ final class AnimeCollection extends BaseController {
*/ */
public function form($id = NULL): void public function form($id = NULL): void
{ {
$this->checkAuth();
$this->setSessionRedirect(); $this->setSessionRedirect();
$action = $id === NULL ? 'Add' : 'Edit'; $action = $id === NULL ? 'Add' : 'Edit';
@ -139,9 +140,12 @@ final class AnimeCollection extends BaseController {
*/ */
public function edit(): void public function edit(): void
{ {
$this->checkAuth();
$data = $this->request->getParsedBody(); $data = $this->request->getParsedBody();
if (array_key_exists('hummingbird_id', $data)) if (array_key_exists('hummingbird_id', $data))
{ {
// @TODO verify data was updated correctly
$this->animeCollectionModel->update($data); $this->animeCollectionModel->update($data);
$this->setFlashMessage('Successfully updated collection item.', 'success'); $this->setFlashMessage('Successfully updated collection item.', 'success');
} }
@ -163,11 +167,22 @@ final class AnimeCollection extends BaseController {
*/ */
public function add(): void public function add(): void
{ {
$this->checkAuth();
$data = $this->request->getParsedBody(); $data = $this->request->getParsedBody();
if (array_key_exists('id', $data)) if (array_key_exists('id', $data))
{ {
$this->animeCollectionModel->add($data); // Check for existing entry
$this->setFlashMessage('Successfully added collection item', 'success'); if ($this->animeCollectionModel->get($data['id']) !== FALSE)
{
$this->setFlashMessage('Anime already exists, can not create duplicate', 'info');
}
else
{
// @TODO actually verify that collection item was added
$this->animeCollectionModel->add($data);
$this->setFlashMessage('Successfully added collection item', 'success');
}
} }
else else
{ {
@ -184,16 +199,19 @@ final class AnimeCollection extends BaseController {
*/ */
public function delete(): void public function delete(): void
{ {
$this->checkAuth();
$data = $this->request->getParsedBody(); $data = $this->request->getParsedBody();
if ( ! array_key_exists('hummingbird_id', $data)) if ( ! array_key_exists('hummingbird_id', $data))
{ {
$this->redirect('/anime-collection/view', 303); $this->redirect('/anime-collection/view', 303);
} }
// @TODO verify that item was actually deleted
$this->animeCollectionModel->delete($data); $this->animeCollectionModel->delete($data);
$this->setFlashMessage('Successfully removed anime from collection.', 'success'); $this->setFlashMessage('Successfully removed anime from collection.', 'success');
$this->redirect('/anime-collection/view', 303); $this->redirect('/anime-collection/view', 303);
} }
} }
// End of CollectionController.php // End of AnimeCollection.php

View File

@ -131,8 +131,6 @@ final class Images extends BaseController {
$data = wait($response->getBody()); $data = wait($response->getBody());
[$origWidth] = getimagesizefromstring($data); [$origWidth] = getimagesizefromstring($data);
$gdImg = imagecreatefromstring($data); $gdImg = imagecreatefromstring($data);
$resizedImg = imagescale($gdImg, $width ?? $origWidth); $resizedImg = imagescale($gdImg, $width ?? $origWidth);

View File

@ -29,8 +29,6 @@ use Aviat\Ion\{Json, StringWrapper};
*/ */
final class Manga extends Controller { final class Manga extends Controller {
use StringWrapper;
/** /**
* The manga model * The manga model
* @var MangaModel $model * @var MangaModel $model
@ -51,9 +49,8 @@ final class Manga extends Controller {
$this->model = $container->get('manga-model'); $this->model = $container->get('manga-model');
$this->baseData = array_merge($this->baseData, [ $this->baseData = array_merge($this->baseData, [
'menu_name' => 'manga_list', 'menu_name' => 'manga_list',
'config' => $this->config, 'other_type' => 'anime',
'url_type' => 'manga', 'url_type' => 'manga',
'other_type' => 'anime'
]); ]);
} }
@ -69,6 +66,18 @@ final class Manga extends Controller {
*/ */
public function index($status = 'all', $view = ''): void public function index($status = 'all', $view = ''): void
{ {
if ( ! in_array($status, [
'all',
'reading',
'plan_to_read',
'dropped',
'on_hold',
'completed',
], TRUE))
{
$this->errorPage(404, 'Not Found', 'Page not found');
}
$statusTitle = MangaReadingStatus::ROUTE_TO_TITLE[$status]; $statusTitle = MangaReadingStatus::ROUTE_TO_TITLE[$status];
$title = $this->formatTitle( $title = $this->formatTitle(
@ -102,6 +111,8 @@ final class Manga extends Controller {
*/ */
public function addForm(): void public function addForm(): void
{ {
$this->checkAuth();
$statuses = MangaReadingStatus::KITSU_TO_TITLE; $statuses = MangaReadingStatus::KITSU_TO_TITLE;
$this->setSessionRedirect(); $this->setSessionRedirect();
@ -124,6 +135,8 @@ final class Manga extends Controller {
*/ */
public function add(): void public function add(): void
{ {
$this->checkAuth();
$data = $this->request->getParsedBody(); $data = $this->request->getParsedBody();
if ( ! array_key_exists('id', $data)) if ( ! array_key_exists('id', $data))
{ {
@ -163,6 +176,8 @@ final class Manga extends Controller {
*/ */
public function edit($id, $status = 'All'): void public function edit($id, $status = 'All'): void
{ {
$this->checkAuth();
$this->setSessionRedirect(); $this->setSessionRedirect();
$item = $this->model->getLibraryItem($id); $item = $this->model->getLibraryItem($id);
$title = $this->formatTitle( $title = $this->formatTitle(
@ -201,6 +216,8 @@ final class Manga extends Controller {
*/ */
public function formUpdate(): void public function formUpdate(): void
{ {
$this->checkAuth();
$data = $this->request->getParsedBody(); $data = $this->request->getParsedBody();
// Do some minor data manipulation for // Do some minor data manipulation for
@ -228,6 +245,8 @@ final class Manga extends Controller {
*/ */
public function increment(): void public function increment(): void
{ {
$this->checkAuth();
if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE) if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE)
{ {
$data = Json::decode((string)$this->request->getBody()); $data = Json::decode((string)$this->request->getBody());
@ -252,6 +271,8 @@ final class Manga extends Controller {
*/ */
public function delete(): void public function delete(): void
{ {
$this->checkAuth();
$body = $this->request->getParsedBody(); $body = $this->request->getParsedBody();
$response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']); $response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']);

View File

@ -57,9 +57,8 @@ final class MangaCollection extends BaseController {
$this->baseData = array_merge($this->baseData, [ $this->baseData = array_merge($this->baseData, [
'collection_type' => 'manga', 'collection_type' => 'manga',
'menu_name' => 'manga-collection', 'menu_name' => 'manga-collection',
'url_type' => 'manga',
'other_type' => 'anime', 'other_type' => 'anime',
'config' => $this->config, 'url_type' => 'manga',
]); ]);
} }

View File

@ -89,6 +89,8 @@ final class Misc extends BaseController {
*/ */
public function logout(): void public function logout(): void
{ {
$this->checkAuth();
$auth = $this->container->get('auth'); $auth = $this->container->get('auth');
$auth->logout(); $auth->logout();

View File

@ -23,6 +23,7 @@ use Aviat\Ion\Di\ContainerInterface;
* Controller for user settings * Controller for user settings
*/ */
final class Settings extends BaseController { final class Settings extends BaseController {
/** /**
* @var \Aviat\AnimeClient\API\Anilist\Model * @var \Aviat\AnimeClient\API\Anilist\Model
*/ */
@ -46,6 +47,9 @@ final class Settings extends BaseController {
$this->anilistModel = $container->get('anilist-model'); $this->anilistModel = $container->get('anilist-model');
$this->settingsModel = $container->get('settings-model'); $this->settingsModel = $container->get('settings-model');
// This is a rare controller where every route is private
$this->checkAuth();
} }
/** /**
@ -78,16 +82,11 @@ final class Settings extends BaseController {
$post = $this->request->getParsedBody(); $post = $this->request->getParsedBody();
unset($post['settings-tabs']); unset($post['settings-tabs']);
// dump($post);
$saved = $this->settingsModel->saveSettingsFile($post); $saved = $this->settingsModel->saveSettingsFile($post);
if ($saved) $saved
{ ? $this->setFlashMessage('Saved config settings.', 'success')
$this->setFlashMessage('Saved config settings.', 'success'); : $this->setFlashMessage('Failed to save config file.', 'error');
} else
{
$this->setFlashMessage('Failed to save config file.', 'error');
}
$this->redirect($this->url->generate('settings'), 303); $this->redirect($this->url->generate('settings'), 303);
} }
@ -144,13 +143,9 @@ final class Settings extends BaseController {
$saved = $this->settingsModel->saveSettingsFile($newSettings); $saved = $this->settingsModel->saveSettingsFile($newSettings);
if ($saved) $saved
{ ? $this->setFlashMessage('Linked Anilist Account', 'success')
$this->setFlashMessage('Linked Anilist Account', 'success'); : $this->setFlashMessage('Error Linking Anilist Account', 'error');
} else
{
$this->setFlashMessage('Error Linking Anilist Account', 'error');
}
$this->redirect($this->url->generate('settings'), 303); $this->redirect($this->url->generate('settings'), 303);
} }

View File

@ -45,10 +45,10 @@ final class Dispatcher extends RoutingBase {
protected $matcher; protected $matcher;
/** /**
* Class wrapper for input superglobals * Routing array
* @var \Psr\Http\Message\ServerRequestInterface * @var array
*/ */
protected $request; protected $routes;
/** /**
* Routes added to router * Routes added to router
@ -67,8 +67,7 @@ final class Dispatcher extends RoutingBase {
$router = $this->container->get('aura-router'); $router = $this->container->get('aura-router');
$this->router = $router->getMap(); $this->router = $router->getMap();
$this->matcher = $router->getMatcher(); $this->matcher = $router->getMatcher();
$this->request = $container->get('request'); $this->routes = $this->config->get('routes');
$this->outputRoutes = $this->setupRoutes(); $this->outputRoutes = $this->setupRoutes();
} }

View File

@ -20,14 +20,14 @@ use Aviat\AnimeClient\FormGenerator;
use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerAware;
/** /**
* MenuGenerator helper wrapper * FormGenerator helper wrapper
*/ */
final class Form { final class Form {
use ContainerAware; use ContainerAware;
/** /**
* Create the html for the selected menu * Create the html for the specified form
* *
* @param string $name * @param string $name
* @param array $form * @param array $form
@ -38,4 +38,3 @@ final class Form {
return (new FormGenerator($this->container))->generate($name, $form); return (new FormGenerator($this->container))->generate($name, $form);
} }
} }
// End of Menu.php

View File

@ -29,6 +29,11 @@ class API {
*/ */
protected function sortByName(array &$array, string $sortKey): void protected function sortByName(array &$array, string $sortKey): void
{ {
if (empty($array))
{
return;
}
$sort = []; $sort = [];
foreach ($array as $key => $item) foreach ($array as $key => $item)
@ -37,5 +42,18 @@ class API {
} }
array_multisort($sort, SORT_ASC, $array); array_multisort($sort, SORT_ASC, $array);
// Re-key array items by their ids
if (array_key_exists('id', $array[0]))
{
$keyed = [];
foreach($array as $item)
{
$keyed[$item['id']] = $item;
}
$array = $keyed;
}
} }
} }

View File

@ -18,6 +18,7 @@ namespace Aviat\AnimeClient\Model;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use PDO; use PDO;
use PDOException;
/** /**
* Model for getting anime collection data * Model for getting anime collection data
@ -88,21 +89,6 @@ final class AnimeCollection extends Collection {
return $output; return $output;
} }
/**
* Get item from collection for editing
*
* @param string $id
* @return array
*/
public function getCollectionEntry($id): array
{
$query = $this->db->from('anime_set')
->where('hummingbird_id', $id)
->get();
return $query->fetch(PDO::FETCH_ASSOC);
}
/** /**
* Get full collection from the database * Get full collection from the database
* *
@ -126,7 +112,7 @@ final class AnimeCollection extends Collection {
// Add genres associated with each item // Add genres associated with each item
$rows = $query->fetchAll(PDO::FETCH_ASSOC); $rows = $query->fetchAll(PDO::FETCH_ASSOC);
$genres = $this->getGenresForList(); $genres = $this->getGenreList();
foreach($rows as &$row) foreach($rows as &$row)
{ {
@ -150,7 +136,16 @@ final class AnimeCollection extends Collection {
*/ */
public function add($data): void public function add($data): void
{ {
$anime = (object)$this->animeModel->getAnimeById($data['id']); $id = $data['id'];
// Check that the anime doesn't already exist
$existing = $this->get($id);
if ($existing === FALSE)
{
return;
}
$anime = (object)$this->animeModel->getAnimeById($id);
$this->db->set([ $this->db->set([
'hummingbird_id' => $data['id'], 'hummingbird_id' => $data['id'],
'slug' => $anime->slug, 'slug' => $anime->slug,
@ -215,9 +210,9 @@ final class AnimeCollection extends Collection {
* Get the details of a collection item * Get the details of a collection item
* *
* @param int $kitsuId * @param int $kitsuId
* @return array * @return array | false
*/ */
public function get($kitsuId): array public function get($kitsuId)
{ {
$query = $this->db->from('anime_set') $query = $this->db->from('anime_set')
->where('hummingbird_id', $kitsuId) ->where('hummingbird_id', $kitsuId)
@ -227,24 +222,55 @@ final class AnimeCollection extends Collection {
} }
/** /**
* Get the list of genres from the database * Get genres for anime collection items
* *
* @param array $filter
* @return array * @return array
*/ */
private function getGenresForList(): array public function getGenreList(array $filter = []): array
{ {
$query = $this->db->select('hummingbird_id, genre')
->from('genres g')
->join('genre_anime_set_link gasl', 'gasl.genre_id=g.id')
->get();
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
$output = []; $output = [];
foreach($rows as $row) // Catch the missing table PDOException
// so that the collection does not show an
// error by default
try
{ {
$output[$row['hummingbird_id']][] = $row['genre']; $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->whereIn('hummingbird_id', $filter);
}
$query = $this->db->orderBy('hummingbird_id')
->orderBy('genre')
->get();
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))
{
$output[$id][] = $genre;
} else
{
$output[$id] = [$genre];
}
}
} }
catch (PDOException $e) {}
return $output; return $output;
} }
@ -304,9 +330,16 @@ final class AnimeCollection extends Collection {
* @return array * @return array
*/ */
private function getGenreData(): array private function getGenreData(): array
{
return [
'genres' => $this->getExistingGenres(),
'links' => $this->getExistingGenreLinkEntries(),
];
}
private function getExistingGenres(): array
{ {
$genres = []; $genres = [];
$links = [];
// Get existing genres // Get existing genres
$query = $this->db->select('id, genre') $query = $this->db->select('id, genre')
@ -317,7 +350,13 @@ final class AnimeCollection extends Collection {
$genres[$genre['id']] = $genre['genre']; $genres[$genre['id']] = $genre['genre'];
} }
// Get existing link table entries return $genres;
}
private function getExistingGenreLinkEntries(): array
{
$links = [];
$query = $this->db->select('hummingbird_id, genre_id') $query = $this->db->select('hummingbird_id, genre_id')
->from('genre_anime_set_link') ->from('genre_anime_set_link')
->get(); ->get();
@ -326,17 +365,13 @@ final class AnimeCollection extends Collection {
if (array_key_exists($link['hummingbird_id'], $links)) if (array_key_exists($link['hummingbird_id'], $links))
{ {
$links[$link['hummingbird_id']][] = $link['genre_id']; $links[$link['hummingbird_id']][] = $link['genre_id'];
} } else
else
{ {
$links[$link['hummingbird_id']] = [$link['genre_id']]; $links[$link['hummingbird_id']] = [$link['genre_id']];
} }
} }
return [ return $links;
'genres' => $genres,
'links' => $links
];
} }
} }
// End of AnimeCollectionModel.php // End of AnimeCollectionModel.php

View File

@ -17,7 +17,6 @@
namespace Aviat\AnimeClient\Model; namespace Aviat\AnimeClient\Model;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use PDO;
use PDOException; use PDOException;
/** /**
@ -25,6 +24,12 @@ use PDOException;
*/ */
class Collection extends DB { class Collection extends DB {
/**
* The query builder object
* @var \Query\Query_Builder_Interface
*/
protected $db;
/** /**
* Whether the database is valid for querying * Whether the database is valid for querying
* @var boolean * @var boolean
@ -43,6 +48,7 @@ class Collection extends DB {
try try
{ {
$this->db = \Query($this->dbConfig); $this->db = \Query($this->dbConfig);
$this->validDatabase = TRUE;
} }
catch (PDOException $e) {} catch (PDOException $e) {}
@ -62,59 +68,10 @@ class Collection extends DB {
$this->validDatabase = FALSE; $this->validDatabase = FALSE;
} }
} }
else else if ($this->db === NULL)
{ {
$this->validDatabase = TRUE; $this->validDatabase = FALSE;
} }
} }
/**
* Get genres for anime collection items
*
* @param array $filter
* @return array
*/
public function getGenreList(array $filter = []): array
{
$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->whereIn('hummingbird_id', $filter);
}
$query = $this->db->orderBy('hummingbird_id')
->orderBy('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))
{
$output[$id][] = $genre;
}
else
{
$output[$id] = [$genre];
}
}
return $output;
}
} }
// End of Collection.php // End of Collection.php

View File

@ -24,12 +24,6 @@ use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
class DB { class DB {
use ContainerAware; use ContainerAware;
/**
* The query builder object
* @var \Query\Query_Builder_Interface
*/
protected $db;
/** /**
* The database connection information array * The database connection information array
* @var array $dbConfig * @var array $dbConfig

View File

@ -88,21 +88,6 @@ final class MangaCollection extends Collection {
return $output; return $output;
} }
/**
* Get item from collection for editing
*
* @param int $id
* @return array
*/
public function getCollectionEntry($id): array
{
$query = $this->db->from('anime_set')
->where('hummingbird_id', $id)
->get();
return $query->fetch(PDO::FETCH_ASSOC);
}
/** /**
* Get full collection from the database * Get full collection from the database
* *

View File

@ -39,10 +39,10 @@ class RoutingBase {
protected $config; protected $config;
/** /**
* Routing array * Class wrapper for input superglobals
* @var array * @var \Psr\Http\Message\ServerRequestInterface
*/ */
protected $routes; protected $request;
/** /**
* Constructor * Constructor
@ -56,7 +56,7 @@ class RoutingBase {
{ {
$this->container = $container; $this->container = $container;
$this->config = $container->get('config'); $this->config = $container->get('config');
$this->routes = $this->config->get('routes'); $this->request = $container->get('request');
} }
/** /**
@ -67,8 +67,7 @@ class RoutingBase {
*/ */
public function path(): string public function path(): string
{ {
$request = $this->container->get('request'); $path = $this->request->getUri()->getPath();
$path = $request->getUri()->getPath();
$cleanedPath = $this->string($path) $cleanedPath = $this->string($path)
->replace('%20', '') ->replace('%20', '')
->trim() ->trim()
@ -117,4 +116,3 @@ class RoutingBase {
return end($segments); return end($segments);
} }
} }
// End of RoutingBase.php

View File

@ -1,98 +0,0 @@
<?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\Tests\API;
use Aviat\AnimeClient\API\XML;
use PHPUnit\Framework\TestCase;
class XMLTest extends TestCase {
protected $malExport;
protected $xml;
protected $expectedXml;
protected $object;
protected $array;
public function setUp()
{
$this->malExport = file_get_contents(__DIR__ . '/../test_data/XML/MALExport.xml');
$this->xml = file_get_contents(__DIR__ . '/../test_data/XML/xmlTestFile.xml');
$this->expectedXml = file_get_contents(__DIR__ . '/../test_data/XML/minifiedXmlTestFile.xml');
$this->array = [
'entry' => [
'foo' => [
'bar' => [
'baz' => 42
]
],
'episode' => '11',
'status' => 'watching',
'score' => '7',
'storage_type' => '1',
'storage_value' => '2.5',
'times_rewatched' => '1',
'rewatch_value' => '3',
'date_start' => '01152015',
'date_finish' => '10232016',
'priority' => '2',
'enable_discussion' => '0',
'enable_rewatching' => '1',
'comments' => 'Should you say something?',
'tags' => 'test tag, 2nd tag'
]
];
$this->object = new XML();
}
public function testToArray()
{
$this->assertEquals($this->array, XML::toArray($this->xml));
}
public function testMALExport()
{
$array = XML::toArray($this->malExport);
$this->assertEquals($array['myanimelist']['myinfo']['user_total_anime'], count($array['myanimelist']['anime']));
// $this->assertEquals($array, XML::toArray($this->malExport));
}
public function testParse()
{
$this->object->setXML($this->xml);
$this->assertEquals($this->array, $this->object->parse());
}
public function testToXML()
{
$this->assertEquals($this->expectedXml, XML::toXML($this->array));
}
public function testCreateXML()
{
$this->object->setData($this->array);
$this->assertEquals($this->expectedXml, $this->object->createXML());
}
public function testToString()
{
$this->object->setData($this->array);
$this->assertEquals($this->expectedXml, $this->object->__toString());
$this->assertEquals($this->expectedXml, (string) $this->object);
}
}

View File

@ -124,7 +124,7 @@ class AnimeClientTestCase extends TestCase {
* @param array $supers * @param array $supers
* @return void * @return void
*/ */
public function setSuperGlobals($supers = []) public function setSuperGlobals($supers = []): void
{ {
$default = [ $default = [
'_SERVER' => $_SERVER, '_SERVER' => $_SERVER,
@ -139,7 +139,7 @@ class AnimeClientTestCase extends TestCase {
array_merge($default, $supers) array_merge($default, $supers)
); );
$this->container->setInstance('request', $request); $this->container->setInstance('request', $request);
$this->container->set('repsone', function() { $this->container->set('response', function() {
return new HttpResponse(); return new HttpResponse();
}); });
} }
@ -151,7 +151,7 @@ class AnimeClientTestCase extends TestCase {
* *
* @return string - contents of the data file * @return string - contents of the data file
*/ */
public function getMockFile() public function getMockFile(): string
{ {
$args = func_get_args(); $args = func_get_args();
array_unshift($args, TEST_DATA_DIR); array_unshift($args, TEST_DATA_DIR);

View File

@ -20,14 +20,6 @@ use Aviat\AnimeClient\RoutingBase;
class RoutingBaseTest extends AnimeClientTestCase { class RoutingBaseTest extends AnimeClientTestCase {
protected $routingBase;
public function setUp()
{
parent::setUp();
$this->routingBase = new RoutingBase($this->container);
}
public function dataSegments() public function dataSegments()
{ {
return [ return [
@ -49,7 +41,7 @@ class RoutingBaseTest extends AnimeClientTestCase {
/** /**
* @dataProvider dataSegments * @dataProvider dataSegments
*/ */
public function testSegments($requestUri, $path, $segments, $lastSegment) public function testSegments(string $requestUri, string $path, array $segments, $lastSegment): void
{ {
$this->setSuperGlobals([ $this->setSuperGlobals([
'_SERVER' => [ '_SERVER' => [
@ -57,13 +49,15 @@ class RoutingBaseTest extends AnimeClientTestCase {
] ]
]); ]);
$this->assertEquals($path, $this->routingBase->path(), "Path is invalid"); $routingBase = new RoutingBase($this->container);
$this->assertEquals($segments, $this->routingBase->segments(), "Segments array is invalid");
$this->assertEquals($lastSegment, $this->routingBase->lastSegment(), "Last segment is invalid"); $this->assertEquals($path, $routingBase->path(), "Path is invalid");
$this->assertEquals($segments, $routingBase->segments(), "Segments array is invalid");
$this->assertEquals($lastSegment, $routingBase->lastSegment(), "Last segment is invalid");
foreach($segments as $i => $value) foreach($segments as $i => $value)
{ {
$this->assertEquals($value, $this->routingBase->getSegment($i), "Segment {$i} is invalid"); $this->assertEquals($value, $routingBase->getSegment($i), "Segment {$i} is invalid");
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<entry><foo><bar><baz>42</baz></bar></foo><episode>11</episode><status>watching</status><score>7</score><storage_type>1</storage_type><storage_value>2.5</storage_value><times_rewatched>1</times_rewatched><rewatch_value>3</rewatch_value><date_start>01152015</date_start><date_finish>10232016</date_finish><priority>2</priority><enable_discussion>0</enable_discussion><enable_rewatching>1</enable_rewatching><comments>Should you say something?</comments><tags>test tag, 2nd tag</tags></entry>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<entry>
<foo>
<bar>
<baz>42</baz>
</bar>
</foo>
<episode>11</episode>
<status>watching</status>
<score>7</score>
<storage_type>1</storage_type>
<storage_value>2.5</storage_value>
<times_rewatched>1</times_rewatched>
<rewatch_value>3</rewatch_value>
<date_start>01152015</date_start>
<date_finish>10232016</date_finish>
<priority>2</priority>
<enable_discussion>0</enable_discussion>
<enable_rewatching>1</enable_rewatching>
<comments>Should you say something?</comments>
<tags>test tag, 2nd tag</tags>
</entry>