Browse Source

Run rector over the code.

pull/845/head
James Cole 3 months ago
parent
commit
b1d47e1835
  1. 80
      .ci/rector.php
  2. 36
      .ci/rector.sh
  3. 11
      app/Console/AutoImports.php
  4. 10
      app/Console/Commands/UpgradeImportConfigurations.php
  5. 2
      app/Console/HaveAccess.php
  6. 12
      app/Console/Kernel.php
  7. 3
      app/Console/VerifyJSON.php
  8. 4
      app/Exceptions/AgreementExpiredException.php
  9. 4
      app/Exceptions/ApiException.php
  10. 4
      app/Exceptions/ApiHttpException.php
  11. 7
      app/Exceptions/Handler.php
  12. 4
      app/Exceptions/ImportException.php
  13. 4
      app/Exceptions/ImporterErrorException.php
  14. 4
      app/Exceptions/ImporterHttpException.php
  15. 4
      app/Exceptions/RateLimitException.php
  16. 4
      app/Exceptions/SpectreErrorException.php
  17. 4
      app/Exceptions/SpectreHttpException.php
  18. 3
      app/Http/Controllers/AutoImportController.php
  19. 3
      app/Http/Controllers/AutoUploadController.php
  20. 7
      app/Http/Controllers/DebugController.php
  21. 12
      app/Http/Controllers/Import/AuthenticateController.php
  22. 14
      app/Http/Controllers/Import/ConfigurationController.php
  23. 27
      app/Http/Controllers/Import/ConversionController.php
  24. 8
      app/Http/Controllers/Import/DownloadController.php
  25. 7
      app/Http/Controllers/Import/DuplicateCheckController.php
  26. 33
      app/Http/Controllers/Import/MapController.php
  27. 3
      app/Http/Controllers/Import/Nordigen/SelectionController.php
  28. 5
      app/Http/Controllers/Import/Spectre/ConnectionController.php
  29. 8
      app/Http/Controllers/Import/SubmitController.php
  30. 11
      app/Http/Controllers/Import/UploadController.php
  31. 12
      app/Http/Controllers/ServiceController.php
  32. 15
      app/Http/Controllers/TokenController.php
  33. 2
      app/Http/Kernel.php
  34. 2
      app/Http/Middleware/Authenticate.php
  35. 3
      app/Http/Middleware/IsReadyForStep.php
  36. 3
      app/Http/Middleware/RedirectIfAuthenticated.php
  37. 2
      app/Http/Request/ConfigurationPostRequest.php
  38. 3
      app/Http/Request/Request.php
  39. 2
      app/Http/Request/RolesPostRequest.php
  40. 2
      app/Http/Request/SelectionRequest.php
  41. 43
      app/Jobs/ProcessImportSubmissionJob.php
  42. 3
      app/Mail/ImportReportMail.php
  43. 6
      app/Providers/AppServiceProvider.php
  44. 3
      app/Rules/Iban.php
  45. 3
      app/Services/CSV/Configuration/ConfigFileProcessor.php
  46. 14
      app/Services/CSV/Conversion/Routine/CSVFileProcessor.php
  47. 11
      app/Services/CSV/Conversion/Routine/ColumnValueConverter.php
  48. 2
      app/Services/CSV/Conversion/Routine/LineProcessor.php
  49. 3
      app/Services/CSV/Conversion/RoutineManager.php
  50. 16
      app/Services/CSV/Conversion/Task/Accounts.php
  51. 14
      app/Services/CSV/Converter/Amount.php
  52. 2
      app/Services/CSV/Converter/AmountNegated.php
  53. 2
      app/Services/CSV/Converter/BankDebitCredit.php
  54. 3
      app/Services/CSV/Converter/ConverterService.php
  55. 12
      app/Services/CSV/Converter/Date.php
  56. 2
      app/Services/CSV/Converter/Description.php
  57. 7
      app/Services/CSV/Converter/Iban.php
  58. 2
      app/Services/CSV/Converter/TagsComma.php
  59. 2
      app/Services/CSV/Converter/TagsSpace.php
  60. 4
      app/Services/CSV/Mapper/ExpenseRevenueAccounts.php
  61. 26
      app/Services/CSV/Mapper/MapperService.php
  62. 21
      app/Services/CSV/Roles/RoleService.php
  63. 5
      app/Services/Camt/Conversion/RoutineManager.php
  64. 5
      app/Services/Camt/Conversion/TransactionConverter.php
  65. 5
      app/Services/Camt/Conversion/TransactionExtractor.php
  66. 24
      app/Services/Camt/Conversion/TransactionMapper.php
  67. 80
      app/Services/Camt/Transaction.php
  68. 16
      app/Services/Enums/AuthenticationStatus.php
  69. 8
      app/Services/Nordigen/Authentication/SecretManager.php
  70. 10
      app/Services/Nordigen/AuthenticationValidator.php
  71. 6
      app/Services/Nordigen/Conversion/Routine/GenerateTransactions.php
  72. 18
      app/Services/Nordigen/Conversion/Routine/TransactionProcessor.php
  73. 3
      app/Services/Nordigen/Conversion/RoutineManager.php
  74. 27
      app/Services/Nordigen/Model/Transaction.php
  75. 2
      app/Services/Nordigen/Request/GetRequisitionRequest.php
  76. 7
      app/Services/Nordigen/Request/PostNewTokenRequest.php
  77. 13
      app/Services/Nordigen/Request/Request.php
  78. 5
      app/Services/Nordigen/Response/ArrayResponse.php
  79. 4
      app/Services/Nordigen/Response/GetTransactionsResponse.php
  80. 4
      app/Services/Nordigen/Response/ListAccountsResponse.php
  81. 4
      app/Services/Nordigen/Response/ListBanksResponse.php
  82. 5
      app/Services/Nordigen/Response/TokenSetResponse.php
  83. 9
      app/Services/Nordigen/TokenManager.php
  84. 6
      app/Services/Shared/Configuration/Configuration.php
  85. 2
      app/Services/Shared/Conversion/CombinedProgressInformation.php
  86. 6
      app/Services/Shared/Conversion/GeneratesIdentifier.php
  87. 40
      app/Services/Shared/Conversion/RoutineStatusManager.php
  88. 5
      app/Services/Shared/File/FileContentSherlock.php
  89. 29
      app/Services/Shared/Import/Routine/ApiSubmitter.php
  90. 4
      app/Services/Shared/Import/Routine/RoutineManager.php
  91. 38
      app/Services/Shared/Import/Status/SubmissionStatusManager.php
  92. 6
      app/Services/Shared/Submission/GeneratesIdentifier.php
  93. 30
      app/Services/SimpleFIN/Conversion/AccountMapper.php
  94. 43
      app/Services/SimpleFIN/Conversion/RoutineManager.php
  95. 45
      app/Services/SimpleFIN/Conversion/TransactionTransformer.php
  96. 25
      app/Services/SimpleFIN/Model/Account.php
  97. 25
      app/Services/SimpleFIN/Model/Transaction.php
  98. 2
      app/Services/SimpleFIN/Request/SimpleFINRequest.php
  99. 2
      app/Services/SimpleFIN/Response/PostAccountResponse.php
  100. 4
      app/Services/SimpleFIN/Response/SimpleFINResponse.php

80
.ci/rector.php

