Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good Details

master
Timothy Warren 5 days ago
commit 5076657b38

6
.gitignore vendored

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

@ -0,0 +1,528 @@
<?php declare(strict_types=1);
use Nexus\CsConfig\Factory;
use PhpCsFixer\{Config, Finder};
$finder = Finder::create()
->in([
__DIR__,
__DIR__ . '/app',
__DIR__ . '/tools',
])
->exclude([
'apidocs',
'build',
'coverage',
'frontEndSrc',
'phinx',
'public',
'tools',
'tmp',
'vendor',
'views',
'templates',
]);
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_continuation_position' => ['position' => 'next_line'],
'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' => true,
'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' => true,
'heredoc_syntax' => true,
'single_quoted' => false,
],
'explicit_indirect_variable' => true,
'explicit_string_variable' => true,
'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_list_call' => true,
'no_trailing_comma_in_singleline_array' => 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',
'constant',
'property',
'method',
],
'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_after_construct' => [
'constructs' => [
'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,
'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,
],
]);

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

@ -16,10 +16,10 @@
use const Aviat\AnimeClient\{
ALPHA_SLUG_PATTERN,
NUM_PATTERN,
SLUG_PATTERN,
DEFAULT_CONTROLLER,
DEFAULT_CONTROLLER_METHOD,
DEFAULT_CONTROLLER
NUM_PATTERN,
SLUG_PATTERN
};
// -------------------------------------------------------------------------
@ -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',
@ -316,7 +316,7 @@ $defaultMap = [
foreach ($routes as &$route)
{
foreach($defaultMap as $key => $val)
foreach ($defaultMap as $key => $val)
{
if ( ! array_key_exists($key, $route))
{

@ -6,12 +6,10 @@
*
* PHP version 8
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @copyright 2015 - 2022 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
* @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient;
@ -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;
});
}
@ -144,7 +143,7 @@ return static function (array $configArray = []): Container {
$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 +157,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'));
@ -178,9 +178,10 @@ return static function (array $configArray = []): 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;
});
@ -196,4 +197,4 @@ return static function (array $configArray = []): Container {
return $container;
};
// End of bootstrap.php
// End of bootstrap.php

@ -1,6 +1,7 @@
<article
class="media"
data-kitsu-id="<?= $item['id'] ?>"
data-anilist-id="<?= $item['anilist_id'] ?>"
data-mal-id="<?= $item['mal_id'] ?>"
>
<?php if ($auth->isAuthenticated()): ?>

@ -1,4 +1,16 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 8
*
* @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2
* @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient;

@ -1,99 +0,0 @@
<?php
declare(strict_types=1);
$file_patterns = [
'app/appConf/*.php',
'app/bootstrap.php',
'migrations/*.php',
'src/**/*.php',
'src/*.php',
'tests/**/*.php',
'tests/*.php',
'index.php',
'Robofile.php'
];
if ( ! function_exists('glob_recursive'))
{
// Does not support flag GLOB_BRACE
function glob_recursive(string $pattern, int $flags = 0): array
{
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir)
{
$files = array_merge($files, glob_recursive($dir . '/' . basename($pattern), $flags));
}
return $files;
}
}
function get_text_to_replace(array $tokens): string
{
$output = '';
// Tokens have the follow structure if arrays:
// [0] => token type constant
// [1] => raw syntax parsed to that token
// [2] => line number
foreach($tokens as $token)
{
// Since we only care about opening docblocks,
// bail out when we get to the namespace token
if (is_array($token) && $token[0] === T_NAMESPACE)
{
break;
}
if (is_array($token))
{
$token = $token[1];
}
$output .= $token;
}
return $output;
}
function get_tokens(string $source): array
{
return token_get_all($source);
}
function replace_files(array $files, string $template): void
{
print_r($files);
foreach ($files as $file)
{
$source = file_get_contents($file);
if ($source === FALSE)
{
continue;
}
if (stripos($source, 'namespace') === FALSE)
{
continue;
}
$tokens = get_tokens($source);
$text_to_replace = get_text_to_replace($tokens);
$header = file_get_contents(__DIR__ . $template);
$new_text = "<?php declare(strict_types=1);\n{$header}";
$new_source = str_replace($text_to_replace, $new_text, $source);
file_put_contents($file, $new_source);
}
}
foreach ($file_patterns as $glob)
{
$files = glob_recursive($glob);
replace_files($files, '/header_comment.txt');
}
echo "Successfully updated headers \n";

