diff --git a/app/Support/Authentication/RemoteUserGuard.php b/app/Support/Authentication/RemoteUserGuard.php index 80a5294d28..df4b583dc5 100644 --- a/app/Support/Authentication/RemoteUserGuard.php +++ b/app/Support/Authentication/RemoteUserGuard.php @@ -75,7 +75,7 @@ class RemoteUserGuard implements Guard } // test value for development - $userID = 'james@firefly-iii.org'; + // $userID = 'james@firefly-iii.org'; if (null === $userID || '' === $userID) { Log::error(sprintf('No user in header "%s".', $header)); diff --git a/app/Support/Twig/AmountFormat.php b/app/Support/Twig/AmountFormat.php deleted file mode 100644 index edc84fedc8..0000000000 --- a/app/Support/Twig/AmountFormat.php +++ /dev/null @@ -1,175 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig; - -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Models\Account as AccountModel; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Support\Facades\Amount; -use Illuminate\Support\Facades\Log; -use Override; -use Twig\Extension\AbstractExtension; -use Twig\TwigFilter; -use Twig\TwigFunction; - -/** - * Contains all amount formatting routines. - */ -class AmountFormat extends AbstractExtension -{ - #[Override] - public function getFilters(): array - { - return [$this->formatAmount(), $this->formatAmountPlain()]; - } - - #[Override] - public function getFunctions(): array - { - return [$this->formatAmountByAccount(), $this->formatAmountBySymbol(), $this->formatAmountByCurrency(), $this->formatAmountByCode()]; - } - - protected function formatAmount(): TwigFilter - { - return new TwigFilter( - 'formatAmount', - static function (string $string): string { - $currency = Amount::getPrimaryCurrency(); - - return Amount::formatAnything($currency, $string, true); - }, - ['is_safe' => ['html']] - ); - } - - /** - * Will format the amount by the currency related to the given account. - * - * TODO Remove me when v2 hits. - */ - protected function formatAmountByAccount(): TwigFunction - { - return new TwigFunction( - 'formatAmountByAccount', - static function (AccountModel $account, string $amount, ?bool $coloured = null): string { - $coloured ??= true; - - /** @var AccountRepositoryInterface $accountRepos */ - $accountRepos = app(AccountRepositoryInterface::class); - $currency = $accountRepos->getAccountCurrency($account) ?? Amount::getPrimaryCurrency(); - - return Amount::formatAnything($currency, $amount, $coloured); - }, - ['is_safe' => ['html']] - ); - } - - /** - * Use the code to format a currency. - */ - protected function formatAmountByCode(): TwigFunction - { - // formatAmountByCode - return new TwigFunction( - 'formatAmountByCode', - static function (string $amount, string $code, ?bool $coloured = null): string { - $coloured ??= true; - - try { - $currency = Amount::getTransactionCurrencyByCode($code); - } catch (FireflyException) { - Log::error(sprintf('Could not find currency with code "%s". Fallback to primary currency.', $code)); - $currency = Amount::getPrimaryCurrency(); - Log::error(sprintf('Fallback currency is "%s".', $currency->code)); - } - - return Amount::formatAnything($currency, $amount, $coloured); - }, - ['is_safe' => ['html']] - ); - } - - /** - * Will format the amount by the currency related to the given account. - */ - protected function formatAmountByCurrency(): TwigFunction - { - return new TwigFunction( - 'formatAmountByCurrency', - static function (TransactionCurrency $currency, string $amount, ?bool $coloured = null): string { - $coloured ??= true; - - return Amount::formatAnything($currency, $amount, $coloured); - }, - ['is_safe' => ['html']] - ); - } - - /** - * Will format the amount by the currency related to the given account. - */ - protected function formatAmountBySymbol(): TwigFunction - { - return new TwigFunction( - 'formatAmountBySymbol', - static function (string $amount, ?string $symbol = null, ?int $decimalPlaces = null, ?bool $coloured = null): string { - if (null === $symbol) { - $message = sprintf( - 'formatAmountBySymbol("%s", %s, %d, %s) was called without a symbol. Please browse to /flush to clear your cache.', - $amount, - var_export($symbol, true), - $decimalPlaces, - var_export($coloured, true) - ); - Log::error($message); - $currency = Amount::getPrimaryCurrency(); - } - if (null !== $symbol) { - $decimalPlaces ??= 2; - $coloured ??= true; - $currency = new TransactionCurrency(); - $currency->symbol = $symbol; - $currency->decimal_places = $decimalPlaces; - } - - return Amount::formatAnything($currency, $amount, $coloured); - }, - ['is_safe' => ['html']] - ); - } - - protected function formatAmountPlain(): TwigFilter - { - return new TwigFilter( - 'formatAmountPlain', - static function (string $string): string { - $currency = Amount::getPrimaryCurrency(); - - return Amount::formatAnything($currency, $string, false); - }, - ['is_safe' => ['html']] - ); - } -} diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php deleted file mode 100644 index e7b0ba8fff..0000000000 --- a/app/Support/Twig/General.php +++ /dev/null @@ -1,405 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig; - -use Carbon\Carbon; -use FireflyIII\Models\Account; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\User\UserRepositoryInterface; -use FireflyIII\Support\Facades\Amount; -use FireflyIII\Support\Facades\AppConfiguration; -use FireflyIII\Support\Facades\Steam; -use FireflyIII\Support\Search\OperatorQuerySearch; -use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Log; -use Illuminate\Support\Facades\Route; -use League\CommonMark\GithubFlavoredMarkdownConverter; -use Override; -use Twig\Extension\AbstractExtension; -use Twig\TwigFilter; -use Twig\TwigFunction; - -use function Safe\parse_url; - -/** - * Class TwigSupport. - */ -class General extends AbstractExtension -{ - #[Override] - public function getFilters(): array - { - return [$this->balance(), $this->formatFilesize(), $this->mimeIcon(), $this->markdown(), $this->phpHostName()]; - } - - #[Override] - public function getFunctions(): array - { - return [ - $this->phpdate(), - $this->activeRouteStrict(), - $this->activeRoutePartial(), - $this->activeRoutePartialObjectType(), - $this->menuOpenRoutePartial(), - $this->formatDate(), - $this->getMetaField(), - $this->hasRole(), - $this->getRootSearchOperator(), - $this->carbonize(), - $this->fireflyIIIConfig(), - $this->bccomp(), - ]; - } - - /** - * Will return "active" when a part of the route matches the argument. - * i.e. "accounts" will match "accounts.index". - */ - protected function activeRoutePartial(): TwigFunction - { - return new TwigFunction('activeRoutePartial', static function (): string { - $args = func_get_args(); - $route = $args[0]; // name of the route. - $name = Route::getCurrentRoute()->getName() ?? ''; - if (str_contains($name, $route)) { - return 'active'; - } - - return ''; - }); - } - - /** - * Will return "active" when a part of the route matches the argument. - * ie. "accounts" will match "accounts.index". - */ - /** - * This function will return "active" when the current route matches the first argument (even partly) - * but, the variable $objectType has been set and matches the second argument. - */ - protected function activeRoutePartialObjectType(): TwigFunction - { - return new TwigFunction( - 'activeRoutePartialObjectType', - static function (array $context): string { - [, $route, $objectType] = func_get_args(); - $activeObjectType = $context['objectType'] ?? false; - - if ($objectType === $activeObjectType && false !== stripos((string) Route::getCurrentRoute()->getName(), (string) $route)) { - return 'active'; - } - - return ''; - }, - ['needs_context' => true] - ); - } - - /** - * Will return "active" when the current route matches the given argument - * exactly. - */ - protected function activeRouteStrict(): TwigFunction - { - return new TwigFunction('activeRouteStrict', static function (): string { - $args = func_get_args(); - $route = $args[0]; // name of the route. - - if (\Route::getCurrentRoute()->getName() === $route) { - return 'active'; - } - - return ''; - }); - } - - /** - * Show account balance. Only used on the front page of Firefly III. - */ - protected function balance(): TwigFilter - { - return new TwigFilter('balance', static function (?Account $account): string { - if (!$account instanceof Account) { - return '0'; - } - - /** @var Carbon $date */ - $date = now(); - - // get the date from the current session. If it's in the future, keep `now()`. - /** @var Carbon $session */ - $session = clone session('end', today(config('app.timezone'))->endOfMonth()); - if ($session->lt($date)) { - $date = $session->copy(); - $date->endOfDay(); - } - Log::debug(sprintf('twig balance: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); - - // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized. - $info = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]; - // $info = Steam::finalAccountBalance($account, $date); - $currency = Steam::getAccountCurrency($account); - $primary = Amount::getPrimaryCurrency(); - $convertToPrimary = Amount::convertToPrimary(); - $usePrimary = $convertToPrimary && $primary->id !== $currency->id; - $currency ??= $primary; - $strings = []; - foreach ($info as $key => $balance) { - if ('balance' === $key) { - // balance in account currency. - if (!$usePrimary) { - $strings[] = Amount::formatAnything($currency, $balance, false); - } - - continue; - } - if ('pc_balance' === $key) { - // balance in primary currency. - if ($usePrimary) { - $strings[] = Amount::formatAnything($primary, $balance, false); - } - - continue; - } - // for multi currency accounts. - if ($usePrimary && $key !== $primary->code) { - $strings[] = Amount::formatAnything(Amount::getTransactionCurrencyByCode($key), $balance, false); - } - } - - return implode(', ', $strings); - - // return \FireflyIII\Support\Facades\Steam::balance($account, $date); - }); - } - - protected function bccomp(): TwigFunction - { - return new TwigFunction('bccomp', static function (string $left, string $right): int { - return bccomp($left, $right, 12); - }); - } - - protected function carbonize(): TwigFunction - { - return new TwigFunction('carbonize', static fn (string $date): Carbon => new Carbon($date, config('app.timezone'))); - } - - /** - * Formats a string as a thing by converting it to a Carbon first. - */ - protected function formatDate(): TwigFunction - { - return new TwigFunction('formatDate', static function (string $date, string $format): string { - $carbon = new Carbon($date); - - return $carbon->isoFormat($format); - }); - } - - /** - * Used to convert 1024 to 1kb etc. - */ - protected function formatFilesize(): TwigFilter - { - return new TwigFilter('filesize', static function (int $size): string { - // less than one GB, more than one MB - if ($size < (1024 * 1024 * 2014) && $size >= (1024 * 1024)) { - return round($size / (1024 * 1024), 2).' MB'; - } - - // less than one MB - if ($size < (1024 * 1024)) { - return round($size / 1024, 2).' KB'; - } - - return $size.' bytes'; - }); - } - - /** - * TODO Remove me when v2 hits. - */ - protected function getMetaField(): TwigFunction - { - return new TwigFunction('accountGetMetaField', static function (Account $account, string $field): string { - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $result = $repository->getMetaValue($account, $field); - if (null === $result) { - return ''; - } - - return $result; - }); - } - - protected function getRootSearchOperator(): TwigFunction - { - return new TwigFunction('getRootSearchOperator', static function (string $operator): string { - $result = OperatorQuerySearch::getRootOperator($operator); - - return str_replace('-', 'not_', $result); - }); - } - - /** - * Will return true if the user is of role X. - */ - protected function hasRole(): TwigFunction - { - return new TwigFunction('hasRole', static function (string $role): bool { - $repository = app(UserRepositoryInterface::class); - - return $repository->hasRole(auth()->user(), $role); - }); - } - - protected function markdown(): TwigFilter - { - return new TwigFilter( - 'markdown', - static function (string $text): string { - $converter = new GithubFlavoredMarkdownConverter(['allow_unsafe_links' => false, 'max_nesting_level' => 5, 'html_input' => 'escape']); - - return (string) $converter->convert($text); - }, - ['is_safe' => ['html']] - ); - } - - /** - * Will return "menu-open" when a part of the route matches the argument. - * ie. "accounts" will match "accounts.index". - */ - protected function menuOpenRoutePartial(): TwigFunction - { - return new TwigFunction('menuOpenRoutePartial', static function (): string { - $args = func_get_args(); - $route = $args[0]; // name of the route. - $name = Route::getCurrentRoute()->getName() ?? ''; - if (str_contains($name, $route)) { - return 'menu-open'; - } - - return ''; - }); - } - - /** - * Show icon with attachment. - * - * @SuppressWarnings("PHPMD.CyclomaticComplexity") - */ - protected function mimeIcon(): TwigFilter - { - return new TwigFilter( - 'mimeIcon', - static fn (string $string): string => match ($string) { - 'application/pdf' => 'fa-file-pdf-o', - 'image/webp', - 'image/png', - 'image/jpeg', - 'image/svg+xml', - 'image/heic', - 'image/heic-sequence', - 'application/vnd.oasis.opendocument.image' => 'fa-file-image-o', - 'application/msword', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', - 'application/x-iwork-pages-sffpages', - 'application/vnd.sun.xml.writer', - 'application/vnd.sun.xml.writer.template', - 'application/vnd.sun.xml.writer.global', - 'application/vnd.stardivision.writer', - 'application/vnd.stardivision.writer-global', - 'application/vnd.oasis.opendocument.text', - 'application/vnd.oasis.opendocument.text-template', - 'application/vnd.oasis.opendocument.text-web', - 'application/vnd.oasis.opendocument.text-master' => 'fa-file-word-o', - 'application/vnd.ms-excel', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', - 'application/vnd.sun.xml.calc', - 'application/vnd.sun.xml.calc.template', - 'application/vnd.stardivision.calc', - 'application/vnd.oasis.opendocument.spreadsheet', - 'application/vnd.oasis.opendocument.spreadsheet-template' => 'fa-file-excel-o', - 'application/vnd.ms-powerpoint', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'application/vnd.openxmlformats-officedocument.presentationml.template', - 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', - 'application/vnd.sun.xml.impress', - 'application/vnd.sun.xml.impress.template', - 'application/vnd.stardivision.impress', - 'application/vnd.oasis.opendocument.presentation', - 'application/vnd.oasis.opendocument.presentation-template' => 'fa-file-powerpoint-o', - 'application/vnd.sun.xml.draw', - 'application/vnd.sun.xml.draw.template', - 'application/vnd.stardivision.draw', - 'application/vnd.oasis.opendocument.chart' => 'bi-paint-bucket', - 'application/vnd.oasis.opendocument.graphics', - 'application/vnd.oasis.opendocument.graphics-template', - 'application/vnd.sun.xml.math', - 'application/vnd.stardivision.math', - 'application/vnd.oasis.opendocument.formula', - 'application/vnd.oasis.opendocument.database' => 'fa-calculator', - default => 'fa-file-o' - }, - ['is_safe' => ['html']] - ); - } - - /** - * Basic example thing for some views. - */ - protected function phpdate(): TwigFunction - { - return new TwigFunction('phpdate', date(...)); - } - - /** - * Show URL host name - */ - protected function phpHostName(): TwigFilter - { - return new TwigFilter('phphost', static function (string $string): string { - $proto = parse_url($string, PHP_URL_SCHEME); - $host = parse_url($string, PHP_URL_HOST); - if (is_array($host)) { - $host = implode(' ', $host); - } - if (is_array($proto)) { - $proto = implode(' ', $proto); - } - - return e(sprintf('%s://%s', $proto, $host)); - }); - } - - private function fireflyIIIConfig(): TwigFunction - { - return new TwigFunction('fireflyiiiconfig', static fn (string $string, mixed $default): mixed => AppConfiguration::get($string, $default)->data); - } -} diff --git a/app/Support/Twig/Rule.php b/app/Support/Twig/Rule.php deleted file mode 100644 index 33335e85df..0000000000 --- a/app/Support/Twig/Rule.php +++ /dev/null @@ -1,82 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig; - -use Override; -use Twig\Extension\AbstractExtension; -use Twig\TwigFunction; - -/** - * Class Rule. - */ -class Rule extends AbstractExtension -{ - public function allActionTriggers(): TwigFunction - { - return new TwigFunction('allRuleActions', static function (): array { - // array of valid values for actions - $ruleActions = array_keys(config('firefly.rule-actions')); - $possibleActions = []; - foreach ($ruleActions as $key) { - $possibleActions[$key] = (string) trans('firefly.rule_action_'.$key.'_choice'); - } - unset($ruleActions); - asort($possibleActions); - - return $possibleActions; - }); - } - - public function allJournalTriggers(): TwigFunction - { - return new TwigFunction('allJournalTriggers', static fn (): array => [ - 'store-journal' => (string) trans('firefly.rule_trigger_store_journal'), - 'update-journal' => (string) trans('firefly.rule_trigger_update_journal'), - 'manual-activation' => (string) trans('firefly.rule_trigger_manual'), - ]); - } - - public function allRuleTriggers(): TwigFunction - { - return new TwigFunction('allRuleTriggers', static function (): array { - $ruleTriggers = array_keys(config('search.operators')); - $possibleTriggers = []; - foreach ($ruleTriggers as $key) { - if ('user_action' !== $key) { - $possibleTriggers[$key] = (string) trans('firefly.rule_trigger_'.$key.'_choice'); - } - } - unset($ruleTriggers); - asort($possibleTriggers); - - return $possibleTriggers; - }); - } - - #[Override] - public function getFunctions(): array - { - return [$this->allJournalTriggers(), $this->allRuleTriggers(), $this->allActionTriggers()]; - } -} diff --git a/app/Support/Twig/TransactionGroupTwig.php b/app/Support/Twig/TransactionGroupTwig.php deleted file mode 100644 index 5082d3d4e3..0000000000 --- a/app/Support/Twig/TransactionGroupTwig.php +++ /dev/null @@ -1,258 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Support\Twig; - -use Carbon\Carbon; -use Carbon\CarbonInterface; -use FireflyIII\Enums\AccountTypeEnum; -use FireflyIII\Enums\TransactionTypeEnum; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionJournalMeta; -use FireflyIII\Support\Facades\Amount; -use Illuminate\Support\Facades\DB; -use Override; -use Twig\Extension\AbstractExtension; -use Twig\TwigFunction; - -use function Safe\json_decode; - -/** - * Class TransactionGroupTwig - */ -class TransactionGroupTwig extends AbstractExtension -{ - #[Override] - public function getFunctions(): array - { - return [$this->journalArrayAmount(), $this->journalObjectAmount(), $this->journalHasMeta(), $this->journalGetMetaDate(), $this->journalGetMetaField()]; - } - - /** - * Shows the amount for a single journal array. - */ - public function journalArrayAmount(): TwigFunction - { - return new TwigFunction( - 'journalArrayAmount', - function (array $array): string { - // if is not a withdrawal, amount positive. - $result = $this->normalJournalArrayAmount($array); - // now append foreign amount, if any. - if (null !== $array['foreign_amount']) { - $foreign = $this->foreignJournalArrayAmount($array); - $result = sprintf('%s (%s)', $result, $foreign); - } - - return $result; - }, - ['is_safe' => ['html']] - ); - } - - public function journalGetMetaDate(): TwigFunction - { - return new TwigFunction('journalGetMetaDate', static function (int $journalId, string $metaField): Carbon|CarbonInterface { - /** @var null|TransactionJournalMeta $entry */ - $entry = DB::table('journal_meta')->where('name', $metaField)->where('transaction_journal_id', $journalId)->whereNull('deleted_at')->first(); - if (null === $entry) { - return today(config('app.timezone')); - } - - return new Carbon(json_decode((string) $entry->data, false)); - }); - } - - public function journalGetMetaField(): TwigFunction - { - return new TwigFunction('journalGetMetaField', static function (int $journalId, string $metaField) { - /** @var null|TransactionJournalMeta $entry */ - $entry = DB::table('journal_meta')->where('name', $metaField)->where('transaction_journal_id', $journalId)->whereNull('deleted_at')->first(); - if (null === $entry) { - return ''; - } - - return json_decode((string) $entry->data, true); - }); - } - - public function journalHasMeta(): TwigFunction - { - return new TwigFunction('journalHasMeta', static function (int $journalId, string $metaField): bool { - $count = DB::table('journal_meta')->where('name', $metaField)->where('transaction_journal_id', $journalId)->whereNull('deleted_at')->count(); - - return 1 === $count; - }); - } - - /** - * Shows the amount for a single journal object. - */ - public function journalObjectAmount(): TwigFunction - { - return new TwigFunction( - 'journalObjectAmount', - function (TransactionJournal $journal): string { - $result = $this->normalJournalObjectAmount($journal); - // now append foreign amount, if any. - if ($this->journalObjectHasForeign($journal)) { - $foreign = $this->foreignJournalObjectAmount($journal); - $result = sprintf('%s (%s)', $result, $foreign); - } - - return $result; - }, - ['is_safe' => ['html']] - ); - } - - /** - * Generate foreign amount for transaction from a transaction group. - */ - private function foreignJournalArrayAmount(array $array): string - { - $type = $array['transaction_type_type'] ?? TransactionTypeEnum::WITHDRAWAL->value; - $amount = $array['foreign_amount'] ?? '0'; - $colored = true; - - $sourceType = $array['source_account_type'] ?? 'invalid'; - $amount = $this->signAmount($amount, $type, $sourceType); - - if (TransactionTypeEnum::TRANSFER->value === $type) { - $colored = false; - } - $result = Amount::formatFlat($array['foreign_currency_symbol'], (int) $array['foreign_currency_decimal_places'], $amount, $colored); - if (TransactionTypeEnum::TRANSFER->value === $type) { - return sprintf('%s', $result); - } - - return $result; - } - - /** - * Generate foreign amount for journal from a transaction group. - */ - private function foreignJournalObjectAmount(TransactionJournal $journal): string - { - $type = $journal->transactionType->type; - - /** @var Transaction $first */ - $first = $journal->transactions()->where('amount', '<', 0)->first(); - $currency = $first->foreignCurrency; - $amount = '' === $first->foreign_amount ? '0' : $first->foreign_amount; - $colored = true; - $sourceType = $first->account->accountType()->first()->type; - - $amount = $this->signAmount($amount, $type, $sourceType); - - if (TransactionTypeEnum::TRANSFER->value === $type) { - $colored = false; - } - $result = Amount::formatFlat($currency->symbol, $currency->decimal_places, $amount, $colored); - if (TransactionTypeEnum::TRANSFER->value === $type) { - return sprintf('%s', $result); - } - - return $result; - } - - private function journalObjectHasForeign(TransactionJournal $journal): bool - { - /** @var Transaction $first */ - $first = $journal->transactions()->where('amount', '<', 0)->first(); - - return '' !== $first->foreign_amount; - } - - /** - * Generate normal amount for transaction from a transaction group. - */ - private function normalJournalArrayAmount(array $array): string - { - $type = $array['transaction_type_type'] ?? TransactionTypeEnum::WITHDRAWAL->value; - $amount = $array['amount'] ?? '0'; - $colored = true; - $sourceType = $array['source_account_type'] ?? 'invalid'; - $amount = $this->signAmount($amount, $type, $sourceType); - - if (TransactionTypeEnum::TRANSFER->value === $type) { - $colored = false; - } - - $result = Amount::formatFlat($array['currency_symbol'], (int) $array['currency_decimal_places'], $amount, $colored); - if (TransactionTypeEnum::TRANSFER->value === $type) { - return sprintf('%s', $result); - } - - return $result; - } - - /** - * Generate normal amount for transaction from a transaction group. - */ - private function normalJournalObjectAmount(TransactionJournal $journal): string - { - $type = $journal->transactionType->type; - - /** @var Transaction $first */ - $first = $journal->transactions()->where('amount', '<', 0)->first(); - $currency = $journal->transactionCurrency; - $amount = $first->amount ?? '0'; - $colored = true; - $sourceType = $first->account->accountType()->first()->type; - - $amount = $this->signAmount($amount, $type, $sourceType); - - if (TransactionTypeEnum::TRANSFER->value === $type) { - $colored = false; - } - $result = Amount::formatFlat($currency->symbol, $currency->decimal_places, $amount, $colored); - if (TransactionTypeEnum::TRANSFER->value === $type) { - return sprintf('%s', $result); - } - - return $result; - } - - private function signAmount(string $amount, string $transactionType, string $sourceType): string - { - // withdrawals stay negative - if (TransactionTypeEnum::WITHDRAWAL->value !== $transactionType) { - $amount = bcmul($amount, '-1'); - } - - // opening balance and it comes from initial balance? its expense. - if (TransactionTypeEnum::OPENING_BALANCE->value === $transactionType && AccountTypeEnum::INITIAL_BALANCE->value !== $sourceType) { - $amount = bcmul($amount, '-1'); - } - - // reconciliation and it comes from reconciliation? - if (TransactionTypeEnum::RECONCILIATION->value === $transactionType && AccountTypeEnum::RECONCILIATION->value !== $sourceType) { - return bcmul($amount, '-1'); - } - - return $amount; - } -} diff --git a/app/Support/Twig/Translation.php b/app/Support/Twig/Translation.php deleted file mode 100644 index 91cbc7dd38..0000000000 --- a/app/Support/Twig/Translation.php +++ /dev/null @@ -1,76 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig; - -use Override; -use Twig\Extension\AbstractExtension; -use Twig\TwigFilter; -use Twig\TwigFunction; - -/** - * Class Budget. - */ -class Translation extends AbstractExtension -{ - #[Override] - public function getFilters(): array - { - return [new TwigFilter('_', static fn (string $name) => (string) trans(sprintf('firefly.%s', $name)), ['is_safe' => ['html']])]; - } - - #[Override] - public function getFunctions(): array - { - return [$this->journalLinkTranslation(), $this->laravelTranslation()]; - } - - public function journalLinkTranslation(): TwigFunction - { - return new TwigFunction( - 'journalLinkTranslation', - static function (string $direction, string $original): string { - $key = sprintf('firefly.%s_%s', $original, $direction); - $translation = (string) trans($key); - if ($key === $translation) { - return $original; - } - - return $translation; - }, - ['is_safe' => ['html']] - ); - } - - public function laravelTranslation(): TwigFunction - { - return new TwigFunction('__', static function (string $key): string { - $translation = (string) trans($key); - if ($key === $translation) { - return $key; - } - - return $translation; - }); - } -} diff --git a/composer.lock b/composer.lock index 2d271f86e8..f1727ddab6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4852f0fa1c992ff23e0205143b73db6f", + "content-hash": "3ee50c523acc2cf01e2ae0c7fc52acad", "packages": [ { "name": "bacon/bacon-qr-code", @@ -1885,16 +1885,16 @@ }, { "name": "laravel/framework", - "version": "v13.23.0", + "version": "v13.24.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "92a707229148e57f08a249211c8a5a194159c619" + "reference": "6d481710375d2aa67656922ef760cdd2b18bcfe0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/92a707229148e57f08a249211c8a5a194159c619", - "reference": "92a707229148e57f08a249211c8a5a194159c619", + "url": "https://api.github.com/repos/laravel/framework/zipball/6d481710375d2aa67656922ef760cdd2b18bcfe0", + "reference": "6d481710375d2aa67656922ef760cdd2b18bcfe0", "shasum": "" }, "require": { @@ -1914,7 +1914,7 @@ "guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/promises": "^2.0.3", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.3.0", + "laravel/prompts": "^0.3.11", "laravel/serializable-closure": "^2.0.10", "league/commonmark": "^2.8.1", "league/flysystem": "^3.25.1", @@ -2108,7 +2108,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-07-27T14:48:58+00:00" + "time": "2026-08-04T15:54:59+00:00" }, { "name": "laravel/passport", @@ -2187,16 +2187,16 @@ }, { "name": "laravel/prompts", - "version": "v0.3.21", + "version": "v0.3.22", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "7753c65c281c2550c7c183f14e18062073b7d821" + "reference": "02b89b39e8972a998db4d5d4ad4719239dd4aee4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/7753c65c281c2550c7c183f14e18062073b7d821", - "reference": "7753c65c281c2550c7c183f14e18062073b7d821", + "url": "https://api.github.com/repos/laravel/prompts/zipball/02b89b39e8972a998db4d5d4ad4719239dd4aee4", + "reference": "02b89b39e8972a998db4d5d4ad4719239dd4aee4", "shasum": "" }, "require": { @@ -2240,9 +2240,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.21" + "source": "https://github.com/laravel/prompts/tree/v0.3.22" }, - "time": "2026-06-26T00:11:25+00:00" + "time": "2026-08-04T14:50:50+00:00" }, { "name": "laravel/serializable-closure", @@ -2307,20 +2307,20 @@ }, { "name": "laravel/slack-notification-channel", - "version": "v3.9.0", + "version": "v3.10.0", "source": { "type": "git", "url": "https://github.com/laravel/slack-notification-channel.git", - "reference": "51a875b9a6dcae218957b9221abad9f5f1c3dca3" + "reference": "f5690359278aaebf5a0e5b07659caea95b6ded40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/51a875b9a6dcae218957b9221abad9f5f1c3dca3", - "reference": "51a875b9a6dcae218957b9221abad9f5f1c3dca3", + "url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/f5690359278aaebf5a0e5b07659caea95b6ded40", + "reference": "f5690359278aaebf5a0e5b07659caea95b6ded40", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^7.0", + "guzzlehttp/guzzle": "^7.0|^8.0", "illuminate/http": "^9.0|^10.0|^11.0|^12.0|^13.0", "illuminate/notifications": "^9.0|^10.0|^11.0|^12.0|^13.0", "illuminate/support": "^9.0|^10.0|^11.0|^12.0|^13.0", @@ -2366,9 +2366,9 @@ ], "support": { "issues": "https://github.com/laravel/slack-notification-channel/issues", - "source": "https://github.com/laravel/slack-notification-channel/tree/v3.9.0" + "source": "https://github.com/laravel/slack-notification-channel/tree/v3.10.0" }, - "time": "2026-06-27T02:02:38+00:00" + "time": "2026-07-24T20:53:54+00:00" }, { "name": "laravel/ui", @@ -5853,78 +5853,6 @@ }, "time": "2026-06-18T03:57:49+00:00" }, - { - "name": "rcrowe/twigbridge", - "version": "v0.14.7", - "source": { - "type": "git", - "url": "https://github.com/rcrowe/TwigBridge.git", - "reference": "03a767c8d5c1d74d5f14e9fc754619a271822663" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rcrowe/TwigBridge/zipball/03a767c8d5c1d74d5f14e9fc754619a271822663", - "reference": "03a767c8d5c1d74d5f14e9fc754619a271822663", - "shasum": "" - }, - "require": { - "illuminate/support": "^9|^10|^11|^12|^13", - "illuminate/view": "^9|^10|^11|^12|^13", - "php": "^8.1", - "twig/twig": "~3.21" - }, - "require-dev": { - "ext-json": "*", - "laravel/framework": "^9|^10|^11|^12|^13", - "mockery/mockery": "^1.3.1", - "phpunit/phpunit": "^8.5.8 || ^9.3.7 || ^10.0 || ^11.0 || ^12.0", - "squizlabs/php_codesniffer": "^3.6" - }, - "type": "library", - "extra": { - "laravel": { - "aliases": { - "Twig": "TwigBridge\\Facade\\Twig" - }, - "providers": [ - "TwigBridge\\ServiceProvider" - ] - }, - "branch-alias": { - "dev-master": "0.14-dev" - } - }, - "autoload": { - "psr-4": { - "TwigBridge\\": "src", - "TwigBridge\\Tests\\": "tests" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Rob Crowe", - "email": "hello@vivalacrowe.com" - }, - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "Adds the power of Twig to Laravel", - "keywords": [ - "laravel", - "twig" - ], - "support": { - "issues": "https://github.com/rcrowe/TwigBridge/issues", - "source": "https://github.com/rcrowe/TwigBridge/tree/v0.14.7" - }, - "time": "2026-03-20T16:59:18+00:00" - }, { "name": "spatie/backtrace", "version": "1.8.2", @@ -9858,86 +9786,6 @@ }, "time": "2025-12-02T11:56:42+00:00" }, - { - "name": "twig/twig", - "version": "v3.28.0", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/597c12ed286fb9d1701a36684ce6e0cbe28ebc8b", - "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b", - "shasum": "" - }, - "require": { - "php": ">=8.1.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" - }, - "require-dev": { - "php-cs-fixer/shim": "^3.0@stable", - "phpstan/phpstan": "^2.0@stable", - "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" - }, - "type": "library", - "autoload": { - "files": [ - "src/Resources/core.php", - "src/Resources/debug.php", - "src/Resources/escaper.php", - "src/Resources/string_loader.php" - ], - "psr-4": { - "Twig\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Twig Team", - "role": "Contributors" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "https://twig.symfony.com", - "keywords": [ - "templating" - ], - "support": { - "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.28.0" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" - } - ], - "time": "2026-07-03T20:44:34+00:00" - }, { "name": "vlucas/phpdotenv", "version": "v5.6.4", @@ -11637,11 +11485,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.2.7", + "version": "2.2.8", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921", - "reference": "692db47b9dddb0487934e5236e77d48594aef921", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e285254e60f33c21902efef4a926ca0987c06804", + "reference": "e285254e60f33c21902efef4a926ca0987c06804", "shasum": "" }, "require": { @@ -11697,7 +11545,7 @@ "type": "github" } ], - "time": "2026-07-29T17:39:32+00:00" + "time": "2026-08-04T22:21:45+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", diff --git a/config/twigbridge.php b/config/twigbridge.php deleted file mode 100644 index a3a6558817..0000000000 --- a/config/twigbridge.php +++ /dev/null @@ -1,291 +0,0 @@ -. - */ - -declare(strict_types=1); - -/* - * This file is part of the TwigBridge package. - * - * @copyright Robert Crowe - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use FireflyIII\Support\Twig\AmountFormat; -use FireflyIII\Support\Twig\General; -use FireflyIII\Support\Twig\Rule; -use FireflyIII\Support\Twig\TransactionGroupTwig; -use FireflyIII\Support\Twig\Translation; -use Illuminate\Contracts\Support\Htmlable; -use TwigBridge\Extension\Laravel\Auth; -use TwigBridge\Extension\Laravel\Config; -use TwigBridge\Extension\Laravel\Dump; -use TwigBridge\Extension\Laravel\Event; -use TwigBridge\Extension\Laravel\Input; -use TwigBridge\Extension\Laravel\Model; -use TwigBridge\Extension\Laravel\Session; -use TwigBridge\Extension\Laravel\Str; -use TwigBridge\Extension\Laravel\Translator; -use TwigBridge\Extension\Laravel\Url; -use TwigBridge\Extension\Loader\Facades; -use TwigBridge\Extension\Loader\Filters; -use TwigBridge\Extension\Loader\Functions; -use TwigBridge\Extension\Loader\Globals; - -// Configuration options for Twig. -return [ - 'twig' => [ - 'extension' => 'twig', - 'environment' => [ - 'debug' => env('APP_DEBUG', false), - 'charset' => 'utf-8', - 'cache' => null, - 'auto_reload' => true, - 'strict_variables' => false, - 'autoescape' => 'html', - 'optimizations' => -1, - ], - /* - |-------------------------------------------------------------------------- - | Safe Classes - |-------------------------------------------------------------------------- - | - | When set, the output of the `__string` method of the following classes will not be escaped. - | default: Laravel's Htmlable, which the HtmlString class implements. - | - */ - 'safe_classes' => [ - Htmlable::class => ['html'], - ], - - /* - |-------------------------------------------------------------------------- - | Global variables - |-------------------------------------------------------------------------- - | - | These will always be passed in and can be accessed as Twig variables. - | NOTE: these will be overwritten if you pass data into the view with the same key. - | - */ - 'globals' => [], - ], - - 'extensions' => [ - /* - |-------------------------------------------------------------------------- - | Extensions - |-------------------------------------------------------------------------- - | - | Enabled extensions. - | - | `Twig\Extension\DebugExtension` is enabled automatically if twig.debug is TRUE. - | - */ - 'enabled' => [ - Facades::class, - Filters::class, - Functions::class, - Event::class, - Globals::class, - Auth::class, - Config::class, - Dump::class, - Input::class, - Session::class, - Str::class, - Translator::class, - Url::class, - Model::class, - // Firefly III - AmountFormat::class, - General::class, - Rule::class, - TransactionGroupTwig::class, - Translation::class, - ], - - /* - |-------------------------------------------------------------------------- - | Facades - |-------------------------------------------------------------------------- - | - | Available facades. Access like `{{ Config.get('foo.bar') }}`. - | - | Each facade can take an optional array of options. To mark the whole facade - | as safe you can set the option `'is_safe' => true`. Setting the facade as - | safe means that any HTML returned will not be escaped. - | - | It is advisable to not set the whole facade as safe and instead mark the - | each appropriate method as safe for security reasons. You can do that with - | the following syntax: - | - | - | 'Form' => [ - | 'is_safe' => [ - | 'open' - | ] - | ] - | - | - | The values of the `is_safe` array must match the called method on the facade - | in order to be marked as safe. - | - */ - 'facades' => [ - 'Breadcrumbs' => [ - 'is_safe' => [ - 'render', - ], - ], - 'Session', - 'Route', - 'Auth', - 'Lang', - 'Preferences', - 'URL', - 'Steam', - 'Config', - 'Request', - 'Html', - 'ExpandedForm' => [ - 'is_safe' => [ - 'date', - 'text', - 'select', - 'balance', - 'optionsList', - 'checkbox', - 'amount', - 'tags', - 'integer', - 'textarea', - 'location', - 'file', - 'staticText', - 'password', - 'passwordWithValue', - 'nonSelectableAmount', - 'number', - 'amountNoCurrency', - 'percentage', - 'objectGroup', - ], - ], - 'AccountForm' => [ - 'is_safe' => [ - 'activeWithdrawalDestinations', - 'activeDepositDestinations', - 'assetAccountCheckList', - 'assetAccountList', - 'longAccountList', - 'assetLiabilityMultiAccountList', - ], - ], - 'CurrencyForm' => [ - 'is_safe' => [ - 'currencyList', - 'currencyListEmpty', - 'balanceAll', - ], - ], - 'PiggyBankForm' => [ - 'is_safe' => [ - 'piggyBankList', - ], - ], - 'RuleForm' => [ - 'is_safe' => [ - 'ruleGroupList', - 'ruleGroupListWithEmpty', - ], - ], - ], - - /* - |-------------------------------------------------------------------------- - | Functions - |-------------------------------------------------------------------------- - | - | Available functions. Access like `{{ secure_url(...) }}`. - | - | Each function can take an optional array of options. These options are - | passed directly to `Twig\TwigFunction`. - | - | So for example, to mark a function as safe you can do the following: - | - | - | 'link_to' => [ - | 'is_safe' => ['html'] - | ] - | - | - | The options array also takes a `callback` that allows you to name the - | function differently in your Twig templates than what it's actually called. - | - | - | 'link' => [ - | 'callback' => 'link_to' - | ] - | - | - */ - 'functions' => [ - 'elixir', - 'head', - 'last', - 'mix', - ], - - /* - |-------------------------------------------------------------------------- - | Filters - |-------------------------------------------------------------------------- - | - | Available filters. Access like `{{ variable|filter }}`. - | - | Each filter can take an optional array of options. These options are - | passed directly to `Twig\TwigFilter`. - | - | So for example, to mark a filter as safe you can do the following: - | - | - | 'studly_case' => [ - | 'is_safe' => ['html'] - | ] - | - | - | The options array also takes a `callback` that allows you to name the - | filter differently in your Twig templates than what is actually called. - | - | - | 'snake' => [ - | 'callback' => 'snake_case' - | ] - | - | - */ - 'filters' => [ - 'get' => 'data_get', - ], - ], -];