@ -0,0 +1,80 @@
<?php
/*
* rector.php
* Copyright (c) 2025 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\EarlyReturn\Rector\If_\ChangeOrIfContinueToMultiContinueRector;
use Rector\Transform\Rector\String_\StringToClassConstantRector;
use RectorLaravel\Set\LaravelLevelSetList;
return RectorConfig::configure()
->withSkip([
ChangeOrIfContinueToMultiContinueRector::class,
StringToClassConstantRector::class => [
__DIR__ . '/../app/Http/Controllers/Auth/LoginController.php',
],
__DIR__.'/../bootstrap/cache/*'
])
->withPaths([
__DIR__ . '/../app',
__DIR__ . '/../bootstrap',
__DIR__ . '/../config',
__DIR__ . '/../public',
__DIR__ . '/../resources/lang/en',
__DIR__ . '/../routes',
__DIR__ . '/../tests',
])
->withSets([
LaravelLevelSetList::UP_TO_LARAVEL_120,
])
// ->withConfiguredRule(ReplaceServiceContainerCallArgRector::class, [
// new ReplaceServiceContainerCallArg('log', new ClassConstFetch(new Name('Illuminate\Support\Facades\Log'), 'class')),
// ])
// uncomment to reach your current PHP version
->withPhpSets()
->withPreparedSets(
codingStyle: false, // leave false
privatization: false, // leave false.
naming: false, // leave false
instanceOf: true,
earlyReturn: true,
strictBooleans: true,
carbon: true,
rectorPreset: true,
phpunitCodeQuality: true,
doctrineCodeQuality: true,
symfonyCodeQuality: true,
symfonyConfigs: true
)
->withComposerBased(
twig: true,
doctrine: true,
phpunit: true,
symfony: true)
->withTypeCoverageLevel(0)
->withDeadCodeLevel(0)
->withCodeQualityLevel(0)
->withImportNames(removeUnusedImports: true);// import statements instead of full classes.

36
.ci/rector.sh

@ -0,0 +1,36 @@
#!/usr/bin/env bash
#
# phpstan.sh
# Copyright (c) 2021 james@firefly-iii.org
#
# This file is part of Firefly III (https://github.com/firefly-iii).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# Install composer packages
#composer install --no-scripts --no-ansi
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
echo $1
if [ "$1" == "--dry-run" ]; then
echo "Running rector in dry run."
$SCRIPT_DIR/../vendor/bin/rector --config $SCRIPT_DIR/rector.php --dry-run
exit $?
fi
$SCRIPT_DIR/../vendor/bin/rector --config $SCRIPT_DIR/rector.php

11
app/Console/AutoImports.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Console;
use JsonException;
use App\Enums\ExitCode;
use App\Events\ImportedTransactions;
use App\Exceptions\ImporterErrorException;
@ -378,7 +379,7 @@ trait AutoImports
try {
$disk->put(sprintf('%s.json', $this->identifier), json_encode($transactions, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error(sprintf('JSON exception: %s', $e->getMessage()));
RoutineStatusManager::setConversionStatus(ConversionStatus::CONVERSION_ERRORED, $this->identifier);
$this->conversionMessages = $manager->getAllMessages();
@ -452,9 +453,9 @@ trait AutoImports
try {
$json = $disk->get($fileName);
$transactions = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
$transactions = json_decode((string) $json, true, 512, JSON_THROW_ON_ERROR);
Log::debug(sprintf('Found %d transactions on the drive.', count($transactions)));
} catch (FileNotFoundException|\JsonException $e) {
} catch (FileNotFoundException|JsonException $e) {
SubmissionStatusManager::setSubmissionStatus(SubmissionStatus::SUBMISSION_ERRORED, $this->identifier);
$message = sprintf('[a101]: File "%s" could not be decoded, cannot continue..', $fileName);
$this->error($message);
@ -601,11 +602,11 @@ trait AutoImports
// compare balance, warn (also a message)
Log::debug(sprintf('Comparing %s and %s', $balance->amount, $localAccount->currentBalance));
if (0 !== bccomp($balance->amount, $localAccount->currentBalance)) {
if (0 !== bccomp($balance->amount, (string) $localAccount->currentBalance)) {
Log::warning(sprintf('GoCardless balance is %s, Firefly III balance is %s.', $balance->amount, $localAccount->currentBalance));
$this->line(sprintf('Balance comparison (%s): Firefly III account #%d: GoCardless reports %s %s, Firefly III reports %s %d', $balance->type, $localAccount->id, $balance->currency, $balance->amount, $localAccount->currencyCode, $localAccount->currentBalance));
}
if (0 === bccomp($balance->amount, $localAccount->currentBalance)) {
if (0 === bccomp($balance->amount, (string) $localAccount->currentBalance)) {
$this->line(sprintf('Balance comparison (%s): Firefly III account #%d: Balance OK', $balance->type, $localAccount->id));
}
}

10
app/Console/Commands/UpgradeImportConfigurations.php

@ -25,6 +25,10 @@ declare(strict_types=1);
namespace App\Console\Commands;
use RecursiveDirectoryIterator;
use FilesystemIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use App\Services\Shared\Configuration\Configuration;
use Illuminate\Console\Command;
@ -66,12 +70,12 @@ final class UpgradeImportConfigurations extends Command
private function processRoot(string $directory): void
{
$dir = new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS);
$files = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST);
$dir = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST);
/**
* @var string $name
* @var \SplFileInfo $object
* @var SplFileInfo $object
*/
foreach ($files as $name => $object) {
$this->processFile($name);

2
app/Console/HaveAccess.php

@ -114,7 +114,7 @@ trait HaveAccess
if ($current === $path) {
return true;
}
if (str_starts_with($path, $current)) {
if (str_starts_with($path, (string) $current)) {
Log::debug(sprintf('SOFT match on isAllowedPath, "%s" is a subdirectory of "%s"', $path, $current));
return true;

12
app/Console/Kernel.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Console;
use Override;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -45,6 +46,7 @@ class Kernel extends ConsoleKernel
/**
* Register the commands for the application.
*/
#[Override]
protected function commands(): void
{
$accessToken = (string) env('FIREFLY_III_ACCESS_TOKEN', '');
@ -53,14 +55,17 @@ class Kernel extends ConsoleKernel
$vanityUrl = (string) env('VANITY_URL', '');
// access token AND client ID cannot be set together
if ('' !== $accessToken && '' !== $clientId) {
echo 'You can\'t set FIREFLY_III_ACCESS_TOKEN together with FIREFLY_III_CLIENT_ID. One must remain empty.'.PHP_EOL;
echo PHP_EOL;
echo 'You can\'t set FIREFLY_III_ACCESS_TOKEN together with FIREFLY_III_CLIENT_ID. One must remain empty.';
echo PHP_EOL;
exit;
}
// if vanity URL is not empty, Firefly III url must also be set.
if ('' !== $vanityUrl && '' === $baseUrl) {
echo 'If you set VANITY_URL you must also set FIREFLY_III_URL'.PHP_EOL;
echo PHP_EOL;
echo 'If you set VANITY_URL you must also set FIREFLY_III_URL';
echo PHP_EOL;
exit;
}
@ -72,5 +77,6 @@ class Kernel extends ConsoleKernel
/**
* Define the application's command schedule.
*/
#[Override]
protected function schedule(Schedule $schedule): void {}
}

3
app/Console/VerifyJSON.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Console;
use JsonException;
use Illuminate\Support\Facades\Log;
/**
@ -39,7 +40,7 @@ trait VerifyJSON
try {
json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
$message = sprintf('The importer can\'t import: could not decode the JSON in the config file: %s', $e->getMessage());
Log::error($message);

4
app/Exceptions/AgreementExpiredException.php

@ -25,10 +25,12 @@ declare(strict_types=1);
namespace App\Exceptions;
use Exception;
/**
* Class AgreementExpiredException
*/
class AgreementExpiredException extends \Exception
class AgreementExpiredException extends Exception
{
public array $json = [];
}

4
app/Exceptions/ApiException.php

@ -25,9 +25,11 @@ declare(strict_types=1);
namespace App\Exceptions;
use Exception;
/**
* Class ApiException
*
* @deprecated
*/
class ApiException extends \Exception {}
class ApiException extends Exception {}

4
app/Exceptions/ApiHttpException.php

@ -25,9 +25,11 @@ declare(strict_types=1);
namespace App\Exceptions;
use Exception;
/**
* Class ApiHttpException
*
* @deprecated
*/
class ApiHttpException extends \Exception {}
class ApiHttpException extends Exception {}

7
app/Exceptions/Handler.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Exceptions;
use Override;
use Throwable;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@ -50,9 +52,10 @@ class Handler extends ExceptionHandler
*
* @return \Illuminate\Http\Response|JsonResponse|Response
*
* @throws \Throwable
* @throws Throwable
*/
public function render($request, \Throwable $e)
#[Override]
public function render($request, Throwable $e)
{
if ($e instanceof ImporterErrorException || $e instanceof ImporterHttpException) {
$isDebug = config('app.debug');

4
app/Exceptions/ImportException.php

@ -25,9 +25,11 @@ declare(strict_types=1);
namespace App\Exceptions;
use Exception;
/**
* Class ImportException
*
* @deprecated
*/
class ImportException extends \Exception {}
class ImportException extends Exception {}

4
app/Exceptions/ImporterErrorException.php

@ -25,10 +25,12 @@ declare(strict_types=1);
namespace App\Exceptions;
use Exception;
/**
* Class ImporterErrorException
*/
class ImporterErrorException extends \Exception
class ImporterErrorException extends Exception
{
public array $json;
public int $statusCode = 0;

4
app/Exceptions/ImporterHttpException.php

@ -25,7 +25,9 @@ declare(strict_types=1);
namespace App\Exceptions;
use Exception;
/**
* Class ImporterHttpException
*/
class ImporterHttpException extends \Exception {}
class ImporterHttpException extends Exception {}

4
app/Exceptions/RateLimitException.php

@ -25,4 +25,6 @@ declare(strict_types=1);
namespace App\Exceptions;
class RateLimitException extends \Exception {}
use Exception;
class RateLimitException extends Exception {}

4
app/Exceptions/SpectreErrorException.php

@ -25,12 +25,14 @@ declare(strict_types=1);
namespace App\Exceptions;
use Exception;
/**
* Class SpectreErrorException
*
* @deprecated
*/
class SpectreErrorException extends \Exception
class SpectreErrorException extends Exception
{
public array $json;
}

4
app/Exceptions/SpectreHttpException.php

@ -25,9 +25,11 @@ declare(strict_types=1);
namespace App\Exceptions;
use Exception;
/**
* Class SpectreHttpException
*
* @deprecated
*/
class SpectreHttpException extends \Exception {}
class SpectreHttpException extends Exception {}

3
app/Http/Controllers/AutoImportController.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Http\Controllers;
use Carbon\Carbon;
use App\Console\AutoImports;
use App\Console\HaveAccess;
use App\Console\VerifyJSON;
@ -96,7 +97,7 @@ class AutoImportController extends Controller
public function line(string $string): void
{
echo sprintf("%s: %s\n", date('Y-m-d H:i:s'), $string);
echo sprintf("%s: %s\n", Carbon::now()->format('Y-m-d H:i:s'), $string);
}
public function error($string, $verbosity = null): void

3
app/Http/Controllers/AutoUploadController.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Http\Controllers;
use Carbon\Carbon;
use App\Console\AutoImports;
use App\Console\HaveAccess;
use App\Console\VerifyJSON;
@ -80,7 +81,7 @@ class AutoUploadController extends Controller
public function line(string $string): void
{
echo sprintf("%s: %s\n", date('Y-m-d H:i:s'), $string);
echo sprintf("%s: %s\n", Carbon::now()->format('Y-m-d H:i:s'), $string);
}
/**

7
app/Http/Controllers/DebugController.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Http\Controllers;
use Exception;
use Carbon\Carbon;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
@ -55,7 +56,7 @@ class DebugController extends Controller
if (null !== $logFile) {
try {
$logContent = (string)file_get_contents($logFile);
} catch (\Exception $e) {
} catch (Exception) {
// @ignoreException
}
}
@ -63,7 +64,7 @@ class DebugController extends Controller
}
if ('' !== $logContent) {
// last few lines
$logContent = 'Truncated from this point <----|'.substr($logContent, -32 * 1024);
$logContent = sprintf('Truncated from this point <----|%s',substr($logContent, -32 * 1024));
}
if (true === config('importer.is_external')) {
$logContent = 'No logs, external installation.';
@ -109,7 +110,7 @@ class DebugController extends Controller
if (file_exists('/var/www/counter-main.txt')) {
$build = trim((string) file_get_contents('/var/www/counter-main.txt'));
}
} catch (\Exception $e) {
} catch (Exception $e) {
Log::debug('Could not check build counter, but that\'s ok.');
Log::warning($e->getMessage());
}

12
app/Http/Controllers/Import/AuthenticateController.php

@ -48,7 +48,7 @@ use Session;
*/
class AuthenticateController extends Controller
{
private const AUTH_ROUTE = '002-authenticate.index';
private const string AUTH_ROUTE = '002-authenticate.index';
public function __construct()
{
@ -69,16 +69,16 @@ class AuthenticateController extends Controller
$pageTitle = 'Authentication';
$flow = $request->cookie(Constants::FLOW_COOKIE);
$subTitle = ucfirst($flow);
$error = \Session::get('error');
$error = Session::get('error');
if ('spectre' === $flow) {
$validator = new SpectreValidator();
$result = $validator->validate();
if ($result->equals(AuthenticationStatus::nodata())) {
if ($result->equals(AuthenticationStatus::NODATA)) {
// show for to enter data. save as cookie.
return view('import.002-authenticate.index')->with(compact('mainTitle', 'flow', 'subTitle', 'pageTitle', 'error'));
}
if ($result->equals(AuthenticationStatus::authenticated())) {
if ($result->equals(AuthenticationStatus::AUTHENTICATED)) {
return redirect(route('003-upload.index'));
}
}
@ -86,14 +86,14 @@ class AuthenticateController extends Controller
if ('nordigen' === $flow) {
$validator = new NordigenValidator();
$result = $validator->validate();
if ($result->equals(AuthenticationStatus::nodata())) {
if ($result->equals(AuthenticationStatus::NODATA)) {
$key = NordigenSecretManager::getKey();
$identifier = NordigenSecretManager::getId();
// show for to enter data. save as cookie.
return view('import.002-authenticate.index')->with(compact('mainTitle', 'flow', 'subTitle', 'pageTitle', 'key', 'identifier'));
}
if ($result->equals(AuthenticationStatus::authenticated())) {
if ($result->equals(AuthenticationStatus::AUTHENTICATED)) {
return redirect(route('003-upload.index'));
}
}

14
app/Http/Controllers/Import/ConfigurationController.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Http\Controllers\Import;
use Exception;
use JsonException;
use App\Exceptions\AgreementExpiredException;
use App\Exceptions\ImporterErrorException;
use App\Http\Controllers\Controller;
@ -169,7 +171,7 @@ class ConfigurationController extends Controller
if (!array_key_exists('name', $account) || null === $account['name']) {
Log::warning('SimpleFIN account data is missing name field, adding default.', ['account_id' => $account['id']]);
$account['name'] = 'Unknown Account (ID: '.$account['id'].')';
$account['name'] = sprintf('Unknown Account (ID: %s)',$account['id']);
}
if (!array_key_exists('currency', $account) || null === $account['currency']) {
@ -254,7 +256,7 @@ class ConfigurationController extends Controller
if (array_key_exists('assets', $fireflyAccounts) && is_array($fireflyAccounts['assets'])) {
foreach ($fireflyAccounts['assets'] as $fireflyAccount) {
$fireflyAccountName = $fireflyAccount->name ?? null;
if (null !== $fireflyAccountName && '' !== $fireflyAccountName && trim(strtolower($fireflyAccountName)) === trim(strtolower($importAccountName))) {
if (null !== $fireflyAccountName && '' !== $fireflyAccountName && trim(strtolower((string) $fireflyAccountName)) === trim(strtolower($importAccountName))) {
return (string)$fireflyAccount->id;
}
}
@ -264,7 +266,7 @@ class ConfigurationController extends Controller
if (array_key_exists('liabilities', $fireflyAccounts) && is_array($fireflyAccounts['liabilities'])) {
foreach ($fireflyAccounts['liabilities'] as $fireflyAccount) {
$fireflyAccountName = $fireflyAccount->name ?? null;
if (null !== $fireflyAccountName && '' !== $fireflyAccountName && trim(strtolower($fireflyAccountName)) === trim(strtolower($importAccountName))) {
if (null !== $fireflyAccountName && '' !== $fireflyAccountName && trim(strtolower((string) $fireflyAccountName)) === trim(strtolower($importAccountName))) {
return (string)$fireflyAccount->id;
}
}
@ -283,8 +285,8 @@ class ConfigurationController extends Controller
$mapper = app(TransactionCurrencies::class);
return $mapper->getMap();
} catch (\Exception $e) {
Log::error('Failed to load currencies: '.$e->getMessage());
} catch (Exception $e) {
Log::error(sprintf('Failed to load currencies: %s',$e->getMessage()));
return [];
}
@ -372,7 +374,7 @@ class ConfigurationController extends Controller
try {
$json = json_encode($configuration->toArray(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT);
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error($e->getMessage());
throw new ImporterErrorException($e->getMessage(), 0, $e);

27
app/Http/Controllers/Import/ConversionController.php

@ -25,6 +25,9 @@ declare(strict_types=1);
namespace App\Http\Controllers\Import;
use Throwable;
use Storage;
use JsonException;
use App\Exceptions\ImporterErrorException;
use App\Http\Controllers\Controller;
use App\Http\Middleware\ConversionControllerMiddleware;
@ -140,11 +143,11 @@ class ConversionController extends Controller
try {
$routine = new SimpleFINRoutineManager($identifier);
Log::debug('SimpleFIN routine manager created successfully.');
} catch (\Throwable $e) {
Log::error('Failed to create SimpleFIN routine manager: '.$e->getMessage());
Log::error('Error class: '.get_class($e));
Log::error('Error file: '.$e->getFile().':'.$e->getLine());
Log::error('Stack trace: '.$e->getTraceAsString());
} catch (Throwable $e) {
Log::error(sprintf('Failed to create SimpleFIN routine manager: %s',$e->getMessage()));
Log::error(sprintf('Error class: %s',$e::class));
Log::error(sprintf('Error file: %s:%d',$e->getFile(),$e->getLine()));
Log::error(sprintf('Stack trace: %s',$e->getTraceAsString()));
throw $e;
}
@ -264,11 +267,11 @@ class ConversionController extends Controller
try {
$routine = new SimpleFINRoutineManager($identifier);
Log::debug('SimpleFIN routine manager created successfully in start method.');
} catch (\Throwable $e) {
Log::error('Failed to create SimpleFIN routine manager in start method: '.$e->getMessage());
Log::error('Error class: '.get_class($e));
Log::error('Error file: '.$e->getFile().':'.$e->getLine());
Log::error('Stack trace: '.$e->getTraceAsString());
} catch (Throwable $e) {
Log::error(sprintf('Failed to create SimpleFIN routine manager in start method: %s',$e->getMessage()));
Log::error(sprintf('Error class: %s',$e::class));
Log::error(sprintf('Error file: %s:%d',$e->getFile(),$e->getLine()));
Log::error(sprintf('Stack trace: %s',$e->getTraceAsString()));
throw $e;
}
@ -303,11 +306,11 @@ class ConversionController extends Controller
}
Log::debug(sprintf('Conversion routine "%s" yielded %d transaction(s).', $flow, count($transactions)));
// save transactions in 'jobs' directory under the same key as the conversion thing.
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
$disk->put(sprintf('%s.json', $identifier), json_encode($transactions, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error(sprintf('JSON exception: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
RoutineStatusManager::setConversionStatus(ConversionStatus::CONVERSION_ERRORED);

8
app/Http/Controllers/Import/DownloadController.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Http\Controllers\Import;
use Carbon\Carbon;
use JsonException;
use App\Http\Controllers\Controller;
use App\Support\Http\RestoresConfiguration;
use Illuminate\Contracts\Foundation\Application;
@ -39,7 +41,7 @@ class DownloadController extends Controller
use RestoresConfiguration;
/**
* @throws \JsonException
* @throws JsonException
*/
public function download(): Application|Response|ResponseFactory
{
@ -48,8 +50,8 @@ class DownloadController extends Controller
$array = $configuration->toArray();
$result = json_encode($array, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
$response = response($result);
$name = sprintf('import_config_%s.json', date('Y-m-d'));
$response->header('Content-disposition', 'attachment; filename='.$name)
$name = sprintf('import_config_%s.json', Carbon::now()->format('Y-m-d'));
$response->header('Content-disposition', sprintf('attachment; filename=%s',$name))
->header('Content-Type', 'application/json')
->header('Content-Description', 'File Transfer')
->header('Connection', 'Keep-Alive')

7
app/Http/Controllers/Import/DuplicateCheckController.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Http\Controllers\Import;
use Exception;
use App\Http\Controllers\Controller;
use App\Http\Middleware\ConfigurationControllerMiddleware;
use App\Services\SimpleFIN\Validation\ConfigurationContractValidator;
@ -54,8 +55,8 @@ class DuplicateCheckController extends Controller
public function checkDuplicate(Request $request): JsonResponse
{
try {
$name = trim($request->input('name', ''));
$type = trim($request->input('type', ''));
$name = trim((string) $request->input('name', ''));
$type = trim((string) $request->input('type', ''));
Log::debug('DUPLICATE_CHECK: Received request', [
'name' => $name,
@ -108,7 +109,7 @@ class DuplicateCheckController extends Controller
'message' => $message,
]);
} catch (\Exception $e) {
} catch (Exception $e) {
Log::error('DUPLICATE_CHECK: Exception during duplicate check', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),

33
app/Http/Controllers/Import/MapController.php

@ -25,6 +25,9 @@ declare(strict_types=1);
namespace App\Http\Controllers\Import;
use InvalidArgumentException;
use App\Services\CSV\Mapper\ExpenseRevenueAccounts;
use JsonException;
use App\Exceptions\ImporterErrorException;
use App\Http\Controllers\Controller;
use App\Http\Middleware\MapControllerMiddleware;
@ -143,7 +146,7 @@ class MapController extends Controller
// create the "mapper" class which will get data from Firefly III.
$class = sprintf('App\Services\CSV\Mapper\%s', $info['mapper']);
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class %s does not exist.', $class));
throw new InvalidArgumentException(sprintf('Class %s does not exist.', $class));
}
Log::debug(sprintf('Associated class is %s', $class));
@ -219,7 +222,7 @@ class MapController extends Controller
// create the "mapper" class which will get data from Firefly III.
$class = sprintf('App\Services\CSV\Mapper\%s', $info['mapper']);
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class %s does not exist.', $class));
throw new InvalidArgumentException(sprintf('Class %s does not exist.', $class));
}
Log::debug(sprintf('Associated class is %s', $class));
@ -263,7 +266,7 @@ class MapController extends Controller
// create the "mapper" class which will get data from Firefly III.
$class = sprintf('App\Services\CSV\Mapper\%s', $opposingName['mapper']);
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class %s does not exist.', $class));
throw new InvalidArgumentException(sprintf('Class %s does not exist.', $class));
}
Log::debug(sprintf('Associated class is %s', $class));
@ -284,7 +287,7 @@ class MapController extends Controller
// create the "mapper" class which will get data from Firefly III.
$class = sprintf('App\Services\CSV\Mapper\%s', $category['mapper']);
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class %s does not exist.', $class));
throw new InvalidArgumentException(sprintf('Class %s does not exist.', $class));
}
Log::debug(sprintf('Associated class is %s', $class));
@ -302,9 +305,9 @@ class MapController extends Controller
$expenseRevenue['values'] = $this->getExpenseRevenueAccounts();
// Use ExpenseRevenueAccounts mapper for SimpleFIN
$class = 'App\Services\CSV\Mapper\ExpenseRevenueAccounts';
$class = ExpenseRevenueAccounts::class;
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class %s does not exist.', $class));
throw new InvalidArgumentException(sprintf('Class %s does not exist.', $class));
}
Log::debug(sprintf('Associated class is %s', $class));
@ -347,7 +350,7 @@ class MapController extends Controller
try {
$array = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
throw new ImporterErrorException(sprintf('Could not decode download: %s', $e->getMessage()), 0, $e);
}
$opposing = [];
@ -365,9 +368,7 @@ class MapController extends Controller
}
$filtered = array_filter(
$opposing,
static function (string $value) {
return '' !== $value;
}
static fn(string $value) => '' !== $value
);
return array_unique($filtered);
@ -402,7 +403,7 @@ class MapController extends Controller
try {
$array = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
throw new ImporterErrorException(sprintf('Could not decode download: %s', $e->getMessage()), 0, $e);
}
$expenseRevenue = [];
@ -446,8 +447,8 @@ class MapController extends Controller
$json = $disk->get(sprintf('%s.json', $downloadIdentifier));
try {
$array = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
$array = json_decode((string) $json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new ImporterErrorException(sprintf('Could not decode download: %s', $e->getMessage()), 0, $e);
}
$categories = [];
@ -464,9 +465,7 @@ class MapController extends Controller
}
$filtered = array_filter(
$categories,
static function (string $value) {
return '' !== $value;
}
static fn(string $value) => '' !== $value
);
return array_unique($filtered);
@ -525,7 +524,7 @@ class MapController extends Controller
// since the configuration saved in the session will omit 'mapping', 'do_mapping' and 'roles'
// these must be set to the configuration file
// no need to do this sooner because toSessionArray would have dropped them anyway.
if (null !== $diskConfig) {
if ($diskConfig instanceof Configuration) {
$configuration->setRoles($diskConfig->getRoles());
$configuration->setDoMapping($diskConfig->getDoMapping());
}

3
app/Http/Controllers/Import/Nordigen/SelectionController.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Http\Controllers\Import\Nordigen;
use JsonException;
use App\Exceptions\AgreementExpiredException;
use App\Exceptions\ImporterErrorException;
use App\Exceptions\ImporterHttpException;
@ -127,7 +128,7 @@ class SelectionController extends Controller
try {
$json = json_encode($configuration->toArray(), JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error($e->getMessage());
}
StorageService::storeContent($json);

5
app/Http/Controllers/Import/Spectre/ConnectionController.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Http\Controllers\Import\Spectre;
use JsonException;
use App\Exceptions\ImporterErrorException;
use App\Http\Controllers\Controller;
use App\Http\Middleware\ConnectionControllerMiddleware;
@ -119,7 +120,7 @@ class ConnectionController extends Controller
try {
$json = json_encode($configuration->toArray(), JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error($e->getMessage());
}
StorageService::storeContent($json);
@ -174,7 +175,7 @@ class ConnectionController extends Controller
try {
$json = json_encode($configuration->toArray(), JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error($e->getMessage());
}
StorageService::storeContent($json);

8
app/Http/Controllers/Import/SubmitController.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Http\Controllers\Import;
use Storage;
use JsonException;
use App\Exceptions\ImporterErrorException;
use App\Http\Controllers\Controller;
use App\Http\Middleware\SubmitControllerMiddleware;
@ -114,7 +116,7 @@ class SubmitController extends Controller
// search for transactions on disk using the import routine's identifier, NOT the submission routine's:
$conversionIdentifier = session()->get(Constants::CONVERSION_JOB_IDENTIFIER);
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
$fileName = sprintf('%s.json', $conversionIdentifier);
// get files from disk:
@ -128,9 +130,9 @@ class SubmitController extends Controller
try {
$json = $disk->get($fileName);
$transactions = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
$transactions = json_decode((string) $json, true, 512, JSON_THROW_ON_ERROR);
Log::debug(sprintf('Found %d transactions on the drive.', count($transactions)));
} catch (FileNotFoundException|\JsonException $e) {
} catch (FileNotFoundException|JsonException $e) {
Log::error(sprintf('The file "%s" on "%s" disk contains error: %s', $fileName, self::DISK_NAME, $e->getMessage()));
// TODO error in logs
SubmissionStatusManager::setSubmissionStatus(SubmissionStatus::SUBMISSION_ERRORED);

11
app/Http/Controllers/Import/UploadController.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Http\Controllers\Import;
use Storage;
use App\Exceptions\ImporterErrorException;
use App\Http\Controllers\Controller;
use App\Http\Middleware\UploadControllerMiddleware;
@ -80,7 +81,7 @@ class UploadController extends Controller
$simpleFinOriginUrl = config('simplefin.origin_url');
// get existing configs.
$disk = \Storage::disk('configurations');
$disk = Storage::disk('configurations');
Log::debug(sprintf('Going to check directory for config files: %s', config('filesystems.disks.configurations.root')));
$all = $disk->files();
@ -157,7 +158,7 @@ class UploadController extends Controller
*/
private function processUploadedFile(string $flow, MessageBag $errors, ?UploadedFile $file): MessageBag
{
if (null === $file && 'file' === $flow) {
if (!$file instanceof UploadedFile && 'file' === $flow) {
$errors->add('importable_file', 'No file was uploaded.');
return $errors;
@ -273,9 +274,9 @@ class UploadController extends Controller
*/
private function processSelection(MessageBag $errors, string $selection, ?UploadedFile $file): MessageBag
{
if (null === $file && '' !== $selection) {
if (!$file instanceof UploadedFile && '' !== $selection) {
Log::debug('User selected a config file from the store.');
$disk = \Storage::disk('configurations');
$disk = Storage::disk('configurations');
$configFileName = StorageService::storeContent($disk->get($selection));
session()->put(Constants::UPLOAD_CONFIG_FILE, $configFileName);
@ -349,7 +350,7 @@ class UploadController extends Controller
return redirect(route('004-configure.index'));
} catch (ImporterErrorException $e) {
Log::error('SimpleFIN connection failed', ['error' => $e->getMessage()]);
$errors->add('connection', 'Failed to connect to SimpleFIN: '.$e->getMessage());
$errors->add('connection', sprintf('Failed to connect to SimpleFIN: %s',$e->getMessage()));
return redirect(route('003-upload.index'))->withErrors($errors);
}

12
app/Http/Controllers/ServiceController.php

@ -54,11 +54,11 @@ class ServiceController extends Controller
$validator = new NordigenValidator();
$result = $validator->validate();
if ($result->equals(AuthenticationStatus::error())) {
if ($result->equals(AuthenticationStatus::ERROR)) {
// send user error:
return response()->json(['result' => 'NOK']);
}
if ($result->equals(AuthenticationStatus::nodata())) {
if ($result->equals(AuthenticationStatus::NODATA)) {
// send user error:
return response()->json(['result' => 'NODATA']);
}
@ -72,13 +72,13 @@ class ServiceController extends Controller
$validator = new SimpleFINValidator();
$result = $validator->validate();
if ($result->equals(AuthenticationStatus::error())) {
if ($result->equals(AuthenticationStatus::ERROR)) {
// send user error:
Log::error('Error: Could not validate app key.');
return response()->json(['result' => 'NOK']);
}
if ($result->equals(AuthenticationStatus::nodata())) {
if ($result->equals(AuthenticationStatus::NODATA)) {
// send user error:
Log::error('No data: Could not validate app key.');
@ -94,11 +94,11 @@ class ServiceController extends Controller
$validator = new SpectreValidator();
$result = $validator->validate();
if ($result->equals(AuthenticationStatus::error())) {
if ($result->equals(AuthenticationStatus::ERROR)) {
// send user error:
return response()->json(['result' => 'NOK']);
}
if ($result->equals(AuthenticationStatus::nodata())) {
if ($result->equals(AuthenticationStatus::NODATA)) {
// send user error:
return response()->json(['result' => 'NODATA']);
}

15
app/Http/Controllers/TokenController.php

@ -25,6 +25,9 @@ declare(strict_types=1);
namespace App\Http\Controllers;
use JsonException;
use Str;
use Throwable;
use App\Exceptions\ImporterErrorException;
use App\Services\Session\Constants;
use App\Services\Shared\Authentication\SecretManager;
@ -56,7 +59,7 @@ class TokenController extends Controller
*
* @throws ImporterErrorException
* @throws GuzzleException
* @throws \Throwable
* @throws Throwable
*/
public function callback(Request $request)
{
@ -98,7 +101,7 @@ class TokenController extends Controller
];
try {
$response = (new Client($opts))->post($finalURL, $params);
$response = new Client($opts)->post($finalURL, $params);
} catch (ClientException|RequestException $e) {
$body = $e->getMessage();
if ($e->hasResponse()) {
@ -113,7 +116,7 @@ class TokenController extends Controller
try {
$data = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error(sprintf('JSON exception when decoding response: %s', $e->getMessage()));
Log::error(sprintf('Response from server: "%s"', (string)$response->getBody()));
@ -269,8 +272,8 @@ class TokenController extends Controller
$vanityURL = rtrim($vanityURL, '/');
Log::debug(sprintf('Now in %s(request, "%s", "%s", %d)', __METHOD__, $baseURL, $vanityURL, $clientId));
$state = \Str::random(40);
$codeVerifier = \Str::random(128);
$state = Str::random(40);
$codeVerifier = Str::random(128);
$request->session()->put('state', $state);
$request->session()->put('code_verifier', $codeVerifier);
$request->session()->put('form_client_id', $clientId);
@ -309,7 +312,7 @@ class TokenController extends Controller
$data = $request->validate(['client_id' => 'required|numeric|min:1|max:65536', 'base_url' => 'url']);
Log::debug('Submitted data: ', $data);
if (true === config('importer.expect_secure_url') && array_key_exists('base_url', $data) && !str_starts_with($data['base_url'], 'https://')) {
if (true === config('importer.expect_secure_url') && array_key_exists('base_url', $data) && !str_starts_with((string) $data['base_url'], 'https://')) {
$request->session()->flash('secure_url', 'URL must start with https://');
return redirect(route('token.index'));

2
app/Http/Kernel.php

@ -94,7 +94,7 @@ class Kernel extends HttpKernel
*
* These middleware may be assigned to groups or used individually.
*/
protected $routeMiddleware
protected $middlewareAliases
= [
'auth' => Authenticate::class,
'auth.basic' => AuthenticateWithBasicAuth::class,

2
app/Http/Middleware/Authenticate.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Http\Middleware;
use Override;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
@ -38,6 +39,7 @@ class Authenticate extends Middleware
*
* @param Request $request
*/
#[Override]
protected function redirectTo($request): ?string
{
if (!$request->expectsJson()) {

3
app/Http/Middleware/IsReadyForStep.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use App\Exceptions\ImporterErrorException;
use App\Services\Session\Constants;
use Illuminate\Http\RedirectResponse;
@ -38,7 +39,7 @@ trait IsReadyForStep
{
public const string TEST = 'test';
public function handle(Request $request, \Closure $next): mixed
public function handle(Request $request, Closure $next): mixed
{
$result = $this->isReadyForStep($request);
if (true === $result) {

3
app/Http/Middleware/RedirectIfAuthenticated.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@ -42,7 +43,7 @@ class RedirectIfAuthenticated
*
* @return mixed
*/
public function handle($request, \Closure $next, $guard = null)
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);

2
app/Http/Request/ConfigurationPostRequest.php

@ -25,9 +25,9 @@ declare(strict_types=1);
namespace App\Http\Request;
use Illuminate\Contracts\Validation\Validator;
use App\Services\Session\Constants;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Validator;
/**
* Class ConfigurationPostRequest

3
app/Http/Request/Request.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Http\Request;
use Exception;
use Carbon\Carbon;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Log;
@ -130,7 +131,7 @@ class Request extends FormRequest
try {
$result = $this->get($field) ? new Carbon($this->get($field)) : null;
} catch (\Exception $e) {
} catch (Exception $e) {
Log::debug(sprintf('Exception when parsing date. Not interesting: %s', $e->getMessage()));
}

2
app/Http/Request/RolesPostRequest.php

@ -25,7 +25,7 @@ declare(strict_types=1);
namespace App\Http\Request;
use Illuminate\Validation\Validator;
use Illuminate\Contracts\Validation\Validator;
/**
* Class RolesPostRequest

2
app/Http/Request/SelectionRequest.php

@ -25,7 +25,7 @@ declare(strict_types=1);
namespace App\Http\Request;
use Illuminate\Validation\Validator;
use Illuminate\Contracts\Validation\Validator;
/**
* Class SelectionRequest

43
app/Jobs/ProcessImportSubmissionJob.php

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Jobs;
use Exception;
use Throwable;
use App\Services\Shared\Configuration\Configuration;
use App\Services\Shared\Import\Routine\RoutineManager;
use App\Services\Shared\Import\Status\SubmissionStatus;
@ -34,32 +36,13 @@ class ProcessImportSubmissionJob implements ShouldQueue
*
* @var int
*/
public $timeout = 1800; // 30 minutes
private string $identifier;
private Configuration $configuration;
private array $transactions;
private string $accessToken;
private string $baseUrl;
private ?string $vanityUrl;
public $timeout = 1800;
/**
* Create a new job instance.
*/
public function __construct(
string $identifier,
Configuration $configuration,
array $transactions,
string $accessToken,
string $baseUrl,
?string $vanityUrl
) {
$this->identifier = $identifier;
$this->configuration = $configuration;
$this->transactions = $transactions;
$this->accessToken = $accessToken;
$this->baseUrl = $baseUrl;
$this->vanityUrl = $vanityUrl;
public function __construct(private string $identifier, private Configuration $configuration, private array $transactions, private string $accessToken, private string $baseUrl, private ?string $vanityUrl)
{
}
/**
@ -74,11 +57,11 @@ class ProcessImportSubmissionJob implements ShouldQueue
// Validate authentication credentials before proceeding
if ('' === $this->accessToken) {
throw new \Exception('Access token is empty - cannot authenticate with Firefly III');
throw new Exception('Access token is empty - cannot authenticate with Firefly III');
}
if ('' === $this->baseUrl) {
throw new \Exception('Base URL is empty - cannot connect to Firefly III');
throw new Exception('Base URL is empty - cannot connect to Firefly III');
}
Log::info('Job authentication credentials validation', [
@ -99,7 +82,7 @@ class ProcessImportSubmissionJob implements ShouldQueue
Log::debug('Original config backup', [
'identifier' => $this->identifier,
'original_token_length' => strlen(
$originalConfig['importer.access_token']
(string) $originalConfig['importer.access_token']
),
'original_url' => $originalConfig['importer.url'],
'original_vanity' => $originalConfig['importer.vanity_url'],
@ -128,18 +111,18 @@ class ProcessImportSubmissionJob implements ShouldQueue
'identifier' => $this->identifier,
'config_token_matches' => $verifyToken === $this->accessToken,
'config_url_matches' => $verifyUrl === $this->baseUrl,
'config_token_length' => strlen($verifyToken),
'config_token_length' => strlen((string) $verifyToken),
'config_url' => $verifyUrl,
]);
if ($verifyToken !== $this->accessToken) {
throw new \Exception(
throw new Exception(
'Failed to set access token in config properly'
);
}
if ($verifyUrl !== $this->baseUrl) {
throw new \Exception(
throw new Exception(
'Failed to set base URL in config properly'
);
}
@ -174,7 +157,7 @@ class ProcessImportSubmissionJob implements ShouldQueue
'warnings' => count($routine->getAllWarnings()),
'errors' => count($routine->getAllErrors()),
]);
} catch (\Throwable $e) {
} catch (Throwable $e) {
Log::error('ProcessImportSubmissionJob failed', [
'identifier' => $this->identifier,
'error' => $e->getMessage(),
@ -202,7 +185,7 @@ class ProcessImportSubmissionJob implements ShouldQueue
/**
* Handle a job failure.
*/
public function failed(\Throwable $exception): void
public function failed(Throwable $exception): void
{
Log::error('ProcessImportSubmissionJob marked as failed', [
'identifier' => $this->identifier,

3
app/Mail/ImportReportMail.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Mail;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
@ -50,7 +51,7 @@ class ImportReportMail extends Mailable
*/
public function __construct(array $log)
{
$this->time = date('Y-m-d \@ H:i:s');
$this->time = Carbon::now()->format('Y-m-d \@ H:i:s');
$this->url = (string) config('importer.url');
$this->version = config('importer.version');
if ('' !== (string) config('importer.vanity_url')) {

6
app/Providers/AppServiceProvider.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Providers;
use Override;
use App\Support\Steam;
use Illuminate\Support\ServiceProvider;
@ -41,13 +42,12 @@ class AppServiceProvider extends ServiceProvider
/**
* Register any application services.
*/
#[Override]
public function register(): void
{
$this->app->bind(
'steam',
static function () {
return new Steam();
}
static fn() => new Steam()
);
}
}

3
app/Rules/Iban.php

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace App\Rules;
use Closure;
use App\Services\CSV\Converter\Iban as IbanConverter;
use Illuminate\Contracts\Validation\ValidationRule;
@ -43,7 +44,7 @@ class Iban implements ValidationRule
/**
* Determine if the given value is a valid IBAN.
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$result = IbanConverter::isValidIban((string)$value);
if (!$result) {

3
app/Services/CSV/Configuration/ConfigFileProcessor.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Configuration;
use JsonException;
use App\Exceptions\ImporterErrorException;
use App\Services\Shared\Configuration\Configuration;
use App\Services\Storage\StorageService;
@ -55,7 +56,7 @@ class ConfigFileProcessor
try {
$json = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error($e->getMessage());
throw new ImporterErrorException(sprintf('Invalid JSON configuration file: %s', $e->getMessage()));

14
app/Services/CSV/Conversion/Routine/CSVFileProcessor.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Conversion\Routine;
use JsonException;
use App\Exceptions\ImporterErrorException;
use App\Services\Shared\Configuration\Configuration;
use App\Services\Shared\Conversion\ProgressInformation;
@ -40,8 +41,6 @@ use League\Csv\Statement;
class CSVFileProcessor
{
use ProgressInformation;
private Configuration $configuration;
private string $delimiter;
private bool $hasHeaders;
private Reader $reader;
@ -49,9 +48,8 @@ class CSVFileProcessor
/**
* CSVFileProcessor constructor.
*/
public function __construct(Configuration $configuration)
public function __construct(private Configuration $configuration)
{
$this->configuration = $configuration;
}
/**
@ -75,7 +73,7 @@ class CSVFileProcessor
Log::debug(sprintf('Offset is %d', $offset));
try {
$stmt = (new Statement())->offset($offset);
$stmt = new Statement()->offset($offset);
$records = $stmt->process($this->reader);
} catch (Exception $e) {
Log::error($e->getMessage());
@ -146,9 +144,7 @@ class CSVFileProcessor
$lineValues = array_values($line);
array_walk(
$lineValues,
static function ($element) {
return trim(str_replace('&nbsp;', ' ', (string) $element));
}
static fn($element) => trim(str_replace('&nbsp;', ' ', (string) $element))
);
return $lineValues;
@ -164,7 +160,7 @@ class CSVFileProcessor
foreach ($array as $index => $line) {
try {
$hash = hash('sha256', json_encode($line, JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error($e->getMessage());
// Log::error($e->getTraceAsString());

11
app/Services/CSV/Conversion/Routine/ColumnValueConverter.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Services\CSV\Conversion\Routine;
use UnexpectedValueException;
use JsonException;
use App\Exceptions\ImporterErrorException;
use App\Services\Shared\Configuration\Configuration;
use App\Services\Shared\Conversion\ProgressInformation;
@ -39,17 +41,14 @@ use Illuminate\Support\Facades\Log;
class ColumnValueConverter
{
use ProgressInformation;
private Configuration $configuration;
private array $roleToTransaction;
/**
* ColumnValueConverter constructor.
*/
public function __construct(Configuration $configuration)
public function __construct(private Configuration $configuration)
{
$this->roleToTransaction = config('csv.role_to_transaction');
$this->configuration = $configuration;
}
/**
@ -116,7 +115,7 @@ class ColumnValueConverter
$transactionField = $this->roleToTransaction[$role] ?? null;
$parsedValue = $value->getParsedValue();
if (null === $transactionField) {
throw new \UnexpectedValueException(sprintf('No place for role "%s"', $value->getRole()));
throw new UnexpectedValueException(sprintf('No place for role "%s"', $value->getRole()));
}
if (null === $parsedValue) {
Log::debug(sprintf('Skip column #%d with role "%s" (in field "%s")', $columnIndex + 1, $role, $transactionField));
@ -190,7 +189,7 @@ class ColumnValueConverter
if (is_array($value)) {
try {
return json_encode($value, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
throw new ImporterErrorException($e->getMessage(), 0, $e);
}
}

2
app/Services/CSV/Conversion/Routine/LineProcessor.php

@ -98,7 +98,7 @@ class LineProcessor
$return = [];
foreach ($line as $columnIndex => $value) {
Log::debug(sprintf('Now at column %d/%d', $columnIndex + 1, $count));
$value = trim($value);
$value = trim((string) $value);
$originalRole = $this->roles[$columnIndex] ?? '_ignore';
Log::debug(sprintf('Now at column #%d (%s), value "%s"', $columnIndex + 1, $originalRole, $value));
if ('_ignore' === $originalRole) {

3
app/Services/CSV/Conversion/RoutineManager.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Conversion;
use Override;
use App\Exceptions\ImporterErrorException;
use App\Services\CSV\Conversion\Routine\ColumnValueConverter;
use App\Services\CSV\Conversion\Routine\CSVFileProcessor;
@ -75,7 +76,7 @@ class RoutineManager implements RoutineManagerInterface
}
}
#[\Override]
#[Override]
public function getServiceAccounts(): array
{
return [];

16
app/Services/CSV/Conversion/Task/Accounts.php

@ -271,10 +271,10 @@ class Accounts extends AbstractTask
private function findAccount(array $array, ?Account $defaultAccount): array
{
Log::debug('Now in findAccount', $array);
if (null === $defaultAccount) {
if (!$defaultAccount instanceof Account) {
Log::debug('findAccount() default account is NULL.');
}
if (null !== $defaultAccount) {
if ($defaultAccount instanceof Account) {
Log::debug(sprintf('Default account is #%d ("%s")', $defaultAccount->id, $defaultAccount->name));
}
@ -284,7 +284,7 @@ class Accounts extends AbstractTask
Log::debug('Will search by ID field.');
$result = $this->findById((string) $array['id']);
}
if (null !== $result) {
if ($result instanceof Account) {
$return = $result->toArray();
Log::debug('Result of findById is not null, returning:', $return);
@ -300,7 +300,7 @@ class Accounts extends AbstractTask
$transactionType = (string) ($array['transaction_type'] ?? null);
$result = $this->findByIban((string) $array['iban'], $transactionType);
}
if (null !== $result) {
if ($result instanceof Account) {
$return = $result->toArray();
Log::debug('Result of findByIBAN is not null, returning:', $return);
@ -318,7 +318,7 @@ class Accounts extends AbstractTask
$transactionType = (string) ($array['transaction_type'] ?? null);
$result = $this->findByNumber((string) $array['number'], $transactionType);
}
if (null !== $result) {
if ($result instanceof Account) {
$return = $result->toArray();
Log::debug('Result of findByNumber is not null, returning:', $return);
@ -333,7 +333,7 @@ class Accounts extends AbstractTask
Log::debug('Search by name.');
$result = $this->findByName((string) $array['name']);
}
if (null !== $result) {
if ($result instanceof Account) {
$return = $result->toArray();
Log::debug('Result of findByName is not null, returning:', $return);
@ -371,7 +371,7 @@ class Accounts extends AbstractTask
}
// if the default account is not NULL, return that one instead:
if (null !== $defaultAccount) {
if ($defaultAccount instanceof Account) {
$default = $defaultAccount->toArray();
Log::debug('At least the default account is not null, so will return that:', $default);
@ -587,7 +587,7 @@ class Accounts extends AbstractTask
/** @var Account $account */
foreach ($response as $account) {
if (in_array($account->type, [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], true)
&& strtolower($account->name) === strtolower($name)) {
&& strtolower((string) $account->name) === strtolower($name)) {
Log::debug(sprintf('[b] Found "%s" account #%d based on name "%s"', $account->type, $account->id, $name));
return $account;

14
app/Services/CSV/Converter/Amount.php

@ -34,7 +34,7 @@ class Amount implements ConverterInterface
public static function negative(string $amount): string
{
if (1 === bccomp($amount, '0')) {
$amount = bcmul($amount, '-1');
return bcmul($amount, '-1');
}
return $amount;
@ -43,7 +43,7 @@ class Amount implements ConverterInterface
public static function positive(string $amount): string
{
if (-1 === bccomp($amount, '0')) {
$amount = bcmul($amount, '-1');
return bcmul($amount, '-1');
}
return $amount;
@ -150,7 +150,7 @@ class Amount implements ConverterInterface
Log::debug(sprintf('No decimal character found. Converted amount from "%s" to "%s".', $original, $value));
}
if (str_starts_with($value, '.')) {
$value = '0'.$value;
$value = sprintf('0%s',$value);
}
if (is_numeric($value)) {
@ -180,11 +180,11 @@ class Amount implements ConverterInterface
// This way of stripping exceptions is unsustainable.
$value = trim((string) str_replace(['€', 'EUR'], '', $value));
$str = preg_replace('/[^\-().,0-9 ]/', '', $value);
$len = strlen($str);
if (str_starts_with($str, '(') && ')' === $str[$len - 1]) {
$str = '-'.substr($str, 1, $len - 2);
$len = strlen((string) $str);
if (str_starts_with((string) $str, '(') && ')' === $str[$len - 1]) {
$str = sprintf('-%s',substr((string) $str, 1, $len - 2));
}
$str = trim($str);
$str = trim((string) $str);
Log::debug(sprintf('Stripped "%s" to "%s"', $value, $str));

2
app/Services/CSV/Converter/AmountNegated.php

@ -40,7 +40,7 @@ class AmountNegated implements ConverterInterface
$converter = app(Amount::class);
$result = $converter->convert($value);
return bcmul($result, '-1');
return bcmul((string) $result, '-1');
}
/**

2
app/Services/CSV/Converter/BankDebitCredit.php

@ -59,7 +59,7 @@ class BankDebitCredit implements ConverterInterface
];
// Lowercase the value and trim it for comparison.
if (in_array(strtolower(trim($value)), $negative, true)) {
if (in_array(strtolower(trim((string) $value)), $negative, true)) {
return -1;
}

3
app/Services/CSV/Converter/ConverterService.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Converter;
use UnexpectedValueException;
use Illuminate\Support\Facades\Log;
/**
@ -51,7 +52,7 @@ class ConverterService
return $object->convert($value);
}
throw new \UnexpectedValueException(sprintf('No such converter: "%s"', $class));
throw new UnexpectedValueException(sprintf('No such converter: "%s"', $class));
}
public static function exists(string $class): bool

12
app/Services/CSV/Converter/Date.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Services\CSV\Converter;
use Exception;
use InvalidArgumentException;
use Carbon\Carbon;
use Carbon\Language;
use Illuminate\Support\Facades\Log;
@ -35,7 +37,7 @@ use Illuminate\Support\Facades\Log;
class Date implements ConverterInterface
{
private string $dateFormat;
private string $dateFormatPattern;
private readonly string $dateFormatPattern;
private string $dateLocale;
/**
@ -45,7 +47,7 @@ class Date implements ConverterInterface
{
$this->dateFormat = 'Y-m-d';
$this->dateLocale = 'en';
$this->dateFormatPattern = '/(?:('.implode('|', array_keys(Language::all())).')\:)?(.+)/';
$this->dateFormatPattern = sprintf('/(?:(%s)\:)?(.+)/', implode('|', array_keys(Language::all())));
}
/**
@ -74,8 +76,8 @@ class Date implements ConverterInterface
try {
$carbon = Carbon::createFromLocaleFormat($this->dateFormat, $this->dateLocale, $string);
} catch (\Exception|\InvalidArgumentException $e) {
Log::error(sprintf('%s converting the date: %s', get_class($e), $e->getMessage()));
} catch (Exception|InvalidArgumentException $e) {
Log::error(sprintf('%s converting the date: %s', $e::class, $e->getMessage()));
Log::debug('Date parsing error, will return today instead.');
return Carbon::today()->startOfDay()->format('Y-m-d H:i:s');
@ -105,7 +107,7 @@ class Date implements ConverterInterface
$dateFormatConfiguration = [];
preg_match($this->dateFormatPattern, $format, $dateFormatConfiguration);
if (3 === count($dateFormatConfiguration)) {
$currentDateLocale = $dateFormatConfiguration[1] ?: $currentDateLocale;
$currentDateLocale = $dateFormatConfiguration[1] !== '' && $dateFormatConfiguration[1] !== '0' ? $dateFormatConfiguration[1] : $currentDateLocale;
$currentDateFormat = $dateFormatConfiguration[2];
}

2
app/Services/CSV/Converter/Description.php

@ -39,7 +39,7 @@ class Description implements ConverterInterface
*/
public function convert($value)
{
return trim($value);
return trim((string) $value);
}
/**

7
app/Services/CSV/Converter/Iban.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Converter;
use ValueError;
use Illuminate\Support\Facades\Log;
/**
@ -43,7 +44,7 @@ class Iban implements ConverterInterface
{
if (self::isValidIban($value)) {
// strip spaces from IBAN and make upper case.
$result = str_replace("\x20", '', strtoupper(app('steam')->cleanStringAndNewlines($value)));
$result = str_replace("\x20", '', strtoupper((string) app('steam')->cleanStringAndNewlines($value)));
Log::debug(sprintf('Converted "%s" to "%s"', $value, $result));
return trim($result);
@ -56,7 +57,7 @@ class Iban implements ConverterInterface
public static function isValidIban(string $value): bool
{
Log::debug(sprintf('isValidIBAN("%s")', $value));
$value = strtoupper(trim(app('steam')->cleanStringAndNewlines($value)));
$value = strtoupper(trim((string) app('steam')->cleanStringAndNewlines($value)));
$value = str_replace("\x20", '', $value);
Log::debug(sprintf('Trim: isValidIBAN("%s")', $value));
$search = [' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
@ -97,7 +98,7 @@ class Iban implements ConverterInterface
try {
$checksum = bcmod($iban, '97');
} catch (\ValueError $e) {
} catch (ValueError $e) {
Log::error(sprintf('Bad IBAN: %s', $e->getMessage()));
$checksum = 2;
}

2
app/Services/CSV/Converter/TagsComma.php

@ -40,7 +40,7 @@ class TagsComma implements ConverterInterface
public function convert($value)
{
$string = app('steam')->cleanStringAndNewlines($value);
$tags = explode(',', $string);
$tags = explode(',', (string) $string);
return array_map('trim', $tags);
}

2
app/Services/CSV/Converter/TagsSpace.php

@ -40,7 +40,7 @@ class TagsSpace implements ConverterInterface
public function convert($value)
{
$string = app('steam')->cleanStringAndNewlines($value);
$tags = explode(' ', $string);
$tags = explode(' ', (string) $string);
return array_map('trim', $tags);
}

4
app/Services/CSV/Mapper/ExpenseRevenueAccounts.php

@ -81,9 +81,7 @@ class ExpenseRevenueAccounts implements MapperInterface
$allAccounts = $this->toArray($response);
// Filter for expense and revenue accounts only
$expenseRevenueAccounts = array_filter($allAccounts, function (Account $account) {
return in_array($account->type, ['expense', 'revenue'], true);
});
$expenseRevenueAccounts = array_filter($allAccounts, fn(Account $account) => in_array($account->type, ['expense', 'revenue'], true));
Log::debug(sprintf('getExpenseRevenueAccounts: Found %d expense/revenue accounts', count($expenseRevenueAccounts)));

26
app/Services/CSV/Mapper/MapperService.php

@ -69,7 +69,7 @@ class MapperService
}
try {
$stmt = (new Statement())->offset($offset);
$stmt = new Statement()->offset($offset);
$records = $stmt->process($reader);
} catch (Exception $e) {
Log::error($e->getMessage());
@ -87,7 +87,7 @@ class MapperService
continue;
}
if ('' !== $column) {
$data[$columnIndex]['values'][] = trim($column);
$data[$columnIndex]['values'][] = trim((string) $column);
}
}
}
@ -163,14 +163,16 @@ class MapperService
$splits = $transaction->countSplits();
foreach (array_keys($mappableFields) as $title) {
if (array_key_exists($title, $data)) {
if (0 !== $splits) {
for ($index = 0; $index < $splits; ++$index) {
$value = $transaction->getFieldByIndex($title, $index);
if ('' !== $value) {
$data[$title]['values'][] = $value;
}
}
if (!array_key_exists($title, $data)) {
continue;
}
if (0 === $splits) {
continue;
}
for ($index = 0; $index < $splits; ++$index) {
$value = $transaction->getFieldByIndex($title, $index);
if ('' !== $value) {
$data[$title]['values'][] = $value;
}
}
}
@ -179,9 +181,7 @@ class MapperService
foreach ($data as $title => $info) {
$filtered = array_filter(
$info['values'],
static function (string $value) {
return '' !== $value;
}
static fn(string $value) => '' !== $value
);
$info['values'] = array_unique($filtered);
sort($info['values']);

21
app/Services/CSV/Roles/RoleService.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\CSV\Roles;
use InvalidArgumentException;
use App\Services\Camt\Transaction;
use App\Services\Session\Constants;
use App\Services\Shared\Configuration\Configuration;
@ -79,14 +80,14 @@ class RoleService
$headers = [];
if (true === $configuration->isHeaders()) {
try {
$stmt = (new Statement())->limit(1)->offset(0);
$stmt = new Statement()->limit(1)->offset(0);
$records = $stmt->process($reader);
$headers = $records->fetchOne();
// @codeCoverageIgnoreStart
} catch (Exception $e) {
Log::error($e->getMessage());
throw new \InvalidArgumentException($e->getMessage());
throw new InvalidArgumentException($e->getMessage());
}
// @codeCoverageIgnoreEnd
Log::debug('Detected file headers:', $headers);
@ -95,7 +96,7 @@ class RoleService
Log::debug('Role service: file has no headers');
try {
$stmt = (new Statement())->limit(1)->offset(0);
$stmt = new Statement()->limit(1)->offset(0);
$records = $stmt->process($reader);
$count = count($records->fetchOne());
Log::debug(sprintf('Role service: first row has %d columns', $count));
@ -106,7 +107,7 @@ class RoleService
} catch (Exception $e) {
Log::error($e->getMessage());
throw new \InvalidArgumentException($e->getMessage());
throw new InvalidArgumentException($e->getMessage());
}
}
@ -146,12 +147,12 @@ class RoleService
// make statement.
try {
$stmt = (new Statement())->limit(self::EXAMPLE_COUNT)->offset($offset);
$stmt = new Statement()->limit(self::EXAMPLE_COUNT)->offset($offset);
// @codeCoverageIgnoreStart
} catch (Exception $e) {
Log::error($e->getMessage());
throw new \InvalidArgumentException($e->getMessage());
throw new InvalidArgumentException($e->getMessage());
}
/** @codeCoverageIgnoreEnd */
@ -164,8 +165,8 @@ class RoleService
$line = array_values($line);
// $line = SpecificService::runSpecifics($line, $configuration->getSpecifics());
foreach ($line as $index => $cell) {
if (strlen($cell) > self::EXAMPLE_LENGTH) {
$cell = sprintf('%s...', substr($cell, 0, self::EXAMPLE_LENGTH));
if (strlen((string) $cell) > self::EXAMPLE_LENGTH) {
$cell = sprintf('%s...', substr((string) $cell, 0, self::EXAMPLE_LENGTH));
}
$examples[$index][] = $cell;
$examples[$index] = array_unique($examples[$index]);
@ -246,9 +247,7 @@ class RoleService
}
foreach ($examples as $key => $list) {
$examples[$key] = array_unique($list);
$examples[$key] = array_filter($examples[$key], function (string $value) {
return '' !== $value;
});
$examples[$key] = array_filter($examples[$key], fn(string $value) => '' !== $value);
}
return $examples;

5
app/Services/Camt/Conversion/RoutineManager.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\Camt\Conversion;
use Override;
use App\Exceptions\ImporterErrorException;
use App\Services\Session\Constants;
use App\Services\Shared\Authentication\IsRunningCli;
@ -72,7 +73,7 @@ class RoutineManager implements RoutineManagerInterface
}
}
#[\Override]
#[Override]
public function getServiceAccounts(): array
{
return [];
@ -105,7 +106,7 @@ class RoutineManager implements RoutineManagerInterface
// get XML file
$camtMessage = $this->getCamtMessage();
if (null === $camtMessage) {
if (!$camtMessage instanceof Message) {
Log::error('The CAMT object is NULL, probably due to a previous error');
$this->addError(0, '[a102]: The CAMT object is NULL, probably due to a previous error');
// at this point there are very few (if not zero) errors from other steps in the routine.

5
app/Services/Camt/Conversion/TransactionConverter.php

@ -14,12 +14,9 @@ class TransactionConverter
{
use ProgressInformation;
private Configuration $configuration;
public function __construct(Configuration $configuration)
public function __construct(private Configuration $configuration)
{
Log::debug('Constructed TransactionConverter.');
$this->configuration = $configuration;
}
/**

5
app/Services/Camt/Conversion/TransactionExtractor.php

@ -15,12 +15,9 @@ class TransactionExtractor
{
use ProgressInformation;
private Configuration $configuration;
public function __construct(Configuration $configuration)
public function __construct(private Configuration $configuration)
{
Log::debug('Now in TransactionExtractor.');
$this->configuration = $configuration;
}
public function extractTransactions(Message $message): array

24
app/Services/Camt/Conversion/TransactionMapper.php

@ -21,15 +21,13 @@ class TransactionMapper
private array $accountIdentificationSuffixes;
private array $allAccounts;
private Configuration $configuration;
/**
* @throws ImporterErrorException
*/
public function __construct(Configuration $configuration)
public function __construct(private Configuration $configuration)
{
Log::debug('Constructed TransactionMapper.');
$this->configuration = $configuration;
$this->allAccounts = $this->getAllAccounts();
$this->accountIdentificationSuffixes = ['id', 'iban', 'number', 'name'];
}
@ -384,7 +382,7 @@ class TransactionMapper
}
// if is positive
if (1 === bccomp($current['amount'], '0')) {
if (1 === bccomp((string) $current['amount'], '0')) {
Log::debug('Swap accounts because amount is positive');
// positive account is deposit (or transfer), so swap accounts.
$current = $this->swapAccounts($current);
@ -415,9 +413,9 @@ class TransactionMapper
// (and they point to the same account). This sanity check must be done again. But not right now.
// amount must be positive
if (-1 === bccomp($current['amount'], '0')) {
if (-1 === bccomp((string) $current['amount'], '0')) {
// negative amount is debit (or transfer)
$current['amount'] = bcmul($current['amount'], '-1');
$current['amount'] = bcmul((string) $current['amount'], '-1');
}
// no description?
@ -432,12 +430,12 @@ class TransactionMapper
// no date?
if (!array_key_exists('date', $current)) {
Log::warning(sprintf('Did not find a date in the transaction, added "%s"', date('Y-m-d')));
$current['date'] = date('Y-m-d');
Log::warning(sprintf('Did not find a date in the transaction, added "%s"', Carbon::now()->format('Y-m-d')));
$current['date'] = Carbon::now()->format('Y-m-d');
}
if (array_key_exists('date', $current) && '' === (string) $current['date']) {
Log::warning(sprintf('Did not find a date in the transaction, added "%s"', date('Y-m-d')));
$current['date'] = date('Y-m-d');
Log::warning(sprintf('Did not find a date in the transaction, added "%s"', Carbon::now()->format('Y-m-d')));
$current['date'] = Carbon::now()->format('Y-m-d');
}
// unset var
@ -497,7 +495,7 @@ class TransactionMapper
Log::debug('Determine transaction type.');
$directions = ['source', 'destination'];
$accountType = [];
$lessThanZero = 1 === bccomp('0', $current['amount']);
$lessThanZero = 1 === bccomp('0', (string) $current['amount']);
Log::debug(sprintf('Amount is "%s", so lessThanZero is %s', $current['amount'], var_export($lessThanZero, true)));
foreach ($directions as $direction) {
@ -603,8 +601,8 @@ class TransactionMapper
Log::error(
sprintf(
'Unknown transaction type: source = "%s", destination = "%s". Fall back to "withdrawal"',
$accountType['source'] ?: null,
$accountType['destination'] ?: null
$accountType['source'] !== null && $accountType['source'] !== '' && $accountType['source'] !== '0' ? $accountType['source'] : null,
$accountType['destination'] !== null && $accountType['destination'] !== '' && $accountType['destination'] !== '0' ? $accountType['destination'] : null
)
); // 285
$current['type'] = 'withdrawal'; // line 382 / 383

80
app/Services/Camt/Transaction.php

@ -6,6 +6,8 @@ declare(strict_types=1);
namespace App\Services\Camt;
use Genkgo\Camt\DTO\DomainBankTransactionCode;
use Genkgo\Camt\DTO\Account;
use App\Exceptions\ImporterErrorException;
use App\Services\Shared\Configuration\Configuration;
use Genkgo\Camt\Camt053\DTO\Statement;
@ -30,25 +32,15 @@ use Money\Money;
class Transaction
{
public const TIME_FORMAT = 'Y-m-d H:i:s';
private Configuration $configuration;
private Message $levelA;
private Statement $levelB;
private Entry $levelC;
private array $levelD;
public function __construct(
Configuration $configuration,
Message $levelA,
Statement $levelB,
Entry $levelC,
array $levelD
private readonly Configuration $configuration,
private readonly Message $levelA,
private readonly Statement $levelB,
private readonly Entry $levelC,
private array $levelD
) {
Log::debug('Constructed a CAMT Transaction');
$this->configuration = $configuration;
$this->levelA = $levelA;
$this->levelB = $levelB;
$this->levelC = $levelC;
$this->levelD = $levelD;
}
public function countSplits(): int
@ -70,7 +62,7 @@ class Transaction
private function getDecimalAmount(?Money $money): string
{
if (null === $money) {
if (!$money instanceof Money) {
return '';
}
$currencies = new ISOCurrencies();
@ -122,25 +114,22 @@ class Transaction
return (string) $set?->getCreditDebitIndicator();
case 'statementAccountIban':
// always the same, since its level B.
$ret = '';
if (IbanAccount::class === get_class($this->levelB->getAccount())) {
$ret = $this->levelB->getAccount()->getIdentification();
if (IbanAccount::class === $this->levelB->getAccount()::class) {
return $this->levelB->getAccount()->getIdentification();
}
return $ret;
return '';
case 'statementAccountNumber':
// always the same, since its level B.
$list = [OtherAccount::class, ProprietaryAccount::class, UPICAccount::class, BBANAccount::class];
$class = get_class($this->levelB->getAccount());
$ret = '';
$class = $this->levelB->getAccount()::class;
if (in_array($class, $list, true)) {
$ret = $this->levelB->getAccount()->getIdentification();
return $this->levelB->getAccount()->getIdentification();
}
// LEVEL C
return $ret;
return '';
case 'entryAccountServicerReference':
// always the same, since its level C.
@ -171,18 +160,17 @@ class Transaction
return (string) $this->levelC->getBookingDate()?->format(self::TIME_FORMAT);
case 'entryBtcDomainCode':
$return = '';
// always the same, since its level C.
if (null !== $this->levelC->getBankTransactionCode()->getDomain()) {
$return = (string) $this->levelC->getBankTransactionCode()->getDomain()->getCode();
if ($this->levelC->getBankTransactionCode()->getDomain() instanceof DomainBankTransactionCode) {
return (string) $this->levelC->getBankTransactionCode()->getDomain()->getCode();
}
return $return;
return '';
case 'entryBtcFamilyCode':
$return = '';
// always the same, since its level C.
if (null !== $this->levelC->getBankTransactionCode()->getDomain()) {
if ($this->levelC->getBankTransactionCode()->getDomain() instanceof DomainBankTransactionCode) {
$return = (string) $this->levelC->getBankTransactionCode()->getDomain()->getFamily()->getCode();
}
@ -191,7 +179,7 @@ class Transaction
case 'entryBtcSubFamilyCode':
$return = '';
// always the same, since its level C.
if (null !== $this->levelC->getBankTransactionCode()->getDomain()) {
if ($this->levelC->getBankTransactionCode()->getDomain() instanceof DomainBankTransactionCode) {
return (string) $this->levelC->getBankTransactionCode()->getDomain()->getFamily()->getSubFamilyCode();
}
@ -253,7 +241,7 @@ class Transaction
// #8994 add info.
$string = (string) $info->getRemittanceInformation()?->getCreditorReferenceInformation()?->getRef();
if ('' !== $string) {
$return = sprintf('%s %s', $return, $string);
return sprintf('%s %s', $return, $string);
}
return $return;
@ -298,7 +286,7 @@ class Transaction
/** @var EntryTransactionDetail $info */
$info = $this->levelD[$index];
if (null !== $info->getBankTransactionCode()->getDomain()) {
$return = (string) $info->getBankTransactionCode()->getDomain()->getCode();
return (string) $info->getBankTransactionCode()->getDomain()->getCode();
}
return $return;
@ -314,7 +302,7 @@ class Transaction
/** @var EntryTransactionDetail $info */
$info = $this->levelD[$index];
if (null !== $info->getBankTransactionCode()->getDomain()) {
$return = (string) $info->getBankTransactionCode()->getDomain()->getFamily()->getCode();
return (string) $info->getBankTransactionCode()->getDomain()->getFamily()->getCode();
}
return $return;
@ -330,7 +318,7 @@ class Transaction
/** @var EntryTransactionDetail $info */
$info = $this->levelD[$index];
if (null !== $info->getBankTransactionCode()->getDomain()) {
$return = (string) $info->getBankTransactionCode()->getDomain()->getFamily()->getSubFamilyCode();
return (string) $info->getBankTransactionCode()->getDomain()->getFamily()->getSubFamilyCode();
}
return $return;
@ -346,7 +334,7 @@ class Transaction
$info = $this->levelD[$index] ?? null;
if (null !== $info) {
$opposingAccount = $this->getOpposingParty($info)?->getAccount();
if (is_object($opposingAccount) && IbanAccount::class === get_class($opposingAccount)) {
if (is_object($opposingAccount) && IbanAccount::class === $opposingAccount::class) {
$result = (string) $opposingAccount->getIdentification();
}
}
@ -364,9 +352,9 @@ class Transaction
/** @var EntryTransactionDetail $info */
$info = $this->levelD[$index];
$opposingAccount = $this->getOpposingParty($info)?->getAccount();
$class = null !== $opposingAccount ? get_class($opposingAccount) : '';
$class = $opposingAccount instanceof Account ? $opposingAccount::class : '';
if (in_array($class, $list, true)) {
$result = (string) $opposingAccount->getIdentification();
return (string) $opposingAccount->getIdentification();
}
return $result;
@ -381,11 +369,11 @@ class Transaction
/** @var EntryTransactionDetail $info */
$info = $this->levelD[$index];
$opposingParty = $this->getOpposingParty($info);
if (null === $opposingParty) {
if (!$opposingParty instanceof RelatedParty) {
Log::debug('In entryDetailOpposingName, opposing party is NULL, return "".');
}
if (null !== $opposingParty) {
$result = $this->getOpposingName($opposingParty);
if ($opposingParty instanceof RelatedParty) {
return $this->getOpposingName($opposingParty);
}
return $result;
@ -399,7 +387,7 @@ class Transaction
{
Log::debug('getOpposingParty(), interested in Creditor.');
$relatedParties = $transactionDetail->getRelatedParties();
$targetRelatedPartyObject = 'Genkgo\Camt\DTO\Creditor';
$targetRelatedPartyObject = Creditor::class;
// get amount from "getAmount":
$amount = $transactionDetail?->getAmount()?->getAmount();
@ -413,11 +401,11 @@ class Transaction
if (null !== $amount && $amount > 0) { // which part in this array is the interesting one?
Log::debug('getOpposingParty(), interested in Debtor!');
$targetRelatedPartyObject = 'Genkgo\Camt\DTO\Debtor';
$targetRelatedPartyObject = Debtor::class;
}
foreach ($relatedParties as $relatedParty) {
Log::debug(sprintf('Found related party of type "%s"', get_class($relatedParty->getRelatedPartyType())));
if (get_class($relatedParty->getRelatedPartyType()) === $targetRelatedPartyObject) {
Log::debug(sprintf('Found related party of type "%s"', $relatedParty->getRelatedPartyType()::class));
if ($relatedParty->getRelatedPartyType()::class === $targetRelatedPartyObject) {
Log::debug('This is the type we are looking for!');
return $relatedParty;
@ -441,7 +429,7 @@ class Transaction
$opposingName = $relatedParty->getRelatedPartyType()->getName();
// but maybe you want also the entire address
if ($useEntireAddress && $addressLine = $this->generateAddressLine($relatedParty->getRelatedPartyType()->getAddress())) {
$opposingName .= ', '.$addressLine;
$opposingName .= sprintf(', %s',$addressLine);
}
}

16
app/Services/Enums/AuthenticationStatus.php

@ -25,13 +25,9 @@ declare(strict_types=1);
namespace App\Services\Enums;
use Spatie\Enum\Enum;
/**
* Class AuthenticationStatus
*
* @method static self authenticated()
* @method static self nodata()
* @method static self error()
*/
class AuthenticationStatus extends Enum {}
enum AuthenticationStatus : string
{
case AUTHENTICATED = 'authenticated';
case NODATA = 'nodata';
case ERROR = 'error';
}

8
app/Services/Nordigen/Authentication/SecretManager.php

@ -50,7 +50,7 @@ class SecretManager
try {
$id = (string) session()->get(self::NORDIGEN_ID);
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
} catch (ContainerExceptionInterface|NotFoundExceptionInterface) {
$id = '(super invalid)';
}
@ -64,7 +64,7 @@ class SecretManager
{
try {
$id = (string) session()->get(self::NORDIGEN_ID);
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
} catch (ContainerExceptionInterface|NotFoundExceptionInterface) {
$id = '';
}
@ -84,7 +84,7 @@ class SecretManager
try {
$key = (string) session()->get(self::NORDIGEN_KEY);
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
} catch (ContainerExceptionInterface|NotFoundExceptionInterface) {
$key = '(super invalid key)';
}
@ -98,7 +98,7 @@ class SecretManager
{
try {
$key = (string) session()->get(self::NORDIGEN_KEY);
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
} catch (ContainerExceptionInterface|NotFoundExceptionInterface) {
$key = '';
}

10
app/Services/Nordigen/AuthenticationValidator.php

@ -47,12 +47,12 @@ class AuthenticationValidator implements AuthenticationValidatorInterface
$key = SecretManager::getKey();
if ('' === $identifier || '' === $key) {
return AuthenticationStatus::nodata();
return AuthenticationStatus::NODATA;
}
// is there a valid access and refresh token?
if (TokenManager::hasValidRefreshToken() && TokenManager::hasValidAccessToken()) {
return AuthenticationStatus::authenticated();
return AuthenticationStatus::AUTHENTICATED;
}
if (TokenManager::hasExpiredRefreshToken()) {
@ -63,10 +63,10 @@ class AuthenticationValidator implements AuthenticationValidatorInterface
// get complete set!
try {
TokenManager::getNewTokenSet($identifier, $key);
} catch (ImporterHttpException $e) {
return AuthenticationStatus::error();
} catch (ImporterHttpException) {
return AuthenticationStatus::ERROR;
}
return AuthenticationStatus::authenticated();
return AuthenticationStatus::AUTHENTICATED;
}
}

6
app/Services/Nordigen/Conversion/Routine/GenerateTransactions.php

@ -204,7 +204,7 @@ class GenerateTransactions
'datetime' => $entry->getDate()->toW3cString(),
'amount' => $entry->transactionAmount,
'description' => $entry->getCleanDescription(),
'payment_date' => null === $valueDate ? '' : $valueDate->format('Y-m-d'),
'payment_date' => $valueDate instanceof Carbon ? $valueDate->format('Y-m-d') : '',
'order' => 0,
'currency_code' => $entry->currencyCode,
'tags' => $entry->tags,
@ -238,10 +238,10 @@ class GenerateTransactions
}
// #9533 add entry reference as tag or as booking date.
if ('' !== $entry->entryReference) {
if (false === strtotime($entry->entryReference)) {
if (false === Carbon::parse($entry->entryReference)->getTimestamp()) {
$transaction['tags'][] = $entry->entryReference;
}
if (false !== strtotime($entry->entryReference)) {
if (false !== Carbon::parse($entry->entryReference)->getTimestamp()) {
try {
$transaction['booking_date'] = Carbon::parse($entry->entryReference)->toW3cString();
} catch (InvalidFormatException) {

18
app/Services/Nordigen/Conversion/Routine/TransactionProcessor.php

@ -47,11 +47,11 @@ class TransactionProcessor
use ProgressInformation;
/** @var string */
private const DATE_TIME_FORMAT = 'Y-m-d H:i:s';
private const string DATE_TIME_FORMAT = 'Y-m-d H:i:s';
private array $accounts;
private Configuration $configuration;
private ?Carbon $notAfter;
private ?Carbon $notBefore;
private ?Carbon $notAfter = null;
private ?Carbon $notBefore = null;
/**
* @throws ImporterErrorException
@ -118,7 +118,7 @@ class TransactionProcessor
$transactions = $request->get();
Log::debug(sprintf('GetTransactionsResponse: count %d transaction(s)', count($transactions)));
} catch (ImporterHttpException|RateLimitException $e) {
Log::debug(sprintf('Ran into %s instead of GetTransactionsResponse', get_class($e)));
Log::debug(sprintf('Ran into %s instead of GetTransactionsResponse', $e::class));
$this->addError(0, $e->getMessage());
$return[$account] = [];
@ -130,7 +130,7 @@ class TransactionProcessor
continue;
} catch (AgreementExpiredException $e) {
Log::debug(sprintf('Ran into %s instead of GetTransactionsResponse', get_class($e)));
Log::debug(sprintf('Ran into %s instead of GetTransactionsResponse', $e::class));
// agreement expired, whoops.
$return[$account] = [];
$this->addError(0, $e->json['detail'] ?? '[a114]: Your EUA has expired.');
@ -168,17 +168,17 @@ class TransactionProcessor
private function filterTransactions(GetTransactionsResponse $transactions): array
{
Log::info(sprintf('Going to filter downloaded transactions. Original set length is %d', count($transactions)));
if (null !== $this->notBefore) {
if ($this->notBefore instanceof Carbon) {
Log::info(sprintf('Will not grab transactions before "%s"', $this->notBefore->format('Y-m-d H:i:s')));
}
if (null !== $this->notAfter) {
if ($this->notAfter instanceof Carbon) {
Log::info(sprintf('Will not grab transactions after "%s"', $this->notAfter->format('Y-m-d H:i:s')));
}
$return = [];
foreach ($transactions as $transaction) {
$madeOn = $transaction->getDate();
if (null !== $this->notBefore && $madeOn->lt($this->notBefore)) {
if ($this->notBefore instanceof Carbon && $madeOn->lt($this->notBefore)) {
Log::debug(
sprintf(
'Skip transaction because "%s" is before "%s".',
@ -189,7 +189,7 @@ class TransactionProcessor
continue;
}
if (null !== $this->notAfter && $madeOn->gt($this->notAfter)) {
if ($this->notAfter instanceof Carbon && $madeOn->gt($this->notAfter)) {
Log::debug(
sprintf(
'Skip transaction because "%s" is after "%s".',

3
app/Services/Nordigen/Conversion/RoutineManager.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\Nordigen\Conversion;
use Override;
use App\Exceptions\AgreementExpiredException;
use App\Exceptions\ImporterErrorException;
use App\Services\Nordigen\Conversion\Routine\FilterTransactions;
@ -76,7 +77,7 @@ class RoutineManager implements RoutineManagerInterface
$this->transactionFilter = new FilterTransactions();
}
#[\Override]
#[Override]
public function getServiceAccounts(): array
{
return $this->transactionProcessor->getAccounts();

27
app/Services/Nordigen/Model/Transaction.php

@ -25,6 +25,9 @@ declare(strict_types=1);
namespace App\Services\Nordigen\Model;
use JsonException;
use DateTimeInterface;
use Validator;
use App\Rules\Iban;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
@ -41,7 +44,7 @@ class Transaction
public string $additionalInformationStructured;
public Balance $balanceAfterTransaction;
public string $bankTransactionCode;
public ?Carbon $bookingDate;
public ?Carbon $bookingDate = null;
public string $checkId;
public string $creditorAccountBban;
public string $creditorAccountCurrency;
@ -88,7 +91,7 @@ class Transaction
public string $ultimateDebtor;
// new fields
public ?Carbon $valueDate;
public ?Carbon $valueDate = null;
/**
* Creates a transaction from a downloaded array.
@ -198,7 +201,7 @@ class Transaction
try {
$hash = hash('sha256', json_encode($array, JSON_THROW_ON_ERROR));
Log::warning('Generated random transaction ID from array!');
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error(sprintf('Could not parse array into JSON: %s', $e->getMessage()));
}
$object->transactionId = sprintf('ff3-%s', Uuid::uuid5(config('importer.namespace'), $hash));
@ -223,7 +226,7 @@ class Transaction
$object->additionalInformationStructured = $array['additional_information_structured'];
$object->balanceAfterTransaction = Balance::fromLocalArray($array['balance_after_transaction']);
$object->bankTransactionCode = $array['bank_transaction_code'];
$object->bookingDate = Carbon::createFromFormat(\DateTimeInterface::W3C, $array['booking_date']);
$object->bookingDate = Carbon::createFromFormat(DateTimeInterface::W3C, $array['booking_date']);
$object->checkId = $array['check_id'];
$object->creditorAgent = $array['creditor_agent'];
$object->creditorId = $array['creditor_id'];
@ -244,7 +247,7 @@ class Transaction
$object->transactionId = $array['transaction_id'];
$object->ultimateCreditor = $array['ultimate_creditor'];
$object->ultimateDebtor = $array['ultimate_debtor'];
$object->valueDate = Carbon::createFromFormat(\DateTimeInterface::W3C, $array['value_date']);
$object->valueDate = Carbon::createFromFormat(DateTimeInterface::W3C, $array['value_date']);
$object->transactionAmount = $array['transaction_amount']['amount'];
$object->currencyCode = $array['transaction_amount']['currency'];
$object->accountIdentifier = $array['account_identifier'];
@ -275,7 +278,7 @@ class Transaction
try {
$hash = hash('sha256', json_encode($array, JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error(sprintf('Could not parse array into JSON: %s', $e->getMessage()));
}
$object->transactionId = (string) Uuid::uuid5(config('importer.namespace'), $hash);
@ -286,12 +289,12 @@ class Transaction
public function getDate(): Carbon
{
if (null !== $this->bookingDate) {
if ($this->bookingDate instanceof Carbon) {
Log::debug('Returning book date');
return $this->bookingDate;
}
if (null !== $this->valueDate) {
if ($this->valueDate instanceof Carbon) {
Log::debug('Returning value date');
return $this->valueDate;
@ -349,7 +352,7 @@ class Transaction
public function getTransactionId(): string
{
return substr(trim(preg_replace('/\s+/', ' ', $this->transactionId)), 0, 250);
return substr(trim((string) preg_replace('/\s+/', ' ', $this->transactionId)), 0, 250);
}
/**
@ -361,7 +364,7 @@ class Transaction
if ('' !== $this->creditorAccountIban) {
$data = ['iban' => $this->creditorAccountIban];
$rules = ['iban' => ['required', new Iban()]];
$validator = \Validator::make($data, $rules);
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
Log::warning(sprintf('Destination IBAN is "%s" (creditor), but it is invalid, so ignoring', $this->creditorAccountIban));
@ -441,7 +444,7 @@ class Transaction
if ('' !== $this->debtorAccountIban) {
$data = ['iban' => $this->debtorAccountIban];
$rules = ['iban' => ['required', new Iban()]];
$validator = \Validator::make($data, $rules);
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
Log::warning(sprintf('Source IBAN is "%s" (debtor), but it is invalid, so ignoring', $this->debtorAccountIban));
@ -496,7 +499,7 @@ class Transaction
public function getValueDate(): ?Carbon
{
if (null !== $this->valueDate) {
if ($this->valueDate instanceof Carbon) {
Log::debug('Returning value date for getValueDate');
return $this->valueDate;

2
app/Services/Nordigen/Request/GetRequisitionRequest.php

@ -38,7 +38,7 @@ use App\Services\Shared\Response\Response;
*/
class GetRequisitionRequest extends Request
{
private string $requisitionId;
private readonly string $requisitionId;
public function __construct(string $url, string $token, string $requisitionId)
{

7
app/Services/Nordigen/Request/PostNewTokenRequest.php

@ -37,13 +37,8 @@ use Illuminate\Support\Facades\Log;
*/
class PostNewTokenRequest extends Request
{
private string $identifier;
private string $key;
public function __construct(string $identifier, string $key)
public function __construct(private readonly string $identifier, private readonly string $key)
{
$this->identifier = $identifier;
$this->key = $key;
}
public function get(): Response {}

13
app/Services/Nordigen/Request/Request.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\Nordigen\Request;
use JsonException;
use App\Exceptions\AgreementExpiredException;
use App\Exceptions\ImporterErrorException;
use App\Exceptions\ImporterHttpException;
@ -117,14 +118,14 @@ abstract class Request
} catch (ClientException|GuzzleException|TransferException $e) {
$statusCode = $e->getCode();
if (429 === $statusCode) {
Log::debug(sprintf('Ran into exception: %s', get_class($e)));
Log::debug(sprintf('Ran into exception: %s', $e::class));
$this->logRateLimitHeaders($e->getResponse(), true);
// $this->reportRateLimit($fullUrl, $e);
$this->pauseForRateLimit($e->getResponse(), true);
return [];
}
Log::error(sprintf('Original error: %s: %s', get_class($e), $e->getMessage()));
Log::error(sprintf('Original error: %s: %s', $e::class, $e->getMessage()));
// crash but there is a response, log it.
if (method_exists($e, 'getResponse') && method_exists($e, 'hasResponse') && $e->hasResponse()) {
@ -143,7 +144,7 @@ abstract class Request
$body = (string) $e->getResponse()->getBody();
$json = json_decode($body, true) ?? [];
}
if (array_key_exists('summary', $json) && str_contains($json['summary'], 'expired')) {
if (array_key_exists('summary', $json) && str_contains((string) $json['summary'], 'expired')) {
$exception = new AgreementExpiredException();
$exception->json = $json;
@ -151,7 +152,7 @@ abstract class Request
}
// if status code is 503, the account does not exist.
$exception = new ImporterErrorException(sprintf('%s: %s', get_class($e), $e->getMessage()), 0, $e);
$exception = new ImporterErrorException(sprintf('%s: %s', $e::class, $e->getMessage()), 0, $e);
$exception->json = $json;
throw $exception;
@ -170,7 +171,7 @@ abstract class Request
try {
$json = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
throw new ImporterHttpException(
sprintf(
'Could not decode JSON (%s). Error[%d] is: %s. Response: %s',
@ -271,7 +272,7 @@ abstract class Request
try {
$json = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
// TODO error response, not an exception.
throw new ImporterHttpException(sprintf('AuthenticatedJsonPost JSON: %s', $e->getMessage()), 0, $e);
}

5
app/Services/Nordigen/Response/ArrayResponse.php

@ -33,10 +33,7 @@ use App\Services\Shared\Response\Response;
*/
class ArrayResponse extends Response
{
public array $data;
public function __construct(array $data)
public function __construct(public array $data)
{
$this->data = $data;
}
}

4
app/Services/Nordigen/Response/GetTransactionsResponse.php

@ -34,9 +34,9 @@ use Iterator;
/**
* Class GetTransactionsResponse
*/
class GetTransactionsResponse extends Response implements \Iterator, \Countable
class GetTransactionsResponse extends Response implements Iterator, Countable
{
private Collection $collection;
private readonly Collection $collection;
private int $position = 0;
public function __construct(array $data)

4
app/Services/Nordigen/Response/ListAccountsResponse.php

@ -35,10 +35,10 @@ use Iterator;
/**
* Class ListAccountsResponse
*/
class ListAccountsResponse extends Response implements \Iterator, \Countable
class ListAccountsResponse extends Response implements Iterator, Countable
{
private array $accounts;
private Collection $collection;
private readonly Collection $collection;
private int $position = 0;
public function __construct(array $data)

4
app/Services/Nordigen/Response/ListBanksResponse.php

@ -35,9 +35,9 @@ use Iterator;
/**
* Class ListBanksResponse
*/
class ListBanksResponse extends Response implements \Iterator, \Countable
class ListBanksResponse extends Response implements Iterator, Countable
{
private Collection $collection;
private readonly Collection $collection;
private array $countries;
private int $position = 0;

5
app/Services/Nordigen/Response/TokenSetResponse.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\Nordigen\Response;
use Carbon\Carbon;
use App\Services\Shared\Response\Response;
/**
@ -42,7 +43,7 @@ class TokenSetResponse extends Response
$this->accessToken = $data['access'];
$this->refreshToken = $data['refresh'];
$this->accessExpires = time() + $data['access_expires'];
$this->refreshExpires = time() + $data['refresh_expires'];
$this->accessExpires = Carbon::now()->getTimestamp() + $data['access_expires'];
$this->refreshExpires = Carbon::now()->getTimestamp() + $data['refresh_expires'];
}
}

9
app/Services/Nordigen/TokenManager.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\Nordigen;
use Carbon\Carbon;
use App\Exceptions\ImporterErrorException;
use App\Exceptions\ImporterHttpException;
use App\Services\Nordigen\Authentication\SecretManager;
@ -96,11 +97,11 @@ class TokenManager
try {
$tokenValidity = session()->get(Constants::NORDIGEN_REFRESH_EXPIRY_TIME) ?? 0;
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
} catch (ContainerExceptionInterface|NotFoundExceptionInterface) {
$tokenValidity = 0;
}
return time() < $tokenValidity;
return Carbon::now()->getTimestamp() < $tokenValidity;
}
public static function hasValidAccessToken(): bool
@ -115,11 +116,11 @@ class TokenManager
try {
$tokenValidity = session()->get(Constants::NORDIGEN_ACCESS_EXPIRY_TIME) ?? 0;
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
} catch (ContainerExceptionInterface|NotFoundExceptionInterface) {
$tokenValidity = 0;
}
// Log::debug(sprintf('Nordigen token is valid until %s', date('Y-m-d H:i:s', $tokenValidity)));
$result = time() < $tokenValidity;
$result = Carbon::now()->getTimestamp() < $tokenValidity;
if (false === $result) {
Log::debug('Nordigen token is no longer valid');

6
app/Services/Shared/Configuration/Configuration.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Services\Shared\Configuration;
use UnexpectedValueException;
use DateTimeInterface;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
@ -194,7 +196,7 @@ class Configuration
return self::fromVersionThree($data);
}
throw new \UnexpectedValueException(sprintf('Configuration file version "%s" cannot be parsed.', $version));
throw new UnexpectedValueException(sprintf('Configuration file version "%s" cannot be parsed.', $version));
}
/**
@ -823,7 +825,7 @@ class Configuration
$array = [
'version' => $this->version,
'source' => sprintf('ff3-importer-%s', config('importer.version')),
'created_at' => date(\DateTimeInterface::W3C),
'created_at' => date(DateTimeInterface::W3C),
'date' => $this->date,
'default_account' => $this->defaultAccount,
'delimiter' => $this->delimiter,

2
app/Services/Shared/Conversion/CombinedProgressInformation.php

@ -74,7 +74,7 @@ trait CombinedProgressInformation
}
}
if (0 === count($return)) {
$return = [];
return [];
}
return $return;

6
app/Services/Shared/Conversion/GeneratesIdentifier.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Services\Shared\Conversion;
use Storage;
use Str;
use Illuminate\Support\Facades\Log;
/**
@ -43,10 +45,10 @@ trait GeneratesIdentifier
protected function generateIdentifier(): void
{
Log::debug('Going to generate conversion routine identifier.');
$disk = \Storage::disk($this->diskName);
$disk = Storage::disk($this->diskName);
$count = 0;
do {
$generatedId = sprintf('conversion-%s', \Str::random(12));
$generatedId = sprintf('conversion-%s', Str::random(12));
++$count;
Log::debug(sprintf('Attempt #%d results in "%s"', $count, $generatedId));
} while ($count < 30 && $disk->exists($generatedId));

40
app/Services/Shared/Conversion/RoutineStatusManager.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Services\Shared\Conversion;
use Storage;
use JsonException;
use App\Exceptions\ImporterErrorException;
use App\Services\Session\Constants;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
@ -44,13 +46,13 @@ class RoutineStatusManager
$lineNo = $index + 1;
Log::debug(sprintf('Add error on index #%d (line no. %d): %s', $index, $lineNo, $error));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
if ($disk->exists($identifier)) {
try {
$status = ConversionStatus::fromArray(json_decode($disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
$status = ConversionStatus::fromArray(json_decode((string) $disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (JsonException $e) {
Log::error($e->getMessage());
$status = new ConversionStatus();
}
@ -68,13 +70,13 @@ class RoutineStatusManager
$lineNo = $index + 1;
Log::debug(sprintf('Add rate limit message on index #%d (line no. %d): %s', $index, $lineNo, $message));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
if ($disk->exists($identifier)) {
try {
$status = ConversionStatus::fromArray(json_decode($disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
$status = ConversionStatus::fromArray(json_decode((string) $disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (JsonException $e) {
Log::error($e->getMessage());
$status = new ConversionStatus();
}
@ -91,11 +93,11 @@ class RoutineStatusManager
{
Log::debug(sprintf('Now in storeConversionStatus(%s): %s', $identifier, $status->status));
Log::debug(sprintf('Messages: %d, warnings: %d, errors: %d', count($status->messages), count($status->warnings), count($status->errors)));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
$disk->put($identifier, json_encode($status->toArray(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
} catch (\JsonException $e) {
} catch (JsonException $e) {
// do nothing
Log::error($e->getMessage());
}
@ -106,13 +108,13 @@ class RoutineStatusManager
$lineNo = $index + 1;
Log::debug(sprintf('Add message on index #%d (line no. %d): %s', $index, $lineNo, $message));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
if ($disk->exists($identifier)) {
try {
$status = ConversionStatus::fromArray(json_decode($disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
$status = ConversionStatus::fromArray(json_decode((string) $disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (JsonException $e) {
Log::error($e->getMessage());
$status = new ConversionStatus();
}
@ -130,13 +132,13 @@ class RoutineStatusManager
$lineNo = $index + 1;
Log::debug(sprintf('Add warning on index #%d (line no. %d): %s', $index, $lineNo, $warning));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
if ($disk->exists($identifier)) {
try {
$status = ConversionStatus::fromArray(json_decode($disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
$status = ConversionStatus::fromArray(json_decode((string) $disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (JsonException $e) {
Log::error($e->getMessage());
$status = new ConversionStatus();
}
@ -157,7 +159,7 @@ class RoutineStatusManager
if (null === $identifier) {
try {
$identifier = session()->get(Constants::CONVERSION_JOB_IDENTIFIER);
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
} catch (ContainerExceptionInterface|NotFoundExceptionInterface) {
throw new ImporterErrorException('No identifier found');
}
}
@ -175,14 +177,14 @@ class RoutineStatusManager
public static function startOrFindConversion(string $identifier): ConversionStatus
{
Log::debug(sprintf('Now in startOrFindConversion(%s)', $identifier));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
// Log::debug(sprintf('Try to see if file exists for conversion "%s".', $identifier));
if ($disk->exists($identifier)) {
// Log::debug(sprintf('Status file exists for conversion "%s".', $identifier));
try {
$array = json_decode($disk->get($identifier), true, 512, JSON_THROW_ON_ERROR);
$array = json_decode((string) $disk->get($identifier), true, 512, JSON_THROW_ON_ERROR);
$status = ConversionStatus::fromArray($array);
} catch (FileNotFoundException|\JsonException $e) {
} catch (FileNotFoundException|JsonException $e) {
Log::error($e->getMessage());
$status = new ConversionStatus();
}
@ -195,7 +197,7 @@ class RoutineStatusManager
try {
$disk->put($identifier, json_encode($status->toArray(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error($e->getMessage());
}

5
app/Services/Shared/File/FileContentSherlock.php

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace App\Services\Shared\File;
use Exception;
use Genkgo\Camt\Config;
use Genkgo\Camt\Reader;
use Illuminate\Support\Facades\Log;
@ -51,7 +52,7 @@ class FileContentSherlock
Log::debug('CAMT.053 Check on file: positive');
return 'camt';
} catch (\Exception $e) {
} catch (Exception $e) {
Log::debug('CAMT.053 Check on file: negative');
Log::debug($e->getMessage());
}
@ -70,7 +71,7 @@ class FileContentSherlock
Log::debug('CAMT.053 Check of content: positive');
return 'camt';
} catch (\Exception $e) {
} catch (Exception) {
Log::debug('CAMT.053 Check of content: negative');
// Log::debug($e->getMessage());
}

29
app/Services/Shared/Import/Routine/ApiSubmitter.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\Shared\Import\Routine;
use Carbon\Carbon;
use App\Exceptions\ImporterErrorException;
use App\Services\Shared\Authentication\SecretManager;
use App\Services\Shared\Configuration\Configuration;
@ -66,7 +67,7 @@ class ApiSubmitter
{
$this->createdTag = false;
$this->tag = $this->parseTag();
$this->tagDate = date('Y-m-d');
$this->tagDate = Carbon::now()->format('Y-m-d');
$count = count($lines);
$uniqueCount = 0;
Log::info(sprintf('Going to submit %d transactions to your Firefly III instance.', $count));
@ -114,20 +115,20 @@ class ApiSubmitter
$customTag = $this->configuration->getCustomTag();
if ('' === $customTag) {
// return default tag:
return sprintf('Data Import on %s', date('Y-m-d \@ H:i'));
return sprintf('Data Import on %s', Carbon::now()->format('Y-m-d \@ H:i'));
}
$items = [
'%year%' => date('Y'),
'%month%' => date('m'),
'%month_full%' => date('F'),
'%day%' => date('d'),
'%day_of_week%' => date('l'),
'%hour%' => date('H'),
'%minute%' => date('i'),
'%second%' => date('s'),
'%date%' => date('Y-m-d'),
'%time%' => date('H:i'),
'%datetime%' => date('Y-m-d \@ H:i'),
'%year%' => Carbon::now()->format('Y'),
'%month%' => Carbon::now()->format('m'),
'%month_full%' => Carbon::now()->format('F'),
'%day%' => Carbon::now()->format('d'),
'%day_of_week%' => Carbon::now()->format('l'),
'%hour%' => Carbon::now()->format('H'),
'%minute%' => Carbon::now()->format('i'),
'%second%' => Carbon::now()->format('s'),
'%date%' => Carbon::now()->format('Y-m-d'),
'%time%' => Carbon::now()->format('H:i'),
'%datetime%' => Carbon::now()->format('Y-m-d \@ H:i'),
'%version%' => config('importer.version'),
];
$result = str_replace(
@ -238,7 +239,7 @@ class ApiSubmitter
$json = json_decode($body, true);
// before we complain, first check what the error is:
if (is_array($json) && array_key_exists('message', $json)) {
if (str_contains($json['message'], '200032')) {
if (str_contains((string) $json['message'], '200032')) {
$isDeleted = true;
}
}

4
app/Services/Shared/Import/Routine/RoutineManager.php

@ -38,13 +38,11 @@ class RoutineManager
private array $allMessages;
private array $allWarnings;
private ApiSubmitter $apiSubmitter;
private string $identifier;
private InfoCollector $infoCollector;
private array $transactions;
public function __construct(string $identifier)
public function __construct(private readonly string $identifier)
{
$this->identifier = $identifier;
$this->transactions = [];
$this->allMessages = [];
$this->allWarnings = [];

38
app/Services/Shared/Import/Status/SubmissionStatusManager.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Services\Shared\Import\Status;
use Storage;
use JsonException;
use App\Services\Session\Constants;
use App\Services\Shared\Submission\GeneratesIdentifier;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
@ -45,13 +47,13 @@ class SubmissionStatusManager
{
$lineNo = $index + 1;
Log::debug(sprintf('Add error on index #%d (line no. %d): %s', $index, $lineNo, $error));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
if ($disk->exists($identifier)) {
try {
$status = SubmissionStatus::fromArray(json_decode($disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
$status = SubmissionStatus::fromArray(json_decode((string) $disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (JsonException) {
$status = new SubmissionStatus();
}
$status->errors[$index] ??= [];
@ -70,11 +72,11 @@ class SubmissionStatusManager
{
Log::debug(sprintf('Now in %s(%s): %s', __METHOD__, $identifier, $status->status));
Log::debug(sprintf('Messages: %d, warnings: %d, errors: %d', count($status->messages), count($status->warnings), count($status->errors)));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
$disk->put($identifier, json_encode($status->toArray(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
} catch (\JsonException $e) {
} catch (JsonException $e) {
// do nothing
Log::error($e->getMessage());
}
@ -85,13 +87,13 @@ class SubmissionStatusManager
$lineNo = $index + 1;
Log::debug(sprintf('Add message on index #%d (line no. %d): %s', $index, $lineNo, $message));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
if ($disk->exists($identifier)) {
try {
$status = SubmissionStatus::fromArray(json_decode($disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
$status = SubmissionStatus::fromArray(json_decode((string) $disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (JsonException) {
$status = new SubmissionStatus();
}
$status->messages[$index] ??= [];
@ -111,13 +113,13 @@ class SubmissionStatusManager
$lineNo = $index + 1;
Log::debug(sprintf('Add warning on index #%d (line no. %d): %s', $index, $lineNo, $warning));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
if ($disk->exists($identifier)) {
try {
$status = SubmissionStatus::fromArray(json_decode($disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
$status = SubmissionStatus::fromArray(json_decode((string) $disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (JsonException) {
$status = new SubmissionStatus();
}
$status->warnings[$index] ??= [];
@ -136,13 +138,13 @@ class SubmissionStatusManager
{
Log::debug(sprintf('Update progress for %s: %d/%d transactions', $identifier, $currentTransaction, $totalTransactions));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
if ($disk->exists($identifier)) {
try {
$status = SubmissionStatus::fromArray(json_decode($disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (\JsonException $e) {
$status = SubmissionStatus::fromArray(json_decode((string) $disk->get($identifier), true, 512, JSON_THROW_ON_ERROR));
} catch (JsonException) {
$status = new SubmissionStatus();
}
@ -184,7 +186,7 @@ class SubmissionStatusManager
public static function startOrFindSubmission(string $identifier): SubmissionStatus
{
Log::debug(sprintf('Now in startOrFindJob(%s)', $identifier));
$disk = \Storage::disk(self::DISK_NAME);
$disk = Storage::disk(self::DISK_NAME);
try {
Log::debug(sprintf('Try to see if file exists for job %s.', $identifier));
@ -192,9 +194,9 @@ class SubmissionStatusManager
Log::debug(sprintf('Status file exists for job %s.', $identifier));
try {
$array = json_decode($disk->get($identifier), true, 512, JSON_THROW_ON_ERROR);
$array = json_decode((string) $disk->get($identifier), true, 512, JSON_THROW_ON_ERROR);
$status = SubmissionStatus::fromArray($array);
} catch (FileNotFoundException|\JsonException $e) {
} catch (FileNotFoundException|JsonException $e) {
Log::error($e->getMessage());
$status = new SubmissionStatus();
}
@ -212,7 +214,7 @@ class SubmissionStatusManager
try {
$json = json_encode($status->toArray(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT);
} catch (\JsonException $e) {
} catch (JsonException $e) {
Log::error($e->getMessage());
$json = '{}';
}

6
app/Services/Shared/Submission/GeneratesIdentifier.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Services\Shared\Submission;
use Storage;
use Str;
use Illuminate\Support\Facades\Log;
/**
@ -38,10 +40,10 @@ trait GeneratesIdentifier
public function generateIdentifier(): string
{
Log::debug('Going to generate submission routine identifier.');
$disk = \Storage::disk($this->diskName);
$disk = Storage::disk($this->diskName);
$count = 0;
do {
$generatedId = sprintf('submission-%s', \Str::random(12));
$generatedId = sprintf('submission-%s', Str::random(12));
++$count;
Log::debug(sprintf('Attempt #%d results in "%s"', $count, $generatedId));
} while ($count < 30 && $disk->exists($generatedId));

30
app/Services/SimpleFIN/Conversion/AccountMapper.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Services\SimpleFIN\Conversion;
use Carbon\Carbon;
use Exception;
use App\Exceptions\ImporterErrorException;
use App\Services\CSV\Converter\Iban as IbanConverter;
use App\Services\Shared\Authentication\SecretManager;
@ -77,7 +79,7 @@ class AccountMapper
if ('map' === $mappingConfig['action'] && isset($mappingConfig['firefly_account_id'])) {
// Map to existing account
$fireflyAccount = $this->getFireflyAccountById((int) $mappingConfig['firefly_account_id']);
if ($fireflyAccount) {
if ($fireflyAccount instanceof \GrumpyDictator\FFIIIApiSupport\Model\Account) {
$mapping[$accountKey] = [
'simplefin_account' => $simplefinAccount,
'firefly_account_id' => $fireflyAccount->id,
@ -89,7 +91,7 @@ class AccountMapper
if ('create' === $mappingConfig['action']) {
// Create new account
$fireflyAccount = $this->createFireflyAccount($simplefinAccount, $mappingConfig);
if ($fireflyAccount) {
if ($fireflyAccount instanceof \GrumpyDictator\FFIIIApiSupport\Model\Account) {
$mapping[$accountKey] = [
'simplefin_account' => $simplefinAccount,
'firefly_account_id' => $fireflyAccount->id,
@ -102,7 +104,7 @@ class AccountMapper
if (!isset($configuration['account_mapping'][$accountKey])) {
// Auto-map by searching for existing accounts
$fireflyAccount = $this->findMatchingFireflyAccount($simplefinAccount);
if ($fireflyAccount) {
if ($fireflyAccount instanceof \GrumpyDictator\FFIIIApiSupport\Model\Account) {
$mapping[$accountKey] = [
'simplefin_account' => $simplefinAccount,
'firefly_account_id' => $fireflyAccount->id,
@ -110,7 +112,7 @@ class AccountMapper
'action' => 'auto_map',
];
}
if (null === $fireflyAccount) {
if (!$fireflyAccount instanceof \GrumpyDictator\FFIIIApiSupport\Model\Account) {
// No mapping found - will need user input
$mapping[$accountKey] = [
'simplefin_account' => $simplefinAccount,
@ -143,9 +145,7 @@ class AccountMapper
$this->loadFireflyAccounts();
// Try to find by name first
$matchingAccounts = array_filter($this->fireflyAccounts, function (Account $account) use ($simplefinAccount) {
return strtolower($account->name) === strtolower($simplefinAccount->getName());
});
$matchingAccounts = array_filter($this->fireflyAccounts, fn(Account $account) => strtolower((string) $account->name) === strtolower($simplefinAccount->getName()));
if (0 === count($matchingAccounts)) {
return reset($matchingAccounts);
@ -198,7 +198,7 @@ class AccountMapper
// Add opening balance date if opening balance is provided
if ('' !== (string)$config['opening_balance'] && is_numeric($config['opening_balance'])) {
$payload['opening_balance_date'] = $config['opening_balance_date'] ?? date('Y-m-d');
$payload['opening_balance_date'] = $config['opening_balance_date'] ?? Carbon::now()->format('Y-m-d');
}
// Add account role for asset accounts
@ -242,7 +242,7 @@ class AccountMapper
if ($response instanceof PostAccountResponse) {
$account = $response->getAccount();
if ($account) {
if ($account instanceof \GrumpyDictator\FFIIIApiSupport\Model\Account) {
Log::info(sprintf('Successfully created account "%s" with ID %d', $accountName, $account->id));
// Add to our local cache
@ -261,7 +261,7 @@ class AccountMapper
Log::error(sprintf('API error creating account "%s": %s', $accountName, $e->getMessage()));
return null;
} catch (\Exception $e) {
} catch (Exception $e) {
Log::error(sprintf('Unexpected error creating account "%s": %s', $accountName, $e->getMessage()));
return null;
@ -273,12 +273,8 @@ class AccountMapper
*/
private function determineAccountType(SimpleFINAccount $simplefinAccount, array $config): string
{
if (isset($config['type'])) {
return $config['type'];
}
// Default to asset account for most SimpleFIN accounts
return AccountType::ASSET;
return $config['type'] ?? AccountType::ASSET;
}
/**
@ -299,7 +295,7 @@ class AccountMapper
}
// 3. Final fallback
return $currency ?: 'EUR';
return $currency !== '' && $currency !== '0' ? $currency : 'EUR';
}
/**
@ -463,7 +459,7 @@ class AccountMapper
// Try to suggest a matching account
$suggested = $this->findMatchingFireflyAccount($simplefinAccount);
if ($suggested) {
if ($suggested instanceof \GrumpyDictator\FFIIIApiSupport\Model\Account) {
$options['suggested_account'] = [
'id' => $suggested->id,
'name' => $suggested->name,

43
app/Services/SimpleFIN/Conversion/RoutineManager.php

@ -25,6 +25,8 @@ declare(strict_types=1);
namespace App\Services\SimpleFIN\Conversion;
use Override;
use Carbon\Carbon;
use App\Exceptions\ImporterErrorException;
use App\Services\Session\Constants;
use App\Services\Shared\Configuration\Configuration;
@ -39,11 +41,11 @@ use Illuminate\Support\Str;
*/
class RoutineManager implements RoutineManagerInterface
{
private AccountMapper $accountMapper;
private readonly AccountMapper $accountMapper;
private Configuration $configuration;
private string $identifier;
private SimpleFINService $simpleFINService;
private TransactionTransformer $transformer;
private readonly string $identifier;
private readonly SimpleFINService $simpleFINService;
private readonly TransactionTransformer $transformer;
/**
* RoutineManager constructor.
@ -63,7 +65,7 @@ class RoutineManager implements RoutineManagerInterface
return $this->identifier;
}
#[\Override]
#[Override]
public function getServiceAccounts(): array
{
return session()->get(Constants::SIMPLEFIN_ACCOUNTS_DATA, []);
@ -120,16 +122,7 @@ class RoutineManager implements RoutineManagerInterface
continue;
}
// Find the SimpleFIN account data for account creation
$simplefinAccountData = null;
foreach ($allAccountsSimpleFINData as $accountData) {
if ($accountData['id'] === $simplefinAccountId) {
$simplefinAccountData = $accountData;
break;
}
}
$simplefinAccountData = array_find($allAccountsSimpleFINData, fn($accountData) => $accountData['id'] === $simplefinAccountId);
if (!$simplefinAccountData) {
Log::error("SimpleFIN account data not found for ID: {$simplefinAccountId}");
@ -147,7 +140,7 @@ class RoutineManager implements RoutineManagerInterface
// Add opening balance if provided
if ('' !== (string) $newAccountData['opening_balance'] && is_numeric($newAccountData['opening_balance'])) {
$accountConfig['opening_balance'] = $newAccountData['opening_balance'];
$accountConfig['opening_balance_date'] = date('Y-m-d');
$accountConfig['opening_balance_date'] = Carbon::now()->format('Y-m-d');
}
Log::info('Creating new Firefly III account', ['simplefin_account_id' => $simplefinAccountId, 'account_config' => $accountConfig]);
@ -157,7 +150,7 @@ class RoutineManager implements RoutineManagerInterface
$accountMapper = new AccountMapper();
$createdAccount = $accountMapper->createFireflyAccount($simplefinAccount, $accountConfig);
if (null !== $createdAccount) {
if ($createdAccount instanceof \GrumpyDictator\FFIIIApiSupport\Model\Account) {
// Account was created immediately - update configuration
$fireflyAccountId = $createdAccount->id;
$updatedAccounts = $this->configuration->getAccounts();
@ -171,7 +164,7 @@ class RoutineManager implements RoutineManagerInterface
Log::info('Successfully created new Firefly III account', ['simplefin_account_id' => $simplefinAccountId, 'firefly_account_id' => $fireflyAccountId, 'account_name' => $createdAccount->name, 'account_type' => $accountConfig['type'], 'currency' => $accountConfig['currency']]);
}
if (null === $createdAccount) {
if (!$createdAccount instanceof \GrumpyDictator\FFIIIApiSupport\Model\Account) {
// Account creation failed - this is a critical error that must be reported
$errorMessage = sprintf('Failed to create Firefly III account "%s" (type: %s, currency: %s). Cannot proceed with transaction import for this account.', $accountConfig['name'], $accountConfig['type'], $accountConfig['currency']);
@ -179,24 +172,14 @@ class RoutineManager implements RoutineManagerInterface
// try to find a matching account.
$createdAccount = $accountMapper->findMatchingFireflyAccount($simplefinAccount);
if (null === $createdAccount) {
if (!$createdAccount instanceof \GrumpyDictator\FFIIIApiSupport\Model\Account) {
Log::error('Could also not find a matching account for SimpleFIN account.', $simplefinAccount);
throw new ImporterErrorException($errorMessage);
}
}
}
// Find the specific SimpleFIN account data array for the current $simplefinAccountId.
// $allAccountsSimpleFINData is an indexed array of account data arrays.
$currentSimpleFINAccountData = null;
foreach ($allAccountsSimpleFINData as $accountDataFromArrayInLoop) {
if (isset($accountDataFromArrayInLoop['id']) && $accountDataFromArrayInLoop['id'] === $simplefinAccountId) {
$currentSimpleFINAccountData = $accountDataFromArrayInLoop;
break;
}
}
$currentSimpleFINAccountData = array_find($allAccountsSimpleFINData, fn($accountDataFromArrayInLoop) => isset($accountDataFromArrayInLoop['id']) && $accountDataFromArrayInLoop['id'] === $simplefinAccountId);
if (null === $currentSimpleFINAccountData) {
Log::error('Failed to find SimpleFIN account raw data in session for current account ID during transformation.', ['simplefin_account_id_sought' => $simplefinAccountId]);

45
app/Services/SimpleFIN/Conversion/TransactionTransformer.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\SimpleFIN\Conversion;
use Exception;
use App\Support\Http\CollectsAccounts;
use Carbon\Carbon;
use App\Services\Shared\Authentication\SecretManager;
@ -188,7 +189,7 @@ class TransactionTransformer
// Try to find existing expense or revenue account first
$existingAccount = $this->findExistingAccount($description, $isDeposit);
if ($existingAccount) {
if ($existingAccount !== null && $existingAccount !== []) {
return [
'id' => $existingAccount['id'],
'name' => $existingAccount['name'],
@ -206,7 +207,7 @@ class TransactionTransformer
if (0 === count($accountsToCheck)) {
$clusteredAccountName = $this->findClusteredAccountName($description, $isDeposit);
if ($clusteredAccountName) {
if ($clusteredAccountName !== null && $clusteredAccountName !== '' && $clusteredAccountName !== '0') {
return [
'id' => null,
'name' => $clusteredAccountName,
@ -274,10 +275,10 @@ class TransactionTransformer
];
foreach ($patterns as $pattern) {
$cleaned = preg_replace($pattern, '', $cleaned);
$cleaned = preg_replace($pattern, '', (string) $cleaned);
}
$cleaned = trim($cleaned);
$cleaned = trim((string) $cleaned);
// If we end up with an empty string, use a generic name
if ('' === $cleaned) {
@ -286,7 +287,7 @@ class TransactionTransformer
// Limit length to reasonable size
if (strlen($cleaned) > 100) {
$cleaned = substr($cleaned, 0, 97).'...';
return substr($cleaned, 0, 97).'...';
}
return $cleaned;
@ -399,7 +400,7 @@ class TransactionTransformer
*/
private function generateImportHash(array $transactionData, array $simpleFINAccountData): string
{
$postedTimestamp = isset($transactionData['posted']) ? (int)$transactionData['posted'] : time();
$postedTimestamp = isset($transactionData['posted']) ? (int)$transactionData['posted'] : Carbon::now()->getTimestamp();
$date = Carbon::createFromTimestamp($postedTimestamp)->format('Y-m-d');
$data = [
@ -485,7 +486,7 @@ class TransactionTransformer
));
$this->accountsCollected = true;
} catch (\Exception $e) {
} catch (Exception $e) {
Log::error(sprintf('Failed to collect accounts: %s', $e->getMessage()));
Log::debug('Continuing without smart expense matching due to collection failure');
$this->expenseAccounts = [];
@ -512,7 +513,7 @@ class TransactionTransformer
$normalizedDescription = $this->normalizeForMatching($description);
// Try exact matches first
foreach ($accountsToSearch as $key => $account) {
foreach ($accountsToSearch as $account) {
$normalizedAccountName = $this->normalizeForMatching($account['name']);
// Check for exact match
@ -525,7 +526,7 @@ class TransactionTransformer
// Try fuzzy matching if no exact match found
$bestMatch = $this->findBestFuzzyMatch($normalizedDescription, $accountsToSearch);
if ($bestMatch) {
if ($bestMatch !== null && $bestMatch !== []) {
Log::debug(sprintf(
'Fuzzy match found: "%s" -> "%s" (similarity: %.2f)',
$description,
@ -557,14 +558,14 @@ class TransactionTransformer
];
foreach ($patterns as $pattern) {
$normalized = preg_replace($pattern, '', $normalized);
$normalized = preg_replace($pattern, '', (string) $normalized);
}
// Remove special characters and extra spaces
$normalized = preg_replace('/[^a-z0-9\s]/', '', $normalized);
$normalized = preg_replace('/\s+/', ' ', $normalized);
$normalized = preg_replace('/[^a-z0-9\s]/', '', (string) $normalized);
$normalized = preg_replace('/\s+/', ' ', (string) $normalized);
return trim($normalized);
return trim((string) $normalized);
}
/**
@ -581,7 +582,7 @@ class TransactionTransformer
$bestSimilarity = 0;
$threshold = config('simplefin.expense_matching_threshold', 0.7);
foreach ($accounts as $key => $account) {
foreach ($accounts as $account) {
$normalizedAccountName = $this->normalizeForMatching($account['name']);
// Calculate similarity using multiple algorithms
@ -619,7 +620,7 @@ class TransactionTransformer
// Check for substring matches (give bonus for contains)
$substringBonus = 0;
if (false !== strpos($str1, $str2) || false !== strpos($str2, $str1)) {
if (str_contains($str1, $str2) || str_contains($str2, $str1)) {
$substringBonus = 0.2;
}
@ -640,7 +641,7 @@ class TransactionTransformer
// Ensure we have a non-empty description
if ('' === $sanitized) {
$sanitized = 'SimpleFIN Transaction';
return 'SimpleFIN Transaction';
}
return $sanitized;
@ -687,7 +688,7 @@ class TransactionTransformer
'normalized_name' => $normalizedDescription,
'descriptions' => [$description],
'count' => 1,
'created_at' => time(),
'created_at' => Carbon::now()->getTimestamp(),
];
Log::debug(sprintf('Created new %s cluster "%s" for "%s"', $accountType, $clusterName, $description));
@ -705,17 +706,17 @@ class TransactionTransformer
// Further normalize for cluster naming
$clusterName = preg_replace('/\b(payment|deposit|transfer|debit|credit|from|to)\b/i', '', $cleaned);
$clusterName = preg_replace('/\s+/', ' ', trim($clusterName));
$clusterName = preg_replace('/\s+/', ' ', trim((string) $clusterName));
// Remove trailing numbers/references that could vary
$clusterName = preg_replace('/\s+\d+\s*$/', '', $clusterName);
$clusterName = preg_replace('/\s+#\w+.*$/', '', $clusterName);
$clusterName = preg_replace('/\s+\d+\s*$/', '', (string) $clusterName);
$clusterName = preg_replace('/\s+#\w+.*$/', '', (string) $clusterName);
// Ensure minimum meaningful length
if (strlen($clusterName) < 3) {
if (strlen((string) $clusterName) < 3) {
$clusterName = $cleaned; // Fall back to basic cleaning
}
return trim($clusterName);
return trim((string) $clusterName);
}
}

25
app/Services/SimpleFIN/Model/Account.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\SimpleFIN\Model;
use InvalidArgumentException;
use Carbon\Carbon;
/**
@ -33,13 +34,13 @@ use Carbon\Carbon;
class Account
{
private array $org;
private string $id;
private string $name;
private string $currency;
private string $balance;
private ?string $availableBalance;
private int $balanceDate;
private array $transactions;
private readonly string $id;
private readonly string $name;
private readonly string $currency;
private readonly string $balance;
private readonly ?string $availableBalance;
private readonly int $balanceDate;
private readonly array $transactions;
private array $extra;
public function __construct(array $data)
@ -183,17 +184,17 @@ class Account
foreach ($requiredFields as $field) {
if (!array_key_exists($field, $data)) {
throw new \InvalidArgumentException(sprintf('Missing required field: %s', $field));
throw new InvalidArgumentException(sprintf('Missing required field: %s', $field));
}
}
// Validate organization structure
if (!is_array($data['org'])) {
throw new \InvalidArgumentException('Organization must be an array');
throw new InvalidArgumentException('Organization must be an array');
}
if (!array_key_exists('sfin-url', $data['org']) && null !== $data['org']['sfin-url']) {
throw new \InvalidArgumentException('Organization must have sfin-url');
throw new InvalidArgumentException('Organization must have sfin-url');
}
@ -203,12 +204,12 @@ class Account
&& null !== $data['org']['domain']
&& null !== $data['org']['name']
) {
throw new \InvalidArgumentException('Organization must have either domain or name');
throw new InvalidArgumentException('Organization must have either domain or name');
}
// Validate balance-date is numeric
if (!is_numeric($data['balance-date'])) {
throw new \InvalidArgumentException('Balance date must be a numeric timestamp');
throw new InvalidArgumentException('Balance date must be a numeric timestamp');
}
}
}

25
app/Services/SimpleFIN/Model/Transaction.php

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace App\Services\SimpleFIN\Model;
use InvalidArgumentException;
use Carbon\Carbon;
/**
@ -32,12 +33,12 @@ use Carbon\Carbon;
*/
class Transaction
{
private string $id;
private int $posted;
private string $amount;
private string $description;
private ?int $transactedAt;
private bool $pending;
private readonly string $id;
private readonly int $posted;
private readonly string $amount;
private readonly string $description;
private readonly ?int $transactedAt;
private readonly bool $pending;
private array $extra;
public function __construct(array $data)
@ -110,7 +111,7 @@ class Transaction
public function getTransactedAtAsCarbon(): ?Carbon
{
return $this->transactedAt ? Carbon::createFromTimestamp($this->transactedAt) : null;
return $this->transactedAt !== null && $this->transactedAt !== 0 ? Carbon::createFromTimestamp($this->transactedAt) : null;
}
public function isPending(): bool
@ -172,28 +173,28 @@ class Transaction
foreach ($requiredFields as $field) {
if (!array_key_exists($field, $data)) {
throw new \InvalidArgumentException(sprintf('Missing required field: %s', $field));
throw new InvalidArgumentException(sprintf('Missing required field: %s', $field));
}
}
// Validate posted is numeric
if (!is_numeric($data['posted'])) {
throw new \InvalidArgumentException('Posted date must be a numeric timestamp');
throw new InvalidArgumentException('Posted date must be a numeric timestamp');
}
// Validate amount is numeric string
if (!is_numeric($data['amount'])) {
throw new \InvalidArgumentException('Amount must be a numeric string');
throw new InvalidArgumentException('Amount must be a numeric string');
}
// Validate transacted_at if present
if (isset($data['transacted_at']) && !is_numeric($data['transacted_at'])) {
throw new \InvalidArgumentException('Transacted at must be a numeric timestamp');
throw new InvalidArgumentException('Transacted at must be a numeric timestamp');
}
// Validate pending if present
if (isset($data['pending']) && !is_bool($data['pending'])) {
throw new \InvalidArgumentException('Pending must be a boolean');
throw new InvalidArgumentException('Pending must be a boolean');
}
}
}

2
app/Services/SimpleFIN/Request/SimpleFINRequest.php

@ -91,7 +91,7 @@ abstract class SimpleFINRequest
// Only add basic auth if userinfo is not already in the apiUrl
// and a token is provided. SimpleFIN typically uses userinfo in the Access URL.
if (false === strpos($this->apiUrl, '@') && '' !== $this->token) {
if (in_array(str_contains($this->apiUrl, '@'), [0, false], true) && '' !== $this->token) {
$options['auth'] = [$this->token, ''];
}

2
app/Services/SimpleFIN/Response/PostAccountResponse.php

@ -13,7 +13,7 @@ use GrumpyDictator\FFIIIApiSupport\Model\Account;
class PostAccountResponse extends Response
{
private ?Account $account;
private array $rawData;
private readonly array $rawData;
/**
* Response constructor.

4
app/Services/SimpleFIN/Response/SimpleFINResponse.php

@ -35,8 +35,8 @@ use Psr\Http\Message\ResponseInterface;
abstract class SimpleFINResponse implements SharedResponseInterface
{
private array $data = [];
private int $statusCode;
private string $rawBody;
private readonly int $statusCode;
private readonly string $rawBody;
public function __construct(ResponseInterface $response)
{

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save