Merge remote-tracking branch 'origin/develop'
This commit is contained in:
commit
73bbc569a7
@ -5,22 +5,12 @@ use PhpCsFixer\{Config, Finder};
|
||||
|
||||
$finder = Finder::create()
|
||||
->in([
|
||||
__DIR__,
|
||||
__DIR__ . '/app',
|
||||
__DIR__ . '/src',
|
||||
__DIR__ . '/tests',
|
||||
__DIR__ . '/tools',
|
||||
])
|
||||
->exclude([
|
||||
'apidocs',
|
||||
'build',
|
||||
'coverage',
|
||||
'frontEndSrc',
|
||||
'phinx',
|
||||
'public',
|
||||
'tools',
|
||||
'tmp',
|
||||
'vendor',
|
||||
'views',
|
||||
'templates',
|
||||
]);
|
||||
|
||||
return (new Config())
|
||||
@ -45,7 +35,7 @@ return (new Config())
|
||||
'blank_line_after_opening_tag' => false,
|
||||
'blank_line_before_statement' => [
|
||||
'statements' => [
|
||||
'case',
|
||||
// 'case',
|
||||
'continue',
|
||||
'declare',
|
||||
'default',
|
||||
@ -128,12 +118,12 @@ return (new Config())
|
||||
'noise_remaining_usages_exclude' => [],
|
||||
],
|
||||
'escape_implicit_backslashes' => [
|
||||
'double_quoted' => true,
|
||||
'heredoc_syntax' => true,
|
||||
'double_quoted' => false,
|
||||
'heredoc_syntax' => false,
|
||||
'single_quoted' => false,
|
||||
],
|
||||
'explicit_indirect_variable' => true,
|
||||
'explicit_string_variable' => true,
|
||||
'explicit_indirect_variable' => false,
|
||||
'explicit_string_variable' => false,
|
||||
'final_class' => false,
|
||||
'final_internal_class' => [
|
||||
'annotation_exclude' => ['@no-final'],
|
||||
@ -167,7 +157,7 @@ return (new Config())
|
||||
],
|
||||
'group_import' => true,
|
||||
'header_comment' => false, // false by default
|
||||
'heredoc_indentation' => ['indentation' => 'start_plus_one'],
|
||||
// 'heredoc_indentation' => ['indentation' => 'start_plus_one'],
|
||||
'heredoc_to_nowdoc' => true,
|
||||
'implode_call' => true,
|
||||
'include' => true,
|
||||
@ -232,8 +222,7 @@ return (new Config())
|
||||
'allow_unused_params' => true,
|
||||
'remove_inheritdoc' => false,
|
||||
],
|
||||
'no_trailing_comma_in_list_call' => true,
|
||||
'no_trailing_comma_in_singleline_array' => true,
|
||||
'no_trailing_comma_in_singleline' => true,
|
||||
'no_trailing_whitespace' => true,
|
||||
'no_trailing_whitespace_in_comment' => true,
|
||||
'no_trailing_whitespace_in_string' => true,
|
||||
@ -270,9 +259,16 @@ return (new Config())
|
||||
'ordered_class_elements' => [
|
||||
'order' => [
|
||||
'use_trait',
|
||||
'constant',
|
||||
'property',
|
||||
'method',
|
||||
'case',
|
||||
'constant_public',
|
||||
'constant_protected',
|
||||
'constant_private',
|
||||
'property_public',
|
||||
'property_protected',
|
||||
'property_private',
|
||||
'construct',
|
||||
'destruct',
|
||||
'magic',
|
||||
],
|
||||
'sort_algorithm' => 'none',
|
||||
],
|
||||
|
@ -1,8 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## Version 5.2
|
||||
* Updated PHP requirement to 8
|
||||
* Updated to support PHP 8.1
|
||||
* Updated PHP requirement to 8.1
|
||||
* Updated to support PHP 8.2
|
||||
* Improve Anilist <-> Kitsu mappings to be more reliable
|
||||
|
||||
## Version 5.1
|
||||
* Added session check, so when coming back to a page, if the session is expired, the page will refresh.
|
||||
|
0
app/logs/.gitkeep
Normal file → Executable file
0
app/logs/.gitkeep
Normal file → Executable file
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Aviat\AnimeClient\Kitsu;
|
||||
use function Aviat\AnimeClient\friendlyTime;
|
||||
|
||||
?>
|
||||
<main class="details fixed">
|
||||
@ -38,14 +38,14 @@ use Aviat\AnimeClient\Kitsu;
|
||||
<?php if (( ! empty($data['episode_length'])) && $data['episode_count'] !== 1): ?>
|
||||
<tr>
|
||||
<td>Episode Length</td>
|
||||
<td><?= Kitsu::friendlyTime($data['episode_length']) ?></td>
|
||||
<td><?= friendlyTime($data['episode_length']) ?></td>
|
||||
</tr>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (isset($data['total_length'], $data['episode_count']) && $data['total_length'] > 0): ?>
|
||||
<tr>
|
||||
<td>Total Length</td>
|
||||
<td><?= Kitsu::friendlyTime($data['total_length']) ?></td>
|
||||
<td><?= friendlyTime($data['total_length']) ?></td>
|
||||
</tr>
|
||||
<?php endif ?>
|
||||
|
||||
|
@ -26,7 +26,7 @@ use Aviat\AnimeClient\Kitsu;
|
||||
<br />
|
||||
<hr />
|
||||
<div class="description">
|
||||
<p><?= str_replace("\n", '</p><p>', $data['description']) ?></p>
|
||||
<p><?= nl2br($data['description']) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -8,6 +8,9 @@
|
||||
<?php foreach ($data['names'] as $name): ?>
|
||||
<h3><?= $name ?></h3>
|
||||
<?php endforeach ?>
|
||||
<?php if ( ! empty($data['birthday'])): ?>
|
||||
<h4><?= $data['birthday'] ?></h4>
|
||||
<?php endif ?>
|
||||
<br />
|
||||
<hr />
|
||||
<div class="description">
|
||||
|
@ -3,6 +3,7 @@ use Aviat\AnimeClient\Kitsu;
|
||||
?>
|
||||
<main class="user-page details">
|
||||
<h2 class="toph">
|
||||
About
|
||||
<?= $helper->a(
|
||||
"https://kitsu.io/users/{$data['slug']}",
|
||||
$data['name'], [
|
||||
@ -11,32 +12,49 @@ use Aviat\AnimeClient\Kitsu;
|
||||
?>
|
||||
</h2>
|
||||
|
||||
<p><?= $escape->html($data['about']) ?></p>
|
||||
|
||||
<section class="flex flex-no-wrap">
|
||||
<aside class="info">
|
||||
<center>
|
||||
<?= $helper->img($data['avatar'], ['alt' => '']); ?>
|
||||
</center>
|
||||
<table class="media-details invisible">
|
||||
<tr>
|
||||
<?php if($data['avatar'] !== null): ?>
|
||||
<td><?= $helper->img($data['avatar'], ['alt' => '', 'width' => '225']); ?></td>
|
||||
<?php endif ?>
|
||||
<td><?= $escape->html($data['about']) ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table class="media-details">
|
||||
<?php foreach ([
|
||||
'joinDate' => 'Joined',
|
||||
'birthday' => 'Birthday',
|
||||
'gender' => 'Gender',
|
||||
'location' => 'Location'
|
||||
] as $key => $label): ?>
|
||||
<?php if ($data[$key] !== null): ?>
|
||||
<tr>
|
||||
<td>Location</td>
|
||||
<td><?= $data['location'] ?></td>
|
||||
<td><?= $label ?></td>
|
||||
<td><?= $data[$key] ?></td>
|
||||
</tr>
|
||||
<?php endif ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if ($data['website'] !== null): ?>
|
||||
<tr>
|
||||
<td>Website</td>
|
||||
<td><?= $helper->a($data['website'], $data['website']) ?></td>
|
||||
</tr>
|
||||
<?php if ( ! empty($data['waifu'])): ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($data['waifu']['character'] !== null): ?>
|
||||
<tr>
|
||||
<td><?= $escape->html($data['waifu']['label']) ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$character = $data['waifu']['character'];
|
||||
echo $helper->a(
|
||||
echo $component->character(
|
||||
$character['names']['canonical'],
|
||||
$url->generate('character', ['slug' => $character['slug']]),
|
||||
$character['names']['canonical']
|
||||
$helper->img(Kitsu::getImage($character))
|
||||
);
|
||||
?>
|
||||
</td>
|
||||
@ -75,7 +93,7 @@ use Aviat\AnimeClient\Kitsu;
|
||||
$rendered[] = $component->character(
|
||||
$item['names']['canonical'],
|
||||
$url->generate('character', ['slug' => $item['slug']]),
|
||||
$helper->img($item['image']['original']['url'])
|
||||
$helper->img(Kitsu::getImage($item))
|
||||
);
|
||||
}
|
||||
else
|
||||
|
@ -1,9 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" stopOnFailure="false" bootstrap="../tests/bootstrap.php" beStrictAboutTestsThatDoNotTestAnything="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" stopOnFailure="false" bootstrap="../tests/bootstrap.php" beStrictAboutTestsThatDoNotTestAnything="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd">
|
||||
<coverage>
|
||||
<include>
|
||||
<directory suffix=".php">../src</directory>
|
||||
</include>
|
||||
<report>
|
||||
<clover outputFile="logs/clover.xml"/>
|
||||
<html outputDirectory="../coverage"/>
|
||||
@ -27,4 +24,9 @@
|
||||
<server name="REQUEST_URI" value="/"/>
|
||||
<server name="REQUEST_METHOD" value="GET"/>
|
||||
</php>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">../src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
|
@ -30,21 +30,20 @@
|
||||
"lock": false
|
||||
},
|
||||
"require": {
|
||||
"amphp/amp": "^2.5.0",
|
||||
"amphp/http-client": "^4.5.0",
|
||||
"aura/html": "^2.5.0",
|
||||
"aura/router": "^3.1.0",
|
||||
"aura/session": "^2.1.0",
|
||||
"aviat/banker": "^4.1.2",
|
||||
"aviat/query": "^4.0.0",
|
||||
"aviat/query": "^4.1.0",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-intl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-pdo": "*",
|
||||
"laminas/laminas-diactoros": "^2.5.0",
|
||||
"laminas/laminas-httphandlerrunner": "^2.1.0",
|
||||
"laminas/laminas-diactoros": "^3.0.0",
|
||||
"laminas/laminas-httphandlerrunner": "^2.6.1",
|
||||
"maximebf/consolekit": "^1.0.3",
|
||||
"monolog/monolog": "^3.0.0",
|
||||
"php": ">= 8.1.0",
|
||||
@ -56,9 +55,9 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.2.0",
|
||||
"phpunit/phpunit": "^9.5.0",
|
||||
"phpunit/phpunit": "^10.0.0",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"spatie/phpunit-snapshot-assertions": "^4.1.0"
|
||||
"spatie/phpunit-snapshot-assertions": "^5.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build:css": "cd public && npm run build:css && cd ..",
|
||||
|
@ -333,7 +333,8 @@ td.danger, td.danger:hover, td.danger:active {
|
||||
.borderless th,
|
||||
.invisible tr,
|
||||
.invisible td,
|
||||
.invisible th {
|
||||
.invisible th,
|
||||
table.invisible {
|
||||
box-shadow: none;
|
||||
border: 0;
|
||||
}
|
||||
@ -836,19 +837,11 @@ aside.info {
|
||||
max-width: 390px;
|
||||
}
|
||||
|
||||
/* .fixed aside.info + article {
|
||||
max-width: inherit;
|
||||
} */
|
||||
|
||||
aside picture, aside img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* aside.info + article {
|
||||
max-width: 66%;
|
||||
} */
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
User page styles
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
@ -1,5 +1,6 @@
|
||||
import _ from './anime-client.js'
|
||||
import { renderSearchResults } from './template-helpers.js'
|
||||
import { getNestedProperty, hasNestedProperty } from "./fns";
|
||||
|
||||
const search = (query, isCollection = false) => {
|
||||
// Show the loader
|
||||
@ -70,6 +71,14 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
|
||||
}
|
||||
};
|
||||
|
||||
const displayMessage = (type, message) => {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage(type, `${message} ${title}`);
|
||||
_.scrollToTop();
|
||||
}
|
||||
|
||||
const showError = () => displayMessage('error', 'Failed to update');
|
||||
|
||||
// If the episode count is 0, and incremented,
|
||||
// change status to currently watching
|
||||
if (isNaN(watchedCount) || watchedCount === 0) {
|
||||
@ -89,36 +98,31 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
success: (res) => {
|
||||
try {
|
||||
const resData = JSON.parse(res);
|
||||
|
||||
if (resData.error) {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('error', `Failed to update ${title}. `);
|
||||
_.scrollToTop();
|
||||
|
||||
// Do a rough sanity check for weird errors
|
||||
let updatedProgress = getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.progress');
|
||||
if (hasNestedProperty(resData, 'error') || updatedProgress !== data.data.progress) {
|
||||
showError();
|
||||
return;
|
||||
}
|
||||
|
||||
// We've completed the series
|
||||
if (resData.data.libraryEntry.update.libraryEntry.status === 'COMPLETED') {
|
||||
if (getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.status') === 'COMPLETED') {
|
||||
_.hide(parentSel);
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('success', `Successfully completed ${title}`);
|
||||
_.scrollToTop();
|
||||
displayMessage('success', 'Completed')
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_.hide('#loading-shadow');
|
||||
|
||||
_.showMessage('success', `Successfully updated ${title}`);
|
||||
// Just a normal update
|
||||
_.$('.completed_number', parentSel)[ 0 ].textContent = ++watchedCount;
|
||||
_.scrollToTop();
|
||||
},
|
||||
error: () => {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('error', `Failed to update ${title}. `);
|
||||
_.scrollToTop();
|
||||
displayMessage('success', 'Updated');
|
||||
} catch (_) {
|
||||
showError();
|
||||
}
|
||||
},
|
||||
error: showError,
|
||||
});
|
||||
});
|
103
frontEndSrc/js/fns.js
Normal file
103
frontEndSrc/js/fns.js
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Make sure properties are in an easily splittable format
|
||||
*
|
||||
* @private
|
||||
* @param {String} props
|
||||
* @param {String} [sep='.'] The default separator
|
||||
* @return {String}
|
||||
*/
|
||||
function _normalizeProperty(props, sep = '.') {
|
||||
// Since we split by period, and property lookup
|
||||
// is the same by dot or [], replace bracket lookups
|
||||
// with periods
|
||||
return props.replace(/\[(.*?)]/g, sep + '$1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell if a nested object has a given property (or array a given index)
|
||||
* given an object such as a.b.c.d = 5, hasNestedProperty(a, 'b.c.d') will return true.
|
||||
*
|
||||
* @param {Object} object the object to get the property from
|
||||
* @param {String} property the path to the property as a string
|
||||
* @returns {boolean} true when property in object, false otherwise
|
||||
*/
|
||||
export function hasNestedProperty(object, property) {
|
||||
if (object && typeof object === 'object') {
|
||||
if (typeof property === 'string' && property !== '') {
|
||||
property = _normalizeProperty(property);
|
||||
|
||||
let split = property.split('.');
|
||||
return split.reduce((obj, prop, idx, array) => {
|
||||
if (idx === array.length - 1) {
|
||||
return !!(obj && obj.hasOwnProperty(prop));
|
||||
}
|
||||
|
||||
return obj && obj[prop];
|
||||
}, object);
|
||||
} else if (typeof property === 'number') {
|
||||
return property in object;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a deeply nested property in an object
|
||||
*
|
||||
* @param {Object} object the object to get the property
|
||||
* @param {string} property the path to the property as a string
|
||||
* @param {string} [sep='.'] The default separator to split on
|
||||
* @return {*} the value of the property
|
||||
*/
|
||||
export function getNestedProperty(object, property, sep = '.') {
|
||||
if (isType('string', property) && property !== '') {
|
||||
// convert numbers to dot syntax
|
||||
property = _normalizeProperty(property, sep);
|
||||
const levels = property.split(sep);
|
||||
|
||||
try {
|
||||
return levels.reduce((obj, prop) => obj[prop], object);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reliably get the type of the value of a variable
|
||||
*
|
||||
* @param {*} x The variable to get the type of
|
||||
* @return {string} The name of the type
|
||||
*/
|
||||
export function getType(x) {
|
||||
// is it an array?
|
||||
if (Array.isArray(x)) {
|
||||
return 'array';
|
||||
}
|
||||
|
||||
// Use typeof for truthy primitives
|
||||
if (typeof x !== 'object') {
|
||||
return (typeof x).toLowerCase();
|
||||
}
|
||||
|
||||
const type = function () {
|
||||
return Object.prototype.toString.call(this).slice(8, -1);
|
||||
}
|
||||
|
||||
// Otherwise, strip the type out of the '[Object x]' toString value
|
||||
return type.call(x).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the value matches the passed type name
|
||||
*
|
||||
* @param {string} type Javascript type name
|
||||
* @param {*} val The value to type check
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isType(type, val) {
|
||||
return getType(val) === String(type).toLowerCase();
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import _ from './anime-client.js'
|
||||
import { renderSearchResults } from './template-helpers.js'
|
||||
import { getNestedProperty, hasNestedProperty } from "./fns";
|
||||
|
||||
const search = (query) => {
|
||||
_.show('.cssload-loader');
|
||||
@ -36,7 +37,7 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
||||
let type = thisSel.classList.contains('plus-one-chapter') ? 'chapter' : 'volume';
|
||||
let completed = parseInt(_.$(`.${type}s_read`, parentSel)[ 0 ].textContent, 10) || 0;
|
||||
let total = parseInt(_.$(`.${type}_count`, parentSel)[ 0 ].textContent, 10);
|
||||
let mangaName = _.$('.name', parentSel)[ 0 ].textContent;
|
||||
let title = _.$('.name', parentSel)[ 0 ].textContent;
|
||||
|
||||
if (isNaN(completed)) {
|
||||
completed = 0;
|
||||
@ -45,12 +46,21 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
||||
// Setup the update data
|
||||
let data = {
|
||||
id: parentSel.dataset.kitsuId,
|
||||
anilist_id: parentSel.dataset.anilistId,
|
||||
mal_id: parentSel.dataset.malId,
|
||||
data: {
|
||||
progress: completed
|
||||
}
|
||||
};
|
||||
|
||||
const displayMessage = (type, message) => {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage(type, `${message} ${title}`);
|
||||
_.scrollToTop();
|
||||
}
|
||||
|
||||
const showError = () => displayMessage('error', 'Failed to update');
|
||||
|
||||
// If the episode count is 0, and incremented,
|
||||
// change status to currently reading
|
||||
if (isNaN(completed) || completed === 0) {
|
||||
@ -73,33 +83,32 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
||||
type: 'POST',
|
||||
mimeType: 'application/json',
|
||||
success: (res) => {
|
||||
const resData = JSON.parse(res)
|
||||
if (resData.error) {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('error', `Failed to update ${mangaName}. `);
|
||||
_.scrollToTop();
|
||||
try {
|
||||
const resData = JSON.parse(res);
|
||||
|
||||
// Do a rough sanity check for weird errors
|
||||
let updatedProgress = getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.progress');
|
||||
if (hasNestedProperty(resData, 'error') || updatedProgress !== data.data.progress) {
|
||||
showError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (String(data.data.status).toUpperCase() === 'COMPLETED') {
|
||||
// We've completed the series
|
||||
if (getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.status') === 'COMPLETED') {
|
||||
_.hide(parentSel);
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('success', `Successfully completed ${mangaName}`);
|
||||
_.scrollToTop();
|
||||
displayMessage('success', 'Completed')
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_.hide('#loading-shadow');
|
||||
|
||||
// Just a normal update
|
||||
_.$(`.${type}s_read`, parentSel)[ 0 ].textContent = String(completed);
|
||||
_.showMessage('success', `Successfully updated ${mangaName}`);
|
||||
_.scrollToTop();
|
||||
},
|
||||
error: () => {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('error', `Failed to update ${mangaName}`);
|
||||
_.scrollToTop();
|
||||
displayMessage('success', 'Updated');
|
||||
|
||||
} catch (_) {
|
||||
showError();
|
||||
}
|
||||
},
|
||||
error: showError,
|
||||
});
|
||||
});
|
@ -15,7 +15,7 @@
|
||||
"cssnano": "^5.0.1",
|
||||
"postcss": "^8.2.6",
|
||||
"postcss-import": "^15.0.0",
|
||||
"postcss-preset-env": "^7.8.2",
|
||||
"postcss-preset-env": "^8.0.1",
|
||||
"watch": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
module.exports = {
|
||||
const { config } = require("@swc/core/spack");
|
||||
|
||||
module.exports = config({
|
||||
entry: {
|
||||
'scripts.min': __dirname + '/js/index.js',
|
||||
'tables.min': __dirname + '/js/base/sort-tables.js',
|
||||
@ -8,12 +10,15 @@ module.exports = {
|
||||
},
|
||||
options: {
|
||||
jsc: {
|
||||
target: 'es3',
|
||||
loose: true,
|
||||
parser: {
|
||||
syntax: "ecmascript",
|
||||
jsx: false,
|
||||
},
|
||||
target: 'es2016',
|
||||
loose: false,
|
||||
},
|
||||
minify: true,
|
||||
module: {
|
||||
type: 'es6'
|
||||
sourceMaps: false,
|
||||
isModule: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
File diff suppressed because it is too large
Load Diff
52
justfile
52
justfile
@ -2,13 +2,25 @@
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# Runs rector, showing what changes will be make
|
||||
rector-dry-run:
|
||||
tools/vendor/bin/rector process --config=tools/rector.php --dry-run src
|
||||
# -------------------------------------------------------------------
|
||||
# Front-end stuff
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# Runs rector, and updates the files
|
||||
rector:
|
||||
tools/vendor/bin/rector process --config=tools/rector.php src
|
||||
# Builds/optimizes JS and CSS
|
||||
build:
|
||||
cd frontEndSrc && npm run build && cd ..
|
||||
|
||||
# Builds/optimizes CSS
|
||||
css:
|
||||
composer run-script build:css
|
||||
|
||||
# Builds/optimizes JS
|
||||
js:
|
||||
composer run-script build:js
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Code Quality and Formatting
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# Check code formatting
|
||||
check-fmt:
|
||||
@ -18,6 +30,22 @@ check-fmt:
|
||||
fmt:
|
||||
tools/vendor/bin/php-cs-fixer fix --verbose
|
||||
|
||||
# Runs phpstan code check
|
||||
phpstan:
|
||||
composer run-script phpstan
|
||||
|
||||
# Runs rector, showing what changes will be make
|
||||
rector-dry-run:
|
||||
tools/vendor/bin/rector process --config=tools/rector.php --dry-run src tests
|
||||
|
||||
# Runs rector, and updates the source files
|
||||
rector:
|
||||
tools/vendor/bin/rector process --config=tools/rector.php src tests
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Testing
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
composer run-script test
|
||||
@ -26,10 +54,14 @@ test:
|
||||
test-update:
|
||||
composer run-script test-update
|
||||
|
||||
# Update the per-file header comments
|
||||
update-headers:
|
||||
php tools/update_header_comments.php
|
||||
|
||||
# Run unit tests and generate test-coverage report
|
||||
coverage:
|
||||
composer run-script coverage
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Misc
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# Update the per-file header comments
|
||||
update-headers:
|
||||
php tools/update_header_comments.php
|
@ -9,11 +9,12 @@ parameters:
|
||||
- ./console
|
||||
- index.php
|
||||
ignoreErrors:
|
||||
- "#Offset 'fields' does not exist on array#"
|
||||
- '#Function imagepalletetotruecolor not found#'
|
||||
- '#Unable to resolve the template type T#'
|
||||
- '#imagepalletetotruecolor not found#'
|
||||
- '#Call to an undefined method Aura\\\Html\\\HelperLocator::[a-zA-Z0-9_]+\(\)#'
|
||||
- '#Call to an undefined method Query\\QueryBuilderInterface::[a-zA-Z0-9_]+\(\)#'
|
||||
excludes_analyse:
|
||||
excludePaths:
|
||||
- src/Ion/Type/Stringy.php
|
||||
- tests/mocks.php
|
||||
- vendor
|
||||
# These are objects that basically can return anything
|
||||
|
2
public/css/auto.min.css
vendored
2
public/css/auto.min.css
vendored
File diff suppressed because one or more lines are too long
2
public/css/dark.min.css
vendored
2
public/css/dark.min.css
vendored
File diff suppressed because one or more lines are too long
2
public/css/light.min.css
vendored
2
public/css/light.min.css
vendored
File diff suppressed because one or more lines are too long
39
public/js/scripts.min.js
vendored
39
public/js/scripts.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/js/tables.min.js
vendored
2
public/js/tables.min.js
vendored
@ -1 +1 @@
|
||||
var LightTableSorter=function(){var th=null;var cellIndex=null;var order="";var text=function(row){return row.cells.item(cellIndex).textContent.toLowerCase()};var sort=function(a,b){var textA=text(a);var textB=text(b);console.log("Comparing "+textA+" and "+textB);if(th.classList.contains("numeric")){var arrayA=textA.replace("episodes: ","").replace("-",0).split("/");var arrayB=textB.replace("episodes: ","").replace("-",0).split("/");if(arrayA.length>1){textA=parseInt(arrayA[0],10)/parseInt(arrayA[1],10);textB=parseInt(arrayB[0],10)/parseInt(arrayB[1],10)}else{textA=parseInt(arrayA[0],10);textB=parseInt(arrayB[0],10)}}else if(parseInt(textA,10)){textA=parseInt(textA,10);textB=parseInt(textB,10)}if(textA>textB)return 1;if(textA<textB)return -1;return 0};var toggle=function(){var c=order!=="sorting-asc"?"sorting-asc":"sorting-desc";th.className=(th.className.replace(order,"")+" "+c).trim();return order=c};var reset=function(){th.classList.remove("sorting-asc","sorting-desc");th.classList.add("sorting");return order=""};var onClickEvent=function(e){if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==="th"){cellIndex=th.cellIndex;var tbody=th.offsetParent.getElementsByTagName("tbody")[0];var rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==="sorting-asc")rows.reverse();toggle();tbody.innerHtml="";rows.forEach(function(row){tbody.appendChild(row)})}}};return{init:function(){var ths=document.getElementsByTagName("th");var results=[];for(var i=0,len=ths.length;i<len;i++){var th=ths[i];th.classList.add("sorting");th.classList.add("testing");results.push(th.onclick=onClickEvent)}return results}}}();LightTableSorter.init();
|
||||
const LightTableSorter=(()=>{let th=null;let cellIndex=null;let order="";const text=row=>row.cells.item(cellIndex).textContent.toLowerCase();const sort=(a,b)=>{let textA=text(a);let textB=text(b);console.log("Comparing "+textA+" and "+textB);if(th.classList.contains("numeric")){let arrayA=textA.replace("episodes: ","").replace("-",0).split("/");let arrayB=textB.replace("episodes: ","").replace("-",0).split("/");if(arrayA.length>1){textA=parseInt(arrayA[0],10)/parseInt(arrayA[1],10);textB=parseInt(arrayB[0],10)/parseInt(arrayB[1],10)}else{textA=parseInt(arrayA[0],10);textB=parseInt(arrayB[0],10)}}else if(parseInt(textA,10)){textA=parseInt(textA,10);textB=parseInt(textB,10)}if(textA>textB)return 1;if(textA<textB)return -1;return 0};const toggle=()=>{const c=order!=="sorting-asc"?"sorting-asc":"sorting-desc";th.className=(th.className.replace(order,"")+" "+c).trim();return order=c};const reset=()=>{th.classList.remove("sorting-asc","sorting-desc");th.classList.add("sorting");return order=""};const onClickEvent=e=>{if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==="th"){cellIndex=th.cellIndex;const tbody=th.offsetParent.getElementsByTagName("tbody")[0];let rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==="sorting-asc")rows.reverse();toggle();tbody.innerHtml="";rows.forEach(row=>{tbody.appendChild(row)})}}};return{init:()=>{let ths=document.getElementsByTagName("th");let results=[];for(let i=0,len=ths.length;i<len;i++){let th=ths[i];th.classList.add("sorting");th.classList.add("testing");results.push(th.onclick=onClickEvent)}return results}}})();LightTableSorter.init();
|
File diff suppressed because one or more lines are too long
@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "Anilist Schema",
|
||||
"schemaPath": "schema.graphql",
|
||||
"extensions": {
|
||||
"endpoints": {
|
||||
"Anilist": {
|
||||
"url": "https://graphql.anilist.co",
|
||||
"headers": {
|
||||
"user-agent": "JS GraphQL"
|
||||
},
|
||||
"introspect": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -189,7 +189,7 @@ final class Model
|
||||
*/
|
||||
public function deleteItem(FormItem $data, string $type): ?Request
|
||||
{
|
||||
$mediaId = $this->getMediaId((array)$data, $type);
|
||||
$mediaId = $this->getMediaId((array) $data, $type);
|
||||
if ($mediaId === NULL)
|
||||
{
|
||||
return NULL;
|
||||
@ -209,7 +209,7 @@ final class Model
|
||||
*/
|
||||
public function getListIdFromData(FormItem $data, string $type = 'ANIME'): ?string
|
||||
{
|
||||
$mediaId = $this->getMediaId((array)$data, $type);
|
||||
$mediaId = $this->getMediaId((array) $data, $type);
|
||||
if ($mediaId === NULL)
|
||||
{
|
||||
return NULL;
|
||||
@ -244,7 +244,7 @@ final class Model
|
||||
/**
|
||||
* Find the id to update by
|
||||
*/
|
||||
private function getMediaId (array $data, string $type = 'ANIME'): ?string
|
||||
private function getMediaId(array $data, string $type = 'ANIME'): ?string
|
||||
{
|
||||
if (isset($data['anilist_id']))
|
||||
{
|
||||
|
8
src/AnimeClient/API/Anilist/graphql.config.yml
Normal file
8
src/AnimeClient/API/Anilist/graphql.config.yml
Normal file
@ -0,0 +1,8 @@
|
||||
schema: schema.graphql
|
||||
extensions:
|
||||
endpoints:
|
||||
Anilist:
|
||||
url: https://graphql.anilist.co
|
||||
headers:
|
||||
user-agent: JS GraphQL
|
||||
introspect: true
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "Kitsu Schema",
|
||||
"schemaPath": "schema.graphql",
|
||||
"extensions": {
|
||||
"endpoints": {
|
||||
"Kitsu": {
|
||||
"url": "https://kitsu.io/api/graphql",
|
||||
"headers": {
|
||||
"user-agent": "JS GraphQL"
|
||||
},
|
||||
"introspect": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -52,7 +52,7 @@ final class Auth
|
||||
->getSegment(SESSION_SEGMENT);
|
||||
$this->model = $container->get('kitsu-model');
|
||||
|
||||
Event::on('::unauthorized::', [$this, 'reAuthenticate']);
|
||||
Event::on('::unauthorized::', $this->reAuthenticate(...));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,7 +133,7 @@ final class Auth
|
||||
/**
|
||||
* Save the new authentication information
|
||||
*/
|
||||
private function storeAuth(array|FALSE $auth): bool
|
||||
private function storeAuth(array|false $auth): bool
|
||||
{
|
||||
if (FALSE !== $auth)
|
||||
{
|
||||
|
@ -34,6 +34,7 @@ use Aviat\AnimeClient\API\{
|
||||
use Aviat\AnimeClient\Enum\MediaType;
|
||||
use Aviat\AnimeClient\Kitsu as K;
|
||||
use Aviat\AnimeClient\Types\{Anime, MangaPage};
|
||||
use Aviat\AnimeClient\Types\{AnimeListItem, MangaListItem};
|
||||
use Aviat\Ion\{
|
||||
Di\ContainerAware,
|
||||
Json
|
||||
@ -282,7 +283,7 @@ final class Model
|
||||
|
||||
if ($list === NULL)
|
||||
{
|
||||
$data = $this->getList(MediaType::ANIME, $status) ?? [];
|
||||
$data = $this->getList(MediaType::ANIME, $status);
|
||||
|
||||
// Bail out on no data
|
||||
if (empty($data))
|
||||
@ -319,7 +320,7 @@ final class Model
|
||||
/**
|
||||
* Get all the anime entries, that are organized for output to html
|
||||
*
|
||||
* @return array<string, mixed[]>
|
||||
* @return array<string, array>
|
||||
*/
|
||||
public function getFullOrganizedAnimeList(): array
|
||||
{
|
||||
@ -330,7 +331,7 @@ final class Model
|
||||
foreach ($statuses as $status)
|
||||
{
|
||||
$mappedStatus = AnimeWatchingStatus::KITSU_TO_TITLE[$status];
|
||||
$output[$mappedStatus] = $this->getAnimeList($status) ?? [];
|
||||
$output[$mappedStatus] = $this->getAnimeList($status);
|
||||
}
|
||||
|
||||
return $output;
|
||||
@ -412,7 +413,7 @@ final class Model
|
||||
|
||||
if ($list === NULL)
|
||||
{
|
||||
$data = $this->getList(MediaType::MANGA, $status) ?? [];
|
||||
$data = $this->getList(MediaType::MANGA, $status);
|
||||
|
||||
// Bail out on no data
|
||||
if (empty($data))
|
||||
@ -534,14 +535,14 @@ final class Model
|
||||
* Get the data for a specific list item, generally for editing
|
||||
*
|
||||
* @param string $listId - The unique identifier of that list item
|
||||
* @return mixed
|
||||
*/
|
||||
public function getListItem(string $listId)
|
||||
public function getListItem(string $listId): AnimeListItem|MangaListItem|array
|
||||
{
|
||||
$baseData = $this->listItem->get($listId);
|
||||
if ( ! isset($baseData['data']['findLibraryEntryById']))
|
||||
{
|
||||
return [];
|
||||
// We need to get the errors...
|
||||
return $baseData;
|
||||
}
|
||||
|
||||
return (new LibraryEntryTransformer())->transform($baseData['data']['findLibraryEntryById']);
|
||||
@ -566,7 +567,7 @@ final class Model
|
||||
// this way is much faster...
|
||||
foreach ($statuses as $status)
|
||||
{
|
||||
foreach ($this->getPages([$this, 'getThumbListPages'], strtoupper($type), $status) as $page)
|
||||
foreach ($this->getPages($this->getThumbListPages(...), strtoupper($type), $status) as $page)
|
||||
{
|
||||
$pages[] = $page;
|
||||
}
|
||||
@ -596,7 +597,7 @@ final class Model
|
||||
// this way is much faster...
|
||||
foreach ($statuses as $status)
|
||||
{
|
||||
foreach ($this->getPages([$this, 'getSyncPages'], strtoupper($type), $status) as $page)
|
||||
foreach ($this->getPages($this->getSyncPages(...), strtoupper($type), $status) as $page)
|
||||
{
|
||||
$pages[] = $page;
|
||||
}
|
||||
@ -626,7 +627,7 @@ final class Model
|
||||
{
|
||||
$pages = [];
|
||||
|
||||
foreach ($this->getPages([$this, 'getListPages'], strtoupper($type), strtoupper($status)) as $page)
|
||||
foreach ($this->getPages($this->getListPages(...), strtoupper($type), strtoupper($status)) as $page)
|
||||
{
|
||||
$pages[] = $page;
|
||||
}
|
||||
@ -786,7 +787,7 @@ final class Model
|
||||
}
|
||||
}
|
||||
|
||||
private function getUserId(): string
|
||||
protected function getUserId(): string
|
||||
{
|
||||
static $userId = NULL;
|
||||
|
||||
|
@ -67,9 +67,6 @@ trait MutationTrait
|
||||
|
||||
/**
|
||||
* Remove a list item
|
||||
*
|
||||
* @param FormItem $data
|
||||
* @return Request
|
||||
*/
|
||||
public function deleteItem(FormItem $data): Request
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ query ($slug: String!) {
|
||||
}
|
||||
categories(first: 100) {
|
||||
nodes {
|
||||
title
|
||||
title(locales: "en")
|
||||
}
|
||||
}
|
||||
characters(first: 100) {
|
||||
@ -29,7 +29,7 @@ query ($slug: String!) {
|
||||
names {
|
||||
alternatives
|
||||
canonical
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
image {
|
||||
original {
|
||||
@ -50,7 +50,7 @@ query ($slug: String!) {
|
||||
startCursor
|
||||
}
|
||||
}
|
||||
description
|
||||
description(locales: "en")
|
||||
startDate
|
||||
endDate
|
||||
episodeCount
|
||||
@ -87,7 +87,7 @@ query ($slug: String!) {
|
||||
names {
|
||||
alternatives
|
||||
canonical
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
slug
|
||||
}
|
||||
@ -118,7 +118,7 @@ query ($slug: String!) {
|
||||
alternatives
|
||||
canonical
|
||||
canonicalLocale
|
||||
localized
|
||||
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||
}
|
||||
totalLength
|
||||
youtubeTrailerVideoId
|
||||
|
@ -19,7 +19,7 @@ query ($id: ID!) {
|
||||
}
|
||||
categories(first: 100) {
|
||||
nodes {
|
||||
title
|
||||
title(locales: "en")
|
||||
}
|
||||
}
|
||||
characters(first: 100) {
|
||||
@ -29,7 +29,7 @@ query ($id: ID!) {
|
||||
names {
|
||||
alternatives
|
||||
canonical
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
image {
|
||||
original {
|
||||
@ -50,7 +50,7 @@ query ($id: ID!) {
|
||||
startCursor
|
||||
}
|
||||
}
|
||||
description
|
||||
description(locales: "en")
|
||||
startDate
|
||||
endDate
|
||||
episodeCount
|
||||
@ -87,7 +87,7 @@ query ($id: ID!) {
|
||||
names {
|
||||
alternatives
|
||||
canonical
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
slug
|
||||
}
|
||||
@ -118,7 +118,7 @@ query ($id: ID!) {
|
||||
alternatives
|
||||
canonical
|
||||
canonicalLocale
|
||||
localized
|
||||
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||
}
|
||||
totalLength
|
||||
youtubeTrailerVideoId
|
||||
|
@ -6,12 +6,12 @@ query ($slug: String!) {
|
||||
url
|
||||
}
|
||||
}
|
||||
description
|
||||
description(locales: "en")
|
||||
names {
|
||||
alternatives
|
||||
canonical
|
||||
canonicalLocale
|
||||
localized
|
||||
localized(locales: "*")
|
||||
},
|
||||
media(first: 100) {
|
||||
nodes {
|
||||
@ -22,7 +22,7 @@ query ($slug: String!) {
|
||||
alternatives
|
||||
canonical
|
||||
canonicalLocale
|
||||
localized
|
||||
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||
}
|
||||
posterImage {
|
||||
original {
|
||||
@ -41,7 +41,7 @@ query ($slug: String!) {
|
||||
type
|
||||
}
|
||||
role
|
||||
voices(first: 10) {
|
||||
voices(first: 10, locale:"*", sort:{direction:ASCENDING, on: UPDATED_AT}) {
|
||||
nodes {
|
||||
id
|
||||
locale
|
||||
@ -53,7 +53,7 @@ query ($slug: String!) {
|
||||
alternatives
|
||||
canonical
|
||||
canonicalLocale
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
image {
|
||||
original {
|
||||
|
@ -57,7 +57,7 @@ query (
|
||||
type
|
||||
titles {
|
||||
canonical
|
||||
localized
|
||||
localized(locales: "*")
|
||||
alternatives
|
||||
}
|
||||
...on Anime {
|
||||
|
@ -16,7 +16,7 @@ query($id: ID!) {
|
||||
ageRating
|
||||
categories(first: 100) {
|
||||
nodes {
|
||||
title
|
||||
title(locales: "*")
|
||||
}
|
||||
}
|
||||
mappings(first: 10) {
|
||||
@ -41,7 +41,7 @@ query($id: ID!) {
|
||||
endDate
|
||||
titles {
|
||||
canonical
|
||||
localized
|
||||
localized(locales: "*")
|
||||
canonicalLocale
|
||||
}
|
||||
type
|
||||
|
@ -33,7 +33,7 @@ query ($slug: String!) {
|
||||
titles {
|
||||
alternatives
|
||||
canonical
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
...on Anime {
|
||||
episodeCount
|
||||
|
@ -19,7 +19,7 @@ query ($slug: String!) {
|
||||
}
|
||||
categories(first: 100) {
|
||||
nodes {
|
||||
title
|
||||
title(locales: "en")
|
||||
}
|
||||
}
|
||||
chapterCount
|
||||
@ -51,7 +51,7 @@ query ($slug: String!) {
|
||||
startCursor
|
||||
}
|
||||
}
|
||||
description
|
||||
description(locales: "en")
|
||||
startDate
|
||||
endDate
|
||||
mappings(first: 10) {
|
||||
@ -98,7 +98,7 @@ query ($slug: String!) {
|
||||
names {
|
||||
alternatives
|
||||
canonical
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
slug
|
||||
}
|
||||
@ -116,7 +116,7 @@ query ($slug: String!) {
|
||||
titles {
|
||||
canonical
|
||||
canonicalLocale
|
||||
localized
|
||||
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ query ($id: ID!) {
|
||||
}
|
||||
categories(first: 100) {
|
||||
nodes {
|
||||
title
|
||||
title(locales: "*")
|
||||
}
|
||||
}
|
||||
chapterCount
|
||||
@ -116,7 +116,7 @@ query ($id: ID!) {
|
||||
titles {
|
||||
canonical
|
||||
canonicalLocale
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
query ($slug: String!) {
|
||||
findPersonBySlug(slug: $slug) {
|
||||
id
|
||||
description
|
||||
description(locales: "en")
|
||||
birthday
|
||||
image {
|
||||
original {
|
||||
@ -20,7 +20,7 @@ query ($slug: String!) {
|
||||
names {
|
||||
alternatives
|
||||
canonical
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
mediaStaff(first: 100) {
|
||||
nodes {
|
||||
@ -47,7 +47,7 @@ query ($slug: String!) {
|
||||
titles {
|
||||
alternatives
|
||||
canonical
|
||||
localized
|
||||
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -91,7 +91,7 @@ query ($slug: String!) {
|
||||
}
|
||||
titles {
|
||||
canonical
|
||||
localized
|
||||
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ query ($type: MediaTypeEnum!) {
|
||||
}
|
||||
categories(first: 100) {
|
||||
nodes {
|
||||
title
|
||||
title(locales: "*")
|
||||
}
|
||||
}
|
||||
characters(first: 100) {
|
||||
@ -36,7 +36,7 @@ query ($type: MediaTypeEnum!) {
|
||||
names {
|
||||
alternatives
|
||||
canonical
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
image {
|
||||
original {
|
||||
@ -90,7 +90,7 @@ query ($type: MediaTypeEnum!) {
|
||||
names {
|
||||
alternatives
|
||||
canonical
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
slug
|
||||
}
|
||||
@ -108,7 +108,7 @@ query ($type: MediaTypeEnum!) {
|
||||
alternatives
|
||||
canonical
|
||||
canonicalLocale
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
...on Anime {
|
||||
episodeCount
|
||||
|
@ -19,7 +19,7 @@ query ($query: String!) {
|
||||
slug
|
||||
titles {
|
||||
canonical
|
||||
localized
|
||||
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||
alternatives
|
||||
}
|
||||
myLibraryEntry {
|
||||
|
@ -19,7 +19,7 @@ query ($query: String!) {
|
||||
slug
|
||||
titles {
|
||||
canonical
|
||||
localized
|
||||
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||
alternatives
|
||||
}
|
||||
myLibraryEntry {
|
||||
|
@ -18,8 +18,10 @@ query ($slug: String!) {
|
||||
}
|
||||
}
|
||||
birthday
|
||||
createdAt
|
||||
id
|
||||
location
|
||||
gender
|
||||
name
|
||||
proMessage
|
||||
proTier
|
||||
@ -52,7 +54,7 @@ query ($slug: String!) {
|
||||
}
|
||||
titles {
|
||||
canonical
|
||||
localized
|
||||
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||
}
|
||||
}
|
||||
...on Manga {
|
||||
@ -72,7 +74,7 @@ query ($slug: String!) {
|
||||
}
|
||||
titles {
|
||||
canonical
|
||||
localized
|
||||
localized(locales: ["en", "en-t-ja", "ja", "ja-jp"])
|
||||
}
|
||||
}
|
||||
...on Person {
|
||||
@ -88,11 +90,12 @@ query ($slug: String!) {
|
||||
width
|
||||
}
|
||||
}
|
||||
name,
|
||||
names {
|
||||
alternatives
|
||||
canonical
|
||||
canonicalLocale
|
||||
localized
|
||||
localized(locales: "*")
|
||||
},
|
||||
}
|
||||
...on Character {
|
||||
@ -107,12 +110,12 @@ query ($slug: String!) {
|
||||
height
|
||||
width
|
||||
}
|
||||
}
|
||||
},
|
||||
names {
|
||||
alternatives
|
||||
canonical
|
||||
canonicalLocale
|
||||
localized
|
||||
localized(locales: "*")
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -150,7 +153,7 @@ query ($slug: String!) {
|
||||
names {
|
||||
canonical
|
||||
alternatives
|
||||
localized
|
||||
localized(locales: "*")
|
||||
}
|
||||
}
|
||||
waifuOrHusbando
|
||||
|
@ -78,7 +78,7 @@ final class RequestBuilder extends APIRequestBuilder
|
||||
elseif ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL)
|
||||
{
|
||||
$token = $sessionSegment->get('auth_token');
|
||||
if ( ! (empty($token) || $cache->has(K::AUTH_TOKEN_CACHE_KEY)))
|
||||
if ( ! empty($token))
|
||||
{
|
||||
$cache->set(K::AUTH_TOKEN_CACHE_KEY, $token);
|
||||
}
|
||||
@ -239,43 +239,4 @@ final class RequestBuilder extends APIRequestBuilder
|
||||
'body' => $body,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a request
|
||||
*/
|
||||
private function request(string $type, string $url, array $options = []): array
|
||||
{
|
||||
$logger = $this->container->getLogger('kitsu-request');
|
||||
$response = $this->getResponse($type, $url, $options);
|
||||
$statusCode = $response->getStatus();
|
||||
|
||||
// Check for requests that are unauthorized
|
||||
if ($statusCode === 401 || $statusCode === 403)
|
||||
{
|
||||
Event::emit(EventType::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$rawBody = wait($response->getBody()->buffer());
|
||||
|
||||
// Any other type of failed request
|
||||
if ($statusCode > 299 || $statusCode < 200)
|
||||
{
|
||||
if ($logger !== NULL)
|
||||
{
|
||||
$logger->warning('Non 2xx response for api call', (array) $response);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Json::decode($rawBody);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// dump($e);
|
||||
dump($rawBody);
|
||||
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,6 @@ trait RequestBuilderTrait
|
||||
|
||||
/**
|
||||
* Set the request builder object
|
||||
*
|
||||
* @return ListItem|Model|RequestBuilderTrait
|
||||
*/
|
||||
public function setRequestBuilder(RequestBuilder $requestBuilder): self
|
||||
{
|
||||
|
@ -36,7 +36,8 @@ final class AnimeTransformer extends AbstractTransformer
|
||||
$characters = [];
|
||||
$links = [];
|
||||
$staff = [];
|
||||
$genres = array_map(static fn ($genre) => $genre['title']['en'], $base['categories']['nodes']);
|
||||
$rawGenres = array_filter($base['categories']['nodes'], static fn ($c) => $c !== NULL);
|
||||
$genres = array_map(static fn ($genre) => $genre['title']['en'], $rawGenres);
|
||||
|
||||
sort($genres);
|
||||
|
||||
@ -56,7 +57,7 @@ final class AnimeTransformer extends AbstractTransformer
|
||||
|
||||
$details = $rawCharacter['character'];
|
||||
$characters[$type][$details['id']] = [
|
||||
'image' => $details['image']['original']['url'] ?? '',
|
||||
'image' => Kitsu::getImage($details),
|
||||
'name' => $details['names']['canonical'],
|
||||
'slug' => $details['slug'],
|
||||
];
|
||||
@ -100,7 +101,7 @@ final class AnimeTransformer extends AbstractTransformer
|
||||
$staff[$role][$person['id']] = [
|
||||
'id' => $person['id'],
|
||||
'name' => $name,
|
||||
'image' => $person['image']['original']['url'],
|
||||
'image' => Kitsu::getImage($person),
|
||||
'slug' => $person['slug'],
|
||||
];
|
||||
|
||||
|
@ -49,7 +49,7 @@ final class CharacterTransformer extends AbstractTransformer
|
||||
'castings' => $castings,
|
||||
'description' => $data['description']['en'],
|
||||
'id' => $data['id'],
|
||||
'image' => $data['image']['original']['url'] ?? 'images/placeholder.png',
|
||||
'image' => Kitsu::getImage($data),
|
||||
'media' => $media,
|
||||
'name' => $name,
|
||||
'names' => $names,
|
||||
@ -130,7 +130,7 @@ final class CharacterTransformer extends AbstractTransformer
|
||||
'person' => [
|
||||
'id' => $voice['person']['id'],
|
||||
'slug' => $voice['person']['slug'],
|
||||
'image' => $voice['person']['image']['original']['url'],
|
||||
'image' => Kitsu::getImage($voice['person']),
|
||||
'name' => $voice['person']['name'],
|
||||
],
|
||||
'series' => [],
|
||||
|
@ -54,10 +54,10 @@ final class MangaTransformer extends AbstractTransformer
|
||||
}
|
||||
|
||||
$details = $rawCharacter['character'];
|
||||
if (array_key_exists($details['id'], $characters[$type]))
|
||||
if (array_key_exists($details['id'], (array)$characters[$type]))
|
||||
{
|
||||
$characters[$type][$details['id']] = [
|
||||
'image' => $details['image']['original']['url'],
|
||||
'image' => Kitsu::getImage($details),
|
||||
'name' => $details['names']['canonical'],
|
||||
'slug' => $details['slug'],
|
||||
];
|
||||
@ -103,7 +103,7 @@ final class MangaTransformer extends AbstractTransformer
|
||||
'id' => $person['id'],
|
||||
'slug' => $person['slug'],
|
||||
'name' => $name,
|
||||
'image' => $person['image']['original']['url'],
|
||||
'image' => Kitsu::getImage($person),
|
||||
];
|
||||
|
||||
usort($staff[$role], static fn ($a, $b) => $a['name'] <=> $b['name']);
|
||||
|
@ -35,7 +35,8 @@ final class PersonTransformer extends AbstractTransformer
|
||||
return Person::from([
|
||||
'id' => $data['id'],
|
||||
'name' => $canonicalName,
|
||||
'image' => $data['image']['original']['url'],
|
||||
'birthday' => $data['birthday'],
|
||||
'image' => Kitsu::getImage($data),
|
||||
'names' => array_diff($data['names']['localized'], [$canonicalName]),
|
||||
'description' => $data['description']['en'] ?? '',
|
||||
'characters' => $orgData['characters'],
|
||||
@ -97,7 +98,12 @@ final class PersonTransformer extends AbstractTransformer
|
||||
{
|
||||
foreach ($data['voices']['nodes'] as $voicing)
|
||||
{
|
||||
$character = $voicing['mediaCharacter']['character'];
|
||||
if ($voicing === NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$character = $voicing['mediaCharacter']['character'] ?? [];
|
||||
$charId = $character['id'];
|
||||
$rawMedia = $voicing['mediaCharacter']['media'];
|
||||
$role = strtolower($voicing['mediaCharacter']['role']);
|
||||
@ -123,7 +129,7 @@ final class PersonTransformer extends AbstractTransformer
|
||||
'character' => [
|
||||
'id' => $character['id'],
|
||||
'slug' => $character['slug'],
|
||||
'image' => $character['image']['original']['url'],
|
||||
'image' => Kitsu::getImage($character),
|
||||
'canonicalName' => $character['names']['canonical'],
|
||||
],
|
||||
'media' => [
|
||||
|
@ -14,11 +14,11 @@
|
||||
|
||||
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
|
||||
|
||||
use Aviat\AnimeClient\Kitsu;
|
||||
|
||||
use Aviat\AnimeClient\Types\User;
|
||||
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||
|
||||
use function Aviat\AnimeClient\{formatDate, friendlyTime, getDateDiff};
|
||||
|
||||
/**
|
||||
* Transform user profile data for display
|
||||
*
|
||||
@ -39,15 +39,22 @@ final class UserTransformer extends AbstractTransformer
|
||||
] : [];
|
||||
|
||||
return User::from([
|
||||
'about' => $base['about'],
|
||||
'avatar' => $base['avatarImage']['original']['url'],
|
||||
'about' => $base['about'] ?? '',
|
||||
'avatar' => $base['avatarImage']['original']['url'] ?? NULL,
|
||||
'birthday' => $base['birthday'] !== NULL
|
||||
? formatDate($base['birthday']) . ' (' .
|
||||
friendlyTime(getDateDiff($base['birthday']), 'year') . ')'
|
||||
: NULL,
|
||||
'joinDate' => formatDate($base['createdAt']) . ' (' .
|
||||
friendlyTime(getDateDiff($base['createdAt']), 'day') . ' ago)',
|
||||
'gender' => $base['gender'],
|
||||
'favorites' => $this->organizeFavorites($favorites),
|
||||
'location' => $base['location'],
|
||||
'name' => $base['name'],
|
||||
'slug' => $base['slug'],
|
||||
'stats' => $this->organizeStats($stats),
|
||||
'waifu' => $waifu,
|
||||
'website' => $base['siteLinks']['nodes'][0]['url'],
|
||||
'website' => $base['siteLinks']['nodes'][0]['url'] ?? NULL,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -81,7 +88,7 @@ final class UserTransformer extends AbstractTransformer
|
||||
if (array_key_exists('animeAmountConsumed', $stats))
|
||||
{
|
||||
$animeStats = [
|
||||
'Time spent watching anime:' => Kitsu::friendlyTime($stats['animeAmountConsumed']['time']),
|
||||
'Time spent watching anime:' => friendlyTime($stats['animeAmountConsumed']['time']),
|
||||
'Anime series watched:' => number_format($stats['animeAmountConsumed']['media']),
|
||||
'Anime episodes watched:' => number_format($stats['animeAmountConsumed']['units']),
|
||||
];
|
||||
|
8
src/AnimeClient/API/Kitsu/graphql.config.yml
Normal file
8
src/AnimeClient/API/Kitsu/graphql.config.yml
Normal file
@ -0,0 +1,8 @@
|
||||
schema: schema.graphql
|
||||
extensions:
|
||||
endpoints:
|
||||
Kitsu:
|
||||
url: https://kitsu.io/api/graphql
|
||||
headers:
|
||||
user-agent: JS GraphQL
|
||||
introspect: true
|
@ -35,7 +35,7 @@ final class ParallelAPIRequest
|
||||
/**
|
||||
* Add a request
|
||||
*/
|
||||
public function addRequest(string|Request $request, string|int|NULL $key = NULL): self
|
||||
public function addRequest(string|Request $request, string|int|null $key = NULL): self
|
||||
{
|
||||
if ($key !== NULL)
|
||||
{
|
||||
@ -56,7 +56,7 @@ final class ParallelAPIRequest
|
||||
*/
|
||||
public function addRequests(array $requests): self
|
||||
{
|
||||
array_walk($requests, [$this, 'addRequest']);
|
||||
array_walk($requests, $this->addRequest(...));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ namespace Aviat\AnimeClient;
|
||||
use Amp\Http\Client\{HttpClient, HttpClientBuilder, Request, Response};
|
||||
|
||||
use Aviat\Ion\{ConfigInterface, ImageBuilder};
|
||||
use DateTimeImmutable;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Throwable;
|
||||
|
||||
@ -25,13 +26,17 @@ use Yosymfony\Toml\{Toml, TomlBuilder};
|
||||
use function Amp\Promise\wait;
|
||||
use function Aviat\Ion\_dir;
|
||||
|
||||
const SECONDS_IN_MINUTE = 60;
|
||||
const MINUTES_IN_HOUR = 60;
|
||||
const MINUTES_IN_DAY = 1440;
|
||||
const MINUTES_IN_YEAR = 525_600;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
//! TOML Functions
|
||||
// ----------------------------------------------------------------------------
|
||||
/**
|
||||
* Load configuration options from .toml files
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $path - Path to load config
|
||||
*/
|
||||
function loadConfig(string $path): array
|
||||
@ -72,8 +77,6 @@ function loadConfig(string $path): array
|
||||
|
||||
/**
|
||||
* Load config from one specific TOML file
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
function loadTomlFile(string $filename): array
|
||||
{
|
||||
@ -131,19 +134,6 @@ function tomlToArray(string $toml): array
|
||||
//! Misc Functions
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
if ( ! function_exists('array_is_list'))
|
||||
{
|
||||
/**
|
||||
* Polyfill for PHP 8
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.array-is-list
|
||||
*/
|
||||
function array_is_list(array $a): bool
|
||||
{
|
||||
return $a === [] || (array_keys($a) === range(0, count($a) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the array sequential, not associative?
|
||||
*/
|
||||
@ -256,8 +246,6 @@ function getLocalImg(string $kitsuUrl, bool $webp = TRUE): string
|
||||
|
||||
/**
|
||||
* Create a transparent placeholder image
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
function createPlaceholderImage(string $path, int $width = 200, int $height = 200, string $text = 'Image Unavailable'): bool
|
||||
{
|
||||
@ -303,15 +291,13 @@ function clearCache(CacheInterface $cache): bool
|
||||
|
||||
$cleared = $cache->clear();
|
||||
|
||||
$saved = (empty($userData)) ? TRUE : $cache->setMultiple($userData);
|
||||
$saved = empty($userData) || $cache->setMultiple($userData);
|
||||
|
||||
return $cleared && $saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a PHP code template as a string
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
function renderTemplate(string $path, array $data): string
|
||||
{
|
||||
@ -322,3 +308,87 @@ function renderTemplate(string $path, array $data): string
|
||||
|
||||
return (is_string($rawOutput)) ? $rawOutput : '';
|
||||
}
|
||||
|
||||
function formatDate(string $date): string
|
||||
{
|
||||
$date = new DateTimeImmutable($date);
|
||||
|
||||
return $date->format('F d, Y');
|
||||
}
|
||||
|
||||
function getDateDiff(string $date): int
|
||||
{
|
||||
$now = new DateTimeImmutable();
|
||||
$then = new DateTimeImmutable($date);
|
||||
|
||||
$interval = $now->diff($then, TRUE);
|
||||
|
||||
$years = $interval->y * SECONDS_IN_MINUTE * MINUTES_IN_YEAR;
|
||||
$days = $interval->d * SECONDS_IN_MINUTE * MINUTES_IN_DAY;
|
||||
$hours = $interval->h * SECONDS_IN_MINUTE * MINUTES_IN_HOUR;
|
||||
$minutes = $interval->i * SECONDS_IN_MINUTE;
|
||||
$seconds = $interval->s;
|
||||
|
||||
return $years + $days + $hours + $minutes + $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a time in seconds to a more human-readable format
|
||||
*/
|
||||
function friendlyTime(int $seconds, string $minUnit = 'second'): string
|
||||
{
|
||||
// All the seconds left
|
||||
$remSeconds = $seconds % SECONDS_IN_MINUTE;
|
||||
$minutes = ($seconds - $remSeconds) / SECONDS_IN_MINUTE;
|
||||
|
||||
// Minutes short of a year
|
||||
$years = (int) floor($minutes / MINUTES_IN_YEAR);
|
||||
$minutes %= MINUTES_IN_YEAR;
|
||||
|
||||
// Minutes short of a day
|
||||
$extraMinutes = $minutes % MINUTES_IN_DAY;
|
||||
$days = ($minutes - $extraMinutes) / MINUTES_IN_DAY;
|
||||
|
||||
// Minutes short of an hour
|
||||
$remMinutes = $extraMinutes % MINUTES_IN_HOUR;
|
||||
$hours = ($extraMinutes - $remMinutes) / MINUTES_IN_HOUR;
|
||||
|
||||
$parts = [];
|
||||
|
||||
foreach ([
|
||||
'year' => $years,
|
||||
'day' => $days,
|
||||
'hour' => $hours,
|
||||
'minute' => $remMinutes,
|
||||
'second' => $remSeconds,
|
||||
] as $label => $value)
|
||||
{
|
||||
if ($value === 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value > 1)
|
||||
{
|
||||
$label .= 's';
|
||||
}
|
||||
|
||||
$parts[] = "{$value} {$label}";
|
||||
|
||||
if ($label === $minUnit || $label === $minUnit . 's')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$last = array_pop($parts);
|
||||
|
||||
if (empty($parts))
|
||||
{
|
||||
return $last ?? '';
|
||||
}
|
||||
|
||||
return (count($parts) > 1)
|
||||
? implode(', ', $parts) . ", and {$last}"
|
||||
: "{$parts[0]}, {$last}";
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ abstract class BaseCommand extends Command
|
||||
/**
|
||||
* Echo text in a box
|
||||
*/
|
||||
public function echoBox(string|array $message, string|int|NULL $fgColor = NULL, string|int|NULL $bgColor = NULL): void
|
||||
public function echoBox(string|array $message, string|int|null $fgColor = NULL, string|int|null $bgColor = NULL): void
|
||||
{
|
||||
if (is_array($message))
|
||||
{
|
||||
@ -131,7 +131,7 @@ abstract class BaseCommand extends Command
|
||||
return $this->_di($configArray, $APP_DIR);
|
||||
}
|
||||
|
||||
private function _line(string $message, int|string|NULL $fgColor = NULL, int|string|NULL $bgColor = NULL): void
|
||||
private function _line(string $message, int|string|null $fgColor = NULL, int|string|null $bgColor = NULL): void
|
||||
{
|
||||
if ($fgColor !== NULL)
|
||||
{
|
||||
|
@ -98,21 +98,23 @@ final class SyncLists extends BaseCommand
|
||||
if ( ! $anilistEnabled)
|
||||
{
|
||||
$this->echoErrorBox('Anlist API is not enabled. Can not sync.');
|
||||
return false;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Authentication is required to update Kitsu
|
||||
$isKitsuAuthenticated = $this->container->get('auth')->isAuthenticated();
|
||||
if ( !$isKitsuAuthenticated)
|
||||
if ( ! $isKitsuAuthenticated)
|
||||
{
|
||||
$this->echoErrorBox('Kitsu is not authenticated. Kitsu list can not be updated.');
|
||||
return false;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->anilistModel = $this->container->get('anilist-model');
|
||||
$this->kitsuModel = $this->container->get('kitsu-model');
|
||||
|
||||
return true;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -148,7 +150,7 @@ final class SyncLists extends BaseCommand
|
||||
*/
|
||||
protected function fetch(string $type): array
|
||||
{
|
||||
$this->echo("Fetching $type List Data");
|
||||
$this->echo("Fetching {$type} List Data");
|
||||
$progress = new Widgets\ProgressBar($this->getConsole(), 2, 50, FALSE);
|
||||
|
||||
$anilist = $this->fetchAnilist($type);
|
||||
|
@ -42,6 +42,11 @@ class Controller
|
||||
{
|
||||
use ContainerAware;
|
||||
|
||||
/**
|
||||
* The global configuration object
|
||||
*/
|
||||
public ConfigInterface $config;
|
||||
|
||||
/**
|
||||
* The authentication object
|
||||
*/
|
||||
@ -52,11 +57,6 @@ class Controller
|
||||
*/
|
||||
protected CacheInterface $cache;
|
||||
|
||||
/**
|
||||
* The global configuration object
|
||||
*/
|
||||
public ConfigInterface $config;
|
||||
|
||||
/**
|
||||
* Request object
|
||||
*/
|
||||
@ -123,10 +123,10 @@ class Controller
|
||||
/**
|
||||
* Set the current url in the session as the target of a future redirect
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @throws ContainerException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
public function setSessionRedirect(?string $url = NULL): void
|
||||
{
|
||||
$serverParams = $this->request->getServerParams();
|
||||
@ -163,9 +163,9 @@ class Controller
|
||||
*
|
||||
* If one is not set, redirect to default url
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
public function sessionRedirect(): void
|
||||
{
|
||||
$target = $this->session->get('redirect_url') ?? '/';
|
||||
@ -176,8 +176,8 @@ class Controller
|
||||
|
||||
/**
|
||||
* Check if the current user is authenticated, else error and exit
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
protected function checkAuth(): void
|
||||
{
|
||||
if ( ! $this->auth->isAuthenticated())
|
||||
@ -192,9 +192,8 @@ class Controller
|
||||
|
||||
/**
|
||||
* Get the string output of a partial template
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
protected function loadPartial(HtmlView $view, string $template, array $data = []): string
|
||||
{
|
||||
$router = $this->container->get('dispatcher');
|
||||
@ -219,9 +218,8 @@ class Controller
|
||||
|
||||
/**
|
||||
* Render a template with header and footer
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
protected function renderFullPage(HtmlView $view, string $template, array $data): HtmlView
|
||||
{
|
||||
$csp = [
|
||||
@ -247,13 +245,13 @@ class Controller
|
||||
/**
|
||||
* 404 action
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
public function notFound(
|
||||
string $title = 'Sorry, page not found',
|
||||
string $message = 'Page Not Found'
|
||||
): void {
|
||||
): never {
|
||||
$this->outputHTML('404', [
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
@ -265,9 +263,9 @@ class Controller
|
||||
/**
|
||||
* Display a generic error page
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
public function errorPage(int $httpCode, string $title, string $message, string $longMessage = ''): void
|
||||
{
|
||||
$this->outputHTML('error', [
|
||||
@ -280,9 +278,9 @@ class Controller
|
||||
/**
|
||||
* Redirect to the default controller/url from an empty path
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
public function redirectToDefaultRoute(): void
|
||||
{
|
||||
$defaultType = $this->config->get('default_list');
|
||||
@ -292,9 +290,8 @@ class Controller
|
||||
/**
|
||||
* Set a session flash variable to display a message on
|
||||
* next page load
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
public function setFlashMessage(string $message, string $type = 'info'): void
|
||||
{
|
||||
static $messages;
|
||||
@ -325,9 +322,9 @@ class Controller
|
||||
/**
|
||||
* Add a message box to the page
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
protected function showMessage(HtmlView $view, string $type, string $message): string
|
||||
{
|
||||
return $this->loadPartial($view, 'message', [
|
||||
@ -339,9 +336,9 @@ class Controller
|
||||
/**
|
||||
* Output a template to HTML, using the provided data
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
protected function outputHTML(string $template, array $data = [], ?HtmlView $view = NULL, int $code = 200): void
|
||||
{
|
||||
if (NULL === $view)
|
||||
@ -356,10 +353,10 @@ class Controller
|
||||
/**
|
||||
* Output a JSON Response
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param int $code - the http status code
|
||||
* @throws DoubleRenderException
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
protected function outputJSON(mixed $data, int $code): void
|
||||
{
|
||||
JsonView::new()
|
||||
@ -370,9 +367,8 @@ class Controller
|
||||
|
||||
/**
|
||||
* Redirect to the selected page
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
protected function redirect(string $url, int $code): void
|
||||
{
|
||||
HttpView::new()
|
||||
|
@ -21,8 +21,7 @@ use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\AnimeClient\Model\Anime as AnimeModel;
|
||||
use Aviat\AnimeClient\Types\FormItem;
|
||||
use Aviat\Ion\Attribute\Controller;
|
||||
use Aviat\Ion\Attribute\Route;
|
||||
use Aviat\Ion\Attribute\{Controller, Route};
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||
use Aviat\Ion\Json;
|
||||
@ -307,8 +306,6 @@ final class Anime extends BaseController
|
||||
'Anime not found',
|
||||
'Anime Not Found'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->outputHTML('anime/details', [
|
||||
@ -346,8 +343,6 @@ final class Anime extends BaseController
|
||||
'Anime not found',
|
||||
'Anime Not Found'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->outputHTML('anime/details', [
|
||||
|
@ -20,11 +20,9 @@ use Aviat\AnimeClient\Model\{
|
||||
Anime as AnimeModel,
|
||||
AnimeCollection as AnimeCollectionModel
|
||||
};
|
||||
use Aviat\Ion\Attribute\Controller;
|
||||
use Aviat\Ion\Attribute\Route;
|
||||
use Aviat\Ion\Attribute\{Controller, Route};
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||
use Aviat\Ion\Json;
|
||||
use Aviat\Ion\Exception\DoubleRenderException;
|
||||
|
||||
use InvalidArgumentException;
|
||||
@ -114,7 +112,6 @@ final class AnimeCollection extends BaseController
|
||||
/**
|
||||
* Show the anime collection add/edit form
|
||||
*
|
||||
* @param int|null $id
|
||||
* @throws ContainerException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
|
@ -18,8 +18,7 @@ use Aviat\AnimeClient\API\Kitsu\Model;
|
||||
use Aviat\AnimeClient\API\Kitsu\Transformer\CharacterTransformer;
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
|
||||
use Aviat\Ion\Attribute\Controller;
|
||||
use Aviat\Ion\Attribute\Route;
|
||||
use Aviat\Ion\Attribute\{Controller, Route};
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||
|
||||
@ -60,8 +59,6 @@ final class Character extends BaseController
|
||||
),
|
||||
'Character Not Found'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$data = (new CharacterTransformer())->transform($rawData)->toArray();
|
||||
|
@ -14,9 +14,8 @@
|
||||
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\Ion\Attribute\Controller;
|
||||
use Aviat\Ion\Attribute\Route;
|
||||
use Aviat\AnimeClient\{Controller as BaseController, Model};
|
||||
use Aviat\Ion\Attribute\{Controller, Route};
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||
|
||||
|
@ -15,8 +15,7 @@
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\Ion\Attribute\Controller;
|
||||
use Aviat\Ion\Attribute\Route;
|
||||
use Aviat\Ion\Attribute\{Controller, Route};
|
||||
use Throwable;
|
||||
use function Amp\Promise\wait;
|
||||
use function Aviat\AnimeClient\{createPlaceholderImage, getResponse};
|
||||
@ -97,7 +96,7 @@ final class Images extends BaseController
|
||||
|
||||
$kitsuUrl .= $imageType['kitsuUrl'];
|
||||
$width = $imageType['width'];
|
||||
$height = $imageType['height'];
|
||||
$height = $imageType['height'] ?? 225;
|
||||
$filePrefix = "{$baseSavePath}/{$type}/{$id}";
|
||||
|
||||
$response = getResponse($kitsuUrl);
|
||||
@ -121,11 +120,11 @@ final class Images extends BaseController
|
||||
|
||||
if ($display)
|
||||
{
|
||||
$this->getPlaceholder("{$baseSavePath}/{$type}", $width, $height);
|
||||
$this->getPlaceholder("{$baseSavePath}/{$type}", $width ?? 225, $height);
|
||||
}
|
||||
else
|
||||
{
|
||||
createPlaceholderImage("{$baseSavePath}/{$type}", $width, $height);
|
||||
createPlaceholderImage("{$baseSavePath}/{$type}", $width ?? 225, $height);
|
||||
}
|
||||
|
||||
return;
|
||||
@ -133,7 +132,13 @@ final class Images extends BaseController
|
||||
|
||||
$data = wait($response->getBody()->buffer());
|
||||
|
||||
[$origWidth] = getimagesizefromstring($data);
|
||||
$size = getimagesizefromstring($data);
|
||||
if ($size === FALSE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
[$origWidth] = $size;
|
||||
$gdImg = imagecreatefromstring($data);
|
||||
if ($gdImg === FALSE)
|
||||
{
|
||||
@ -183,15 +188,15 @@ final class Images extends BaseController
|
||||
/**
|
||||
* Get a placeholder for a missing image
|
||||
*/
|
||||
private function getPlaceholder(string $path, ?int $width = 200, ?int $height = NULL): void
|
||||
private function getPlaceholder(string $path, ?int $width = NULL, ?int $height = NULL): void
|
||||
{
|
||||
$height ??= $width;
|
||||
$height ??= $width ?? 200;
|
||||
|
||||
$filename = $path . '/placeholder.png';
|
||||
|
||||
if ( ! file_exists($path . '/placeholder.png'))
|
||||
{
|
||||
createPlaceholderImage($path, $width, $height);
|
||||
createPlaceholderImage($path, $width ?? 200, $height);
|
||||
}
|
||||
|
||||
header('Content-Type: image/png');
|
||||
|
@ -14,21 +14,16 @@
|
||||
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aura\Router\Exception\RouteNotFound;
|
||||
use Aviat\AnimeClient\API\Kitsu\Transformer\MangaListTransformer;
|
||||
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\AnimeClient\Model\Manga as MangaModel;
|
||||
use Aviat\AnimeClient\Types\FormItem;
|
||||
use Aviat\Ion\Attribute\Controller;
|
||||
use Aviat\Ion\Attribute\Route;
|
||||
use Aviat\Ion\Attribute\{Controller, Route};
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||
use Aviat\Ion\Json;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Controller for manga list
|
||||
*/
|
||||
@ -282,8 +277,6 @@ final class Manga extends BaseController
|
||||
'Manga not found',
|
||||
'Manga Not Found'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->outputHTML('manga/details', [
|
||||
@ -311,8 +304,6 @@ final class Manga extends BaseController
|
||||
'Manga not found',
|
||||
'Manga Not Found'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->outputHTML('manga/details', [
|
||||
|
@ -15,12 +15,10 @@
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\AnimeClient\API\Kitsu\Model;
|
||||
use Aviat\AnimeClient\API\Kitsu\Transformer\CharacterTransformer;
|
||||
use Aviat\AnimeClient\API\Kitsu\Transformer\PersonTransformer;
|
||||
use Aviat\AnimeClient\API\Kitsu\Transformer\{CharacterTransformer, PersonTransformer};
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\AnimeClient\Enum\EventType;
|
||||
use Aviat\Ion\Attribute\DefaultController;
|
||||
use Aviat\Ion\Attribute\Route;
|
||||
use Aviat\Ion\Attribute\{DefaultController, Route};
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Event;
|
||||
use Aviat\Ion\View\HtmlView;
|
||||
@ -103,11 +101,7 @@ final class Misc extends BaseController
|
||||
}
|
||||
|
||||
$this->setFlashMessage('Invalid username or password.');
|
||||
|
||||
$redirectUrl = $this->url->generate('login');
|
||||
$redirectUrl = ($redirectUrl !== FALSE) ? $redirectUrl : '';
|
||||
|
||||
$this->redirect($redirectUrl, 303);
|
||||
$this->redirect($this->url->generate('login'), 303);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,8 +141,6 @@ final class Misc extends BaseController
|
||||
),
|
||||
'Character Not Found'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$data = (new CharacterTransformer())->transform($rawData)->toArray();
|
||||
@ -180,8 +172,6 @@ final class Misc extends BaseController
|
||||
),
|
||||
'Person Not Found'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->outputHTML('person/details', [
|
||||
|
@ -18,8 +18,7 @@ use Aviat\AnimeClient\API\Kitsu\Model;
|
||||
use Aviat\AnimeClient\API\Kitsu\Transformer\PersonTransformer;
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
|
||||
use Aviat\Ion\Attribute\Controller;
|
||||
use Aviat\Ion\Attribute\Route;
|
||||
use Aviat\Ion\Attribute\{Controller, Route};
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||
|
||||
@ -61,8 +60,6 @@ final class People extends BaseController
|
||||
),
|
||||
'Person Not Found'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->outputHTML('person/details', [
|
||||
|
@ -18,8 +18,7 @@ use Aura\Router\Exception\RouteNotFound;
|
||||
use Aviat\AnimeClient\API\Anilist\Model as AnilistModel;
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\AnimeClient\Model\Settings as SettingsModel;
|
||||
use Aviat\Ion\Attribute\Controller;
|
||||
use Aviat\Ion\Attribute\Route;
|
||||
use Aviat\Ion\Attribute\{Controller, Route};
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||
|
||||
@ -87,10 +86,7 @@ final class Settings extends BaseController
|
||||
? $this->setFlashMessage('Saved config settings.', 'success')
|
||||
: $this->setFlashMessage('Failed to save config file.', 'error');
|
||||
|
||||
$redirectUrl = $this->url->generate('settings');
|
||||
$redirectUrl = ($redirectUrl !== FALSE) ? $redirectUrl : '';
|
||||
|
||||
$this->redirect($redirectUrl, 303);
|
||||
$this->redirect($this->url->generate('settings'), 303);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -153,9 +149,6 @@ final class Settings extends BaseController
|
||||
? $this->setFlashMessage('Linked Anilist Account', 'success')
|
||||
: $this->setFlashMessage('Error Linking Anilist Account', 'error');
|
||||
|
||||
$redirectUrl = $this->url->generate('settings');
|
||||
$redirectUrl = ($redirectUrl !== FALSE) ? $redirectUrl : '';
|
||||
|
||||
$this->redirect($redirectUrl, 303);
|
||||
$this->redirect($this->url->generate('settings'), 303);
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,7 @@ use Aviat\AnimeClient\API\Kitsu\Model;
|
||||
use Aviat\AnimeClient\API\Kitsu\Transformer\UserTransformer;
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
|
||||
use Aviat\Ion\Attribute\Controller;
|
||||
use Aviat\Ion\Attribute\Route;
|
||||
use Aviat\Ion\Attribute\{Controller, Route};
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||
|
||||
@ -70,6 +69,11 @@ final class User extends BaseController
|
||||
: $username;
|
||||
|
||||
$rawData = $this->kitsuModel->getUserData($username);
|
||||
if ($rawData['data']['findProfileBySlug'] === NULL)
|
||||
{
|
||||
$this->notFound('Sorry, user not found', "The user '{$username}' does not seem to exist.");
|
||||
}
|
||||
|
||||
$data = (new UserTransformer())->transform($rawData)->toArray();
|
||||
|
||||
$this->outputHTML('user/details', [
|
||||
|
@ -69,6 +69,44 @@ final class Dispatcher extends RoutingBase
|
||||
$this->outputRoutes = $this->setupRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the current route
|
||||
*
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function __invoke(?object $route = NULL): void
|
||||
{
|
||||
$logger = $this->container->getLogger();
|
||||
|
||||
if ($route === NULL)
|
||||
{
|
||||
$route = $this->getRoute();
|
||||
|
||||
$logger?->info('Dispatcher - Route invoke arguments');
|
||||
$logger?->info(print_r($route, TRUE));
|
||||
}
|
||||
|
||||
if ( ! $route)
|
||||
{
|
||||
// If not route was matched, return an appropriate http
|
||||
// error message
|
||||
$errorRoute = $this->getErrorParams();
|
||||
$controllerName = DEFAULT_CONTROLLER;
|
||||
$actionMethod = $errorRoute['action_method'];
|
||||
$params = $errorRoute['params'];
|
||||
$this->call($controllerName, $actionMethod, $params);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$parsed = $this->processRoute(new Friend($route));
|
||||
$controllerName = $parsed['controller_name'];
|
||||
$actionMethod = $parsed['action_method'];
|
||||
$params = $parsed['params'];
|
||||
|
||||
$this->call($controllerName, $actionMethod, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current route object, if one matches
|
||||
*/
|
||||
@ -100,47 +138,6 @@ final class Dispatcher extends RoutingBase
|
||||
return $this->outputRoutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the current route
|
||||
*
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function __invoke(?object $route = NULL): void
|
||||
{
|
||||
$logger = $this->container->getLogger();
|
||||
|
||||
if ($route === NULL)
|
||||
{
|
||||
$route = $this->getRoute();
|
||||
|
||||
if ($logger !== NULL)
|
||||
{
|
||||
$logger->info('Dispatcher - Route invoke arguments');
|
||||
$logger->info(print_r($route, TRUE));
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $route)
|
||||
{
|
||||
// If not route was matched, return an appropriate http
|
||||
// error message
|
||||
$errorRoute = $this->getErrorParams();
|
||||
$controllerName = DEFAULT_CONTROLLER;
|
||||
$actionMethod = $errorRoute['action_method'];
|
||||
$params = $errorRoute['params'];
|
||||
$this->call($controllerName, $actionMethod, $params);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$parsed = $this->processRoute(new Friend($route));
|
||||
$controllerName = $parsed['controller_name'];
|
||||
$actionMethod = $parsed['action_method'];
|
||||
$params = $parsed['params'];
|
||||
|
||||
$this->call($controllerName, $actionMethod, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse out the arguments for the appropriate controller for
|
||||
* the current route
|
||||
@ -183,10 +180,7 @@ final class Dispatcher extends RoutingBase
|
||||
}
|
||||
|
||||
$logger = $this->container->getLogger();
|
||||
if ($logger !== NULL)
|
||||
{
|
||||
$logger->info(Json::encode($params));
|
||||
}
|
||||
$logger?->info(Json::encode($params));
|
||||
|
||||
return [
|
||||
'controller_name' => $controllerName,
|
||||
@ -208,10 +202,7 @@ final class Dispatcher extends RoutingBase
|
||||
$controller = reset($segments);
|
||||
|
||||
$logger = $this->container->getLogger();
|
||||
if ($logger !== NULL)
|
||||
{
|
||||
$logger->info('Controller: ' . $controller);
|
||||
}
|
||||
$logger?->info('Controller: ' . $controller);
|
||||
|
||||
if (empty($controller))
|
||||
{
|
||||
@ -224,7 +215,7 @@ final class Dispatcher extends RoutingBase
|
||||
/**
|
||||
* Get the list of controllers in the default namespace
|
||||
*
|
||||
* @return mixed[]
|
||||
* @return array
|
||||
*/
|
||||
public function getControllerList(): array
|
||||
{
|
||||
@ -300,7 +291,6 @@ final class Dispatcher extends RoutingBase
|
||||
/**
|
||||
* Get the appropriate params for the error page
|
||||
* passed on the failed route
|
||||
* @return mixed[][]
|
||||
*/
|
||||
protected function getErrorParams(): array
|
||||
{
|
||||
@ -317,7 +307,8 @@ final class Dispatcher extends RoutingBase
|
||||
|
||||
$params = [];
|
||||
|
||||
switch ($failure->failedRule) {
|
||||
switch ($failure?->failedRule)
|
||||
{
|
||||
case Rule\Allows::class:
|
||||
$params = [
|
||||
'http_code' => 405,
|
||||
@ -348,8 +339,6 @@ final class Dispatcher extends RoutingBase
|
||||
|
||||
/**
|
||||
* Select controller based on the current url, and apply its relevant routes
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
protected function setupRoutes(): array
|
||||
{
|
||||
|
@ -31,10 +31,6 @@ final class Kitsu
|
||||
public const ANIME_HISTORY_LIST_CACHE_KEY = 'kitsu-anime-history-list';
|
||||
public const MANGA_HISTORY_LIST_CACHE_KEY = 'kitsu-manga-history-list';
|
||||
public const GRAPHQL_ENDPOINT = 'https://kitsu.io/api/graphql';
|
||||
public const SECONDS_IN_MINUTE = 60;
|
||||
public const MINUTES_IN_HOUR = 60;
|
||||
public const MINUTES_IN_DAY = 1440;
|
||||
public const MINUTES_IN_YEAR = 525_600;
|
||||
|
||||
/**
|
||||
* Determine whether an anime is airing, finished airing, or has not yet aired
|
||||
@ -72,18 +68,18 @@ final class Kitsu
|
||||
}
|
||||
|
||||
$monthMap = [
|
||||
'01' => 'Jan',
|
||||
'02' => 'Feb',
|
||||
'03' => 'Mar',
|
||||
'04' => 'Apr',
|
||||
'01' => 'January',
|
||||
'02' => 'February',
|
||||
'03' => 'March',
|
||||
'04' => 'April',
|
||||
'05' => 'May',
|
||||
'06' => 'Jun',
|
||||
'07' => 'Jul',
|
||||
'08' => 'Aug',
|
||||
'09' => 'Sep',
|
||||
'10' => 'Oct',
|
||||
'11' => 'Nov',
|
||||
'12' => 'Dec',
|
||||
'06' => 'June',
|
||||
'07' => 'July',
|
||||
'08' => 'August',
|
||||
'09' => 'September',
|
||||
'10' => 'October',
|
||||
'11' => 'November',
|
||||
'12' => 'December',
|
||||
];
|
||||
|
||||
[$startYear, $startMonth, $startDay] = explode('-', $startDate);
|
||||
@ -305,7 +301,16 @@ final class Kitsu
|
||||
{
|
||||
// Really don't care about languages that aren't english
|
||||
// or Japanese for titles
|
||||
if ( ! in_array($locale, ['en', 'en_us', 'en_jp', 'ja_jp'], TRUE))
|
||||
if ( ! in_array($locale, [
|
||||
'en',
|
||||
'en-jp',
|
||||
'en-us',
|
||||
'en_jp',
|
||||
'en_us',
|
||||
'ja-jp',
|
||||
'ja_jp',
|
||||
'jp',
|
||||
], TRUE))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -326,15 +331,29 @@ final class Kitsu
|
||||
/**
|
||||
* Get the url of the posterImage from Kitsu, with fallbacks
|
||||
*/
|
||||
public static function getPosterImage(array $base, int $size = 1): string
|
||||
public static function getPosterImage(array $base, int $sizeId = 1): string
|
||||
{
|
||||
$rawUrl = $base['posterImage']['views'][$size]['url']
|
||||
$rawUrl = $base['posterImage']['views'][$sizeId]['url']
|
||||
?? $base['posterImage']['original']['url']
|
||||
?? '/public/images/placeholder.png';
|
||||
|
||||
$parts = explode('?', $rawUrl);
|
||||
|
||||
return (empty($parts)) ? $rawUrl : $parts[0];
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url of the image from Kitsu, with fallbacks
|
||||
*/
|
||||
public static function getImage(array $base, int $sizeId = 1): string
|
||||
{
|
||||
$rawUrl = $base['image']['original']['url']
|
||||
?? $base['image']['views'][$sizeId]['url']
|
||||
?? '/public/images/placeholder.png';
|
||||
|
||||
$parts = explode('?', $rawUrl);
|
||||
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -418,62 +437,6 @@ final class Kitsu
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a time in seconds to a more human-readable format
|
||||
*/
|
||||
public static function friendlyTime(int $seconds): string
|
||||
{
|
||||
// All the seconds left
|
||||
$remSeconds = $seconds % self::SECONDS_IN_MINUTE;
|
||||
$minutes = ($seconds - $remSeconds) / self::SECONDS_IN_MINUTE;
|
||||
|
||||
// Minutes short of a year
|
||||
$years = (int) floor($minutes / self::MINUTES_IN_YEAR);
|
||||
$minutes %= self::MINUTES_IN_YEAR;
|
||||
|
||||
// Minutes short of a day
|
||||
$extraMinutes = $minutes % self::MINUTES_IN_DAY;
|
||||
$days = ($minutes - $extraMinutes) / self::MINUTES_IN_DAY;
|
||||
|
||||
// Minutes short of an hour
|
||||
$remMinutes = $extraMinutes % self::MINUTES_IN_HOUR;
|
||||
$hours = ($extraMinutes - $remMinutes) / self::MINUTES_IN_HOUR;
|
||||
|
||||
$parts = [];
|
||||
|
||||
foreach ([
|
||||
'year' => $years,
|
||||
'day' => $days,
|
||||
'hour' => $hours,
|
||||
'minute' => $remMinutes,
|
||||
'second' => $remSeconds,
|
||||
] as $label => $value)
|
||||
{
|
||||
if ($value === 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value > 1)
|
||||
{
|
||||
$label .= 's';
|
||||
}
|
||||
|
||||
$parts[] = "{$value} {$label}";
|
||||
}
|
||||
|
||||
$last = array_pop($parts);
|
||||
|
||||
if (empty($parts))
|
||||
{
|
||||
return $last ?? '';
|
||||
}
|
||||
|
||||
return (count($parts) > 1)
|
||||
? implode(', ', $parts) . ", and {$last}"
|
||||
: "{$parts[0]}, {$last}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an alternate title is unique enough to list
|
||||
*/
|
||||
@ -486,7 +449,7 @@ final class Kitsu
|
||||
|
||||
foreach ($existingTitles as $existing)
|
||||
{
|
||||
$isSubset = mb_substr_count($existing, $title) > 0;
|
||||
$isSubset = mb_substr_count(mb_strtolower($existing), mb_strtolower($title)) > 0;
|
||||
$diff = levenshtein(mb_strtolower($existing), mb_strtolower($title));
|
||||
|
||||
if ($diff <= 4 || $isSubset || mb_strlen($title) > 45 || mb_strlen($existing) > 50)
|
||||
|
@ -36,6 +36,19 @@ final class MenuGenerator extends UrlGenerator
|
||||
*/
|
||||
protected ServerRequestInterface $request;
|
||||
|
||||
/**
|
||||
* MenuGenerator constructor.
|
||||
*
|
||||
* @throws ContainerException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
private function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->helper = $container->get('html-helper');
|
||||
$this->request = $container->get('request');
|
||||
}
|
||||
|
||||
public static function new(ContainerInterface $container): self
|
||||
{
|
||||
return new self($container);
|
||||
@ -80,19 +93,6 @@ final class MenuGenerator extends UrlGenerator
|
||||
return (string) $this->helper->ul();
|
||||
}
|
||||
|
||||
/**
|
||||
* MenuGenerator constructor.
|
||||
*
|
||||
* @throws ContainerException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
private function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->helper = $container->get('html-helper');
|
||||
$this->request = $container->get('request');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the full menu structure from the config files
|
||||
*
|
||||
|
@ -91,7 +91,7 @@ final class AnimeCollection extends Collection
|
||||
$genres = $this->getGenreList();
|
||||
$media = $this->getMediaList();
|
||||
|
||||
if ($rows === FALSE)
|
||||
if (empty($rows))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
@ -133,7 +133,7 @@ final class AnimeCollection extends Collection
|
||||
->get();
|
||||
|
||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($rows === FALSE)
|
||||
if (empty($rows))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
@ -349,7 +349,7 @@ final class AnimeCollection extends Collection
|
||||
->get()
|
||||
->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($mediaRows === FALSE)
|
||||
if (empty($mediaRows))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
@ -411,7 +411,7 @@ final class AnimeCollection extends Collection
|
||||
->get();
|
||||
|
||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($rows === FALSE)
|
||||
if (empty($rows))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
@ -479,7 +479,7 @@ final class AnimeCollection extends Collection
|
||||
->get();
|
||||
|
||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($rows === FALSE)
|
||||
if (empty($rows))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
@ -659,7 +659,7 @@ final class AnimeCollection extends Collection
|
||||
->get();
|
||||
|
||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($rows === FALSE)
|
||||
if (empty($rows))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
@ -691,7 +691,7 @@ final class AnimeCollection extends Collection
|
||||
->get();
|
||||
|
||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($rows === FALSE)
|
||||
if (empty($rows))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
@ -737,7 +737,7 @@ final class AnimeCollection extends Collection
|
||||
|
||||
// Add genres associated with each item
|
||||
$rows = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($rows === FALSE)
|
||||
if (empty($rows))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ trait MediaTrait
|
||||
* Get information about a specific list item
|
||||
* for editing/updating that item
|
||||
*/
|
||||
public function getItem(string $itemId): AnimeListItem|MangaListItem
|
||||
public function getItem(string $itemId): AnimeListItem|MangaListItem|array
|
||||
{
|
||||
return $this->kitsuModel->getListItem($itemId);
|
||||
}
|
||||
|
@ -95,14 +95,7 @@ final class Settings
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $values) && is_scalar($values[$key]))
|
||||
{
|
||||
$value['value'] = $values[$key];
|
||||
}
|
||||
else
|
||||
{
|
||||
$value['value'] = $value['default'] ?? '';
|
||||
}
|
||||
$value['value'] = array_key_exists($key, $values) && is_scalar($values[$key]) ? $values[$key] : $value['default'] ?? '';
|
||||
|
||||
foreach (['readonly', 'disabled'] as $flag)
|
||||
{
|
||||
|
@ -20,37 +20,6 @@ use Stringable;
|
||||
|
||||
abstract class AbstractType implements ArrayAccess, Countable, Stringable
|
||||
{
|
||||
/**
|
||||
* Populate values for un-serializing data
|
||||
*/
|
||||
public static function __set_state(mixed $properties): self
|
||||
{
|
||||
return new static($properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the shape of the object, and return the array equivalent
|
||||
*/
|
||||
final public static function check(array $data = []): ?array
|
||||
{
|
||||
$currentClass = static::class;
|
||||
|
||||
if (get_parent_class($currentClass) !== FALSE)
|
||||
{
|
||||
return static::class::from($data)->toArray();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static constructor
|
||||
*/
|
||||
final public static function from(mixed $data): static
|
||||
{
|
||||
return new static($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the properties by using the constructor
|
||||
*/
|
||||
@ -73,6 +42,14 @@ abstract class AbstractType implements ArrayAccess, Countable, Stringable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate values for un-serializing data
|
||||
*/
|
||||
public static function __set_state(mixed $properties): self
|
||||
{
|
||||
return new static($properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* See if a property is set
|
||||
*/
|
||||
@ -123,6 +100,29 @@ abstract class AbstractType implements ArrayAccess, Countable, Stringable
|
||||
return print_r($this, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the shape of the object, and return the array equivalent
|
||||
*/
|
||||
final public static function check(array $data = []): ?array
|
||||
{
|
||||
$currentClass = static::class;
|
||||
|
||||
if (get_parent_class($currentClass) !== FALSE)
|
||||
{
|
||||
return static::class::from($data)->toArray();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static constructor
|
||||
*/
|
||||
final public static function from(mixed $data): static
|
||||
{
|
||||
return new static($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementing ArrayAccess
|
||||
*/
|
||||
@ -201,10 +201,8 @@ abstract class AbstractType implements ArrayAccess, Countable, Stringable
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
final protected function fromObject(mixed $parent = NULL): float|NULL|bool|int|array|string
|
||||
#[\PHPUnit\Framework\Attributes\CodeCoverageIgnore]
|
||||
final protected function fromObject(mixed $parent = NULL): float|null|bool|int|array|string
|
||||
{
|
||||
$object = $parent ?? $this;
|
||||
|
||||
|
@ -21,6 +21,7 @@ final class Person extends AbstractType
|
||||
{
|
||||
public string $id;
|
||||
public ?string $name;
|
||||
public ?string $birthday;
|
||||
public string $image;
|
||||
public array $names = [];
|
||||
public ?string $description;
|
||||
|
@ -21,11 +21,14 @@ final class User extends AbstractType
|
||||
{
|
||||
public ?string $about;
|
||||
public ?string $avatar;
|
||||
public ?string $birthday;
|
||||
public string $joinDate;
|
||||
public ?string $gender;
|
||||
public ?array $favorites;
|
||||
public ?string $location;
|
||||
public ?string $name;
|
||||
public ?string $slug;
|
||||
public ?array $stats;
|
||||
public ?array $waifu;
|
||||
public array $waifu;
|
||||
public ?string $website;
|
||||
}
|
||||
|
@ -17,6 +17,9 @@ namespace Aviat\Ion\Attribute;
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
class Controller {
|
||||
public function __construct(public string $prefix = '') {}
|
||||
class Controller
|
||||
{
|
||||
public function __construct(public string $prefix = '')
|
||||
{
|
||||
}
|
||||
}
|
@ -17,4 +17,6 @@ namespace Aviat\Ion\Attribute;
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
class DefaultController {}
|
||||
class DefaultController
|
||||
{
|
||||
}
|
||||
|
@ -17,16 +17,15 @@ namespace Aviat\Ion\Attribute;
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
class Route {
|
||||
public const GET = 'get';
|
||||
public const POST = 'post';
|
||||
class Route
|
||||
{
|
||||
final public const GET = 'get';
|
||||
final public const POST = 'post';
|
||||
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $path,
|
||||
public string $verb = self::GET,
|
||||
)
|
||||
{
|
||||
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,10 @@ class Json
|
||||
/**
|
||||
* Encode data in json format
|
||||
*
|
||||
* @throws JsonException
|
||||
* @param mixed $data
|
||||
* @param int $options
|
||||
* @param int<1, max> $depth
|
||||
* @return string
|
||||
*/
|
||||
public static function encode(mixed $data, int $options = 0, int $depth = 512): string
|
||||
{
|
||||
@ -54,7 +57,11 @@ class Json
|
||||
/**
|
||||
* Decode data from json
|
||||
*
|
||||
* @throws JsonException
|
||||
* @param string|null $json
|
||||
* @param bool $assoc
|
||||
* @param int<1, max> $depth
|
||||
* @param int $options
|
||||
* @return mixed
|
||||
*/
|
||||
public static function decode(?string $json, bool $assoc = TRUE, int $depth = 512, int $options = 0): mixed
|
||||
{
|
||||
@ -74,7 +81,11 @@ class Json
|
||||
/**
|
||||
* Decode json data loaded from the passed filename
|
||||
*
|
||||
* @throws JsonException
|
||||
* @param string $filename
|
||||
* @param bool $assoc
|
||||
* @param int<1, max> $depth
|
||||
* @param int $options
|
||||
* @return mixed
|
||||
*/
|
||||
public static function decodeFile(string $filename, bool $assoc = TRUE, int $depth = 512, int $options = 0): mixed
|
||||
{
|
||||
|
@ -33,7 +33,7 @@ abstract class AbstractTransformer implements TransformerInterface
|
||||
{
|
||||
$list = (array) $collection;
|
||||
|
||||
return array_map([$this, 'transform'], $list);
|
||||
return array_map($this->transform(...), $list);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,14 +65,6 @@ class ArrayType
|
||||
'pop' => 'array_pop',
|
||||
];
|
||||
|
||||
/**
|
||||
* Create an ArrayType wrapper class from an array
|
||||
*/
|
||||
public static function from(array $arr): ArrayType
|
||||
{
|
||||
return new ArrayType($arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ArrayType wrapper class
|
||||
*/
|
||||
@ -108,6 +100,14 @@ class ArrayType
|
||||
throw new InvalidArgumentException("Method '{$method}' does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ArrayType wrapper class from an array
|
||||
*/
|
||||
public static function from(array $arr): ArrayType
|
||||
{
|
||||
return new ArrayType($arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the passed key exist in the current array?
|
||||
*/
|
||||
@ -156,7 +156,7 @@ class ArrayType
|
||||
/**
|
||||
* Find an array key by its associated value
|
||||
*/
|
||||
public function search(mixed $value, bool $strict = TRUE): int|string|FALSE|null
|
||||
public function search(mixed $value, bool $strict = TRUE): int|string|false|null
|
||||
{
|
||||
return array_search($value, $this->arr, $strict);
|
||||
}
|
||||
@ -172,7 +172,7 @@ class ArrayType
|
||||
/**
|
||||
* Return the array, or a key
|
||||
*/
|
||||
public function &get(string|int|NULL $key = NULL): mixed
|
||||
public function &get(string|int|null $key = NULL): mixed
|
||||
{
|
||||
$value = NULL;
|
||||
if ($key === NULL)
|
||||
|
@ -24,9 +24,9 @@ final class StringType extends Stringy
|
||||
/**
|
||||
* Alias for `create` static constructor
|
||||
*/
|
||||
public static function from(string $str): self
|
||||
public static function from(string $str = '', ?string $encoding = NULL): self
|
||||
{
|
||||
return self::create($str);
|
||||
return self::create($str, $encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,24 +21,36 @@ use Exception;
|
||||
use InvalidArgumentException;
|
||||
use IteratorAggregate;
|
||||
use OutOfBoundsException;
|
||||
use RuntimeException;
|
||||
use Traversable;
|
||||
use function mb_convert_case;
|
||||
use function mb_ereg_match;
|
||||
use function mb_ereg_replace;
|
||||
use function mb_internal_encoding;
|
||||
use function mb_regex_encoding;
|
||||
use function mb_split;
|
||||
use function mb_stripos;
|
||||
use function mb_strlen;
|
||||
use function mb_strrpos;
|
||||
use function mb_strtolower;
|
||||
use function mb_strtoupper;
|
||||
use function mb_substr;
|
||||
use function mb_substr_count;
|
||||
use const MB_CASE_TITLE;
|
||||
|
||||
/**
|
||||
* Vendored, slightly modernized version of Stringy
|
||||
*/
|
||||
abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess
|
||||
{
|
||||
/**
|
||||
* An instance's string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $str;
|
||||
|
||||
/**
|
||||
* The string's encoding, which should be one of the mbstring module's
|
||||
* supported encodings.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $encoding;
|
||||
|
||||
@ -51,25 +63,36 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*
|
||||
* @param mixed $str Value to modify, after being cast to string
|
||||
* @param string|null $encoding The character encoding
|
||||
* @throws \InvalidArgumentException if an array or object without a
|
||||
* @throws InvalidArgumentException if an array or object without a
|
||||
* __toString method is passed as the first argument
|
||||
*/
|
||||
public function __construct(mixed $str = '', ?string $encoding = NULL)
|
||||
final public function __construct(mixed $str = '', ?string $encoding = NULL)
|
||||
{
|
||||
if (is_array($str))
|
||||
{
|
||||
throw new InvalidArgumentException(
|
||||
'Passed value cannot be an array'
|
||||
);
|
||||
} elseif (is_object($str) && ! method_exists($str, '__toString'))
|
||||
}
|
||||
if (is_object($str) && ! method_exists($str, '__toString'))
|
||||
{
|
||||
throw new InvalidArgumentException(
|
||||
'Passed object must have a __toString method'
|
||||
);
|
||||
}
|
||||
|
||||
$this->str = (string)$str;
|
||||
$this->encoding = $encoding ?: \mb_internal_encoding();
|
||||
$this->str = (string) $str;
|
||||
$this->encoding = $encoding ?: mb_internal_encoding();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value in $str.
|
||||
*
|
||||
* @return string The current value of the $str property
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->str;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,25 +104,15 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*
|
||||
* @param mixed $str Value to modify, after being cast to string
|
||||
* @param string|null $encoding The character encoding
|
||||
* @return static A Stringy object
|
||||
* @throws \InvalidArgumentException if an array or object without a
|
||||
* @throws InvalidArgumentException if an array or object without a
|
||||
* __toString method is passed as the first argument
|
||||
* @return static A Stringy object
|
||||
*/
|
||||
public static function create(mixed $str = '', ?string $encoding = NULL): self
|
||||
{
|
||||
return new static($str, $encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value in $str.
|
||||
*
|
||||
* @return string The current value of the $str property
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new string with $string appended.
|
||||
*
|
||||
@ -140,7 +153,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
return static::create('', $this->encoding);
|
||||
}
|
||||
|
||||
$substrIndex = $startIndex + \mb_strlen($start, $this->encoding);
|
||||
$substrIndex = $startIndex + mb_strlen($start, $this->encoding);
|
||||
$endIndex = $this->indexOf($end, $substrIndex);
|
||||
if ($endIndex === FALSE)
|
||||
{
|
||||
@ -165,10 +178,10 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
$stringy->str = preg_replace_callback(
|
||||
'/[-_\s]+(.)?/u',
|
||||
function ($match) use ($encoding) {
|
||||
static function ($match) use ($encoding): string {
|
||||
if (isset($match[1]))
|
||||
{
|
||||
return \mb_strtoupper($match[1], $encoding);
|
||||
return mb_strtoupper($match[1], $encoding);
|
||||
}
|
||||
|
||||
return '';
|
||||
@ -178,9 +191,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
$stringy->str = preg_replace_callback(
|
||||
'/[\d]+(.)?/u',
|
||||
function ($match) use ($encoding) {
|
||||
return \mb_strtoupper($match[0], $encoding);
|
||||
},
|
||||
static fn ($match) => mb_strtoupper($match[0], $encoding),
|
||||
$stringy->str
|
||||
);
|
||||
|
||||
@ -195,6 +206,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
public function chars(): array
|
||||
{
|
||||
$chars = [];
|
||||
|
||||
for ($i = 0, $l = $this->length(); $i < $l; $i++)
|
||||
{
|
||||
$chars[] = $this->at($i)->str;
|
||||
@ -230,10 +242,10 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
if ($caseSensitive)
|
||||
{
|
||||
return (\mb_strpos($this->str, $needle, 0, $encoding) !== FALSE);
|
||||
return \mb_strpos($this->str, $needle, 0, $encoding) !== FALSE;
|
||||
}
|
||||
|
||||
return (\mb_stripos($this->str, $needle, 0, $encoding) !== FALSE);
|
||||
return mb_stripos($this->str, $needle, 0, $encoding) !== FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -313,13 +325,13 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
{
|
||||
if ($caseSensitive)
|
||||
{
|
||||
return \mb_substr_count($this->str, $substring, $this->encoding);
|
||||
return mb_substr_count($this->str, $substring, $this->encoding);
|
||||
}
|
||||
|
||||
$str = \mb_strtoupper($this->str, $this->encoding);
|
||||
$substring = \mb_strtoupper($substring, $this->encoding);
|
||||
$str = mb_strtoupper($this->str, $this->encoding);
|
||||
$substring = mb_strtoupper($substring, $this->encoding);
|
||||
|
||||
return \mb_substr_count($str, $substring, $this->encoding);
|
||||
return mb_substr_count($str, $substring, $this->encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,7 +361,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
$this->regexEncoding($this->encoding);
|
||||
|
||||
$str = $this->eregReplace('\B([A-Z])', '-\1', $this->trim()->__toString());
|
||||
$str = \mb_strtolower($str, $this->encoding);
|
||||
$str = mb_strtolower($str, $this->encoding);
|
||||
$str = $this->eregReplace('[-_\s]+', $delimiter, $str);
|
||||
|
||||
$this->regexEncoding($regexEncoding);
|
||||
@ -368,19 +380,23 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*/
|
||||
public function endsWith(string $substring, bool $caseSensitive = TRUE): bool
|
||||
{
|
||||
$substringLength = \mb_strlen($substring, $this->encoding);
|
||||
$substringLength = mb_strlen($substring, $this->encoding);
|
||||
$strLength = $this->length();
|
||||
|
||||
$endOfStr = \mb_substr($this->str, $strLength - $substringLength,
|
||||
$substringLength, $this->encoding);
|
||||
$endOfStr = mb_substr(
|
||||
$this->str,
|
||||
$strLength - $substringLength,
|
||||
$substringLength,
|
||||
$this->encoding
|
||||
);
|
||||
|
||||
if ( ! $caseSensitive)
|
||||
{
|
||||
$substring = \mb_strtolower($substring, $this->encoding);
|
||||
$endOfStr = \mb_strtolower($endOfStr, $this->encoding);
|
||||
$substring = mb_strtolower($substring, $this->encoding);
|
||||
$endOfStr = mb_strtolower($endOfStr, $this->encoding);
|
||||
}
|
||||
|
||||
return (string)$substring === $endOfStr;
|
||||
return (string) $substring === $endOfStr;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -462,6 +478,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
if ($n < 0)
|
||||
{
|
||||
$stringy->str = '';
|
||||
|
||||
return $stringy;
|
||||
}
|
||||
|
||||
@ -513,7 +530,6 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
return $this->matchesPattern('.*[[:upper:]]');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert all HTML entities to their applicable characters. An alias of
|
||||
* html_entity_decode. For a list of flags, refer to
|
||||
@ -564,12 +580,16 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*
|
||||
* @param string $needle Substring to look for
|
||||
* @param int $offset Offset from which to search
|
||||
* @return int|bool The occurrence's index if found, otherwise false
|
||||
* @return bool|int The occurrence's index if found, otherwise false
|
||||
*/
|
||||
public function indexOf(string $needle, int $offset = 0): int|false
|
||||
{
|
||||
return \mb_strpos($this->str, (string)$needle,
|
||||
(int)$offset, $this->encoding);
|
||||
return \mb_strpos(
|
||||
$this->str,
|
||||
(string) $needle,
|
||||
(int) $offset,
|
||||
$this->encoding
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -580,12 +600,16 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*
|
||||
* @param string $needle Substring to look for
|
||||
* @param int $offset Offset from which to search
|
||||
* @return int|bool The last occurrence's index if found, otherwise false
|
||||
* @return bool|int The last occurrence's index if found, otherwise false
|
||||
*/
|
||||
public function indexOfLast(string $needle, int $offset = 0): int|false
|
||||
{
|
||||
return \mb_strrpos($this->str, (string)$needle,
|
||||
(int)$offset, $this->encoding);
|
||||
return mb_strrpos(
|
||||
$this->str,
|
||||
(string) $needle,
|
||||
(int) $offset,
|
||||
$this->encoding
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -603,9 +627,13 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
return $stringy;
|
||||
}
|
||||
|
||||
$start = \mb_substr($stringy->str, 0, $index, $stringy->encoding);
|
||||
$end = \mb_substr($stringy->str, $index, $stringy->length(),
|
||||
$stringy->encoding);
|
||||
$start = mb_substr($stringy->str, 0, $index, $stringy->encoding);
|
||||
$end = mb_substr(
|
||||
$stringy->str,
|
||||
$index,
|
||||
$stringy->length(),
|
||||
$stringy->encoding
|
||||
);
|
||||
|
||||
$stringy->str = $start . $substring . $end;
|
||||
|
||||
@ -672,7 +700,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
json_decode($this->str);
|
||||
|
||||
return (json_last_error() === JSON_ERROR_NONE);
|
||||
return json_last_error() === JSON_ERROR_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -696,7 +724,6 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
return $this->str === 'b:0;' || @unserialize($this->str) !== FALSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the string is base64 encoded, false otherwise.
|
||||
*
|
||||
@ -704,7 +731,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*/
|
||||
public function isBase64(): bool
|
||||
{
|
||||
return (base64_encode(base64_decode($this->str, TRUE)) === $this->str);
|
||||
return base64_encode(base64_decode($this->str, TRUE)) === $this->str;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -731,6 +758,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
if ($n <= 0)
|
||||
{
|
||||
$stringy->str = '';
|
||||
|
||||
return $stringy;
|
||||
}
|
||||
|
||||
@ -744,7 +772,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*/
|
||||
public function length(): int
|
||||
{
|
||||
return \mb_strlen($this->str, $this->encoding);
|
||||
return mb_strlen($this->str, $this->encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -756,7 +784,9 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
public function lines(): array
|
||||
{
|
||||
$array = $this->split('[\r\n]{1,2}', $this->str);
|
||||
for ($i = 0; $i < count($array); $i++)
|
||||
$arrayCount = count($array);
|
||||
|
||||
for ($i = 0; $i < $arrayCount; $i++)
|
||||
{
|
||||
$array[$i] = static::create($array[$i], $this->encoding);
|
||||
}
|
||||
@ -773,17 +803,19 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
public function longestCommonPrefix(string $otherStr): self
|
||||
{
|
||||
$encoding = $this->encoding;
|
||||
$maxLength = min($this->length(), \mb_strlen($otherStr, $encoding));
|
||||
$maxLength = min($this->length(), mb_strlen($otherStr, $encoding));
|
||||
|
||||
$longestCommonPrefix = '';
|
||||
|
||||
for ($i = 0; $i < $maxLength; $i++)
|
||||
{
|
||||
$char = \mb_substr($this->str, $i, 1, $encoding);
|
||||
$char = mb_substr($this->str, $i, 1, $encoding);
|
||||
|
||||
if ($char == \mb_substr($otherStr, $i, 1, $encoding))
|
||||
if ($char === mb_substr($otherStr, $i, 1, $encoding))
|
||||
{
|
||||
$longestCommonPrefix .= $char;
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -801,17 +833,19 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
public function longestCommonSuffix(string $otherStr): self
|
||||
{
|
||||
$encoding = $this->encoding;
|
||||
$maxLength = min($this->length(), \mb_strlen($otherStr, $encoding));
|
||||
$maxLength = min($this->length(), mb_strlen($otherStr, $encoding));
|
||||
|
||||
$longestCommonSuffix = '';
|
||||
|
||||
for ($i = 1; $i <= $maxLength; $i++)
|
||||
{
|
||||
$char = \mb_substr($this->str, -$i, 1, $encoding);
|
||||
$char = mb_substr($this->str, -$i, 1, $encoding);
|
||||
|
||||
if ($char == \mb_substr($otherStr, -$i, 1, $encoding))
|
||||
if ($char === mb_substr($otherStr, -$i, 1, $encoding))
|
||||
{
|
||||
$longestCommonSuffix = $char . $longestCommonSuffix;
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -834,28 +868,32 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
$encoding = $this->encoding;
|
||||
$stringy = static::create($this->str, $encoding);
|
||||
$strLength = $stringy->length();
|
||||
$otherLength = \mb_strlen($otherStr, $encoding);
|
||||
$otherLength = mb_strlen($otherStr, $encoding);
|
||||
|
||||
// Return if either string is empty
|
||||
if ($strLength == 0 || $otherLength == 0)
|
||||
if ($strLength === 0 || $otherLength === 0)
|
||||
{
|
||||
$stringy->str = '';
|
||||
|
||||
return $stringy;
|
||||
}
|
||||
|
||||
$len = 0;
|
||||
$end = 0;
|
||||
$table = array_fill(0, $strLength + 1,
|
||||
array_fill(0, $otherLength + 1, 0));
|
||||
$table = array_fill(
|
||||
0,
|
||||
$strLength + 1,
|
||||
array_fill(0, $otherLength + 1, 0)
|
||||
);
|
||||
|
||||
for ($i = 1; $i <= $strLength; $i++)
|
||||
{
|
||||
for ($j = 1; $j <= $otherLength; $j++)
|
||||
{
|
||||
$strChar = \mb_substr($stringy->str, $i - 1, 1, $encoding);
|
||||
$otherChar = \mb_substr($otherStr, $j - 1, 1, $encoding);
|
||||
$strChar = mb_substr($stringy->str, $i - 1, 1, $encoding);
|
||||
$otherChar = mb_substr($otherStr, $j - 1, 1, $encoding);
|
||||
|
||||
if ($strChar == $otherChar)
|
||||
if ($strChar === $otherChar)
|
||||
{
|
||||
$table[$i][$j] = $table[$i - 1][$j - 1] + 1;
|
||||
if ($table[$i][$j] > $len)
|
||||
@ -863,14 +901,15 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
$len = $table[$i][$j];
|
||||
$end = $i;
|
||||
}
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
$table[$i][$j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stringy->str = \mb_substr($stringy->str, $end - $len, $len, $encoding);
|
||||
$stringy->str = mb_substr($stringy->str, $end - $len, $len, $encoding);
|
||||
|
||||
return $stringy;
|
||||
}
|
||||
@ -882,11 +921,15 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*/
|
||||
public function lowerCaseFirst(): self
|
||||
{
|
||||
$first = \mb_substr($this->str, 0, 1, $this->encoding);
|
||||
$rest = \mb_substr($this->str, 1, $this->length() - 1,
|
||||
$this->encoding);
|
||||
$first = mb_substr($this->str, 0, 1, $this->encoding);
|
||||
$rest = mb_substr(
|
||||
$this->str,
|
||||
1,
|
||||
$this->length() - 1,
|
||||
$this->encoding
|
||||
);
|
||||
|
||||
$str = \mb_strtolower($first, $this->encoding) . $rest;
|
||||
$str = mb_strtolower($first, $this->encoding) . $rest;
|
||||
|
||||
return static::create($str, $this->encoding);
|
||||
}
|
||||
@ -897,19 +940,19 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
* part of the ArrayAccess interface.
|
||||
*
|
||||
* @param mixed $offset The index to check
|
||||
* @return boolean Whether or not the index exists
|
||||
* @return bool Whether or not the index exists
|
||||
*/
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
$length = $this->length();
|
||||
$offset = (int)$offset;
|
||||
$offset = (int) $offset;
|
||||
|
||||
if ($offset >= 0)
|
||||
{
|
||||
return ($length > $offset);
|
||||
return $length > $offset;
|
||||
}
|
||||
|
||||
return ($length >= abs($offset));
|
||||
return $length >= abs($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -919,13 +962,13 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
* does not exist.
|
||||
*
|
||||
* @param mixed $offset The index from which to retrieve the char
|
||||
* @return string The character at the specified index
|
||||
* @throws \OutOfBoundsException If the positive or negative offset does
|
||||
* @throws OutOfBoundsException If the positive or negative offset does
|
||||
* not exist
|
||||
* @return string The character at the specified index
|
||||
*/
|
||||
public function offsetGet(mixed $offset): string
|
||||
{
|
||||
$offset = (int)$offset;
|
||||
$offset = (int) $offset;
|
||||
$length = $this->length();
|
||||
|
||||
if (($offset >= 0 && $length <= $offset) || $length < abs($offset))
|
||||
@ -933,7 +976,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
throw new OutOfBoundsException('No character exists at the index');
|
||||
}
|
||||
|
||||
return \mb_substr($this->str, $offset, 1, $this->encoding);
|
||||
return mb_substr($this->str, $offset, 1, $this->encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -942,7 +985,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*
|
||||
* @param mixed $offset The index of the character
|
||||
* @param mixed $value Value to set
|
||||
* @throws \Exception When called
|
||||
* @throws Exception When called
|
||||
*/
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
@ -955,7 +998,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
* when called. This maintains the immutability of Stringy objects.
|
||||
*
|
||||
* @param mixed $offset The index of the character
|
||||
* @throws \Exception When called
|
||||
* @throws Exception When called
|
||||
*/
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
@ -973,13 +1016,13 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
* @param int $length Desired string length after padding
|
||||
* @param string $padStr String used to pad, defaults to space
|
||||
* @param string $padType One of 'left', 'right', 'both'
|
||||
* @return static Object with a padded $str
|
||||
* @throws /InvalidArgumentException If $padType isn't one of 'right',
|
||||
* 'left' or 'both'
|
||||
* @return static Object with a padded $str
|
||||
*/
|
||||
public function pad(int $length, string $padStr = ' ', string $padType = 'right'): self
|
||||
{
|
||||
if ( ! in_array($padType, ['left', 'right', 'both']))
|
||||
if ( ! in_array($padType, ['left', 'right', 'both'], TRUE))
|
||||
{
|
||||
throw new InvalidArgumentException('Pad expects $padType ' .
|
||||
"to be one of 'left', 'right' or 'both'");
|
||||
@ -989,8 +1032,10 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
{
|
||||
case 'left':
|
||||
return $this->padLeft($length, $padStr);
|
||||
|
||||
case 'right':
|
||||
return $this->padRight($length, $padStr);
|
||||
|
||||
default:
|
||||
return $this->padBoth($length, $padStr);
|
||||
}
|
||||
@ -1008,8 +1053,11 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
{
|
||||
$padding = $length - $this->length();
|
||||
|
||||
return $this->applyPadding(floor($padding / 2), ceil($padding / 2),
|
||||
$padStr);
|
||||
return $this->applyPadding(
|
||||
floor($padding / 2),
|
||||
ceil($padding / 2),
|
||||
$padStr
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1084,7 +1132,8 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
if ($stringy->startsWith($substring))
|
||||
{
|
||||
$substringLength = \mb_strlen($substring, $stringy->encoding);
|
||||
$substringLength = mb_strlen($substring, $stringy->encoding);
|
||||
|
||||
return $stringy->substr($substringLength);
|
||||
}
|
||||
|
||||
@ -1103,7 +1152,8 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
if ($stringy->endsWith($substring))
|
||||
{
|
||||
$substringLength = \mb_strlen($substring, $stringy->encoding);
|
||||
$substringLength = mb_strlen($substring, $stringy->encoding);
|
||||
|
||||
return $stringy->substr(0, $stringy->length() - $substringLength);
|
||||
}
|
||||
|
||||
@ -1148,7 +1198,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
// Loop from last index of string to first
|
||||
for ($i = $strLength - 1; $i >= 0; $i--)
|
||||
{
|
||||
$reversed .= \mb_substr($this->str, $i, 1, $this->encoding);
|
||||
$reversed .= mb_substr($this->str, $i, 1, $this->encoding);
|
||||
}
|
||||
|
||||
return static::create($reversed, $this->encoding);
|
||||
@ -1174,19 +1224,19 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
// Need to further trim the string so we can append the substring
|
||||
$encoding = $stringy->encoding;
|
||||
$substringLength = \mb_strlen($substring, $encoding);
|
||||
$substringLength = mb_strlen($substring, $encoding);
|
||||
$length = $length - $substringLength;
|
||||
|
||||
$truncated = \mb_substr($stringy->str, 0, $length, $encoding);
|
||||
$truncated = mb_substr($stringy->str, 0, $length, $encoding);
|
||||
|
||||
// If the last word was truncated
|
||||
if (mb_strpos($stringy->str, ' ', $length - 1, $encoding) != $length)
|
||||
if (mb_strpos($stringy->str, ' ', $length - 1, $encoding) !== $length)
|
||||
{
|
||||
// Find pos of the last occurrence of a space, get up to that
|
||||
$lastPos = \mb_strrpos($truncated, ' ', 0, $encoding);
|
||||
$lastPos = mb_strrpos($truncated, ' ', 0, $encoding);
|
||||
if ($lastPos !== FALSE)
|
||||
{
|
||||
$truncated = \mb_substr($truncated, 0, $lastPos, $encoding);
|
||||
$truncated = mb_substr($truncated, 0, $lastPos, $encoding);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1195,7 +1245,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
return $stringy;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* A multibyte str_shuffle() function. It returns a string with its
|
||||
* characters in random order.
|
||||
*
|
||||
@ -1207,9 +1257,10 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
shuffle($indexes);
|
||||
|
||||
$shuffledStr = '';
|
||||
|
||||
foreach ($indexes as $i)
|
||||
{
|
||||
$shuffledStr .= \mb_substr($this->str, $i, 1, $this->encoding);
|
||||
$shuffledStr .= mb_substr($this->str, $i, 1, $this->encoding);
|
||||
}
|
||||
|
||||
return static::create($shuffledStr, $this->encoding);
|
||||
@ -1233,7 +1284,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
$stringy->str = str_replace('@', $replacement, $stringy);
|
||||
$quotedReplacement = preg_quote($replacement);
|
||||
$pattern = "/[^a-zA-Z\d\s-_$quotedReplacement]/u";
|
||||
$pattern = "/[^a-zA-Z\\d\\s-_{$quotedReplacement}]/u";
|
||||
$stringy->str = preg_replace($pattern, '', $stringy);
|
||||
|
||||
return $stringy->toLowerCase()->delimit($replacement)
|
||||
@ -1252,17 +1303,21 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*/
|
||||
public function startsWith(string $substring, bool $caseSensitive = TRUE): bool
|
||||
{
|
||||
$substringLength = \mb_strlen($substring, $this->encoding);
|
||||
$startOfStr = \mb_substr($this->str, 0, $substringLength,
|
||||
$this->encoding);
|
||||
$substringLength = mb_strlen($substring, $this->encoding);
|
||||
$startOfStr = mb_substr(
|
||||
$this->str,
|
||||
0,
|
||||
$substringLength,
|
||||
$this->encoding
|
||||
);
|
||||
|
||||
if ( ! $caseSensitive)
|
||||
{
|
||||
$substring = \mb_strtolower($substring, $this->encoding);
|
||||
$startOfStr = \mb_strtolower($startOfStr, $this->encoding);
|
||||
$substring = mb_strtolower($substring, $this->encoding);
|
||||
$startOfStr = mb_strtolower($startOfStr, $this->encoding);
|
||||
}
|
||||
|
||||
return (string)$substring === $startOfStr;
|
||||
return (string) $substring === $startOfStr;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1303,18 +1358,21 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
* @param int $end Optional index at which to end extraction
|
||||
* @return static Object with its $str being the extracted substring
|
||||
*/
|
||||
public function slice(int $start, int $end = NULL): self
|
||||
public function slice(int $start, ?int $end = NULL): self
|
||||
{
|
||||
if ($end === NULL)
|
||||
{
|
||||
$length = $this->length();
|
||||
} elseif ($end >= 0 && $end <= $start)
|
||||
}
|
||||
elseif ($end >= 0 && $end <= $start)
|
||||
{
|
||||
return static::create('', $this->encoding);
|
||||
} elseif ($end < 0)
|
||||
}
|
||||
elseif ($end < 0)
|
||||
{
|
||||
$length = $this->length() + $end - $start;
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
$length = $end - $start;
|
||||
}
|
||||
@ -1331,7 +1389,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
* @param int $limit Optional maximum number of results to return
|
||||
* @return static[] An array of Stringy objects
|
||||
*/
|
||||
public function split(string $pattern, int $limit = NULL): array
|
||||
public function split(string $pattern, ?int $limit = NULL): array
|
||||
{
|
||||
if ($limit === 0)
|
||||
{
|
||||
@ -1350,7 +1408,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
// mb_split returns the remaining unsplit string in the last index when
|
||||
// supplying a limit
|
||||
$limit = ($limit > 0) ? $limit += 1 : -1;
|
||||
$limit = ($limit > 0) ? ++$limit : -1;
|
||||
|
||||
static $functionExists;
|
||||
if ($functionExists === NULL)
|
||||
@ -1360,10 +1418,11 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
if ($functionExists)
|
||||
{
|
||||
$array = \mb_split($pattern, $this->str, $limit);
|
||||
} else if ($this->supportsEncoding())
|
||||
$array = mb_split($pattern, $this->str, $limit);
|
||||
}
|
||||
elseif ($this->supportsEncoding())
|
||||
{
|
||||
$array = \preg_split("/$pattern/", $this->str, $limit);
|
||||
$array = \preg_split("/{$pattern}/", $this->str, $limit);
|
||||
}
|
||||
|
||||
$this->regexEncoding($regexEncoding);
|
||||
@ -1372,8 +1431,9 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
{
|
||||
array_pop($array);
|
||||
}
|
||||
$arrayCount = count($array);
|
||||
|
||||
for ($i = 0; $i < count($array); $i++)
|
||||
for ($i = 0; $i < $arrayCount; $i++)
|
||||
{
|
||||
$array[$i] = static::create($array[$i], $this->encoding);
|
||||
}
|
||||
@ -1405,7 +1465,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
public function substr(int $start, ?int $length = NULL): self
|
||||
{
|
||||
$length = $length === NULL ? $this->length() : $length;
|
||||
$str = \mb_substr($this->str, $start, $length, $this->encoding);
|
||||
$str = mb_substr($this->str, $start, $length, $this->encoding);
|
||||
|
||||
return static::create($str, $this->encoding);
|
||||
}
|
||||
@ -1436,13 +1496,13 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
$stringy->str = preg_replace_callback(
|
||||
'/[\S]/u',
|
||||
function ($match) use ($encoding) {
|
||||
if ($match[0] == \mb_strtoupper($match[0], $encoding))
|
||||
static function ($match) use ($encoding): string {
|
||||
if ($match[0] === mb_strtoupper($match[0], $encoding))
|
||||
{
|
||||
return \mb_strtolower($match[0], $encoding);
|
||||
return mb_strtolower($match[0], $encoding);
|
||||
}
|
||||
|
||||
return \mb_strtoupper($match[0], $encoding);
|
||||
return mb_strtoupper($match[0], $encoding);
|
||||
},
|
||||
$stringy->str
|
||||
);
|
||||
@ -1489,15 +1549,15 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
$stringy->str = preg_replace_callback(
|
||||
'/([\S]+)/u',
|
||||
function ($match) use ($encoding, $ignore) {
|
||||
if ($ignore && in_array($match[0], $ignore))
|
||||
static function ($match) use ($encoding, $ignore): string {
|
||||
if ($ignore && in_array($match[0], $ignore, TRUE))
|
||||
{
|
||||
return $match[0];
|
||||
}
|
||||
|
||||
$stringy = static::create($match[0], $encoding);
|
||||
|
||||
return (string)$stringy->toLowerCase()->upperCaseFirst();
|
||||
return (string) $stringy->toLowerCase()->upperCaseFirst();
|
||||
},
|
||||
$stringy->str
|
||||
);
|
||||
@ -1563,18 +1623,19 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
'false' => FALSE,
|
||||
'0' => FALSE,
|
||||
'off' => FALSE,
|
||||
'no' => FALSE
|
||||
'no' => FALSE,
|
||||
];
|
||||
|
||||
if (array_key_exists($key, $map))
|
||||
{
|
||||
return $map[$key];
|
||||
} elseif (is_numeric($this->str))
|
||||
}
|
||||
if (is_numeric($this->str))
|
||||
{
|
||||
return (intval($this->str) > 0);
|
||||
return (int) ($this->str) > 0;
|
||||
}
|
||||
|
||||
return (bool)$this->regexReplace('[[:space:]]', '')->str;
|
||||
return (bool) $this->regexReplace('[[:space:]]', '')->str;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1585,7 +1646,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*/
|
||||
public function toLowerCase(): self
|
||||
{
|
||||
$str = \mb_strtolower($this->str, $this->encoding);
|
||||
$str = mb_strtolower($this->str, $this->encoding);
|
||||
|
||||
return static::create($str, $this->encoding);
|
||||
}
|
||||
@ -1628,7 +1689,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*/
|
||||
public function toTitleCase(): self
|
||||
{
|
||||
$str = \mb_convert_case($this->str, \MB_CASE_TITLE, $this->encoding);
|
||||
$str = mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding);
|
||||
|
||||
return static::create($str, $this->encoding);
|
||||
}
|
||||
@ -1641,7 +1702,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*/
|
||||
public function toUpperCase(): self
|
||||
{
|
||||
$str = \mb_strtoupper($this->str, $this->encoding);
|
||||
$str = mb_strtoupper($this->str, $this->encoding);
|
||||
|
||||
return static::create($str, $this->encoding);
|
||||
}
|
||||
@ -1658,7 +1719,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
{
|
||||
$chars = ($chars) ? preg_quote($chars) : '[:space:]';
|
||||
|
||||
return $this->regexReplace("^[$chars]+|[$chars]+\$", '');
|
||||
return $this->regexReplace("^[{$chars}]+|[{$chars}]+\$", '');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1673,7 +1734,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
{
|
||||
$chars = ($chars) ? preg_quote($chars) : '[:space:]';
|
||||
|
||||
return $this->regexReplace("^[$chars]+", '');
|
||||
return $this->regexReplace("^[{$chars}]+", '');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1688,7 +1749,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
{
|
||||
$chars = ($chars) ? preg_quote($chars) : '[:space:]';
|
||||
|
||||
return $this->regexReplace("[$chars]+\$", '');
|
||||
return $this->regexReplace("[{$chars}]+\$", '');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1709,10 +1770,10 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
}
|
||||
|
||||
// Need to further trim the string so we can append the substring
|
||||
$substringLength = \mb_strlen($substring, $stringy->encoding);
|
||||
$substringLength = mb_strlen($substring, $stringy->encoding);
|
||||
$length = $length - $substringLength;
|
||||
|
||||
$truncated = \mb_substr($stringy->str, 0, $length, $stringy->encoding);
|
||||
$truncated = mb_substr($stringy->str, 0, $length, $stringy->encoding);
|
||||
$stringy->str = $truncated . $substring;
|
||||
|
||||
return $stringy;
|
||||
@ -1750,11 +1811,15 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
*/
|
||||
public function upperCaseFirst(): self
|
||||
{
|
||||
$first = \mb_substr($this->str, 0, 1, $this->encoding);
|
||||
$rest = \mb_substr($this->str, 1, $this->length() - 1,
|
||||
$this->encoding);
|
||||
$first = mb_substr($this->str, 0, 1, $this->encoding);
|
||||
$rest = mb_substr(
|
||||
$this->str,
|
||||
1,
|
||||
$this->length() - 1,
|
||||
$this->encoding
|
||||
);
|
||||
|
||||
$str = \mb_strtoupper($first, $this->encoding) . $rest;
|
||||
$str = mb_strtoupper($first, $this->encoding) . $rest;
|
||||
|
||||
return static::create($str, $this->encoding);
|
||||
}
|
||||
@ -1767,7 +1832,10 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
protected function charsArray(): array
|
||||
{
|
||||
static $charsArray;
|
||||
if (isset($charsArray)) return $charsArray;
|
||||
if (isset($charsArray))
|
||||
{
|
||||
return $charsArray;
|
||||
}
|
||||
|
||||
return $charsArray = [
|
||||
'0' => ['°', '₀', '۰', '0'],
|
||||
@ -1960,17 +2028,11 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
],
|
||||
'bg' => [
|
||||
['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'],
|
||||
['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y']
|
||||
]
|
||||
['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'],
|
||||
],
|
||||
];
|
||||
|
||||
if (isset($languageSpecific[$language]))
|
||||
{
|
||||
$charsArray[$language] = $languageSpecific[$language];
|
||||
} else
|
||||
{
|
||||
$charsArray[$language] = [];
|
||||
}
|
||||
$charsArray[$language] = $languageSpecific[$language] ?? [];
|
||||
|
||||
return $charsArray[$language];
|
||||
}
|
||||
@ -1987,7 +2049,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
protected function applyPadding(int $left = 0, int $right = 0, string $padStr = ' '): self
|
||||
{
|
||||
$stringy = static::create($this->str, $this->encoding);
|
||||
$length = \mb_strlen($padStr, $stringy->encoding);
|
||||
$length = mb_strlen($padStr, $stringy->encoding);
|
||||
|
||||
$strLength = $stringy->length();
|
||||
$paddedLength = $strLength + $left + $right;
|
||||
@ -1997,10 +2059,18 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
return $stringy;
|
||||
}
|
||||
|
||||
$leftPadding = \mb_substr(str_repeat($padStr, ceil($left / $length)), 0,
|
||||
$left, $stringy->encoding);
|
||||
$rightPadding = \mb_substr(str_repeat($padStr, ceil($right / $length)),
|
||||
0, $right, $stringy->encoding);
|
||||
$leftPadding = mb_substr(
|
||||
str_repeat($padStr, ceil($left / $length)),
|
||||
0,
|
||||
$left,
|
||||
$stringy->encoding
|
||||
);
|
||||
$rightPadding = mb_substr(
|
||||
str_repeat($padStr, ceil($right / $length)),
|
||||
0,
|
||||
$right,
|
||||
$stringy->encoding
|
||||
);
|
||||
|
||||
$stringy->str = $leftPadding . $stringy->str . $rightPadding;
|
||||
|
||||
@ -2018,7 +2088,7 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
$regexEncoding = $this->regexEncoding();
|
||||
$this->regexEncoding($this->encoding);
|
||||
|
||||
$match = \mb_ereg_match($pattern, $this->str);
|
||||
$match = mb_ereg_match($pattern, $this->str);
|
||||
$this->regexEncoding($regexEncoding);
|
||||
|
||||
return $match;
|
||||
@ -2038,11 +2108,13 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
|
||||
if ($functionExists)
|
||||
{
|
||||
return \mb_ereg_replace($pattern, $replacement, $string, $option);
|
||||
} else if ($this->supportsEncoding())
|
||||
return mb_ereg_replace($pattern, $replacement, $string, $option);
|
||||
}
|
||||
if ($this->supportsEncoding())
|
||||
{
|
||||
$option = str_replace('r', '', (string)$option);
|
||||
return \preg_replace("/$pattern/u$option", $replacement, $string);
|
||||
$option = str_replace('r', '', (string) $option);
|
||||
|
||||
return \preg_replace("/{$pattern}/u{$option}", $replacement, $string);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2062,7 +2134,8 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
if ($functionExists)
|
||||
{
|
||||
$args = func_get_args();
|
||||
return call_user_func_array('\mb_regex_encoding', $args);
|
||||
|
||||
return mb_regex_encoding(...$args);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2073,11 +2146,10 @@ abstract class Stringy implements Countable, IteratorAggregate, ArrayAccess {
|
||||
if (isset($supported[$this->encoding]))
|
||||
{
|
||||
return TRUE;
|
||||
} else
|
||||
{
|
||||
throw new \RuntimeException('Stringy method requires the ' .
|
||||
}
|
||||
|
||||
throw new RuntimeException('Stringy method requires the ' .
|
||||
'mbstring module for encodings other than ASCII and UTF-8. ' .
|
||||
'Encoding used: ' . $this->encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,11 +34,11 @@ class HtmlView extends HttpView
|
||||
/**
|
||||
* Create the Html View
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->setContainer($container);
|
||||
$this->setContainer(func_get_arg(0));
|
||||
$this->response = new HtmlResponse('');
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ use InvalidArgumentException;
|
||||
|
||||
use Laminas\Diactoros\Response;
|
||||
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
|
||||
use PHPUnit\Framework\Attributes\CodeCoverageIgnore;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Stringable;
|
||||
|
||||
@ -168,10 +169,10 @@ class HttpView implements HttpViewInterface, Stringable
|
||||
/**
|
||||
* Send the appropriate response
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @throws DoubleRenderException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
#[CodeCoverageIgnore]
|
||||
protected function output(): void
|
||||
{
|
||||
if ($this->hasRendered)
|
||||
|
@ -40,8 +40,10 @@ final class APIRequestBuilderTest extends TestCase
|
||||
$this->builder->setLogger(new NullLogger());
|
||||
}
|
||||
|
||||
public function testGzipRequest(): void
|
||||
public function testGzipRequest(): never
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$request = $this->builder->newRequest('GET', 'gzip')
|
||||
->getFullRequest();
|
||||
$response = getResponse($request);
|
||||
@ -49,15 +51,19 @@ final class APIRequestBuilderTest extends TestCase
|
||||
$this->assertTrue($body['gzipped']);
|
||||
}
|
||||
|
||||
public function testInvalidRequestMethod(): void
|
||||
public function testInvalidRequestMethod(): never
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->builder->newRequest('FOO', 'gzip')
|
||||
->getFullRequest();
|
||||
}
|
||||
|
||||
public function testRequestWithBasicAuth(): void
|
||||
public function testRequestWithBasicAuth(): never
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$request = $this->builder->newRequest('GET', 'headers')
|
||||
->setBasicAuth('username', 'password')
|
||||
->getFullRequest();
|
||||
@ -68,8 +74,10 @@ final class APIRequestBuilderTest extends TestCase
|
||||
$this->assertSame('Basic dXNlcm5hbWU6cGFzc3dvcmQ=', $body['headers']['Authorization']);
|
||||
}
|
||||
|
||||
public function testRequestWithQueryString(): void
|
||||
public function testRequestWithQueryString(): never
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$query = [
|
||||
'foo' => 'bar',
|
||||
'bar' => [
|
||||
@ -96,8 +104,10 @@ final class APIRequestBuilderTest extends TestCase
|
||||
$this->assertSame($expected, $body['args']);
|
||||
}
|
||||
|
||||
public function testFormValueRequest(): void
|
||||
public function testFormValueRequest(): never
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$formValues = [
|
||||
'bar' => 'foo',
|
||||
'foo' => 'bar',
|
||||
@ -113,8 +123,10 @@ final class APIRequestBuilderTest extends TestCase
|
||||
$this->assertSame($formValues, $body['form']);
|
||||
}
|
||||
|
||||
public function testFullUrlRequest(): void
|
||||
public function testFullUrlRequest(): never
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$data = [
|
||||
'foo' => [
|
||||
'bar' => 1,
|
||||
|
@ -38,7 +38,7 @@ final class AnimeListTransformerTest extends AnimeClientTestCase
|
||||
$this->transformer = new AnimeListTransformer();
|
||||
}
|
||||
|
||||
public function testTransform(): void
|
||||
public function testTransform(): never
|
||||
{
|
||||
$this->markTestSkipped('Old test data');
|
||||
|
||||
@ -46,11 +46,11 @@ final class AnimeListTransformerTest extends AnimeClientTestCase
|
||||
$this->assertMatchesSnapshot($actual);
|
||||
}
|
||||
|
||||
public function dataUntransform(): array
|
||||
public static function dataUntransform(): array
|
||||
{
|
||||
return [[
|
||||
'input' => [
|
||||
'id' => 14047981,
|
||||
'id' => 14_047_981,
|
||||
'watching_status' => 'current',
|
||||
'user_rating' => 8,
|
||||
'episodes_watched' => 38,
|
||||
@ -60,7 +60,7 @@ final class AnimeListTransformerTest extends AnimeClientTestCase
|
||||
],
|
||||
], [
|
||||
'input' => [
|
||||
'id' => 14047981,
|
||||
'id' => 14_047_981,
|
||||
'mal_id' => '12345',
|
||||
'watching_status' => 'current',
|
||||
'user_rating' => 8,
|
||||
@ -73,7 +73,7 @@ final class AnimeListTransformerTest extends AnimeClientTestCase
|
||||
],
|
||||
], [
|
||||
'input' => [
|
||||
'id' => 14047983,
|
||||
'id' => 14_047_983,
|
||||
'mal_id' => '12347',
|
||||
'watching_status' => 'current',
|
||||
'user_rating' => 0,
|
||||
@ -87,9 +87,7 @@ final class AnimeListTransformerTest extends AnimeClientTestCase
|
||||
]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataUntransform
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('dataUntransform')]
|
||||
public function testUntransform(array $input): void
|
||||
{
|
||||
$actual = $this->transformer->untransform($input);
|
||||
|
@ -37,7 +37,7 @@ final class AnimeTransformerTest extends AnimeClientTestCase
|
||||
$this->transformer = new AnimeTransformer();
|
||||
}
|
||||
|
||||
public function testTransform()
|
||||
public function testTransform(): never
|
||||
{
|
||||
$this->markTestSkipped('May fail on CI');
|
||||
$actual = $this->transformer->transform($this->beforeTransform);
|
||||
|
@ -35,7 +35,7 @@ final class CharacterTransformerTest extends AnimeClientTestCase
|
||||
$this->beforeTransform = $raw;
|
||||
}
|
||||
|
||||
public function testTransform(): void
|
||||
public function testTransform(): never
|
||||
{
|
||||
$this->markTestSkipped('Fails on CI');
|
||||
$actual = (new CharacterTransformer())->transform($this->beforeTransform);
|
||||
|
@ -35,7 +35,7 @@ final class HistoryTransformerTest extends AnimeClientTestCase
|
||||
$this->beforeTransform = $raw;
|
||||
}
|
||||
|
||||
public function testAnimeTransform(): void
|
||||
public function testAnimeTransform(): never
|
||||
{
|
||||
$this->markTestSkipped('Old test data');
|
||||
|
||||
|
@ -35,7 +35,7 @@ final class PersonTransformerTest extends AnimeClientTestCase
|
||||
$this->beforeTransform = $raw;
|
||||
}
|
||||
|
||||
public function testTransform(): void
|
||||
public function testTransform(): never
|
||||
{
|
||||
$this->markTestSkipped('Fails on CI');
|
||||
$actual = (new PersonTransformer())->transform($this->beforeTransform);
|
||||
|
@ -38,6 +38,10 @@ final class UserTransformerTest extends AnimeClientTestCase
|
||||
public function testTransform(): void
|
||||
{
|
||||
$actual = (new UserTransformer())->transform($this->beforeTransform);
|
||||
|
||||
// Unset the time value that will change every day, so the test is consistent
|
||||
$actual->joinDate = '';
|
||||
|
||||
$this->assertMatchesSnapshot($actual);
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ id: '20286'
|
||||
manga_type: MANGA
|
||||
status: Completed
|
||||
staff:
|
||||
'Story & Art': [{ id: '8712', slug: ruri-miyahara, name: 'Ruri Miyahara', image: 'https://media.kitsu.io/people/images/8712/original.jpg?1533271952' }]
|
||||
'Story & Art': [{ id: '8712', slug: ruri-miyahara, name: 'Ruri Miyahara', image: 'https://media.kitsu.io/people/images/8712/original.jpg' }]
|
||||
synopsis: "Usa, a high-school student aspiring to begin a bachelor lifestyle, moves into a new apartment only to discover that he not only shares a room with a perverted roommate that has an obsession for underaged girls, but also that another girl, Ritsu, a love-at-first-sight, is living in the same building as well!\r\n(Source: Kirei Cake)"
|
||||
title: 'Bokura wa Minna Kawai-sou'
|
||||
titles:
|
||||
|
File diff suppressed because one or more lines are too long
@ -15,11 +15,17 @@
|
||||
namespace Aviat\AnimeClient\Tests;
|
||||
|
||||
use DateTime;
|
||||
use function Aviat\AnimeClient\{arrayToToml, checkFolderPermissions, clearCache, colNotEmpty, getLocalImg, getResponse, isSequentialArray, tomlToArray};
|
||||
use PHPUnit\Framework\Attributes\IgnoreFunctionForCodeCoverage;
|
||||
use function Aviat\AnimeClient\{arrayToToml, checkFolderPermissions, clearCache, colNotEmpty, friendlyTime, getLocalImg, getResponse, isSequentialArray, tomlToArray};
|
||||
use const Aviat\AnimeClient\{MINUTES_IN_DAY, MINUTES_IN_HOUR, MINUTES_IN_YEAR, SECONDS_IN_MINUTE};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[IgnoreFunctionForCodeCoverage('Aviat\AnimeClient\loadConfig')]
|
||||
#[IgnoreFunctionForCodeCoverage('Aviat\AnimeClient\createPlaceholderImage')]
|
||||
#[IgnoreFunctionForCodeCoverage('Aviat\AnimeClient\renderTemplate')]
|
||||
#[IgnoreFunctionForCodeCoverage('Aviat\AnimeClient\getLocalImg')]
|
||||
final class AnimeClientTest extends AnimeClientTestCase
|
||||
{
|
||||
public function testArrayToToml(): void
|
||||
@ -128,4 +134,33 @@ final class AnimeClientTest extends AnimeClientTestCase
|
||||
{
|
||||
$this->assertTrue(clearCache($this->container->get('cache')));
|
||||
}
|
||||
|
||||
public static function getFriendlyTime(): array
|
||||
{
|
||||
$SECONDS_IN_DAY = SECONDS_IN_MINUTE * MINUTES_IN_DAY;
|
||||
$SECONDS_IN_HOUR = SECONDS_IN_MINUTE * MINUTES_IN_HOUR;
|
||||
$SECONDS_IN_YEAR = SECONDS_IN_MINUTE * MINUTES_IN_YEAR;
|
||||
|
||||
return [[
|
||||
'seconds' => $SECONDS_IN_YEAR,
|
||||
'expected' => '1 year',
|
||||
], [
|
||||
'seconds' => $SECONDS_IN_HOUR,
|
||||
'expected' => '1 hour',
|
||||
], [
|
||||
'seconds' => (2 * $SECONDS_IN_YEAR) + 30,
|
||||
'expected' => '2 years, 30 seconds',
|
||||
], [
|
||||
'seconds' => (5 * $SECONDS_IN_YEAR) + (3 * $SECONDS_IN_DAY) + (17 * SECONDS_IN_MINUTE),
|
||||
'expected' => '5 years, 3 days, and 17 minutes',
|
||||
]];
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('getFriendlyTime')]
|
||||
public function testGetFriendlyTime(int $seconds, string $expected): void
|
||||
{
|
||||
$actual = friendlyTime($seconds);
|
||||
|
||||
$this->assertSame($expected, $actual);
|
||||
}
|
||||
}
|
||||
|
@ -40,10 +40,10 @@ class AnimeClientTestCase extends TestCase
|
||||
use MatchesSnapshots;
|
||||
|
||||
// Test directory constants
|
||||
public const ROOT_DIR = AC_TEST_ROOT_DIR;
|
||||
public const SRC_DIR = SRC_DIR;
|
||||
public const TEST_DATA_DIR = __DIR__ . '/test_data';
|
||||
public const TEST_VIEW_DIR = __DIR__ . '/test_views';
|
||||
final public const ROOT_DIR = AC_TEST_ROOT_DIR;
|
||||
final public const SRC_DIR = SRC_DIR;
|
||||
final public const TEST_DATA_DIR = __DIR__ . '/test_data';
|
||||
final public const TEST_VIEW_DIR = __DIR__ . '/test_views';
|
||||
|
||||
protected ContainerInterface $container;
|
||||
|
||||
@ -97,7 +97,7 @@ class AnimeClientTestCase extends TestCase
|
||||
$container = $di($config_array);
|
||||
|
||||
// Use mock session handler
|
||||
$container->set('session-handler', static function () {
|
||||
$container->set('session-handler', static function (): TestSessionHandler {
|
||||
$session_handler = new TestSessionHandler();
|
||||
session_set_save_handler($session_handler, TRUE);
|
||||
|
||||
@ -123,7 +123,7 @@ class AnimeClientTestCase extends TestCase
|
||||
];
|
||||
|
||||
$request = call_user_func_array(
|
||||
[ServerRequestFactory::class, 'fromGlobals'],
|
||||
ServerRequestFactory::fromGlobals(...),
|
||||
array_values(array_merge($default, $supers)),
|
||||
);
|
||||
$this->container->setInstance('request', $request);
|
||||
|
@ -71,7 +71,7 @@ final class DispatcherTest extends AnimeClientTestCase
|
||||
$this->assertIsObject($this->router);
|
||||
}
|
||||
|
||||
public function dataRoute(): array
|
||||
public static function dataRoute(): array
|
||||
{
|
||||
$defaultConfig = [
|
||||
'routes' => [
|
||||
@ -141,14 +141,8 @@ final class DispatcherTest extends AnimeClientTestCase
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataRoute
|
||||
* @param mixed $config
|
||||
* @param mixed $controller
|
||||
* @param mixed $host
|
||||
* @param mixed $uri
|
||||
*/
|
||||
public function testRoute($config, $controller, $host, $uri): void
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('dataRoute')]
|
||||
public function testRoute(mixed $config, mixed $controller, mixed $host, mixed $uri): void
|
||||
{
|
||||
$this->doSetUp($config, $uri, $host);
|
||||
|
||||
@ -209,8 +203,8 @@ final class DispatcherTest extends AnimeClientTestCase
|
||||
}
|
||||
|
||||
#[ArrayShape(['controller_list_sanity_check' => 'array', 'empty_controller_list' => 'array'])]
|
||||
public function dataGetControllerList(): array
|
||||
{
|
||||
public static function dataGetControllerList(): array
|
||||
{
|
||||
$expectedList = [
|
||||
'anime' => Controller\Anime::class,
|
||||
'anime-collection' => Controller\AnimeCollection::class,
|
||||
@ -248,11 +242,9 @@ final class DispatcherTest extends AnimeClientTestCase
|
||||
'expected' => $expectedList,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataGetControllerList
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('dataGetControllerList')]
|
||||
public function testGetControllerList(array $config, array $expected): void
|
||||
{
|
||||
$this->doSetUp($config, '/', 'localhost');
|
||||
|
@ -251,7 +251,7 @@ final class FormGeneratorTest extends AnimeClientTestCase
|
||||
{
|
||||
$generator = FormGenerator::new($this->container);
|
||||
|
||||
foreach (SETTINGS_MAP as $section => $fields)
|
||||
foreach (SETTINGS_MAP as $fields)
|
||||
{
|
||||
foreach ($fields as $name => $config)
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user