Merge branch 'develop' of timw4mail/HummingBirdAnimeClient into master
This commit is contained in:
commit
82c8fa8661
@ -35,7 +35,7 @@ use Zend\Diactoros\{Response, ServerRequestFactory};
|
||||
// -----------------------------------------------------------------------------
|
||||
// Setup DI container
|
||||
// -----------------------------------------------------------------------------
|
||||
return function ($configArray = []) {
|
||||
return static function ($configArray = []) {
|
||||
$container = new Container();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -57,36 +57,38 @@ return function ($configArray = []) {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Create Config Object
|
||||
$container->set('config', function() use ($configArray) {
|
||||
$container->set('config', static function() use ($configArray) {
|
||||
return new Config($configArray);
|
||||
});
|
||||
|
||||
// Create Cache Object
|
||||
$container->set('cache', function($container) {
|
||||
$container->set('cache', static function($container) {
|
||||
$logger = $container->getLogger();
|
||||
$config = $container->get('config')->get('cache');
|
||||
return new Pool($config, $logger);
|
||||
});
|
||||
|
||||
// Create List Cache
|
||||
|
||||
// Create Aura Router Object
|
||||
$container->set('aura-router', function() {
|
||||
$container->set('aura-router', static function() {
|
||||
return new RouterContainer;
|
||||
});
|
||||
|
||||
// Create Html helper Object
|
||||
$container->set('html-helper', function($container) {
|
||||
$container->set('html-helper', static function($container) {
|
||||
$htmlHelper = (new HelperLocatorFactory)->newInstance();
|
||||
$htmlHelper->set('menu', function() use ($container) {
|
||||
$htmlHelper->set('menu', static function() use ($container) {
|
||||
$menuHelper = new Helper\Menu();
|
||||
$menuHelper->setContainer($container);
|
||||
return $menuHelper;
|
||||
});
|
||||
$htmlHelper->set('field', function() use ($container) {
|
||||
$htmlHelper->set('field', static function() use ($container) {
|
||||
$formHelper = new Helper\Form();
|
||||
$formHelper->setContainer($container);
|
||||
return $formHelper;
|
||||
});
|
||||
$htmlHelper->set('picture', function() use ($container) {
|
||||
$htmlHelper->set('picture', static function() use ($container) {
|
||||
$pictureHelper = new Helper\Picture();
|
||||
$pictureHelper->setContainer($container);
|
||||
return $pictureHelper;
|
||||
@ -96,7 +98,7 @@ return function ($configArray = []) {
|
||||
});
|
||||
|
||||
// Create Request/Response Objects
|
||||
$container->set('request', function() {
|
||||
$container->set('request', static function() {
|
||||
return ServerRequestFactory::fromGlobals(
|
||||
$_SERVER,
|
||||
$_GET,
|
||||
@ -105,22 +107,22 @@ return function ($configArray = []) {
|
||||
$_FILES
|
||||
);
|
||||
});
|
||||
$container->set('response', function() {
|
||||
$container->set('response', static function() {
|
||||
return new Response;
|
||||
});
|
||||
|
||||
// Create session Object
|
||||
$container->set('session', function() {
|
||||
$container->set('session', static function() {
|
||||
return (new SessionFactory())->newInstance($_COOKIE);
|
||||
});
|
||||
|
||||
// Miscellaneous helper methods
|
||||
$container->set('util', function($container) {
|
||||
$container->set('util', static function($container) {
|
||||
return new Util($container);
|
||||
});
|
||||
|
||||
// Models
|
||||
$container->set('kitsu-model', function($container) {
|
||||
$container->set('kitsu-model', static function($container) {
|
||||
$requestBuilder = new KitsuRequestBuilder();
|
||||
$requestBuilder->setLogger($container->getLogger('kitsu-request'));
|
||||
|
||||
@ -136,7 +138,7 @@ return function ($configArray = []) {
|
||||
$model->setCache($cache);
|
||||
return $model;
|
||||
});
|
||||
$container->set('anilist-model', function($container) {
|
||||
$container->set('anilist-model', static function($container) {
|
||||
$requestBuilder = new Anilist\AnilistRequestBuilder();
|
||||
$requestBuilder->setLogger($container->getLogger('anilist-request'));
|
||||
|
||||
@ -151,39 +153,39 @@ return function ($configArray = []) {
|
||||
return $model;
|
||||
});
|
||||
|
||||
$container->set('api-model', function($container) {
|
||||
$container->set('api-model', static function($container) {
|
||||
return new Model\API($container);
|
||||
});
|
||||
$container->set('anime-model', function($container) {
|
||||
$container->set('anime-model', static function($container) {
|
||||
return new Model\Anime($container);
|
||||
});
|
||||
$container->set('manga-model', function($container) {
|
||||
$container->set('manga-model', static function($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);
|
||||
});
|
||||
$container->set('manga-collection-model', function($container) {
|
||||
$container->set('manga-collection-model', static function($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->setContainer($container);
|
||||
return $model;
|
||||
});
|
||||
|
||||
// Miscellaneous Classes
|
||||
$container->set('auth', function($container) {
|
||||
$container->set('auth', static function($container) {
|
||||
return new Kitsu\Auth($container);
|
||||
});
|
||||
$container->set('url-generator', function($container) {
|
||||
$container->set('url-generator', static function($container) {
|
||||
return new UrlGenerator($container);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Dispatcher
|
||||
// -------------------------------------------------------------------------
|
||||
$container->set('dispatcher', function($container) {
|
||||
$container->set('dispatcher', static function($container) {
|
||||
return new Dispatcher($container);
|
||||
});
|
||||
|
||||
|
@ -8,4 +8,4 @@ user = ""
|
||||
pass = ""
|
||||
port = ""
|
||||
database = ""
|
||||
file = "anime_collection.sqlite"
|
||||
file = "anime_collection.sqlite3"
|
||||
|
@ -14,13 +14,13 @@
|
||||
<tr>
|
||||
<td class="align-right"><label for="title">Title</label></td>
|
||||
<td class="align-left">
|
||||
<input type="text" name="title" value="<?= $item['title'] ?>" />
|
||||
<input type="text" id="title" name="title" value="<?= $item['title'] ?>" />
|
||||
</td>
|
||||
</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">
|
||||
<input type="text" name="alternate_title" value="<?= $item['alternate_title'] ?>"/>
|
||||
<input type="text" id="alternate_title" name="alternate_title" value="<?= $item['alternate_title'] ?>"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -28,7 +28,7 @@
|
||||
<td class="align-left">
|
||||
<select name="media_id" id="media_id">
|
||||
<?php foreach($media_items as $id => $name): ?>
|
||||
<option <?= $item['media_id'] === $id ? 'selected="selected"' : '' ?> value="<?= $id ?>"><?= $name ?></option>
|
||||
<option <?= $item['media_id'] === (string)$id ? 'selected="selected"' : '' ?> value="<?= $id ?>"><?= $name ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</td>
|
||||
|
@ -10,6 +10,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script nomodule src="https://polyfill.io/v3/polyfill.min.js?features=es5%2CObject.assign"></script>
|
||||
<?php if ($auth->isAuthenticated()): ?>
|
||||
<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>
|
||||
@ -18,4 +19,4 @@
|
||||
<script type="module" src="<?= $urlGenerator->assetUrl('js/src/index.js') ?>"></script>
|
||||
<?php endif ?>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -32,7 +32,7 @@ use Aviat\AnimeClient\API\Kitsu;
|
||||
<?php foreach ($casting as $sid => $series): ?>
|
||||
<article class="media">
|
||||
<?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']]);
|
||||
$titles = Kitsu::filterTitles($series);
|
||||
?>
|
||||
|
@ -20,7 +20,7 @@
|
||||
<log type="coverage-clover" target="logs/clover.xml"/>
|
||||
<log type="coverage-crap4j" target="logs/crap4j.xml"/>
|
||||
<log type="coverage-xml" target="logs/coverage" />
|
||||
<log type="junit" target="logs/junit.xml" logIncompleteSkipped="false"/>
|
||||
<log type="junit" target="logs/junit.xml" />
|
||||
</logging>
|
||||
<php>
|
||||
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0" />
|
||||
|
@ -1,9 +1,9 @@
|
||||
paths:
|
||||
migrations: %%PHINX_CONFIG_DIR%%/migrations
|
||||
migrations: '%%PHINX_CONFIG_DIR%%/migrations'
|
||||
|
||||
environments:
|
||||
default_migration_table: phinxlog
|
||||
default_database: development
|
||||
development:
|
||||
adapter: sqlite
|
||||
name: ./anime_collection.sqlite
|
||||
name: ./anime_collection # Phinx will add a .sqlite3 suffix
|
||||
|
2
public/css/app.min.css
vendored
2
public/css/app.min.css
vendored
File diff suppressed because one or more lines are too long
@ -52,8 +52,41 @@ small {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
color: #111;
|
||||
input, input[type], select, textarea {
|
||||
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 {
|
||||
@ -131,5 +164,8 @@ input, select, textarea {
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
|
||||
.streaming-logo {
|
||||
-webkit-filter: drop-shadow(0 0 2px #fff);
|
||||
filter: drop-shadow(0 0 2px #fff);
|
||||
}
|
||||
|
||||
|
2
public/css/dark.min.css
vendored
2
public/css/dark.min.css
vendored
@ -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)}
|
@ -22,8 +22,33 @@ body {
|
||||
}
|
||||
|
||||
button {
|
||||
background: rgba(255, 255, 255, 0.65);
|
||||
background: #fff;
|
||||
background: linear-gradient(#ddd, #eee, #fff, #eee, #ddd);
|
||||
border-radius: 0.5em;
|
||||
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 {
|
||||
@ -182,16 +207,19 @@ td .media-wrap-flex {
|
||||
.danger {
|
||||
background-color: #ff4136;
|
||||
border-color: #924949;
|
||||
color: #fff;
|
||||
color: #924949;
|
||||
/* color: #fff; */
|
||||
}
|
||||
|
||||
.danger:hover, .danger:active {
|
||||
background-color: #924949;
|
||||
border-color: #ff4136;
|
||||
color: #fff;
|
||||
color: #ff4136;
|
||||
/* color: #fff; */
|
||||
}
|
||||
|
||||
.user-btn {
|
||||
background: transparent;
|
||||
border-color: var(--edit-link-color);
|
||||
color: var(--edit-link-color);
|
||||
text-shadow: var(--link-shadow);
|
||||
@ -200,8 +228,14 @@ td .media-wrap-flex {
|
||||
}
|
||||
|
||||
.user-btn:hover, .user-btn:active {
|
||||
background: transparent;
|
||||
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 {
|
||||
@ -533,21 +567,12 @@ picture.cover {
|
||||
border-color: hsla(0, 0%, 100%, .65);
|
||||
position: absolute;
|
||||
top: 138px;
|
||||
top: calc(50% - 21.5px);
|
||||
top: calc(50% - 21.2px);
|
||||
left: 44px;
|
||||
left: calc(50% - 66.5px);
|
||||
left: calc(50% - 57.8px);
|
||||
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
|
||||
------------------------------------------------------------------------------*/
|
||||
@ -565,9 +590,9 @@ picture.cover {
|
||||
position: absolute;
|
||||
top: 86px;
|
||||
/* top: calc(50% - 58.5px); */
|
||||
top: calc(50% - 22.4px);
|
||||
top: calc(50% - 21.2px);
|
||||
left: 43.5px;
|
||||
left: calc(50% - 66.5px);
|
||||
left: calc(50% - 57.8px);
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
@ -575,15 +600,6 @@ picture.cover {
|
||||
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
|
||||
------------------------------------------------------------------------------*/
|
||||
@ -617,7 +633,6 @@ picture.cover {
|
||||
position: absolute;
|
||||
top: 147px;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 5;
|
||||
}
|
||||
@ -660,10 +675,6 @@ picture.cover {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.fixed .text {
|
||||
max-width: 40em;
|
||||
}
|
||||
|
||||
.details .cover {
|
||||
display: block;
|
||||
}
|
||||
|
@ -96,8 +96,8 @@ textarea {
|
||||
}
|
||||
|
||||
*,::before,::after {
|
||||
border-style:solid;
|
||||
border-width:0;
|
||||
/* border-style:solid;
|
||||
border-width:0; */
|
||||
box-sizing:inherit;
|
||||
}
|
||||
|
||||
@ -124,7 +124,7 @@ audio,canvas,iframe,img,svg,video {
|
||||
vertical-align:middle;
|
||||
}
|
||||
|
||||
button,input,select,textarea {
|
||||
input,/*select*/,textarea {
|
||||
border:.1rem solid #ccc;
|
||||
color:inherit;
|
||||
font-family:inherit;
|
||||
@ -296,7 +296,7 @@ img {
|
||||
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-radius:0;
|
||||
display:inline-block;
|
||||
@ -320,7 +320,7 @@ input[type=color] {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -336,7 +336,7 @@ input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus {
|
||||
outline:.1rem solid thin #444;
|
||||
}
|
||||
|
||||
input[type=text][disabled],input[type=password][disabled],input[type=email][disabled],input[type=url][disabled],input[type=date][disabled],input[type=month][disabled],input[type=time][disabled],input[type=datetime][disabled],input[type=datetime-local][disabled],input[type=week][disabled],input[type=number][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=color][disabled],select[disabled],textarea[disabled] {
|
||||
input[type=text][disabled],input[type=password][disabled],input[type=email][disabled],input[type=url][disabled],input[type=date][disabled],input[type=month][disabled],input[type=time][disabled],input[type=datetime][disabled],input[type=datetime-local][disabled],input[type=week][disabled],input[type=number][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=color][disabled],/*select[disabled]*/,textarea[disabled] {
|
||||
background-color:#efefef;
|
||||
color:#777;
|
||||
cursor:not-allowed;
|
||||
@ -348,13 +348,13 @@ input:not([type])[disabled] {
|
||||
cursor:not-allowed;
|
||||
}
|
||||
|
||||
input[readonly],select[readonly],textarea[readonly] {
|
||||
input[readonly],/*select[readonly]*/,textarea[readonly] {
|
||||
background-color:#efefef;
|
||||
border-color:#ccc;
|
||||
color:#777;
|
||||
}
|
||||
|
||||
input:focus:invalid,textarea:focus:invalid,select:focus:invalid {
|
||||
input:focus:invalid,textarea:focus:invalid/*,select:focus:invalid*/ {
|
||||
border-color:#e9322d;
|
||||
color:#b94a48;
|
||||
}
|
||||
@ -363,10 +363,10 @@ input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus,input
|
||||
outline-color:#ff4136;
|
||||
}
|
||||
|
||||
select {
|
||||
/* select {
|
||||
background-color:#fff;
|
||||
border:.1rem solid #ccc;
|
||||
}
|
||||
}*/
|
||||
|
||||
select[multiple] {
|
||||
height:auto;
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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;"}
|
@ -9,13 +9,13 @@
|
||||
"watch": "concurrently \"npm:watch:css\" \"npm:watch:js\" --kill-others"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ampproject/rollup-plugin-closure-compiler": "^0.8.3",
|
||||
"@ampproject/rollup-plugin-closure-compiler": "^0.9.0",
|
||||
"concurrently": "^4.0.1",
|
||||
"cssnano": "^4.0.5",
|
||||
"postcss-cachify": "^1.3.1",
|
||||
"postcss-cssnext": "^3.0.0",
|
||||
"postcss-import": "^12.0.0",
|
||||
"rollup": "^0.66.6",
|
||||
"rollup": "^1.11.3",
|
||||
"rollup-plugin-closure-compiler-js": "^1.0.6",
|
||||
"watch": "^1.0.2"
|
||||
}
|
||||
|
863
public/yarn.lock
863
public/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -422,8 +422,14 @@ final class Model {
|
||||
$item['included'] = $included;
|
||||
}
|
||||
$transformed = $this->animeListTransformer->transformCollection($data['data']);
|
||||
$keyed = [];
|
||||
|
||||
$cacheItem->set($transformed);
|
||||
foreach($transformed as $item)
|
||||
{
|
||||
$keyed[$item['id']] = $item;
|
||||
}
|
||||
|
||||
$cacheItem->set($keyed);
|
||||
$cacheItem->save();
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
: [];
|
||||
|
||||
|
280
src/API/XML.php
280
src/API/XML.php
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -33,6 +33,12 @@ class Controller {
|
||||
|
||||
use ContainerAware;
|
||||
|
||||
/**
|
||||
* The authentication object
|
||||
* @var \Aviat\AnimeClient\API\Kitsu\Auth $auth ;
|
||||
*/
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* Cache manager
|
||||
* @var \Psr\Cache\CacheItemPoolInterface
|
||||
@ -79,11 +85,7 @@ class Controller {
|
||||
* Common data to be sent to views
|
||||
* @var array
|
||||
*/
|
||||
protected $baseData = [
|
||||
'url_type' => 'anime',
|
||||
'other_type' => 'manga',
|
||||
'menu_name' => ''
|
||||
];
|
||||
protected $baseData = [];
|
||||
|
||||
/**
|
||||
* Controller constructor.
|
||||
@ -95,46 +97,30 @@ class Controller {
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->setContainer($container);
|
||||
|
||||
$auraUrlGenerator = $container->get('aura-router')->getGenerator();
|
||||
$session = $container->get('session');
|
||||
$urlGenerator = $container->get('url-generator');
|
||||
|
||||
$this->auth = $container->get('auth');
|
||||
$this->cache = $container->get('cache');
|
||||
$this->config = $container->get('config');
|
||||
$this->request = $container->get('request');
|
||||
$this->response = $container->get('response');
|
||||
|
||||
$this->baseData = array_merge($this->baseData, [
|
||||
'url' => $auraUrlGenerator,
|
||||
'urlGenerator' => $urlGenerator,
|
||||
'auth' => $container->get('auth'),
|
||||
'config' => $this->config
|
||||
]);
|
||||
|
||||
$this->session = $session->getSegment(SESSION_SEGMENT);
|
||||
$this->url = $auraUrlGenerator;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
|
||||
$session = $container->get('session');
|
||||
$this->session = $session->getSegment(SESSION_SEGMENT);
|
||||
|
||||
// Set a 'previous' flash value for better redirects
|
||||
$serverParams = $this->request->getServerParams();
|
||||
if (array_key_exists('HTTP_REFERER', $serverParams) && false === stripos($serverParams['HTTP_REFERER'], 'login'))
|
||||
{
|
||||
$this->session->setFlash('previous', $serverParams['HTTP_REFERER']);
|
||||
}
|
||||
|
||||
// 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);
|
||||
$this->baseData = [
|
||||
'auth' => $container->get('auth'),
|
||||
'config' => $this->config,
|
||||
'menu_name' => '',
|
||||
'message' => $this->session->getFlash('message'), // Get message box data if it exists
|
||||
'other_type' => 'manga',
|
||||
'url' => $auraUrlGenerator,
|
||||
'url_type' => 'anime',
|
||||
'urlGenerator' => $urlGenerator,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,7 +145,7 @@ class Controller {
|
||||
|
||||
// Don't attempt to set the redirect url if
|
||||
// the page is one of the form type pages,
|
||||
// and the previous page is also a form type page_segments
|
||||
// and the previous page is also a form type
|
||||
if ($doubleFormPage || $isLoginPage)
|
||||
{
|
||||
return;
|
||||
@ -178,6 +164,8 @@ class Controller {
|
||||
/**
|
||||
* Redirect to the url previously set in the session
|
||||
*
|
||||
* If one is not set, redirect to default url
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws \Aviat\Ion\Di\Exception\ContainerException
|
||||
* @throws \Aviat\Ion\Di\Exception\NotFoundException
|
||||
@ -185,15 +173,25 @@ class Controller {
|
||||
*/
|
||||
public function sessionRedirect(): void
|
||||
{
|
||||
$target = $this->session->get('redirect_url');
|
||||
if (empty($target))
|
||||
$target = $this->session->get('redirect_url') ?? '/';
|
||||
|
||||
$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();
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->redirect($target, 303);
|
||||
$this->session->set('redirect_url', NULL);
|
||||
$this->errorPage(
|
||||
403,
|
||||
'Forbidden',
|
||||
'You must <a href="/login">log in</a> to perform this action.'
|
||||
);
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +216,7 @@ class Controller {
|
||||
}
|
||||
|
||||
$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");
|
||||
@ -275,7 +273,7 @@ class Controller {
|
||||
public function notFound(
|
||||
string $title = 'Sorry, page not found',
|
||||
string $message = 'Page Not Found'
|
||||
): void
|
||||
): void
|
||||
{
|
||||
$this->outputHTML('404', [
|
||||
'title' => $title,
|
||||
@ -408,7 +406,6 @@ class Controller {
|
||||
(new JsonView($this->container))
|
||||
->setStatusCode($code)
|
||||
->setOutput($data);
|
||||
// ->send();
|
||||
exit();
|
||||
}
|
||||
|
||||
@ -421,8 +418,8 @@ class Controller {
|
||||
*/
|
||||
protected function redirect(string $url, int $code): void
|
||||
{
|
||||
$http = new HttpView($this->container);
|
||||
$http->redirect($url, $code);
|
||||
(new HttpView($this->container))->redirect($url, $code);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
// End of BaseController.php
|
@ -28,6 +28,7 @@ use Aviat\Ion\Json;
|
||||
* Controller for Anime-related pages
|
||||
*/
|
||||
final class Anime extends BaseController {
|
||||
|
||||
/**
|
||||
* The anime list model
|
||||
* @var \Aviat\AnimeClient\Model\Anime $model
|
||||
@ -49,12 +50,9 @@ final class Anime extends BaseController {
|
||||
|
||||
$this->baseData = array_merge($this->baseData, [
|
||||
'menu_name' => 'anime_list',
|
||||
'url_type' => 'anime',
|
||||
'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
|
||||
{
|
||||
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)
|
||||
? $this->formatTitle(
|
||||
$this->config->get('whose_list') . "'s Anime List",
|
||||
@ -102,6 +112,8 @@ final class Anime extends BaseController {
|
||||
*/
|
||||
public function addForm(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$this->setSessionRedirect();
|
||||
$this->outputHTML('anime/add', [
|
||||
'title' => $this->formatTitle(
|
||||
@ -122,6 +134,8 @@ final class Anime extends BaseController {
|
||||
*/
|
||||
public function add(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$data = $this->request->getParsedBody();
|
||||
|
||||
if (empty($data['mal_id']))
|
||||
@ -157,6 +171,8 @@ final class Anime extends BaseController {
|
||||
*/
|
||||
public function edit(string $id, $status = 'all'): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$item = $this->model->getLibraryItem($id);
|
||||
$this->setSessionRedirect();
|
||||
|
||||
@ -194,6 +210,8 @@ final class Anime extends BaseController {
|
||||
*/
|
||||
public function formUpdate(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$data = $this->request->getParsedBody();
|
||||
|
||||
// Do some minor data manipulation for
|
||||
@ -222,6 +240,8 @@ final class Anime extends BaseController {
|
||||
*/
|
||||
public function increment(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE)
|
||||
{
|
||||
$data = Json::decode((string)$this->request->getBody());
|
||||
@ -231,6 +251,12 @@ final class Anime extends BaseController {
|
||||
$data = $this->request->getParsedBody();
|
||||
}
|
||||
|
||||
if (empty($data))
|
||||
{
|
||||
$this->errorPage(400, 'Bad Request', '');
|
||||
die();
|
||||
}
|
||||
|
||||
$response = $this->model->incrementLibraryItem(new FormItem($data));
|
||||
|
||||
$this->cache->clear();
|
||||
@ -244,6 +270,8 @@ final class Anime extends BaseController {
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$body = $this->request->getParsedBody();
|
||||
$response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']);
|
||||
|
||||
|
@ -56,9 +56,8 @@ final class AnimeCollection extends BaseController {
|
||||
$this->baseData = array_merge($this->baseData, [
|
||||
'collection_type' => 'anime',
|
||||
'menu_name' => 'collection',
|
||||
'url_type' => 'anime',
|
||||
'other_type' => 'manga',
|
||||
'config' => $this->config,
|
||||
'url_type' => 'anime',
|
||||
]);
|
||||
}
|
||||
|
||||
@ -112,6 +111,8 @@ final class AnimeCollection extends BaseController {
|
||||
*/
|
||||
public function form($id = NULL): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$this->setSessionRedirect();
|
||||
|
||||
$action = $id === NULL ? 'Add' : 'Edit';
|
||||
@ -139,9 +140,12 @@ final class AnimeCollection extends BaseController {
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$data = $this->request->getParsedBody();
|
||||
if (array_key_exists('hummingbird_id', $data))
|
||||
{
|
||||
// @TODO verify data was updated correctly
|
||||
$this->animeCollectionModel->update($data);
|
||||
$this->setFlashMessage('Successfully updated collection item.', 'success');
|
||||
}
|
||||
@ -163,11 +167,22 @@ final class AnimeCollection extends BaseController {
|
||||
*/
|
||||
public function add(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$data = $this->request->getParsedBody();
|
||||
if (array_key_exists('id', $data))
|
||||
{
|
||||
$this->animeCollectionModel->add($data);
|
||||
$this->setFlashMessage('Successfully added collection item', 'success');
|
||||
// Check for existing entry
|
||||
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
|
||||
{
|
||||
@ -184,16 +199,19 @@ final class AnimeCollection extends BaseController {
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$data = $this->request->getParsedBody();
|
||||
if ( ! array_key_exists('hummingbird_id', $data))
|
||||
{
|
||||
$this->redirect('/anime-collection/view', 303);
|
||||
}
|
||||
|
||||
// @TODO verify that item was actually deleted
|
||||
$this->animeCollectionModel->delete($data);
|
||||
$this->setFlashMessage('Successfully removed anime from collection.', 'success');
|
||||
|
||||
$this->redirect('/anime-collection/view', 303);
|
||||
}
|
||||
}
|
||||
// End of CollectionController.php
|
||||
// End of AnimeCollection.php
|
@ -131,8 +131,6 @@ final class Images extends BaseController {
|
||||
|
||||
$data = wait($response->getBody());
|
||||
|
||||
|
||||
|
||||
[$origWidth] = getimagesizefromstring($data);
|
||||
$gdImg = imagecreatefromstring($data);
|
||||
$resizedImg = imagescale($gdImg, $width ?? $origWidth);
|
||||
|
@ -29,8 +29,6 @@ use Aviat\Ion\{Json, StringWrapper};
|
||||
*/
|
||||
final class Manga extends Controller {
|
||||
|
||||
use StringWrapper;
|
||||
|
||||
/**
|
||||
* The manga model
|
||||
* @var MangaModel $model
|
||||
@ -51,9 +49,8 @@ final class Manga extends Controller {
|
||||
$this->model = $container->get('manga-model');
|
||||
$this->baseData = array_merge($this->baseData, [
|
||||
'menu_name' => 'manga_list',
|
||||
'config' => $this->config,
|
||||
'other_type' => 'anime',
|
||||
'url_type' => 'manga',
|
||||
'other_type' => 'anime'
|
||||
]);
|
||||
}
|
||||
|
||||
@ -69,6 +66,18 @@ final class Manga extends Controller {
|
||||
*/
|
||||
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];
|
||||
|
||||
$title = $this->formatTitle(
|
||||
@ -102,6 +111,8 @@ final class Manga extends Controller {
|
||||
*/
|
||||
public function addForm(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$statuses = MangaReadingStatus::KITSU_TO_TITLE;
|
||||
|
||||
$this->setSessionRedirect();
|
||||
@ -124,6 +135,8 @@ final class Manga extends Controller {
|
||||
*/
|
||||
public function add(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$data = $this->request->getParsedBody();
|
||||
if ( ! array_key_exists('id', $data))
|
||||
{
|
||||
@ -163,6 +176,8 @@ final class Manga extends Controller {
|
||||
*/
|
||||
public function edit($id, $status = 'All'): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$this->setSessionRedirect();
|
||||
$item = $this->model->getLibraryItem($id);
|
||||
$title = $this->formatTitle(
|
||||
@ -201,6 +216,8 @@ final class Manga extends Controller {
|
||||
*/
|
||||
public function formUpdate(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$data = $this->request->getParsedBody();
|
||||
|
||||
// Do some minor data manipulation for
|
||||
@ -228,6 +245,8 @@ final class Manga extends Controller {
|
||||
*/
|
||||
public function increment(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE)
|
||||
{
|
||||
$data = Json::decode((string)$this->request->getBody());
|
||||
@ -252,6 +271,8 @@ final class Manga extends Controller {
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$body = $this->request->getParsedBody();
|
||||
$response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']);
|
||||
|
||||
@ -316,4 +337,4 @@ final class Manga extends Controller {
|
||||
// @TODO: implement
|
||||
}
|
||||
}
|
||||
// End of MangaController.php
|
||||
// End of MangaController.php
|
||||
|
@ -57,9 +57,8 @@ final class MangaCollection extends BaseController {
|
||||
$this->baseData = array_merge($this->baseData, [
|
||||
'collection_type' => 'manga',
|
||||
'menu_name' => 'manga-collection',
|
||||
'url_type' => 'manga',
|
||||
'other_type' => 'anime',
|
||||
'config' => $this->config,
|
||||
'url_type' => 'manga',
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -89,6 +89,8 @@ final class Misc extends BaseController {
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$auth = $this->container->get('auth');
|
||||
$auth->logout();
|
||||
|
||||
|
@ -23,6 +23,7 @@ use Aviat\Ion\Di\ContainerInterface;
|
||||
* Controller for user settings
|
||||
*/
|
||||
final class Settings extends BaseController {
|
||||
|
||||
/**
|
||||
* @var \Aviat\AnimeClient\API\Anilist\Model
|
||||
*/
|
||||
@ -46,6 +47,9 @@ final class Settings extends BaseController {
|
||||
|
||||
$this->anilistModel = $container->get('anilist-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();
|
||||
unset($post['settings-tabs']);
|
||||
|
||||
// dump($post);
|
||||
$saved = $this->settingsModel->saveSettingsFile($post);
|
||||
|
||||
if ($saved)
|
||||
{
|
||||
$this->setFlashMessage('Saved config settings.', 'success');
|
||||
} else
|
||||
{
|
||||
$this->setFlashMessage('Failed to save config file.', 'error');
|
||||
}
|
||||
$saved
|
||||
? $this->setFlashMessage('Saved config settings.', 'success')
|
||||
: $this->setFlashMessage('Failed to save config file.', 'error');
|
||||
|
||||
$this->redirect($this->url->generate('settings'), 303);
|
||||
}
|
||||
@ -144,13 +143,9 @@ final class Settings extends BaseController {
|
||||
|
||||
$saved = $this->settingsModel->saveSettingsFile($newSettings);
|
||||
|
||||
if ($saved)
|
||||
{
|
||||
$this->setFlashMessage('Linked Anilist Account', 'success');
|
||||
} else
|
||||
{
|
||||
$this->setFlashMessage('Error Linking Anilist Account', 'error');
|
||||
}
|
||||
$saved
|
||||
? $this->setFlashMessage('Linked Anilist Account', 'success')
|
||||
: $this->setFlashMessage('Error Linking Anilist Account', 'error');
|
||||
|
||||
$this->redirect($this->url->generate('settings'), 303);
|
||||
}
|
||||
|
@ -45,10 +45,10 @@ final class Dispatcher extends RoutingBase {
|
||||
protected $matcher;
|
||||
|
||||
/**
|
||||
* Class wrapper for input superglobals
|
||||
* @var \Psr\Http\Message\ServerRequestInterface
|
||||
* Routing array
|
||||
* @var array
|
||||
*/
|
||||
protected $request;
|
||||
protected $routes;
|
||||
|
||||
/**
|
||||
* Routes added to router
|
||||
@ -67,8 +67,7 @@ final class Dispatcher extends RoutingBase {
|
||||
$router = $this->container->get('aura-router');
|
||||
$this->router = $router->getMap();
|
||||
$this->matcher = $router->getMatcher();
|
||||
$this->request = $container->get('request');
|
||||
|
||||
$this->routes = $this->config->get('routes');
|
||||
$this->outputRoutes = $this->setupRoutes();
|
||||
}
|
||||
|
||||
|
@ -20,14 +20,14 @@ use Aviat\AnimeClient\FormGenerator;
|
||||
use Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
/**
|
||||
* MenuGenerator helper wrapper
|
||||
* FormGenerator helper wrapper
|
||||
*/
|
||||
final class Form {
|
||||
|
||||
use ContainerAware;
|
||||
|
||||
/**
|
||||
* Create the html for the selected menu
|
||||
* Create the html for the specified form
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $form
|
||||
@ -38,4 +38,3 @@ final class Form {
|
||||
return (new FormGenerator($this->container))->generate($name, $form);
|
||||
}
|
||||
}
|
||||
// End of Menu.php
|
@ -29,6 +29,11 @@ class API {
|
||||
*/
|
||||
protected function sortByName(array &$array, string $sortKey): void
|
||||
{
|
||||
if (empty($array))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$sort = [];
|
||||
|
||||
foreach ($array as $key => $item)
|
||||
@ -37,5 +42,18 @@ class API {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* Model for getting anime collection data
|
||||
@ -88,21 +89,6 @@ final class AnimeCollection extends Collection {
|
||||
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
|
||||
*
|
||||
@ -126,7 +112,7 @@ final class AnimeCollection extends Collection {
|
||||
|
||||
// Add genres associated with each item
|
||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
$genres = $this->getGenresForList();
|
||||
$genres = $this->getGenreList();
|
||||
|
||||
foreach($rows as &$row)
|
||||
{
|
||||
@ -150,7 +136,16 @@ final class AnimeCollection extends Collection {
|
||||
*/
|
||||
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([
|
||||
'hummingbird_id' => $data['id'],
|
||||
'slug' => $anime->slug,
|
||||
@ -215,9 +210,9 @@ final class AnimeCollection extends Collection {
|
||||
* Get the details of a collection item
|
||||
*
|
||||
* @param int $kitsuId
|
||||
* @return array
|
||||
* @return array | false
|
||||
*/
|
||||
public function get($kitsuId): array
|
||||
public function get($kitsuId)
|
||||
{
|
||||
$query = $this->db->from('anime_set')
|
||||
->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
|
||||
*/
|
||||
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 = [];
|
||||
|
||||
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;
|
||||
}
|
||||
@ -304,9 +330,16 @@ final class AnimeCollection extends Collection {
|
||||
* @return array
|
||||
*/
|
||||
private function getGenreData(): array
|
||||
{
|
||||
return [
|
||||
'genres' => $this->getExistingGenres(),
|
||||
'links' => $this->getExistingGenreLinkEntries(),
|
||||
];
|
||||
}
|
||||
|
||||
private function getExistingGenres(): array
|
||||
{
|
||||
$genres = [];
|
||||
$links = [];
|
||||
|
||||
// Get existing genres
|
||||
$query = $this->db->select('id, genre')
|
||||
@ -317,7 +350,13 @@ final class AnimeCollection extends Collection {
|
||||
$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')
|
||||
->from('genre_anime_set_link')
|
||||
->get();
|
||||
@ -326,17 +365,13 @@ final class AnimeCollection extends Collection {
|
||||
if (array_key_exists($link['hummingbird_id'], $links))
|
||||
{
|
||||
$links[$link['hummingbird_id']][] = $link['genre_id'];
|
||||
}
|
||||
else
|
||||
} else
|
||||
{
|
||||
$links[$link['hummingbird_id']] = [$link['genre_id']];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'genres' => $genres,
|
||||
'links' => $links
|
||||
];
|
||||
return $links;
|
||||
}
|
||||
}
|
||||
// End of AnimeCollectionModel.php
|
@ -17,7 +17,6 @@
|
||||
namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
@ -25,6 +24,12 @@ use PDOException;
|
||||
*/
|
||||
class Collection extends DB {
|
||||
|
||||
/**
|
||||
* The query builder object
|
||||
* @var \Query\Query_Builder_Interface
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Whether the database is valid for querying
|
||||
* @var boolean
|
||||
@ -43,6 +48,7 @@ class Collection extends DB {
|
||||
try
|
||||
{
|
||||
$this->db = \Query($this->dbConfig);
|
||||
$this->validDatabase = TRUE;
|
||||
}
|
||||
catch (PDOException $e) {}
|
||||
|
||||
@ -62,59 +68,10 @@ class Collection extends DB {
|
||||
$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
|
@ -24,12 +24,6 @@ use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
||||
class DB {
|
||||
use ContainerAware;
|
||||
|
||||
/**
|
||||
* The query builder object
|
||||
* @var \Query\Query_Builder_Interface
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* The database connection information array
|
||||
* @var array $dbConfig
|
||||
|
@ -88,21 +88,6 @@ final class MangaCollection extends Collection {
|
||||
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
|
||||
*
|
||||
|
@ -39,10 +39,10 @@ class RoutingBase {
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Routing array
|
||||
* @var array
|
||||
* Class wrapper for input superglobals
|
||||
* @var \Psr\Http\Message\ServerRequestInterface
|
||||
*/
|
||||
protected $routes;
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -56,7 +56,7 @@ class RoutingBase {
|
||||
{
|
||||
$this->container = $container;
|
||||
$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
|
||||
{
|
||||
$request = $this->container->get('request');
|
||||
$path = $request->getUri()->getPath();
|
||||
$path = $this->request->getUri()->getPath();
|
||||
$cleanedPath = $this->string($path)
|
||||
->replace('%20', '')
|
||||
->trim()
|
||||
@ -116,5 +115,4 @@ class RoutingBase {
|
||||
$segments = $this->segments();
|
||||
return end($segments);
|
||||
}
|
||||
}
|
||||
// End of RoutingBase.php
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -124,7 +124,7 @@ class AnimeClientTestCase extends TestCase {
|
||||
* @param array $supers
|
||||
* @return void
|
||||
*/
|
||||
public function setSuperGlobals($supers = [])
|
||||
public function setSuperGlobals($supers = []): void
|
||||
{
|
||||
$default = [
|
||||
'_SERVER' => $_SERVER,
|
||||
@ -139,7 +139,7 @@ class AnimeClientTestCase extends TestCase {
|
||||
array_merge($default, $supers)
|
||||
);
|
||||
$this->container->setInstance('request', $request);
|
||||
$this->container->set('repsone', function() {
|
||||
$this->container->set('response', function() {
|
||||
return new HttpResponse();
|
||||
});
|
||||
}
|
||||
@ -151,7 +151,7 @@ class AnimeClientTestCase extends TestCase {
|
||||
*
|
||||
* @return string - contents of the data file
|
||||
*/
|
||||
public function getMockFile()
|
||||
public function getMockFile(): string
|
||||
{
|
||||
$args = func_get_args();
|
||||
array_unshift($args, TEST_DATA_DIR);
|
||||
|
@ -19,14 +19,6 @@ namespace Aviat\AnimeClient\Tests;
|
||||
use Aviat\AnimeClient\RoutingBase;
|
||||
|
||||
class RoutingBaseTest extends AnimeClientTestCase {
|
||||
|
||||
protected $routingBase;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->routingBase = new RoutingBase($this->container);
|
||||
}
|
||||
|
||||
public function dataSegments()
|
||||
{
|
||||
@ -49,7 +41,7 @@ class RoutingBaseTest extends AnimeClientTestCase {
|
||||
/**
|
||||
* @dataProvider dataSegments
|
||||
*/
|
||||
public function testSegments($requestUri, $path, $segments, $lastSegment)
|
||||
public function testSegments(string $requestUri, string $path, array $segments, $lastSegment): void
|
||||
{
|
||||
$this->setSuperGlobals([
|
||||
'_SERVER' => [
|
||||
@ -57,13 +49,15 @@ class RoutingBaseTest extends AnimeClientTestCase {
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals($path, $this->routingBase->path(), "Path is invalid");
|
||||
$this->assertEquals($segments, $this->routingBase->segments(), "Segments array is invalid");
|
||||
$this->assertEquals($lastSegment, $this->routingBase->lastSegment(), "Last segment is invalid");
|
||||
$routingBase = new RoutingBase($this->container);
|
||||
|
||||
$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)
|
||||
{
|
||||
$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
@ -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>
|
@ -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>
|
Loading…
Reference in New Issue
Block a user