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
// -----------------------------------------------------------------------------
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);
});

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -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);
?>

View File

@ -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" />

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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);
}

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 {
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;
}

View File

@ -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

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"
},
"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"
}

File diff suppressed because it is too large Load Diff

View File

@ -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();
}

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)
: [];

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;
/**
* 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

View File

@ -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']);

View File

@ -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

View File

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

View File

@ -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']);

View File

@ -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',
]);
}

View File

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

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
*

View File

@ -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()
@ -117,4 +116,3 @@ class RoutingBase {
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
* @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);

View File

@ -20,14 +20,6 @@ use Aviat\AnimeClient\RoutingBase;
class RoutingBaseTest extends AnimeClientTestCase {
protected $routingBase;
public function setUp()
{
parent::setUp();
$this->routingBase = new RoutingBase($this->container);
}
public function dataSegments()
{
return [
@ -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

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>