Merge branch 'develop'
All checks were successful
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good

This commit is contained in:
Timothy Warren 2024-01-23 13:35:40 -05:00
commit 144718866d
373 changed files with 15763 additions and 14296 deletions

6
.gitignore vendored
View File

@ -150,4 +150,8 @@ public/mal_mappings.json
.is-dev
tmp
tmp
tools/vendor/
tools/phinx/vendor/
/.php-cs-fixer.php
/.php-cs-fixer.cache

534
.php-cs-fixer.dist.php Normal file
View File

@ -0,0 +1,534 @@
<?php declare(strict_types=1);
use Nexus\CsConfig\Factory;
use PhpCsFixer\{Config, Finder};
$finder = Finder::create()
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
__DIR__ . '/tools',
])
->exclude([
'vendor',
]);
return (new Config())
->setRiskyAllowed(TRUE)
->setFinder($finder)
->setIndent(' ')
->setRules([
'align_multiline_comment' => false,
'array_indentation' => true,
'array_push' => true,
'array_syntax' => ['syntax' => 'short'],
'assign_null_coalescing_to_coalesce_equal' => true,
'backtick_to_shell_exec' => true,
'binary_operator_spaces' => [
'default' => 'single_space',
'operators' => [
'=' => NULL,
'&' => NULL,
]
],
'blank_line_after_namespace' => true,
'blank_line_after_opening_tag' => false,
'blank_line_before_statement' => [
'statements' => [
// 'case',
'continue',
'declare',
'default',
'do',
'exit',
'for',
'foreach',
'goto',
'return',
'switch',
'throw',
'try',
'while',
'yield',
'yield_from',
],
],
// 'braces' => [
// 'allow_single_line_anonymous_class_with_empty_body' => true,
// 'allow_single_line_closure' => true,
// 'position_after_anonymous_constructs' => 'same',
// 'position_after_control_structures' => 'next',
// 'position_after_functions_and_oop_constructs' => 'next',
// ],
'cast_spaces' => ['space' => 'single'],
'class_attributes_separation' => [
'elements' => [
'const' => 'none',
'property' => 'none',
'method' => 'one',
'trait_import' => 'none',
],
],
'class_definition' => [
'multi_line_extends_each_single_line' => true,
'single_item_single_line' => true,
'single_line' => true,
'space_before_parenthesis' => true,
],
'class_reference_name_casing' => true,
'clean_namespace' => true,
'combine_consecutive_issets' => true,
'combine_consecutive_unsets' => true,
'combine_nested_dirname' => true,
'comment_to_phpdoc' => [
'ignored_tags' => [
'todo',
'codeCoverageIgnore',
'codeCoverageIgnoreStart',
'codeCoverageIgnoreEnd',
'phpstan-ignore-line',
'phpstan-ignore-next-line',
],
],
'compact_nullable_typehint' => true,
'concat_space' => ['spacing' => 'one'],
'constant_case' => ['case' => 'upper'],
'control_structure_braces' => true,
'control_structure_continuation_position' => ['position' => 'next_line'],
'curly_braces_position' => [
'allow_single_line_anonymous_functions' => true,
'allow_single_line_empty_anonymous_classes' => true,
'anonymous_functions_opening_brace' => 'same_line',
'classes_opening_brace' => 'next_line_unless_newline_at_signature_end',
'control_structures_opening_brace' => 'next_line_unless_newline_at_signature_end',
'functions_opening_brace' => 'next_line_unless_newline_at_signature_end',
],
'date_time_immutable' => false,
'declare_equal_normalize' => ['space' => 'none'],
'declare_parentheses' => true,
'declare_strict_types' => true,
'dir_constant' => true,
'doctrine_annotation_array_assignment' => false,
'doctrine_annotation_braces' => false,
'doctrine_annotation_indentation' => false,
'doctrine_annotation_spaces' => false,
'echo_tag_syntax' => [
'format' => 'short',
'long_function' => 'echo',
'shorten_simple_statements_only' => false,
],
'elseif' => false,
'empty_loop_body' => ['style' => 'braces'],
'empty_loop_condition' => ['style' => 'while'],
'encoding' => true,
'error_suppression' => [
'mute_deprecation_error' => true,
'noise_remaining_usages' => false,
'noise_remaining_usages_exclude' => [],
],
'escape_implicit_backslashes' => [
'double_quoted' => false,
'heredoc_syntax' => false,
'single_quoted' => false,
],
'explicit_indirect_variable' => false,
'explicit_string_variable' => false,
'final_class' => false,
'final_internal_class' => [
'annotation_exclude' => ['@no-final'],
'annotation_include' => ['@internal'],
'consider_absent_docblock_as_internal_class' => false,
],
'final_public_method_for_abstract_class' => false,
'fopen_flag_order' => true,
'fopen_flags' => ['b_mode' => true],
'full_opening_tag' => true,
'fully_qualified_strict_types' => true,
'function_declaration' => ['closure_function_spacing' => 'one'],
'function_to_constant' => [
'functions' => [
'get_called_class',
'get_class',
'get_class_this',
'php_sapi_name',
'phpversion',
'pi',
],
],
'function_typehint_space' => true,
'general_phpdoc_annotation_remove' => false,
'general_phpdoc_tag_rename' => false,
'get_class_to_class_keyword' => false,
'global_namespace_import' => [
'import_constants' => true,
'import_functions' => true,
'import_classes' => true,
],
'group_import' => true,
'header_comment' => false, // false by default
// 'heredoc_indentation' => ['indentation' => 'start_plus_one'],
'heredoc_to_nowdoc' => true,
'implode_call' => true,
'include' => true,
'increment_style' => ['style' => 'post'],
'indentation_type' => true,
'integer_literal_case' => true,
'is_null' => true,
'lambda_not_used_import' => true,
'line_ending' => true,
'linebreak_after_opening_tag' => false,
'list_syntax' => ['syntax' => 'short'],
'logical_operators' => true,
'lowercase_cast' => true,
'lowercase_keywords' => true,
'lowercase_static_reference' => true,
'magic_constant_casing' => true,
'magic_method_casing' => true,
'mb_str_functions' => false,
'method_argument_space' => [
'after_heredoc' => false,
'keep_multiple_spaces_after_comma' => false,
'on_multiline' => 'ensure_fully_multiline',
],
'method_chaining_indentation' => true,
'modernize_strpos' => false, // requires 8.0+
'modernize_types_casting' => true,
'multiline_comment_opening_closing' => true,
'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'],
'native_constant_invocation' => false,
'native_function_casing' => true,
'native_function_invocation' => false,
'native_function_type_declaration_casing' => true,
'new_with_braces' => true,
'no_alias_functions' => ['sets' => ['@all']],
'no_alias_language_construct_call' => true,
'no_alternative_syntax' => ['fix_non_monolithic_code' => false],
'no_binary_string' => true,
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_blank_lines_before_namespace' => false, // conflicts with `single_blank_line_before_namespace`
'no_break_comment' => ['comment_text' => 'no break'],
'no_closing_tag' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => ['tokens' => ['extra']],
'no_homoglyph_names' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => ['use' => 'echo'],
'no_multiline_whitespace_around_double_arrow' => true,
'no_null_property_initialization' => true,
'no_short_bool_cast' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_space_around_double_colon' => true,
'no_spaces_after_function_name' => true,
'no_spaces_around_offset' => ['positions' => ['inside', 'outside']],
'no_spaces_inside_parenthesis' => true,
'no_superfluous_elseif' => true,
'no_superfluous_phpdoc_tags' => [
'allow_mixed' => true,
'allow_unused_params' => true,
'remove_inheritdoc' => false,
],
'no_trailing_comma_in_singleline' => true,
'no_trailing_whitespace' => true,
'no_trailing_whitespace_in_comment' => true,
'no_trailing_whitespace_in_string' => true,
'no_unneeded_control_parentheses' => [
'statements' => [
'break',
'clone',
'continue',
'echo_print',
'return',
'switch_case',
'yield',
],
],
'no_unneeded_curly_braces' => ['namespaces' => true],
'no_unneeded_final_method' => ['private_methods' => true],
'no_unneeded_import_alias' => true,
'no_unreachable_default_argument_value' => true,
'no_unset_cast' => true,
'no_unset_on_property' => false,
'no_unused_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'no_useless_sprintf' => true,
'no_whitespace_before_comma_in_array' => ['after_heredoc' => true],
'no_whitespace_in_blank_line' => true,
'non_printable_character' => ['use_escape_sequences_in_strings' => true],
'normalize_index_brace' => true,
'not_operator_with_space' => true,
'not_operator_with_successor_space' => true,
'nullable_type_declaration_for_default_null_value' => ['use_nullable_type_declaration' => true],
'object_operator_without_whitespace' => true,
'operator_linebreak' => ['only_booleans' => true, 'position' => 'beginning'],
'ordered_class_elements' => [
'order' => [
'use_trait',
'case',
'constant_public',
'constant_protected',
'constant_private',
'property_public',
'property_protected',
'property_private',
'construct',
'destruct',
'magic',
],
'sort_algorithm' => 'none',
],
'ordered_imports' => [
'sort_algorithm' => 'alpha',
'imports_order' => ['class', 'function', 'const'],
],
'ordered_interfaces' => false,
'ordered_traits' => false,
'php_unit_construct' => [
'assertions' => [
'assertSame',
'assertEquals',
'assertNotEquals',
'assertNotSame',
],
],
'php_unit_dedicate_assert' => ['target' => 'newest'],
'php_unit_dedicate_assert_internal_type' => ['target' => 'newest'],
'php_unit_expectation' => ['target' => 'newest'],
'php_unit_fqcn_annotation' => true,
'php_unit_internal_class' => ['types' => ['final']],
'php_unit_method_casing' => ['case' => 'camel_case'],
'php_unit_mock' => ['target' => 'newest'],
'php_unit_mock_short_will_return' => true,
'php_unit_namespaced' => ['target' => 'newest'],
'php_unit_no_expectation_annotation' => [
'target' => 'newest',
'use_class_const' => true,
],
'php_unit_set_up_tear_down_visibility' => true,
'php_unit_size_class' => false,
// 'php_unit_strict' => [
// 'assertions' => [
// 'assertAttributeEquals',
// 'assertAttributeNotEquals',
// 'assertEquals',
// 'assertNotEquals',
// ],
// ],
'php_unit_test_annotation' => ['style' => 'prefix'],
'php_unit_test_case_static_method_calls' => [
'call_type' => 'this',
'methods' => [],
],
'php_unit_test_class_requires_covers' => false,
'phpdoc_add_missing_param_annotation' => ['only_untyped' => true],
'phpdoc_align' => [
'align' => 'left'
],
'phpdoc_annotation_without_dot' => false,
'phpdoc_indent' => true,
'phpdoc_inline_tag_normalizer' => [
'tags' => [
'example',
'id',
'internal',
'inheritdoc',
'inheritdocs',
'link',
'source',
'toc',
'tutorial',
],
],
'phpdoc_line_span' => [
'const' => 'multi',
'method' => 'multi',
'property' => 'multi',
],
'phpdoc_no_access' => true,
'phpdoc_no_empty_return' => false,
'phpdoc_no_package' => false,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_order' => true,
'phpdoc_order_by_value' => [
'annotations' => [
'author',
'covers',
'coversNothing',
'dataProvider',
'depends',
'group',
'internal',
'method',
'property',
'property-read',
'property-write',
'requires',
'throws',
'uses',
],
],
'phpdoc_return_self_reference' => [
'replacements' => [
'this' => '$this',
'@this' => '$this',
'$self' => 'self',
'@self' => 'self',
'$static' => 'static',
'@static' => 'static',
],
],
'phpdoc_scalar' => [
'types' => [
'boolean',
'callback',
'double',
'integer',
'real',
'str',
],
],
'phpdoc_separation' => false,
'phpdoc_single_line_var_spacing' => true,
'phpdoc_summary' => false,
'phpdoc_tag_casing' => ['tags' => ['inheritDoc']],
'phpdoc_tag_type' => ['tags' => ['inheritDoc' => 'inline']],
'phpdoc_to_comment' => false,
'phpdoc_to_param_type' => false,
'phpdoc_to_property_type' => false,
'phpdoc_to_return_type' => false,
'phpdoc_trim' => true,
'phpdoc_trim_consecutive_blank_line_separation' => true,
'phpdoc_types' => ['groups' => ['simple', 'alias', 'meta']],
'phpdoc_types_order' => [
'null_adjustment' => 'always_last',
'sort_algorithm' => 'alpha',
],
'phpdoc_var_annotation_correct_order' => true,
'phpdoc_var_without_name' => true,
'pow_to_exponentiation' => true,
'protected_to_private' => true,
'psr_autoloading' => ['dir' => null],
'random_api_migration' => [
'replacements' => [
'getrandmax' => 'mt_getrandmax',
'rand' => 'mt_rand',
'srand' => 'mt_srand',
],
],
'regular_callable_call' => true,
'return_assignment' => true,
'return_type_declaration' => ['space_before' => 'none'],
'self_accessor' => false,
'self_static_accessor' => true,
'semicolon_after_instruction' => false,
'set_type_to_cast' => true,
'short_scalar_cast' => true,
'simple_to_complex_string_variable' => true,
'simplified_if_return' => true,
'simplified_null_return' => false,
'single_blank_line_at_eof' => true,
'single_blank_line_before_namespace' => true,
'single_class_element_per_statement' => ['elements' => ['const', 'property']],
'single_import_per_statement' => false,
'single_line_after_imports' => true,
'single_line_comment_style' => ['comment_types' => ['asterisk', 'hash']],
'single_line_throw' => false,
'single_quote' => ['strings_containing_single_quote_chars' => false],
'single_space_around_construct' => [
'constructs_followed_by_a_single_space' => [
'abstract',
'as',
'attribute',
'break',
'case',
'catch',
'class',
'clone',
'comment',
'const',
'const_import',
'continue',
'do',
'echo',
'else',
'elseif',
'extends',
'final',
'finally',
'for',
'foreach',
'function',
'function_import',
'global',
'goto',
'if',
'implements',
'include',
'include_once',
'instanceof',
'insteadof',
'interface',
'match',
'named_argument',
'new',
'open_tag_with_echo',
'php_doc',
'php_open',
'print',
'private',
'protected',
'public',
'require',
'require_once',
'return',
'static',
'throw',
'trait',
'try',
'use',
'use_lambda',
'use_trait',
'var',
'while',
'yield',
'yield_from',
],
],
'single_trait_insert_per_statement' => true,
'space_after_semicolon' => ['remove_in_empty_for_expressions' => true],
'standardize_increment' => true,
'standardize_not_equals' => true,
'statement_indentation' => true,
'static_lambda' => true,
'strict_comparison' => true,
'strict_param' => true,
'string_length_to_empty' => true,
'string_line_ending' => true,
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
'switch_continue_to_break' => true,
'ternary_operator_spaces' => true,
'ternary_to_elvis_operator' => true,
'ternary_to_null_coalescing' => true,
'trailing_comma_in_multiline' => [
'after_heredoc' => true,
'elements' => ['arrays'],
],
'trim_array_spaces' => true,
'types_spaces' => ['space' => 'none'],
'unary_operator_spaces' => false,
'use_arrow_functions' => true,
'visibility_required' => ['elements' => ['const', 'method', 'property']],
'void_return' => false, // changes method signature
'whitespace_after_comma_in_array' => true,
'yoda_style' => [
'equal' => false,
'identical' => null,
'less_and_greater' => false,
'always_move_variable' => false,
],
]);