@ -27,10 +27,7 @@
}
},
"config": {
"lock": false,
"platform": {
"php": "8"
}
"lock": false
},
"require": {
"amphp/amp": "^2.5.0",
@ -38,22 +35,20 @@
"aura/html": "^2.5.0",
"aura/router": "^3.1.0",
"aura/session": "^2.1.0",
"aviat/banker": "^3.0.0 || ^4.0.0",
"aviat/query": "^3.0.0",
"danielstjules/stringy": "^3.1.0",
"aviat/banker": "^4.1.2",
"aviat/query": "^4.0.0",
"ext-dom": "*",
"ext-gd": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-gd": "*",
"ext-mbstring": "*",
"ext-pdo": "*",
"laminas/laminas-diactoros": "^2.5.0",
"laminas/laminas-httphandlerrunner": "^2.1.0",
"maximebf/consolekit": "^1.0.3",
"monolog/monolog": "^2.0.2",
"php": ">= 8.0.0",
"monolog/monolog": "^3.0.0",
"php": ">= 8.1.0",
"psr/http-message": "^1.0.1",
"psr/log": "*",
"robmorgan/phinx": "^0.12.4",
"symfony/polyfill-mbstring": "^1.0.0",
"symfony/polyfill-util": "^1.0.0",
"tracy/tracy": "^2.8.0",
@ -68,7 +63,7 @@
"scripts": {
"build:css": "cd public && npm run build:css && cd ..",
"build:js": "cd public && npm run build:js && cd ..",
"coverage": "phpdbg -qrr -- vendor/bin/phpunit -c build",
"coverage": "php -dpcov.enabled=1 -dpcov.directory=. -dpcov.exclude=\"~vendor~\" ./vendor/bin/phpunit -c build",
"phpstan": "phpstan analyse -c phpstan.neon",
"watch:css": "cd public && npm run watch:css",
"watch:js": "cd public && npm run watch:js",

@ -63,6 +63,7 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
// Setup the update data
let data = {
id: parentSel.dataset.kitsuId,
anilist_id: parentSel.dataset.anilistId,
mal_id: parentSel.dataset.malId,
data: {
progress: watchedCount + 1
@ -94,11 +95,18 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
_.hide('#loading-shadow');
_.showMessage('error', `Failed to update ${title}. `);
_.scrollToTop();
return;
}
// We've completed the series
if (resData.data.libraryEntry.update.libraryEntry.status === 'COMPLETED') {
_.hide(parentSel);
_.hide('#loading-shadow');
_.showMessage('success', `Successfully completed ${title}`);
_.scrollToTop();
return;
}
_.hide('#loading-shadow');

@ -83,6 +83,11 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
if (String(data.data.status).toUpperCase() === 'COMPLETED') {
_.hide(parentSel);
_.hide('#loading-shadow');
_.showMessage('success', `Successfully completed ${mangaName}`);
_.scrollToTop();
return;
}
_.hide('#loading-shadow');

@ -5,6 +5,7 @@ import _ from './anime-client.js';
_.on('main', 'change', '.big-check', (e) => {
const id = e.target.id;
document.getElementById(`mal_${id}`).checked = true;
document.getElementById(`anilist_${id}`).checked = true;
});
/**
@ -55,6 +56,7 @@ export function renderSearchResults (type, data, isCollection = false) {
return `
<article class="media search ${disabled}">
<div class="name">
<input type="radio" class="mal-check" id="anilist_${item.slug}" name="anilist_id" value="${item.anilist_id}" ${disabled} />
<input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" ${disabled} />
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${item.id}" ${disabled} />
<label for="${item.slug}">

@ -11,11 +11,11 @@
"devDependencies": {
"@swc/cli": "^0.1.39",
"@swc/core": "^1.2.54",
"concurrently": "^6.0.2",
"concurrently": "^7.4.0",
"cssnano": "^5.0.1",
"postcss": "^8.2.6",
"postcss-import": "^14.0.0",
"postcss-preset-env": "^6.7.0",
"postcss-import": "^15.0.0",
"postcss-preset-env": "^7.8.2",
"watch": "^1.0.2"
}
}

File diff suppressed because it is too large Load Diff

@ -6,12 +6,10 @@
*
* PHP version 8
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @copyright 2015 - 2022 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
* @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient;
@ -25,8 +23,8 @@ setlocale(LC_CTYPE, 'en_US');
// Load composer autoloader
require_once __DIR__ . '/vendor/autoload.php';
Debugger::$strictMode = E_ALL & ~E_DEPRECATED; // all errors except deprecated notices
Debugger::$showBar = false;
Debugger::$strictMode = E_ALL;
Debugger::$showBar = FALSE;
Debugger::enable(Debugger::DEVELOPMENT, __DIR__ . '/app/logs');
// Define base directories
@ -37,7 +35,7 @@ $CONF_DIR = _dir($APP_DIR, 'config');
// -----------------------------------------------------------------------------
// Dependency Injection setup
// -----------------------------------------------------------------------------
$baseConfig = require "{$APPCONF_DIR}/base_config.php";
$baseConfig = require _dir($APPCONF_DIR, 'base_config.php');
$di = require "{$APP_DIR}/bootstrap.php";
$config = loadConfig($CONF_DIR);
@ -59,7 +57,7 @@ if (is_array($checkedConfig) && array_key_exists('timezone', $checkedConfig) &&
{
date_default_timezone_set($checkedConfig['timezone']);
}
else if (is_string($timezone) && $timezone !== '')
elseif (is_string($timezone) && $timezone !== '')
{
date_default_timezone_set($timezone);
}
@ -76,4 +74,4 @@ unset($APP_DIR, $CONF_DIR, $APPCONF_DIR);
// -----------------------------------------------------------------------------
// Dispatch to the current route
// -----------------------------------------------------------------------------
$container->get('dispatcher')();
$container->get('dispatcher')();

@ -0,0 +1,35 @@
# Lists the available actions
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
# Runs rector, and updates the files
rector:
tools/vendor/bin/rector process --config=tools/rector.php src
# Check code formatting
check-fmt:
tools/vendor/bin/php-cs-fixer fix --dry-run --verbose
# Fix code formatting
fmt:
tools/vendor/bin/php-cs-fixer fix --verbose
# Run tests
test:
composer run-script test
# Run tests, update snapshots
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

@ -2,7 +2,8 @@
use Phinx\Migration\AbstractMigration;
class FirstMigration extends AbstractMigration {
class FirstMigration extends AbstractMigration
{
/**
* Migrate up
*/
@ -16,7 +17,7 @@ class FirstMigration extends AbstractMigration {
// Add items to media table
if ($this->hasTable('media'))
{
foreach(['DVD & Blu-ray', 'Blu-ray', 'DVD', 'Bootleg DVD'] as $type)
foreach (['DVD & Blu-ray', 'Blu-ray', 'DVD', 'Bootleg DVD'] as $type)
{
$this->execute('INSERT INTO "media" ("type") VALUES (\'' . $type . '\')');
}
@ -25,11 +26,11 @@ class FirstMigration extends AbstractMigration {
// Create anime_set table
$anime_set = $this->table('anime_set', ['id' => FALSE, 'primary_key' => ['hummingbird_id']]);
$anime_set->addColumn('hummingbird_id', 'biginteger')
->addColumn('slug', 'string', ['comment' => "URL slug used for image caching and generating links"])
->addColumn('slug', 'string', ['comment' => 'URL slug used for image caching and generating links'])
->addColumn('title', 'string')
->addColumn('alternate_title', 'string', ['null' => TRUE])
->addColumn('media_id', 'integer', ['default' => 3, 'null' => TRUE])
->addColumn('show_type', 'string', ['default' => 'TV', 'null' => TRUE, 'comment' => "TV Series/OVA/etc"])
->addColumn('show_type', 'string', ['default' => 'TV', 'null' => TRUE, 'comment' => 'TV Series/OVA/etc'])
->addColumn('age_rating', 'string', ['default' => 'PG13', 'null' => TRUE])
->addColumn('cover_image', 'string', ['null' => TRUE])
->addColumn('episode_count', 'integer', ['null' => TRUE])

@ -1,35 +1,35 @@
<?php
<?php declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
class CacheMigration extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$cacheTable = $this->table('cache', ['id' => FALSE, 'primary_key' => ['key']]);
$cacheTable->addColumn('key', 'text')
->addColumn('value', 'text')
->create();
}
}
}

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
@ -11,7 +11,7 @@ class ReorganizeAnimeCollectionMedia extends AbstractMigration
{
$newLinkTable = $this->table('anime_set_media_link', [
'id' => FALSE,
'primary_key' => ['hummingbird_id', 'media_id']
'primary_key' => ['hummingbird_id', 'media_id'],
]);
$newLinkTable->addColumn('hummingbird_id', 'biginteger')
@ -31,6 +31,7 @@ class ReorganizeAnimeCollectionMedia extends AbstractMigration
foreach ($rows as $row)
{
$keys = array_keys($row);
foreach ($keys as $k)
{
if (is_numeric($k))
@ -49,6 +50,7 @@ class ReorganizeAnimeCollectionMedia extends AbstractMigration
// and replace those rows with the individual entries
$linkRows = $this->fetchAll('SELECT hummingbird_id FROM anime_set_media_link WHERE media_id=1');
$insertRows = [];
foreach ($linkRows as $row)
{
$insertRows[] = [

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
@ -18,6 +18,7 @@ class AnimeCollectionRefactorCleanup extends AbstractMigration
{
// Add some new media types
$moreMediaTypes = [];
foreach ($this->newMediaTypes as $id => $medium)
{
$moreMediaTypes[] = [
@ -47,7 +48,7 @@ class AnimeCollectionRefactorCleanup extends AbstractMigration
$this->execute("UPDATE media SET type='Bootleg DVD' WHERE id=4");
// Remove the new media types
$values = array_map(fn ($medium) => "'{$medium}'", $this->newMediaTypes);
$values = array_map(static fn ($medium) => "'{$medium}'", $this->newMediaTypes);
$valueList = implode(',', $values);
$this->execute("DELETE FROM media WHERE type IN ({$valueList})");
}

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
use Phinx\Migration\AbstractMigration;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

@ -6,31 +6,33 @@
*
* PHP version 8
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @copyright 2015 - 2022 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
* @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API;
use const Aviat\AnimeClient\USER_AGENT;
use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use Amp\Http\Client\Body\FormBody;
use Amp\Http\Client\Request;
use Amp\Http\Client\Body\FormBody;
use Aviat\Ion\Json;
use Error;
use InvalidArgumentException;
use Psr\Log\LoggerAwareTrait;
use Throwable;
use TypeError;
use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use const Aviat\AnimeClient\USER_AGENT;
/**
* Wrapper around Http\Client to make it easier to build API requests
*/
abstract class APIRequestBuilder {
abstract class APIRequestBuilder
{
use LoggerAwareTrait;
/**
@ -70,9 +72,6 @@ abstract class APIRequestBuilder {
/**
* Do a basic minimal GET request
*
* @param string $uri
* @return Request
*/
public static function simpleRequest(string $uri): Request
{
@ -89,7 +88,6 @@ abstract class APIRequestBuilder {
*
* @param string $type The type of authorization, eg, basic, bearer, etc.
* @param string $value The authorization value
* @return self
*/
public function setAuth(string $type, string $value): self
{
@ -101,26 +99,21 @@ abstract class APIRequestBuilder {
/**
* Set a basic authentication header
*
* @param string $username
* @param string $password
* @return self
*/
public function setBasicAuth(string $username, string $password): self
{
$this->setAuth('basic', base64_encode($username . ':' . $password));
return $this;
}
/**
* Set the request body
*
* @param FormBody|string $body
* @return self
*/
public function setBody(FormBody|string $body): self
{
$this->request->setBody($body);
return $this;
}
@ -128,7 +121,6 @@ abstract class APIRequestBuilder {
* Set body as form fields
*
* @param array $fields Mapping of field names to values
* @return self
*/
public function setFormFields(array $fields): self
{
@ -140,24 +132,18 @@ abstract class APIRequestBuilder {
/**
* Unset a request header
*
* @param string $name
* @return self
*/
public function unsetHeader(string $name): self
{
$this->request->removeHeader($name);
return $this;
}
/**
* Set a request header
*
* @param string $name
* @param string|null $value
* @return self
*/
public function setHeader(string $name, string $value = NULL): self
public function setHeader(string $name, ?string $value = NULL): self
{
if (NULL === $value)
{
@ -175,9 +161,6 @@ abstract class APIRequestBuilder {
* Set multiple request headers
*
* name => value
*
* @param array $headers
* @return self
*/
public function setHeaders(array $headers): self
{
@ -191,36 +174,30 @@ abstract class APIRequestBuilder {
/**
* Set the request body
*
* @param mixed $body
* @return self
*/
public function setJsonBody(mixed $body): self
{
$requestBody = ( ! is_string($body))
? Json::encode($body)
: $body;
$requestBody = (is_string($body))
? $body
: Json::encode($body);
return $this->setBody($requestBody);
}
/**
* Append a query string in array format
*
* @param array $params
* @return self
*/
public function setQuery(array $params): self
{
$this->query = http_build_query($params);
return $this;
}
/**
* Return the promise for the current request
*
* @return Request
* @throws \Throwable
* @throws Throwable
*/
public function getFullRequest(): Request
{
@ -235,7 +212,7 @@ abstract class APIRequestBuilder {
$this->request->getBody()
->createBodyStream()
->read()
)
),
]);
}
@ -245,25 +222,22 @@ abstract class APIRequestBuilder {
/**
* Get the data from the response of the passed request
*
* @param Request $request
* @throws Error
* @throws Throwable
* @throws TypeError
* @return mixed
* @throws \Error
* @throws \Throwable