View File

@ -4,6 +4,8 @@ install:
- composer install --ignore-platform-reqs
php:
- 8.0
- 8.1
- nightly
script:

View File

@ -1,7 +1,12 @@
# Changelog
## Version 5.3
* Update PHP requirement to 8.2
## Version 5.2
* Updated PHP requirement to 8
* 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.

27
Jenkinsfile vendored
View File

@ -10,10 +10,23 @@ pipeline {
sh 'php composer.phar install --ignore-platform-reqs'
}
}
stage('PHP 8') {
stage('PHP 8.1') {
agent {
docker {
image 'php:8-cli-alpine'
image 'php:8.1-cli-alpine'
args '-u root --privileged'
}
}
steps {
sh 'apk add --no-cache git icu-dev'
sh 'docker-php-ext-configure intl && docker-php-ext-install intl'
sh 'php ./vendor/bin/phpunit --colors=never'
}
}
stage('PHP 8.2') {
agent {
docker {
image 'php:8.2-cli-alpine'
args '-u root --privileged'
}
}
@ -36,16 +49,6 @@ pipeline {
sh 'php ./vendor/bin/phpunit --colors=never'
}
}
stage('Code Cleanliness') {
agent any
steps {
sh "php ./vendor/bin/phpstan analyse -c phpstan.neon -n --no-progress --no-ansi --error-format=checkstyle | awk '{\$1=\$1;print}' > build/logs/phpstan.log"
recordIssues(
failOnError: false,
tools: [phpStan(reportEncoding: 'UTF-8', pattern: 'build/logs/phpstan.log')]
)
}
}
stage('Coverage') {
agent any
steps {

View File

@ -31,12 +31,18 @@ Update your anime/manga list on Kitsu.io and Anilist
### Requirements
* PHP 8
* PDO SQLite or PDO PostgreSQL (For collection tab)
* GD extension for caching images
* PHP 8.2
* ext-dom (For editing the DOM)
* ext-gd (For caching images)
* ext-intl (For time localization)
* ext-json
* ext-mbstring
* ext-pdo
### Highly Recommended
* Redis or Memcached for caching
* PDO SQLite or PDO PostgreSQL (For collection tab)
### Installation

View File

@ -57,4 +57,4 @@ return array_merge($tomlConfig, [
// Included config files
'routes' => require 'routes.php',
]);
]);

View File

@ -16,10 +16,10 @@
use const Aviat\AnimeClient\{
ALPHA_SLUG_PATTERN,
NUM_PATTERN,
SLUG_PATTERN,
DEFAULT_CONTROLLER,
DEFAULT_CONTROLLER_METHOD,
DEFAULT_CONTROLLER
SLUG_PATTERN,
NUM_PATTERN,
};
// -------------------------------------------------------------------------
@ -27,7 +27,7 @@ use const Aviat\AnimeClient\{
//
// Maps paths to controllers and methods
// -------------------------------------------------------------------------
$routes = [
$base_routes = [
// ---------------------------------------------------------------------
// AJAX Routes
// ---------------------------------------------------------------------
@ -190,14 +190,14 @@ $routes = [
'character' => [
'path' => '/character/{slug}',
'tokens' => [
'slug' => SLUG_PATTERN
]
'slug' => SLUG_PATTERN,
],
],
'person' => [
'path' => '/people/{slug}',
'tokens' => [
'slug' => SLUG_PATTERN,
]
],
],
'default_user_info' => [
'path' => '/me',
@ -209,8 +209,8 @@ $routes = [
'controller' => 'user',
'action' => 'about',
'tokens' => [
'username' => '.*?'
]
'username' => '.*?',
],
],
// ---------------------------------------------------------------------
// Default / Shared routes
@ -231,8 +231,8 @@ $routes = [
'controller' => 'images',
'tokens' => [
'type' => SLUG_PATTERN,
'file' => '[a-z0-9\-]+\.[a-z]{3,4}'
]
'file' => '[a-z0-9\-]+\.[a-z]{3,4}',
],
],
'settings' => [
'path' => '/settings',
@ -259,8 +259,8 @@ $routes = [
'controller' => 'history',
'path' => '/history/{type}',
'tokens' => [
'type' => SLUG_PATTERN
]
'type' => SLUG_PATTERN,
],
],
'increment' => [
'path' => '/{controller}/increment',
@ -291,7 +291,7 @@ $routes = [
'action' => 'edit',
'tokens' => [
'id' => SLUG_PATTERN,
'status' => '([a-zA-Z\-_]|%20)+',
'status' => SLUG_PATTERN,
],
],
'list' => [
@ -314,15 +314,10 @@ $defaultMap = [
'verb' => 'get',
];
foreach ($routes as &$route)
$routes = [];
foreach ($base_routes as $name => $route)
{
foreach($defaultMap as $key => $val)
{
if ( ! array_key_exists($key, $route))
{
$route[$key] = $val;
}
}
$routes[$name] = array_merge($defaultMap, $route);
}
return $routes;

View File

@ -4,11 +4,9 @@
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 8
* PHP version 8.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @copyright 2015 - 2023 Timothy J. Warren <tim@timshome.page>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
@ -20,12 +18,10 @@ use Aura\Html\HelperLocatorFactory;
use Aura\Router\RouterContainer;
use Aura\Session\SessionFactory;
use Aviat\AnimeClient\API\{Anilist, Kitsu};
use Aviat\AnimeClient\Component;
use Aviat\AnimeClient\Model;
use Aviat\AnimeClient\{Component, Model};
use Aviat\Banker\Teller;
use Aviat\Ion\Config;
use Aviat\Ion\Di\Container;
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Di\{Container, ContainerInterface};
use Laminas\Diactoros\ServerRequestFactory;
use Monolog\Formatter\JsonFormatter;
use Monolog\Handler\RotatingFileHandler;
@ -38,7 +34,7 @@ if ( ! defined('HB_APP_DIR'))
{
define('HB_APP_DIR', __DIR__);
define('ROOT_DIR', dirname(HB_APP_DIR));
define('TEMPLATE_DIR', _dir(HB_APP_DIR, 'templates'));
define('TEMPLATE_DIR', _dir(HB_APP_DIR, 'templates'));
}
// -----------------------------------------------------------------------------
@ -74,18 +70,19 @@ return static function (array $configArray = []): Container {
$container->set('config', static fn () => new Config($configArray));
// Create Cache Object
$container->set('cache', static function(ContainerInterface $container): CacheInterface {
$container->set('cache', static function (ContainerInterface $container): CacheInterface {
$logger = $container->getLogger();
$config = $container->get('config')->get('cache');
return new Teller($config, $logger);
});
// Create Aura Router Object
$container->set('aura-router', static fn() => new RouterContainer);
$container->set('aura-router', static fn () => new RouterContainer());
// Create Html helpers
$container->set('html-helper', static function(ContainerInterface $container) {
$htmlHelper = (new HelperLocatorFactory)->newInstance();
$container->set('html-helper', static function (ContainerInterface $container) {
$htmlHelper = (new HelperLocatorFactory())->newInstance();
$helpers = [
'menu' => Helper\Menu::class,
'field' => Helper\Form::class,
@ -94,9 +91,10 @@ return static function (array $configArray = []): Container {
foreach ($helpers as $name => $class)
{
$htmlHelper->set($name, static function() use ($class, $container) {
$helper = new $class;
$htmlHelper->set($name, static function () use ($class, $container) {
$helper = new $class();
$helper->setContainer($container);
return $helper;
});
}
@ -106,7 +104,7 @@ return static function (array $configArray = []): Container {
// Create Component helpers
$container->set('component-helper', static function (ContainerInterface $container) {
$helper = (new HelperLocatorFactory)->newInstance();
$helper = (new HelperLocatorFactory())->newInstance();
$components = [
'animeCover' => Component\AnimeCover::class,
'mangaCover' => Component\MangaCover::class,
@ -119,8 +117,9 @@ return static function (array $configArray = []): Container {
foreach ($components as $name => $componentClass)
{
$helper->set($name, static function () use ($container, $componentClass) {
$helper = new $componentClass;
$helper = new $componentClass();
$helper->setContainer($container);
return $helper;
});
}
@ -140,11 +139,8 @@ return static function (array $configArray = []): Container {
// Create session Object
$container->set('session', static fn () => (new SessionFactory())->newInstance($_COOKIE));
// Miscellaneous helper methods
$container->set('util', static fn ($container) => new Util($container));
// Models
$container->set('kitsu-model', static function(ContainerInterface $container): Kitsu\Model {
$container->set('kitsu-model', static function (ContainerInterface $container): Kitsu\Model {
$requestBuilder = new Kitsu\RequestBuilder($container);
$requestBuilder->setLogger($container->getLogger('kitsu-request'));
@ -158,9 +154,10 @@ return static function (array $configArray = []): Container {
$cache = $container->get('cache');
$model->setCache($cache);
return $model;
});
$container->set('anilist-model', static function(ContainerInterface $container): Anilist\Model {
$container->set('anilist-model', static function (ContainerInterface $container): Anilist\Model {
$requestBuilder = new Anilist\RequestBuilder($container);
$requestBuilder->setLogger($container->getLogger('anilist-request'));
@ -174,26 +171,29 @@ return static function (array $configArray = []): Container {
return $model;
});
$container->set('anime-model', static fn ($container) => new Model\Anime($container));
$container->set('manga-model', static fn ($container) => new Model\Manga($container));
$container->set('anime-collection-model', static fn ($container) => new Model\AnimeCollection($container));
$container->set('manga-collection-model', static fn ($container) => new Model\MangaCollection($container));
$container->set('settings-model', static function($container) {
$container->set('settings-model', static function ($container) {
$model = new Model\Settings($container->get('config'));
$model->setContainer($container);
return $model;
});
$container->setSimple('anime-model', Model\Anime::class);
$container->setSimple('manga-model', Model\Manga::class);
$container->setSimple('anime-collection-model', Model\AnimeCollection::class);
// Miscellaneous Classes
$container->set('auth', static fn ($container) => new Kitsu\Auth($container));
$container->set('url-generator', static fn ($container) => new UrlGenerator($container));
$container->setSimple('util', Util::class);
$container->setSimple('auth', Kitsu\Auth::class);
$container->setSimple('url-generator', UrlGenerator::class);
$container->setSimple('render-helper', RenderHelper::class);
// -------------------------------------------------------------------------
// Dispatcher
// -------------------------------------------------------------------------
$container->set('dispatcher', static fn ($container) => new Dispatcher($container));
$container->setSimple('dispatcher', Dispatcher::class);
return $container;
};
// End of bootstrap.php
// End of bootstrap.php

View File

@ -0,0 +1,6 @@
################################################################################
# Anilist API #
################################################################################
client_id = "your_client_id"
client_secret = "your_client_secret"
username = "user123"

View File

@ -14,6 +14,9 @@ show_anime_collection = true
# do you wish to show the manga collection?
show_manga_collection = false
# what theme would you like to use? light, dark, or auto
theme = "auto"
################################################################################
# Default views and paths
################################################################################

0
app/logs/.gitkeep Normal file → Executable file
View File

View File

@ -1,15 +1,16 @@
<article
class="media"
data-kitsu-id="<?= $item['id'] ?>"
data-anilist-id="<?= $item['anilist_id'] ?>"
data-mal-id="<?= $item['mal_id'] ?>"
>
<?php if ($auth->isAuthenticated()): ?>
<?php if ($_->isAuthenticated()): ?>
<button title="Increment episode count" class="plus-one" hidden>+1 Episode</button>
<?php endif ?>
<?= $helper->picture("images/anime/{$item['anime']['id']}.webp") ?>
<?= $_->h->img($item['anime']['cover_image'], ['width' => 220, 'loading' => 'lazy']) ?>
<div class="name">
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]) ?>">
<a href="<?= $_->urlFromRoute('anime.details', ['id' => $item['anime']['slug']]) ?>">
<span class="canonical"><?= $item['anime']['title'] ?></span>
<?php foreach ($item['anime']['titles'] as $title): ?>
<br/>
@ -49,7 +50,7 @@
<?php if ($link['meta']['link']): ?>
<a href="<?= $link['link'] ?>"
title="Stream '<?= $item['anime']['title'] ?>' on <?= $link['meta']['name'] ?>">
<?= $helper->img("/public/images/{$link['meta']['image']}", [
<?= $_->h->img("/public/images/{$link['meta']['image']}", [
'class' => 'streaming-logo',
'width' => 20,
'height' => 20,
@ -57,7 +58,7 @@
]); ?>
</a>
<?php else: ?>
<?= $helper->img("/public/images/{$link['meta']['image']}", [
<?= $_->h->img("/public/images/{$link['meta']['image']}", [
'class' => 'streaming-logo',
'width' => 20,
'height' => 20,
@ -69,11 +70,11 @@
</div>
<?php endif ?>
<?php if ($auth->isAuthenticated()): ?>
<?php if ($_->isAuthenticated()): ?>
<div class="row">
<span class="edit">
<a class="bracketed" title="Edit information about this anime" href="<?=
$url->generate('edit', [
$_->urlFromRoute('edit', [
'controller' => 'anime',
'id' => $item['id'],
'status' => $item['watching_status']
@ -91,9 +92,9 @@
</div>
</div>
<div class="row">
<div class="media_type"><?= $escape->html($item['anime']['show_type']) ?></div>
<div class="airing-status"><?= $escape->html($item['airing']['status']) ?></div>
<div class="age-rating"><?= $escape->html($item['anime']['age_rating']) ?></div>
<div class="media_type"><?= $_->escape->html($item['anime']['show_type']) ?></div>
<div class="airing-status"><?= $_->escape->html($item['airing']['status']) ?></div>
<div class="age-rating"><?= $_->escape->html($item['anime']['age_rating']) ?></div>
</div>
</div>
</article>

View File

@ -1,26 +1,25 @@
<article class="media" data-kitsu-id="<?= $item['id'] ?>" data-mal-id="<?= $item['mal_id'] ?>">
<?php if ($auth->isAuthenticated()): ?>
<?php if ($_->isAuthenticated()): ?>
<div class="edit-buttons" hidden>
<button class="plus-one-chapter">+1 Chapter</button>
<?php /* <button class="plus-one-volume">+1 Volume</button> */ ?>
</div>
<?php endif ?>
<?= $helper->picture("images/manga/{$item['manga']['id']}.webp") ?>
<?= $_->h->img($item['manga']['image'], ['width' => 220, 'loading' => 'lazy']) ?>
<div class="name">
<a href="<?= $url->generate('manga.details', ['id' => $item['manga']['slug']]) ?>">
<?= $escape->html($item['manga']['title']) ?>
<a href="<?= $_->urlFromRoute('manga.details', ['id' => $item['manga']['slug']]) ?>">
<?= $_->escape->html($item['manga']['title']) ?>
<?php foreach($item['manga']['titles'] as $title): ?>
<br /><small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
<div class="table">
<?php if ($auth->isAuthenticated()): ?>
<?php if ($_->isAuthenticated()): ?>
<div class="row">
<span class="edit">
<a class="bracketed"
title="Edit information about this manga"
href="<?= $url->generate('edit', [
href="<?= $_->urlFromRoute('edit', [
'controller' => 'manga',
'id' => $item['id'],
'status' => $name
@ -64,11 +63,6 @@
Chapters: <span class="chapters_read"><?= $item['chapters']['read'] ?></span> /
<span class="chapter_count"><?= $item['chapters']['total'] ?></span>
</div>
<?php /* </div>
<div class="row"> */ ?>
<div class="volume_completion">
Volumes: <span class="volume_count"><?= $item['volumes']['total'] ?></span>
</div>
</div>
</div>
</article>

View File

@ -1,4 +1,4 @@
<?php if ($auth->isAuthenticated()): ?>
<?php if ($_->isAuthenticated()): ?>
<main>
<h2>Add Anime to your List</h2>
<form action="<?= $action_url ?>" method="post">

View File

@ -1,6 +1,6 @@
<main class="media-list">
<?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate('anime.add.get') ?>">Add Item</a>
<?php if ($_->isAuthenticated()): ?>
<a class="bracketed" href="<?= $_->urlFromRoute('anime.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
@ -11,16 +11,16 @@
<?php foreach ($sections as $name => $items): ?>
<?php if (empty($items)): ?>
<section class="status">
<h2><?= $escape->html($name) ?></h2>
<h2><?= $_->escape->html($name) ?></h2>
<h3>There's nothing here!</h3>
</section>
<?php else: ?>
<section class="status">
<h2><?= $escape->html($name) ?></h2>
<h2><?= $_->escape->html($name) ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<?php if ($item['private'] && ! $auth->isAuthenticated()) continue; ?>
<?= $component->animeCover($item) ?>
<?php if ($item['private'] && ! $_->isAuthenticated()) continue; ?>
<?= $_->component->animeCover($item) ?>
<?php endforeach ?>
</section>
</section>

View File

@ -1,13 +1,12 @@
<?php
use Aviat\AnimeClient\Kitsu;
use function Aviat\AnimeClient\getLocalImg;
use function Aviat\AnimeClient\friendlyTime;
?>
<main class="details fixed">
<section class="flex" unselectable>
<section class="flex">
<aside class="info">
<?= $helper->picture("images/anime/{$data['id']}-original.webp") ?>
<?= $_->h->img($data['cover_image'], ['width' => '390']) ?>
<br />
@ -17,6 +16,13 @@ use function Aviat\AnimeClient\getLocalImg;
<td><?= $data['status'] ?></td>
</tr>
<?php if ( ! empty($data['airDate'])): ?>
<tr>
<td>Original Airing</td>
<td><?= $data['airDate'] ?></td>
</tr>
<?php endif ?>
<tr>
<td>Show Type</td>
<td><?= (strlen($data['show_type']) > 3) ? ucfirst(strtolower($data['show_type'])) : $data['show_type'] ?></td>
@ -32,14 +38,14 @@ use function Aviat\AnimeClient\getLocalImg;
<?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 ?>
@ -104,7 +110,7 @@ use function Aviat\AnimeClient\getLocalImg;
href="<?= $link['link'] ?>"
title="Stream '<?= $data['title'] ?>' on <?= $link['meta']['name'] ?>"
>
<?= $helper->img("/public/images/{$link['meta']['image']}", [
<?= $_->h->img("/public/images/{$link['meta']['image']}", [
'class' => 'streaming-logo',
'width' => 50,
'height' => 50,
@ -113,7 +119,7 @@ use function Aviat\AnimeClient\getLocalImg;
&nbsp;&nbsp;<?= $link['meta']['name'] ?>
</a>
<?php else: ?>
<?= $helper->img("/public/images/{$link['meta']['image']}", [
<?= $_->h->img("/public/images/{$link['meta']['image']}", [
'class' => 'streaming-logo',
'width' => 50,
'height' => 50,
@ -122,8 +128,8 @@ use function Aviat\AnimeClient\getLocalImg;
&nbsp;&nbsp;<?= $link['meta']['name'] ?>
<?php endif ?>
</td>
<td><?= implode(', ', $link['subs']) ?></td>
<td><?= implode(', ', $link['dubs']) ?></td>
<td><?= implode(', ', array_map(fn ($sub) => Locale::getDisplayLanguage($sub, 'en'), $link['subs'])) ?></td>
<td><?= implode(', ', array_map(fn ($dub) => Locale::getDisplayLanguage($dub, 'en'), $link['dubs'])) ?></td>
</tr>
<?php endforeach ?>
</tbody>
@ -151,18 +157,18 @@ use function Aviat\AnimeClient\getLocalImg;
<section>
<h2>Characters</h2>
<?= $component->tabs('character-types', $data['characters'], static function ($characterList, $role)
use ($component, $url, $helper) {
<?= $_->component->tabs('character-types', $data['characters'], static function ($characterList, $role)
use ($_) {
$rendered = [];
foreach ($characterList as $id => $character):
if (empty($character['image']['original']))
if (empty($character['image']))
{
continue;
}
$rendered[] = $component->character(
$rendered[] = $_->component->character(
$character['name'],
$url->generate('character', ['slug' => $character['slug']]),
$helper->picture("images/characters/{$id}.webp"),
$_->urlFromRoute('character', ['slug' => $character['slug']]),
$_->h->img($character['image']),
(strtolower($role) !== 'main') ? 'small-character' : 'character'
);
endforeach;
@ -176,18 +182,18 @@ use function Aviat\AnimeClient\getLocalImg;
<section>
<h2>Staff</h2>
<?= $component->verticalTabs('staff-role', $data['staff'], static function ($staffList)
use ($component, $url, $helper) {
<?= $_->component->verticalTabs('staff-role', $data['staff'], static function ($staffList)
use ($_) {
$rendered = [];
foreach ($staffList as $id => $person):
if (empty($person['image']['original']))
if (empty($person['image']))
{
continue;
}
$rendered[] = $component->character(
$rendered[] = $_ ->component->character(
$person['name'],
$url->generate('person', ['slug' => $person['slug']]),
$helper->picture(getLocalImg($person['image']['original'] ?? NULL)),
$_->urlFromRoute('person', ['slug' => $person['slug']]),
$_->h->img($person['image']),
'character small-person',
);
endforeach;

View File

@ -1,4 +1,4 @@
<?php if ($auth->isAuthenticated()): ?>
<?php if ($_->isAuthenticated()): ?>
<main>
<h2>Edit Anime List Item</h2>
<form action="<?= $action ?>" method="post">
@ -6,9 +6,9 @@
<thead>
<tr>
<th>
<h3><?= $escape->html($item['anime']['title']) ?></h3>
<h3><?= $_->escape->html($item['anime']['title']) ?></h3>
<?php foreach($item['anime']['titles'] as $title): ?>
<h4><?= $escape->html($title) ?></h4>
<h4><?= $_->escape->html($title) ?></h4>
<?php endforeach ?>
</th>
</tr>
@ -16,7 +16,7 @@
<tbody>
<tr>
<td rowspan="9">
<?= $helper->picture("images/anime/{$item['anime']['id']}-original.webp", "jpg", [], ["width" => "390"]) ?>
<?= $_->h->img($item['anime']['cover_image']) ?>
</td>
</tr>
<tr>
@ -70,7 +70,7 @@
<tr>
<td><label for="notes">Notes</label></td>
<td>
<textarea name="notes" id="notes"><?= $escape->html($item['notes']) ?></textarea>
<textarea name="notes" id="notes"><?= $_->escape->html($item['notes']) ?></textarea>
</td>
</tr>
<tr>
@ -87,7 +87,7 @@
</tbody>
</table>
</form>
<form class="js-delete" action="<?= $url->generate('anime.delete') ?>" method="post">
<form class="js-delete" action="<?= $_->urlFromRoute('anime.delete') ?>" method="post">
<fieldset>
<legend>Danger Zone</legend>
<table class="form invisible">

View File

@ -1,7 +1,7 @@
<?php use function Aviat\AnimeClient\colNotEmpty; ?>
<main class="media-list">
<?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate('anime.add.get') ?>">Add Item</a>
<?php if ($_->isAuthenticated()): ?>
<a class="bracketed" href="<?= $_->urlFromRoute('anime.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
@ -20,26 +20,26 @@
<table class='media-wrap'>
<thead>
<tr>
<?php if($auth->isAuthenticated()): ?>
<?php if($_->isAuthenticated()): ?>
<td class="no-border">&nbsp;</td>
<?php endif ?>
<th>Title</th>
<th>Airing Status</th>
<th>Score</th>
<th class='numeric'>Score</th>
<th>Type</th>
<th>Progress</th>
<th>Rated</th>
<th class='numeric'>Progress</th>
<th class='rating'>Age Rating</th>
<th>Attributes</th>
<?php if($hasNotes): ?><th>Notes</th><?php endif ?>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<?php if ($item['private'] && ! $auth->isAuthenticated()) continue; ?>
<?php if ($item['private'] && ! $_->isAuthenticated()) continue; ?>
<tr id="a-<?= $item['id'] ?>">
<?php if ($auth->isAuthenticated()): ?>
<?php if ($_->isAuthenticated()): ?>
<td>
<a class="bracketed" href="<?= $url->generate('edit', [
<a class="bracketed" href="<?= $_->urlFromRoute('edit', [
'controller' => 'anime',
'id' => $item['id'],
'status' => $item['watching_status']
@ -47,7 +47,7 @@
</td>
<?php endif ?>
<td class="align-left justify">