2015-02-07 08:23:44 +01:00
< ? php
2022-12-24 05:06:39 +01:00
2016-05-20 12:41:23 +02:00
/**
* Steam . php
2020-02-16 13:56:52 +01:00
* Copyright ( c ) 2019 james @ firefly - iii . org
2016-05-20 12:41:23 +02:00
*
2019-10-02 06:37:26 +02:00
* This file is part of Firefly III ( https :// github . com / firefly - iii ) .
2016-10-05 06:52:15 +02:00
*
2019-10-02 06:37:26 +02:00
* 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 .
2017-10-21 08:40:00 +02:00
*
2019-10-02 06:37:26 +02:00
* This program is distributed in the hope that it will be useful ,
2017-10-21 08:40:00 +02:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
2019-10-02 06:37:26 +02:00
* GNU Affero General Public License for more details .
2017-10-21 08:40:00 +02:00
*
2019-10-02 06:37:26 +02:00
* 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 />.
2016-05-20 12:41:23 +02:00
*/
2017-04-09 07:44:22 +02:00
declare ( strict_types = 1 );
2015-02-07 08:23:44 +01:00
namespace FireflyIII\Support ;
use Carbon\Carbon ;
2024-01-19 20:23:04 +01:00
use Carbon\Exceptions\InvalidFormatException ;
2021-09-18 10:20:19 +02:00
use FireflyIII\Exceptions\FireflyException ;
2015-02-07 08:23:44 +01:00
use FireflyIII\Models\Account ;
2015-07-10 07:39:59 +02:00
use FireflyIII\Models\Transaction ;
2020-03-20 04:37:45 +01:00
use FireflyIII\Models\TransactionCurrency ;
2018-04-21 20:28:41 +02:00
use FireflyIII\Repositories\Account\AccountRepositoryInterface ;
2023-07-25 09:01:44 +02:00
use FireflyIII\Support\Http\Api\ExchangeRateConverter ;
2017-06-05 22:11:54 +02:00
use Illuminate\Support\Collection ;
2023-11-08 20:06:23 +01:00
use Illuminate\Support\Facades\Log ;
2015-02-07 08:23:44 +01:00
/**
2017-11-15 12:25:49 +01:00
* Class Steam .
2015-02-07 08:23:44 +01:00
*/
class Steam
{
2024-12-22 08:43:12 +01:00
public function balanceByTransactions ( Account $account , Carbon $date , ? TransactionCurrency $currency ) : array
{
2024-12-22 16:41:55 +01:00
$cache = new CacheProperties ();
2024-12-22 08:43:12 +01:00
$cache -> addProperty ( $account -> id );
$cache -> addProperty ( 'balance-by-transactions' );
$cache -> addProperty ( $date );
$cache -> addProperty ( null !== $currency ? $currency -> id : 0 );
if ( $cache -> has ()) {
return $cache -> get ();
}
2024-12-22 16:41:55 +01:00
$query = $account -> transactions ()
-> leftJoin ( 'transaction_journals' , 'transactions.transaction_journal_id' , '=' , 'transaction_journals.id' )
-> orderBy ( 'transaction_journals.date' , 'desc' )
-> orderBy ( 'transaction_journals.order' , 'asc' )
-> orderBy ( 'transaction_journals.description' , 'desc' )
-> orderBy ( 'transactions.amount' , 'desc' );
2024-12-22 08:43:12 +01:00
if ( null !== $currency ) {
$query -> where ( 'transactions.transaction_currency_id' , $currency -> id );
$query -> limit ( 1 );
$result = $query -> get ([ 'transactions.transaction_currency_id' , 'transactions.balance_after' ]) -> first ();
$key = ( int ) $result -> transaction_currency_id ;
$return = [ $key => $result -> balance_after ];
$cache -> store ( $return );
return $return ;
}
$return = [];
$result = $query -> get ([ 'transactions.transaction_currency_id' , 'transactions.balance_after' ]);
foreach ( $result as $entry ) {
2024-12-22 16:41:55 +01:00
$key = ( int ) $entry -> transaction_currency_id ;
2024-12-22 08:43:12 +01:00
if ( array_key_exists ( $key , $return )) {
continue ;
}
$return [ $key ] = $entry -> balance_after ;
}
return $return ;
}
2024-12-22 16:41:55 +01:00
/**
* @ deprecated
*/
2024-12-22 08:43:12 +01:00
public function balanceConvertedIgnoreVirtual ( Account $account , Carbon $date , TransactionCurrency $currency ) : string
{
2024-12-22 16:41:55 +01:00
$balance = $this -> balanceConverted ( $account , $date , $currency );
$virtual = null === $account -> virtual_balance ? '0' : $account -> virtual_balance ;
2024-12-22 08:43:12 +01:00
// currency of account
2024-12-22 16:41:55 +01:00
$repository = app ( AccountRepositoryInterface :: class );
2024-12-22 08:43:12 +01:00
$repository -> setUser ( $account -> user );
$accountCurrency = $repository -> getAccountCurrency ( $account ) ? ? app ( 'amount' ) -> getDefaultCurrencyByUserGroup ( $account -> user -> userGroup );
if ( $accountCurrency -> id !== $currency -> id && 0 !== bccomp ( $virtual , '0' )) {
// convert amount to given currency.
Log :: debug ( sprintf ( 'Created new ExchangeRateConverter in %s' , __METHOD__ ));
$converter = new ExchangeRateConverter ();
$virtual = $converter -> convert ( $accountCurrency , $currency , $date , $virtual );
}
return bcsub ( $balance , $virtual );
}
2024-12-22 16:41:55 +01:00
//
2024-12-22 08:43:12 +01:00
/**
2024-12-22 16:41:55 +01:00
* @ throws FireflyException
*
* @ SuppressWarnings ( PHPMD . ExcessiveMethodLength )
* @ deprecated
2024-12-22 08:43:12 +01:00
* selection of transactions
* 1 : all normal transactions . No foreign currency info . In $currency . Need conversion .
* 2 : all normal transactions . No foreign currency info . In $native . Need NO conversion .
* 3 : all normal transactions . No foreign currency info . In neither currency . Need conversion .
* Then , select everything with foreign currency info :
* 4. All transactions with foreign currency info in $native . Normal currency value is ignored . Do not need
* conversion .
* 5. All transactions with foreign currency info NOT in $native , but currency info in $currency . Need conversion .
* 6. All transactions with foreign currency info NOT in $native , and currency info NOT in $currency . Need
* conversion .
*
* Gets balance at the end of current month by default . Returns the balance converted
* to the indicated currency ( $native ) .
*
*/
public function balanceConverted ( Account $account , Carbon $date , TransactionCurrency $native ) : string
{
Log :: debug ( sprintf ( 'Now in balanceConverted (%s) for account #%d, converting to %s' , $date -> format ( 'Y-m-d' ), $account -> id , $native -> code ));
2024-12-22 16:41:55 +01:00
$cache = new CacheProperties ();
2024-12-22 08:43:12 +01:00
$cache -> addProperty ( $account -> id );
2024-12-22 16:41:55 +01:00
$cache -> addProperty ( 'balance-converted' );
2024-12-22 08:43:12 +01:00
$cache -> addProperty ( $date );
$cache -> addProperty ( $native -> id );
if ( $cache -> has ()) {
Log :: debug ( 'Cached!' );
2024-12-22 16:41:55 +01:00
// return $cache->get();
2024-12-22 08:43:12 +01:00
}
/** @var AccountRepositoryInterface $repository */
$repository = app ( AccountRepositoryInterface :: class );
$currency = $repository -> getAccountCurrency ( $account );
$currency = null === $currency ? app ( 'amount' ) -> getDefaultCurrencyByUserGroup ( $account -> user -> userGroup ) : $currency ;
if ( $native -> id === $currency -> id ) {
Log :: debug ( 'No conversion necessary!' );
return $this -> balance ( $account , $date );
}
2024-12-22 16:41:55 +01:00
$new = [];
$existing = [];
$new [] = $account -> transactions () // 1
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> where ( 'transactions.transaction_currency_id' , $currency -> id )
-> whereNull ( 'transactions.foreign_currency_id' )
-> get ([ 'transaction_journals.date' , 'transactions.amount' ]) -> toArray ();
2024-12-22 08:43:12 +01:00
Log :: debug ( sprintf ( '%d transaction(s) in set #1' , count ( $new [ 0 ])));
$existing [] = $account -> transactions () // 2
2024-12-22 16:41:55 +01:00
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> where ( 'transactions.transaction_currency_id' , $native -> id )
-> whereNull ( 'transactions.foreign_currency_id' )
-> get ([ 'transactions.amount' ]) -> toArray ();
2024-12-22 08:43:12 +01:00
Log :: debug ( sprintf ( '%d transaction(s) in set #2' , count ( $existing [ 0 ])));
2024-12-22 16:41:55 +01:00
$new [] = $account -> transactions () // 3
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> where ( 'transactions.transaction_currency_id' , '!=' , $currency -> id )
-> where ( 'transactions.transaction_currency_id' , '!=' , $native -> id )
-> whereNull ( 'transactions.foreign_currency_id' )
-> get ([ 'transaction_journals.date' , 'transactions.amount' ]) -> toArray ();
2024-12-22 08:43:12 +01:00
Log :: debug ( sprintf ( '%d transactions in set #3' , count ( $new [ 1 ])));
$existing [] = $account -> transactions () // 4
2024-12-22 16:41:55 +01:00
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> where ( 'transactions.foreign_currency_id' , $native -> id )
-> whereNotNull ( 'transactions.foreign_amount' )
-> get ([ 'transactions.foreign_amount' ]) -> toArray ();
2024-12-22 08:43:12 +01:00
Log :: debug ( sprintf ( '%d transactions in set #4' , count ( $existing [ 1 ])));
2024-12-22 16:41:55 +01:00
$new [] = $account -> transactions () // 5
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> where ( 'transactions.transaction_currency_id' , $currency -> id )
-> where ( 'transactions.foreign_currency_id' , '!=' , $native -> id )
-> whereNotNull ( 'transactions.foreign_amount' )
-> get ([ 'transaction_journals.date' , 'transactions.amount' ]) -> toArray ();
2024-12-22 08:43:12 +01:00
Log :: debug ( sprintf ( '%d transactions in set #5' , count ( $new [ 2 ])));
2024-12-22 16:41:55 +01:00
$new [] = $account -> transactions () // 6
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> where ( 'transactions.transaction_currency_id' , '!=' , $currency -> id )
-> where ( 'transactions.foreign_currency_id' , '!=' , $native -> id )
-> whereNotNull ( 'transactions.foreign_amount' )
-> get ([ 'transaction_journals.date' , 'transactions.amount' ]) -> toArray ();
2024-12-22 08:43:12 +01:00
Log :: debug ( sprintf ( '%d transactions in set #6' , count ( $new [ 3 ])));
// process both sets of transactions. Of course, no need to convert set "existing".
2024-12-22 16:41:55 +01:00
$balance = $this -> sumTransactions ( $existing [ 0 ], 'amount' );
$balance = bcadd ( $balance , $this -> sumTransactions ( $existing [ 1 ], 'foreign_amount' ));
2024-12-22 08:43:12 +01:00
Log :: debug ( sprintf ( 'Balance from set #2 and #4 is %f' , $balance ));
// need to convert the others. All sets use the "amount" value as their base (that's easy)
// but we need to convert each transaction separately because the date difference may
// incur huge currency changes.
Log :: debug ( sprintf ( 'Created new ExchangeRateConverter in %s' , __METHOD__ ));
2024-12-22 16:41:55 +01:00
$start = clone $date ;
$end = clone $date ;
$converter = new ExchangeRateConverter ();
2024-12-22 08:43:12 +01:00
foreach ( $new as $set ) {
foreach ( $set as $transaction ) {
$currentDate = false ;
try {
$currentDate = Carbon :: parse ( $transaction [ 'date' ], config ( 'app.timezone' ));
} catch ( InvalidFormatException $e ) {
Log :: error ( sprintf ( 'Could not parse date "%s" in %s' , $transaction [ 'date' ], __METHOD__ ));
}
if ( false === $currentDate ) {
$currentDate = today ( config ( 'app.timezone' ));
}
if ( $currentDate -> lte ( $start )) {
$start = clone $currentDate ;
}
}
}
unset ( $currentDate );
$converter -> prepare ( $currency , $native , $start , $end );
foreach ( $new as $set ) {
foreach ( $set as $transaction ) {
2024-12-22 16:41:55 +01:00
$currentDate = false ;
2024-12-22 08:43:12 +01:00
try {
$currentDate = Carbon :: parse ( $transaction [ 'date' ], config ( 'app.timezone' ));
} catch ( InvalidFormatException $e ) {
Log :: error ( sprintf ( 'Could not parse date "%s" in %s' , $transaction [ 'date' ], __METHOD__ ));
}
if ( false === $currentDate ) {
$currentDate = today ( config ( 'app.timezone' ));
}
$rate = $converter -> getCurrencyRate ( $currency , $native , $currentDate );
$convertedAmount = bcmul ( $transaction [ 'amount' ], $rate );
$balance = bcadd ( $balance , $convertedAmount );
}
}
// add virtual balance (also needs conversion)
2024-12-22 16:41:55 +01:00
$virtual = null === $account -> virtual_balance ? '0' : $account -> virtual_balance ;
$virtual = $converter -> convert ( $currency , $native , $account -> created_at , $virtual );
$balance = bcadd ( $balance , $virtual );
2024-12-22 08:43:12 +01:00
$converter -> summarize ();
$cache -> store ( $balance );
$converter -> summarize ();
return $balance ;
}
2024-12-22 16:41:55 +01:00
/**
* Balance of an ( asset ) account in the user ' s native currency .
* Is calculated by summing up three numbers .
*
* - Transactions in foreign amount that happen to be in the native currency .
* - The rest of the transactions in the native currency .
* - Where both are zero or NULL , the normal amount converted ( and stored ! )
*
* @ SuppressWarnings ( PHPMD . ExcessiveMethodLength )
*/
public function balanceNative ( Account $account , Carbon $date ) : string
{
$native = app ( 'amount' ) -> getDefaultCurrency ();
Log :: debug ( sprintf ( 'Now in balanceNative (%s) for account #%d, converting to %s' , $date -> format ( 'Y-m-d' ), $account -> id , $native -> code ));
$cache = new CacheProperties ();
$cache -> addProperty ( $account -> id );
$cache -> addProperty ( 'balance-native' );
$cache -> addProperty ( $date );
$cache -> addProperty ( $native -> id );
if ( $cache -> has ()) {
$value = $cache -> get ();
Log :: debug ( sprintf ( 'Return cached value %s' , $value ));
return $value ;
}
/** @var AccountRepositoryInterface $repository */
$repository = app ( AccountRepositoryInterface :: class );
$currency = $repository -> getAccountCurrency ( $account );
$currency = null === $currency ? $native : $currency ;
if ( $native -> id === $currency -> id ) {
Log :: debug ( 'No conversion necessary!' );
return $this -> balance ( $account , $date );
}
$balance = '0' ;
// transactions in foreign amount that happen to be in the native currency:
$set = $account -> transactions ()
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> where ( 'transactions.foreign_currency_id' , $native -> id )
-> get ([ 'transactions.foreign_amount' ]) -> toArray ();
$balance = bcadd ( $this -> sumTransactions ( $set , 'foreign_amount' ), $balance );
Log :: debug ( sprintf ( 'The balance is now %s' , $balance ));
// transactions in the native amount.
$set = $account -> transactions ()
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> whereNull ( 'transactions.foreign_currency_id' )
-> whereNotNull ( 'transactions.native_amount' )
-> get ([ 'transactions.native_amount' ]) -> toArray ();
$balance = bcadd ( $this -> sumTransactions ( $set , 'native_amount' ), $balance );
Log :: debug ( sprintf ( 'The balance is now %s' , $balance ));
// transactions in the normal amount with no native amount set.
$set = $account -> transactions ()
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> whereNull ( 'transactions.foreign_currency_id' )
-> whereNull ( 'transactions.native_amount' )
-> get ([ 'transactions.amount' ]) -> toArray ();
$balance = bcadd ( $this -> sumTransactions ( $set , 'amount' ), $balance );
Log :: debug ( sprintf ( 'The balance is now %s' , $balance ));
// add virtual balance (also needs conversion)
$virtualNative = null === $account -> native_virtual_balance ? '0' : $account -> native_virtual_balance ;
$final = bcadd ( $virtualNative , $balance );
Log :: debug ( sprintf ( 'Final balance is %s' , $final ));
$cache -> store ( $final );
return $final ;
}
2024-12-22 08:43:12 +01:00
/**
* Gets balance at the end of current month by default
*
* @ throws FireflyException
*/
public function balance ( Account $account , Carbon $date , ? TransactionCurrency $currency = null ) : string
{
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
// abuse chart properties:
2024-12-22 16:41:55 +01:00
$cache = new CacheProperties ();
2024-12-22 08:43:12 +01:00
$cache -> addProperty ( $account -> id );
$cache -> addProperty ( 'balance' );
$cache -> addProperty ( $date );
$cache -> addProperty ( null !== $currency ? $currency -> id : 0 );
if ( $cache -> has ()) {
return $cache -> get ();
}
/** @var AccountRepositoryInterface $repository */
2024-12-22 16:41:55 +01:00
$repository = app ( AccountRepositoryInterface :: class );
2024-12-22 08:43:12 +01:00
if ( null === $currency ) {
$currency = $repository -> getAccountCurrency ( $account ) ? ? app ( 'amount' ) -> getDefaultCurrencyByUserGroup ( $account -> user -> userGroup );
}
// first part: get all balances in own currency:
2024-12-22 16:41:55 +01:00
$transactions = $account -> transactions ()
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> where ( 'transactions.transaction_currency_id' , $currency -> id )
-> get ([ 'transactions.amount' ]) -> toArray ();
$nativeBalance = $this -> sumTransactions ( $transactions , 'amount' );
2024-12-22 08:43:12 +01:00
// get all balances in foreign currency:
$transactions = $account -> transactions ()
2024-12-22 16:41:55 +01:00
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> where ( 'transactions.foreign_currency_id' , $currency -> id )
-> where ( 'transactions.transaction_currency_id' , '!=' , $currency -> id )
-> get ([ 'transactions.foreign_amount' ]) -> toArray ();
2024-12-22 08:43:12 +01:00
$foreignBalance = $this -> sumTransactions ( $transactions , 'foreign_amount' );
$balance = bcadd ( $nativeBalance , $foreignBalance );
$virtual = null === $account -> virtual_balance ? '0' : $account -> virtual_balance ;
$balance = bcadd ( $balance , $virtual );
$cache -> store ( $balance );
return $balance ;
}
2024-07-31 20:19:17 +02:00
/**
* @ deprecated
*/
2016-04-27 19:23:24 +02:00
public function balanceIgnoreVirtual ( Account $account , Carbon $date ) : string
2016-04-27 19:21:47 +02:00
{
2024-12-14 17:32:03 +01:00
throw new FireflyException ( 'Deprecated method balanceIgnoreVirtual.' );
2024-12-14 21:55:42 +01:00
2018-07-22 16:35:46 +02:00
/** @var AccountRepositoryInterface $repository */
2024-12-22 16:41:55 +01:00
$repository = app ( AccountRepositoryInterface :: class );
2018-07-22 16:35:46 +02:00
$repository -> setUser ( $account -> user );
2017-06-05 15:09:17 +02:00
2024-12-22 16:41:55 +01:00
$currencyId = ( int ) $repository -> getMetaValue ( $account , 'currency_id' );
$transactions = $account -> transactions ()
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> where ( 'transactions.transaction_currency_id' , $currencyId )
-> get ([ 'transactions.amount' ]) -> toArray ();
$nativeBalance = $this -> sumTransactions ( $transactions , 'amount' );
2016-04-27 19:21:47 +02:00
2017-06-05 15:09:17 +02:00
// get all balances in foreign currency:
2024-12-22 16:41:55 +01:00
$transactions = $account -> transactions ()
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> where ( 'transactions.foreign_currency_id' , $currencyId )
-> where ( 'transactions.transaction_currency_id' , '!=' , $currencyId )
-> get ([ 'transactions.foreign_amount' ]) -> toArray ();
2024-12-14 17:32:03 +01:00
2024-12-22 08:43:12 +01:00
$foreignBalance = $this -> sumTransactions ( $transactions , 'foreign_amount' );
return bcadd ( $nativeBalance , $foreignBalance );
2024-12-14 17:32:03 +01:00
}
2023-06-21 12:34:58 +02:00
public function sumTransactions ( array $transactions , string $key ) : string
{
$sum = '0' ;
2023-12-20 19:35:52 +01:00
2023-06-21 12:34:58 +02:00
/** @var array $transaction */
foreach ( $transactions as $transaction ) {
2024-07-31 13:09:55 +02:00
$value = ( string ) ( $transaction [ $key ] ? ? '0' );
2023-06-21 12:34:58 +02:00
$value = '' === $value ? '0' : $value ;
$sum = bcadd ( $sum , $value );
}
return $sum ;
}
2024-12-22 16:41:55 +01:00
2020-03-14 16:56:58 +01:00
/**
2017-11-15 12:25:49 +01:00
* Gets the balance for the given account during the whole range , using this format :.
2015-12-27 08:39:29 +01:00
*
* [ yyyy - mm - dd ] => 123 , 2
2015-12-29 22:48:55 +01:00
*
2021-09-18 10:21:29 +02:00
* @ throws FireflyException
2023-12-22 20:12:38 +01:00
*/
2020-03-20 04:37:45 +01:00
public function balanceInRange ( Account $account , Carbon $start , Carbon $end , ? TransactionCurrency $currency = null ) : array
2015-12-27 08:39:29 +01:00
{
2024-08-05 20:37:29 +02:00
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
2024-12-22 16:41:55 +01:00
$cache = new CacheProperties ();
2015-12-27 08:39:29 +01:00
$cache -> addProperty ( $account -> id );
$cache -> addProperty ( 'balance-in-range' );
2023-11-05 09:40:45 +01:00
$cache -> addProperty ( null !== $currency ? $currency -> id : 0 );
2015-12-27 08:39:29 +01:00
$cache -> addProperty ( $start );
$cache -> addProperty ( $end );
if ( $cache -> has ()) {
2021-09-18 10:26:12 +02:00
return $cache -> get ();
2015-12-27 08:39:29 +01:00
}
$start -> subDay ();
$end -> addDay ();
2024-12-22 16:41:55 +01:00
$balances = [];
$formatted = $start -> format ( 'Y-m-d' );
$startBalance = $this -> balance ( $account , $start , $currency );
2018-02-20 17:17:14 +01:00
2017-06-05 22:11:54 +02:00
$balances [ $formatted ] = $startBalance ;
2020-03-20 04:37:45 +01:00
if ( null === $currency ) {
$repository = app ( AccountRepositoryInterface :: class );
$repository -> setUser ( $account -> user );
2024-12-22 16:41:55 +01:00
$currency = $repository -> getAccountCurrency ( $account ) ? ? app ( 'amount' ) -> getDefaultCurrencyByUserGroup ( $account -> user -> userGroup );
2018-08-28 05:21:23 +02:00
}
2024-12-22 16:41:55 +01:00
$currencyId = $currency -> id ;
2018-08-28 05:21:23 +02:00
2015-12-27 08:39:29 +01:00
$start -> addDay ();
// query!
2024-12-22 16:41:55 +01:00
$set = $account -> transactions ()
-> leftJoin ( 'transaction_journals' , 'transactions.transaction_journal_id' , '=' , 'transaction_journals.id' )
-> where ( 'transaction_journals.date' , '>=' , $start -> format ( 'Y-m-d 00:00:00' ))
-> where ( 'transaction_journals.date' , '<=' , $end -> format ( 'Y-m-d 23:59:59' ))
-> groupBy ( 'transaction_journals.date' )
-> groupBy ( 'transactions.transaction_currency_id' )
-> groupBy ( 'transactions.foreign_currency_id' )
-> orderBy ( 'transaction_journals.date' , 'ASC' )
-> whereNull ( 'transaction_journals.deleted_at' )
-> get (
[ // @phpstan-ignore-line
'transaction_journals.date' ,
'transactions.transaction_currency_id' ,
\DB :: raw ( 'SUM(transactions.amount) AS modified' ),
'transactions.foreign_currency_id' ,
\DB :: raw ( 'SUM(transactions.foreign_amount) AS modified_foreign' ),
]
);
$currentBalance = $startBalance ;
2023-12-20 19:35:52 +01:00
2017-06-05 15:09:17 +02:00
/** @var Transaction $entry */
2015-12-27 08:39:29 +01:00
foreach ( $set as $entry ) {
2017-06-05 22:11:54 +02:00
// normal amount and foreign amount
2024-07-31 13:09:55 +02:00
$modified = ( string ) ( null === $entry -> modified ? '0' : $entry -> modified );
$foreignModified = ( string ) ( null === $entry -> modified_foreign ? '0' : $entry -> modified_foreign );
2017-06-05 22:11:54 +02:00
$amount = '0' ;
2024-07-31 13:09:55 +02:00
if ( $currencyId === ( int ) $entry -> transaction_currency_id || 0 === $currencyId ) {
2017-06-05 22:11:54 +02:00
// use normal amount:
$amount = $modified ;
}
2024-07-31 13:09:55 +02:00
if ( $currencyId === ( int ) $entry -> foreign_currency_id ) {
2017-08-10 20:48:29 +02:00
// use foreign amount:
2017-06-05 22:11:54 +02:00
$amount = $foreignModified ;
}
2023-12-22 06:14:14 +01:00
// Log::debug(sprintf('Trying to add %s and %s.', var_export($currentBalance, true), var_export($amount, true)));
2017-06-05 22:11:54 +02:00
$currentBalance = bcadd ( $currentBalance , $amount );
2020-07-11 08:16:31 +02:00
$carbon = new Carbon ( $entry -> date , config ( 'app.timezone' ));
2017-06-05 15:09:17 +02:00
$date = $carbon -> format ( 'Y-m-d' );
$balances [ $date ] = $currentBalance ;
2015-12-27 08:39:29 +01:00
}
$cache -> store ( $balances );
return $balances ;
}
2023-07-25 09:01:44 +02:00
/**
* @ throws FireflyException
2023-12-22 17:28:42 +01:00
*
* @ SuppressWarnings ( PHPMD . ExcessiveMethodLength )
2023-07-25 09:01:44 +02:00
*/
public function balanceInRangeConverted ( Account $account , Carbon $start , Carbon $end , TransactionCurrency $native ) : array
{
2024-08-12 05:07:37 +02:00
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
2024-12-22 16:41:55 +01:00
$cache = new CacheProperties ();
2023-07-25 09:01:44 +02:00
$cache -> addProperty ( $account -> id );
$cache -> addProperty ( 'balance-in-range-converted' );
$cache -> addProperty ( $native -> id );
$cache -> addProperty ( $start );
$cache -> addProperty ( $end );
if ( $cache -> has ()) {
2024-12-22 16:41:55 +01:00
//return $cache->get();
2023-07-25 09:01:44 +02:00
}
2023-12-29 09:01:42 +01:00
Log :: debug ( sprintf ( 'balanceInRangeConverted for account #%d to %s' , $account -> id , $native -> code ));
2023-07-25 09:01:44 +02:00
$start -> subDay ();
$end -> addDay ();
$balances = [];
$formatted = $start -> format ( 'Y-m-d' );
$currencies = [];
$startBalance = $this -> balanceConverted ( $account , $start , $native ); // already converted to native amount
$balances [ $formatted ] = $startBalance ;
2023-12-29 09:01:42 +01:00
Log :: debug ( sprintf ( 'Start balance on %s is %s' , $formatted , $startBalance ));
2023-12-29 12:00:15 +01:00
Log :: debug ( sprintf ( 'Created new ExchangeRateConverter in %s' , __METHOD__ ));
2024-12-22 16:41:55 +01:00
$converter = new ExchangeRateConverter ();
2023-07-25 09:01:44 +02:00
// not sure why this is happening:
$start -> addDay ();
// grab all transactions between start and end:
2024-12-22 16:41:55 +01:00
$set = $account -> transactions ()
-> leftJoin ( 'transaction_journals' , 'transactions.transaction_journal_id' , '=' , 'transaction_journals.id' )
-> where ( 'transaction_journals.date' , '>=' , $start -> format ( 'Y-m-d 00:00:00' ))
-> where ( 'transaction_journals.date' , '<=' , $end -> format ( 'Y-m-d 23:59:59' ))
-> orderBy ( 'transaction_journals.date' , 'ASC' )
-> whereNull ( 'transaction_journals.deleted_at' )
-> get (
[
'transaction_journals.date' ,
'transactions.transaction_currency_id' ,
'transactions.amount' ,
'transactions.foreign_currency_id' ,
'transactions.foreign_amount' ,
]
) -> toArray ();
2023-07-25 09:01:44 +02:00
// loop the set and convert if necessary:
2024-12-22 16:41:55 +01:00
$currentBalance = $startBalance ;
2023-12-20 19:35:52 +01:00
2023-07-25 09:01:44 +02:00
/** @var Transaction $transaction */
foreach ( $set as $transaction ) {
2024-12-22 16:41:55 +01:00
$day = false ;
2024-01-20 08:07:27 +01:00
try {
$day = Carbon :: parse ( $transaction [ 'date' ], config ( 'app.timezone' ));
} catch ( InvalidFormatException $e ) {
Log :: error ( sprintf ( 'Could not parse date "%s" in %s: %s' , $transaction [ 'date' ], __METHOD__ , $e -> getMessage ()));
}
2023-11-28 05:31:26 +01:00
if ( false === $day ) {
$day = today ( config ( 'app.timezone' ));
}
2024-12-22 16:41:55 +01:00
$format = $day -> format ( 'Y-m-d' );
2023-07-25 09:01:44 +02:00
// if the transaction is in the expected currency, change nothing.
2024-07-31 13:09:55 +02:00
if (( int ) $transaction [ 'transaction_currency_id' ] === $native -> id ) {
2023-07-25 09:01:44 +02:00
// change the current balance, set it to today, continue the loop.
$currentBalance = bcadd ( $currentBalance , $transaction [ 'amount' ]);
$balances [ $format ] = $currentBalance ;
2023-12-29 09:01:42 +01:00
Log :: debug ( sprintf ( '%s: transaction in %s, new balance is %s.' , $format , $native -> code , $currentBalance ));
2023-12-20 19:35:52 +01:00
2023-07-25 09:01:44 +02:00
continue ;
}
// if foreign currency is in the expected currency, do nothing:
2024-07-31 13:09:55 +02:00
if (( int ) $transaction [ 'foreign_currency_id' ] === $native -> id ) {
2023-07-25 09:01:44 +02:00
$currentBalance = bcadd ( $currentBalance , $transaction [ 'foreign_amount' ]);
$balances [ $format ] = $currentBalance ;
2023-12-29 09:01:42 +01:00
Log :: debug ( sprintf ( '%s: transaction in %s (foreign), new balance is %s.' , $format , $native -> code , $currentBalance ));
2023-12-20 19:35:52 +01:00
2023-07-25 09:01:44 +02:00
continue ;
}
// otherwise, convert 'amount' to the necessary currency:
2024-07-31 13:09:55 +02:00
$currencyId = ( int ) $transaction [ 'transaction_currency_id' ];
2023-11-04 07:18:03 +01:00
$currency = $currencies [ $currencyId ] ? ? TransactionCurrency :: find ( $currencyId );
$currencies [ $currencyId ] = $currency ;
2023-07-25 09:01:44 +02:00
2024-12-22 16:41:55 +01:00
$rate = $converter -> getCurrencyRate ( $currency , $native , $day );
$convertedAmount = bcmul ( $transaction [ 'amount' ], $rate );
$currentBalance = bcadd ( $currentBalance , $convertedAmount );
$balances [ $format ] = $currentBalance ;
2023-07-25 09:01:44 +02:00
2023-12-29 09:01:42 +01:00
Log :: debug ( sprintf (
2024-12-22 16:41:55 +01:00
'%s: transaction in %s(!). Conversion rate is %s. %s %s = %s %s' ,
$format ,
$currency -> code ,
$rate ,
$currency -> code ,
$transaction [ 'amount' ],
$native -> code ,
$convertedAmount
));
2023-07-25 09:01:44 +02:00
}
$cache -> store ( $balances );
2023-12-22 06:14:14 +01:00
$converter -> summarize ();
2023-07-25 09:01:44 +02:00
return $balances ;
}
2024-12-22 16:41:55 +01:00
/**
* @ throws FireflyException
*
* @ SuppressWarnings ( PHPMD . ExcessiveMethodLength )
*/
public function balanceInRangeNative ( Account $account , Carbon $start , Carbon $end ) : array
{
$native = app ( 'amount' ) -> getDefaultCurrency ();
Log :: debug ( sprintf ( 'balanceInRangeNative for account #%d, to %s' , $account -> id , $native -> code ));
$repository = app ( AccountRepositoryInterface :: class );
$repository -> setUser ( $account -> user );
$currency = $repository -> getAccountCurrency ( $account ) ? ? $native ;
if ( $native -> id === $currency -> id ) {
Log :: debug ( 'No need to get native balance, account prefers this currency.' );
return $this -> balanceInRange ( $account , $start , $end , $native );
}
$cache = new CacheProperties ();
$cache -> addProperty ( $account -> id );
$cache -> addProperty ( 'balance-in-range-native' );
$cache -> addProperty ( $native -> id );
$cache -> addProperty ( $start );
$cache -> addProperty ( $end );
if ( $cache -> has ()) {
$value = $cache -> get ();
Log :: debug ( 'Return cached values' );
//return $value;
}
$start -> subDay ();
$end -> addDay ();
$balances = [];
$formatted = $start -> format ( 'Y-m-d' );
$startBalance = $this -> balanceNative ( $account , $start ); // already converted to native amount
$balances [ $formatted ] = $startBalance ;
Log :: debug ( sprintf ( 'Start balance on %s is %s' , $formatted , $startBalance ));
// not sure why this is happening:
$start -> addDay ();
// grab all transactions between start and end:
$set = $account -> transactions ()
-> leftJoin ( 'transaction_journals' , 'transactions.transaction_journal_id' , '=' , 'transaction_journals.id' )
-> where ( 'transaction_journals.date' , '>=' , $start -> format ( 'Y-m-d 00:00:00' ))
-> where ( 'transaction_journals.date' , '<=' , $end -> format ( 'Y-m-d 23:59:59' ))
-> orderBy ( 'transaction_journals.date' , 'ASC' )
-> whereNull ( 'transaction_journals.deleted_at' )
-> get (
[
'transaction_journals.date' ,
'transactions.transaction_currency_id' ,
'transactions.amount' ,
'transactions.native_amount' ,
'transactions.foreign_currency_id' ,
'transactions.foreign_amount' ,
]
) -> toArray ();
// loop the set
$currentBalance = $startBalance ;
/** @var Transaction $transaction */
foreach ( $set as $transaction ) {
$day = false ;
try {
$day = Carbon :: parse ( $transaction [ 'date' ], config ( 'app.timezone' ));
} catch ( InvalidFormatException $e ) {
Log :: error ( sprintf ( 'Could not parse date "%s" in %s: %s' , $transaction [ 'date' ], __METHOD__ , $e -> getMessage ()));
}
if ( false === $day ) {
$day = today ( config ( 'app.timezone' ));
}
$format = $day -> format ( 'Y-m-d' );
// first, check the native amount. If not NULL, add it, and continue.
if ( null !== $transaction [ 'native_amount' ]) {
$currentBalance = bcadd ( $currentBalance , $transaction [ 'native_amount' ]);
$balances [ $format ] = $currentBalance ;
Log :: debug ( sprintf ( '%s: transaction in %s (native), new balance is %s.' , $format , $native -> code , $currentBalance ));
continue ;
}
// if the foreign amount is in the native currency, add it and continue.
if (( int ) $transaction [ 'foreign_currency_id' ] === $native -> id ) {
$currentBalance = bcadd ( $currentBalance , $transaction [ 'foreign_amount' ]);
$balances [ $format ] = $currentBalance ;
Log :: debug ( sprintf ( '%s: transaction in %s (foreign), new balance is %s.' , $format , $native -> code , $currentBalance ));
continue ;
}
// anything else is added as is. Warning in logs.
Log :: warning ( sprintf ( 'Account "%s" (#%d) has transactions that are not converted in the native currency. Please run "php artisan firefly-iii:recalculate-native-amounts"' , $account -> name , $account -> id ));
$currentBalance = bcadd ( $currentBalance , $transaction [ 'amount' ]);
$balances [ $format ] = $currentBalance ;
Log :: debug ( sprintf ( '%s: transaction BAD currency, new balance is %s.' , $format , $currentBalance ));
}
$cache -> store ( $balances );
return $balances ;
}
2015-07-10 07:39:59 +02:00
/**
2020-07-28 06:31:56 +02:00
* This method always ignores the virtual balance .
2015-07-10 07:39:59 +02:00
*
2023-02-22 18:03:31 +01:00
* @ throws FireflyException
2015-07-10 07:39:59 +02:00
*/
2020-07-28 06:31:56 +02:00
public function balancesByAccounts ( Collection $accounts , Carbon $date ) : array
2015-07-10 07:39:59 +02:00
{
2024-08-12 05:07:37 +02:00
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
2024-12-22 16:41:55 +01:00
$ids = $accounts -> pluck ( 'id' ) -> toArray ();
2017-01-19 21:54:27 +01:00
// cache this property.
2024-12-22 16:41:55 +01:00
$cache = new CacheProperties ();
2015-07-10 07:39:59 +02:00
$cache -> addProperty ( $ids );
$cache -> addProperty ( 'balances' );
$cache -> addProperty ( $date );
if ( $cache -> has ()) {
2021-09-18 10:26:12 +02:00
return $cache -> get ();
2015-07-10 07:39:59 +02:00
}
2017-06-05 22:11:54 +02:00
// need to do this per account.
2015-07-10 07:39:59 +02:00
$result = [];
2023-12-20 19:35:52 +01:00
2017-06-05 22:11:54 +02:00
/** @var Account $account */
foreach ( $accounts as $account ) {
$result [ $account -> id ] = $this -> balance ( $account , $date );
2015-07-10 07:39:59 +02:00
}
$cache -> store ( $result );
2018-08-27 08:08:51 +02:00
return $result ;
}
2023-08-06 11:22:36 +02:00
/**
* This method always ignores the virtual balance .
*
* @ throws FireflyException
*/
public function balancesByAccountsConverted ( Collection $accounts , Carbon $date ) : array
{
2024-08-12 05:07:37 +02:00
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
2024-12-22 16:41:55 +01:00
$ids = $accounts -> pluck ( 'id' ) -> toArray ();
2023-08-06 11:22:36 +02:00
// cache this property.
2024-12-22 16:41:55 +01:00
$cache = new CacheProperties ();
2023-08-06 11:22:36 +02:00
$cache -> addProperty ( $ids );
$cache -> addProperty ( 'balances-converted' );
$cache -> addProperty ( $date );
if ( $cache -> has ()) {
2024-08-05 05:06:53 +02:00
return $cache -> get ();
2023-08-06 11:22:36 +02:00
}
// need to do this per account.
$result = [];
2023-12-20 19:35:52 +01:00
2023-08-06 11:22:36 +02:00
/** @var Account $account */
foreach ( $accounts as $account ) {
2023-10-29 05:54:01 +01:00
$default = app ( 'amount' ) -> getDefaultCurrencyByUserGroup ( $account -> user -> userGroup );
2023-11-05 19:41:37 +01:00
$result [ $account -> id ]
2023-08-06 11:22:36 +02:00
= [
2024-12-22 16:41:55 +01:00
'balance' => $this -> balance ( $account , $date ),
'native_balance' => $this -> balanceConverted ( $account , $date , $default ),
];
2023-08-06 11:22:36 +02:00
}
$cache -> store ( $result );
return $result ;
}
2018-08-27 08:08:51 +02:00
/**
2020-07-28 06:31:56 +02:00
* Same as above , but also groups per currency .
2018-08-27 08:08:51 +02:00
*/
2020-07-28 06:31:56 +02:00
public function balancesPerCurrencyByAccounts ( Collection $accounts , Carbon $date ) : array
2018-08-27 08:08:51 +02:00
{
2024-08-12 05:07:37 +02:00
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
2024-12-22 16:41:55 +01:00
$ids = $accounts -> pluck ( 'id' ) -> toArray ();
2018-08-27 08:08:51 +02:00
// cache this property.
2024-12-22 16:41:55 +01:00
$cache = new CacheProperties ();
2018-08-27 08:08:51 +02:00
$cache -> addProperty ( $ids );
$cache -> addProperty ( 'balances-per-currency' );
$cache -> addProperty ( $date );
if ( $cache -> has ()) {
2021-09-18 10:26:12 +02:00
return $cache -> get ();
2018-08-27 08:08:51 +02:00
}
// need to do this per account.
$result = [];
2023-12-20 19:35:52 +01:00
2018-08-27 08:08:51 +02:00
/** @var Account $account */
foreach ( $accounts as $account ) {
$result [ $account -> id ] = $this -> balancePerCurrency ( $account , $date );
}
$cache -> store ( $result );
2015-07-10 07:39:59 +02:00
return $result ;
}
2023-06-21 12:34:58 +02:00
public function balancePerCurrency ( Account $account , Carbon $date ) : array
{
2024-08-05 20:37:29 +02:00
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
2023-06-21 12:34:58 +02:00
// abuse chart properties:
2024-12-22 16:41:55 +01:00
$cache = new CacheProperties ();
2023-06-21 12:34:58 +02:00
$cache -> addProperty ( $account -> id );
$cache -> addProperty ( 'balance-per-currency' );
$cache -> addProperty ( $date );
if ( $cache -> has ()) {
return $cache -> get ();
}
$query = $account -> transactions ()
2024-12-22 16:41:55 +01:00
-> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' )
-> where ( 'transaction_journals.date' , '<=' , $date -> format ( 'Y-m-d 23:59:59' ))
-> groupBy ( 'transactions.transaction_currency_id' );
2023-12-20 19:35:52 +01:00
$balances = $query -> get ([ 'transactions.transaction_currency_id' , \DB :: raw ( 'SUM(transactions.amount) as sum_for_currency' )]); // @phpstan-ignore-line
2023-12-22 06:14:14 +01:00
$return = [];
2023-12-20 19:35:52 +01:00
/** @var \stdClass $entry */
2023-06-21 12:34:58 +02:00
foreach ( $balances as $entry ) {
2024-07-31 13:09:55 +02:00
$return [( int ) $entry -> transaction_currency_id ] = ( string ) $entry -> sum_for_currency ;
2023-06-21 12:34:58 +02:00
}
$cache -> store ( $return );
return $return ;
}
2022-12-29 19:42:26 +01:00
/**
* https :// stackoverflow . com / questions / 1642614 / how - to - ceil - floor - and - round - bcmath - numbers
*/
public function bcround ( ? string $number , int $precision = 0 ) : string
{
if ( null === $number ) {
return '0' ;
}
if ( '' === trim ( $number )) {
return '0' ;
}
// if the number contains "E", it's in scientific notation, so we need to convert it to a normal number first.
if ( false !== stripos ( $number , 'e' )) {
2023-05-15 06:33:30 +02:00
$number = sprintf ( '%.12f' , $number );
2022-12-29 19:42:26 +01:00
}
2023-12-29 09:01:42 +01:00
// Log::debug(sprintf('Trying bcround("%s",%d)', $number, $precision));
2022-12-29 19:42:26 +01:00
if ( str_contains ( $number , '.' )) {
2023-12-20 19:35:52 +01:00
if ( '-' !== $number [ 0 ]) {
2024-12-22 16:41:55 +01:00
return bcadd ( $number , '0.' . str_repeat ( '0' , $precision ) . '5' , $precision );
2022-12-29 19:42:26 +01:00
}
2024-12-22 16:41:55 +01:00
return bcsub ( $number , '0.' . str_repeat ( '0' , $precision ) . '5' , $precision );
2022-12-29 19:42:26 +01:00
}
return $number ;
}
2022-03-29 14:59:58 +02:00
public function filterSpaces ( string $string ) : string
{
$search = [
" \ u { 0001} " , // start of heading
" \ u { 0002} " , // start of text
" \ u { 0003} " , // end of text
" \ u { 0004} " , // end of transmission
" \ u { 0005} " , // enquiry
" \ u { 0006} " , // ACK
" \ u { 0007} " , // BEL
" \ u { 0008} " , // backspace
" \ u { 000E} " , // shift out
" \ u { 000F} " , // shift in
" \ u { 0010} " , // data link escape
" \ u { 0011} " , // DC1
" \ u { 0012} " , // DC2
" \ u { 0013} " , // DC3
" \ u { 0014} " , // DC4
" \ u { 0015} " , // NAK
" \ u { 0016} " , // SYN
" \ u { 0017} " , // ETB
" \ u { 0018} " , // CAN
" \ u { 0019} " , // EM
" \ u { 001A} " , // SUB
" \ u { 001B} " , // escape
" \ u { 001C} " , // file separator
" \ u { 001D} " , // group separator
" \ u { 001E} " , // record separator
" \ u { 001F} " , // unit separator
" \ u { 007F} " , // DEL
" \ u { 00A0} " , // non-breaking space
" \ u { 1680} " , // ogham space mark
" \ u { 180E} " , // mongolian vowel separator
" \ u { 2000} " , // en quad
" \ u { 2001} " , // em quad
" \ u { 2002} " , // en space
" \ u { 2003} " , // em space
" \ u { 2004} " , // three-per-em space
" \ u { 2005} " , // four-per-em space
" \ u { 2006} " , // six-per-em space
" \ u { 2007} " , // figure space
" \ u { 2008} " , // punctuation space
" \ u { 2009} " , // thin space
" \ u { 200A} " , // hair space
" \ u { 200B} " , // zero width space
" \ u { 202F} " , // narrow no-break space
" \ u { 3000} " , // ideographic space
" \ u { FEFF} " , // zero width no -break space
2024-05-18 06:42:09 +02:00
" \x20 " , // plain old normal space,
2024-05-18 06:49:29 +02:00
' ' ,
2022-03-29 14:59:58 +02:00
];
2022-09-24 17:43:49 +02:00
// clear zalgo text
2023-03-08 20:40:51 +01:00
$string = preg_replace ( '/(\pM{2})\pM+/u' , '\1' , $string );
2024-05-18 06:42:09 +02:00
$string = preg_replace ( '/\s+/' , '' , $string );
2022-09-24 17:43:49 +02:00
2022-03-29 14:59:58 +02:00
return str_replace ( $search , '' , $string );
}
2023-05-29 13:56:55 +02:00
/**
2023-02-22 18:14:14 +01:00
* @ throws FireflyException
*/
public function getHostName ( string $ipAddress ) : string
{
2024-12-22 06:33:37 +01:00
$host = '' ;
2023-02-22 18:14:14 +01:00
try {
$hostName = gethostbyaddr ( $ipAddress );
2024-12-22 06:33:37 +01:00
} catch ( \Exception $e ) {
app ( 'log' ) -> error ( $e -> getMessage ());
$hostName = $ipAddress ;
}
if ( '' !== ( string ) $hostName && $hostName !== $ipAddress ) {
$host = $hostName ;
2023-02-22 18:14:14 +01:00
}
2023-12-20 19:35:52 +01:00
2024-12-22 06:33:37 +01:00
return ( string ) $host ;
2023-02-22 18:14:14 +01:00
}
2016-04-05 22:00:03 +02:00
public function getLastActivities ( array $accounts ) : array
2016-01-20 15:23:36 +01:00
{
$list = [];
2024-12-22 16:41:55 +01:00
$set = auth () -> user () -> transactions ()
-> whereIn ( 'transactions.account_id' , $accounts )
-> groupBy ([ 'transactions.account_id' , 'transaction_journals.user_id' ])
-> get ([ 'transactions.account_id' , \DB :: raw ( 'MAX(transaction_journals.date) AS max_date' )]) // @phpstan-ignore-line
2023-12-20 19:35:52 +01:00
;
2016-01-20 15:23:36 +01:00
2023-11-05 19:41:37 +01:00
/** @var Transaction $entry */
2016-01-20 15:23:36 +01:00
foreach ( $set as $entry ) {
2024-12-22 16:41:55 +01:00
$date = new Carbon ( $entry -> max_date , config ( 'app.timezone' ));
2020-07-17 18:52:34 +02:00
$date -> setTimezone ( config ( 'app.timezone' ));
2023-11-05 19:41:37 +01:00
$list [ $entry -> account_id ] = $date ;
2016-01-20 15:23:36 +01:00
}
return $list ;
}
2021-09-18 10:26:12 +02:00
/**
* Get user ' s locale .
*/
public function getLocale () : string // get preference
{
$locale = app ( 'preferences' ) -> get ( 'locale' , config ( 'firefly.default_locale' , 'equal' )) -> data ;
2023-11-28 05:31:26 +01:00
if ( is_array ( $locale )) {
$locale = 'equal' ;
}
2021-09-18 10:26:12 +02:00
if ( 'equal' === $locale ) {
$locale = $this -> getLanguage ();
}
2024-07-31 13:09:55 +02:00
$locale = ( string ) $locale ;
2023-11-28 05:31:26 +01:00
2021-09-18 10:26:12 +02:00
// Check for Windows to replace the locale correctly.
2023-12-20 19:35:52 +01:00
if ( 'WIN' === strtoupper ( substr ( PHP_OS , 0 , 3 ))) {
2021-09-18 10:26:12 +02:00
$locale = str_replace ( '_' , '-' , $locale );
}
return $locale ;
}
/**
2023-06-21 12:34:58 +02:00
* Get user ' s language .
*
* @ throws FireflyException
*/
public function getLanguage () : string // get preference
{
$preference = app ( 'preferences' ) -> get ( 'language' , config ( 'firefly.default_language' , 'en_US' )) -> data ;
if ( ! is_string ( $preference )) {
throw new FireflyException ( sprintf ( 'Preference "language" must be a string, but is unexpectedly a "%s".' , gettype ( $preference )));
}
2023-12-20 19:35:52 +01:00
2023-10-14 07:04:07 +02:00
return str_replace ( '-' , '_' , $preference );
2023-06-21 12:34:58 +02:00
}
2021-09-18 10:26:12 +02:00
public function getLocaleArray ( string $locale ) : array
{
return [
sprintf ( '%s.utf8' , $locale ),
sprintf ( '%s.UTF-8' , $locale ),
];
}
2022-03-29 14:59:58 +02:00
/**
* Returns the previous URL but refuses to send you to specific URLs .
*
* - outside domain
* - to JS files , API or JSON routes
*
* Uses the session ' s previousUrl () function as inspired by GitHub user @ z1r0 -
*
* session () -> previousUrl () uses getSafeUrl () so we can safely return it :
*/
public function getSafePreviousUrl () : string
{
2023-12-29 09:01:42 +01:00
// Log::debug(sprintf('getSafePreviousUrl: "%s"', session()->previousUrl()));
2022-03-29 14:59:58 +02:00
return session () -> previousUrl () ? ? route ( 'index' );
}
/**
* Make sure URL is safe .
*/
public function getSafeUrl ( string $unknownUrl , string $safeUrl ) : string
{
2023-12-29 09:01:42 +01:00
// Log::debug(sprintf('getSafeUrl(%s, %s)', $unknownUrl, $safeUrl));
2024-12-22 16:41:55 +01:00
$returnUrl = $safeUrl ;
$unknownHost = parse_url ( $unknownUrl , PHP_URL_HOST );
$safeHost = parse_url ( $safeUrl , PHP_URL_HOST );
2022-03-29 14:59:58 +02:00
if ( null !== $unknownHost && $unknownHost === $safeHost ) {
$returnUrl = $unknownUrl ;
}
// URL must not lead to weird pages
$forbiddenWords = [ 'jscript' , 'json' , 'debug' , 'serviceworker' , 'offline' , 'delete' , '/login' , '/attachments/view' ];
2023-12-20 19:35:52 +01:00
if ( \Str :: contains ( $returnUrl , $forbiddenWords )) {
2022-03-29 14:59:58 +02:00
$returnUrl = $safeUrl ;
}
return $returnUrl ;
}
2017-06-17 22:49:44 +02:00
public function negative ( string $amount ) : string
{
2021-04-05 06:14:13 +02:00
if ( '' === $amount ) {
return '0' ;
}
2022-03-26 18:13:02 +01:00
$amount = $this -> floatalize ( $amount );
2017-11-15 12:25:49 +01:00
if ( 1 === bccomp ( $amount , '0' )) {
2017-06-17 22:49:44 +02:00
$amount = bcmul ( $amount , '-1' );
}
return $amount ;
}
2017-06-20 21:04:25 +02:00
/**
2023-06-21 12:34:58 +02:00
* https :// framework . zend . com / downloads / archives
*
* Convert a scientific notation to float
* Additionally fixed a problem with PHP <= 5.2 . x with big integers
*/
public function floatalize ( string $value ) : string
{
2024-12-22 16:41:55 +01:00
$value = strtoupper ( $value );
2023-06-21 12:34:58 +02:00
if ( ! str_contains ( $value , 'E' )) {
return $value ;
}
2024-07-24 14:57:51 +02:00
Log :: debug ( sprintf ( 'Floatalizing %s' , $value ));
2023-06-21 12:34:58 +02:00
2024-07-31 13:09:55 +02:00
$number = substr ( $value , 0 , ( int ) strpos ( $value , 'E' ));
2023-06-21 12:34:58 +02:00
if ( str_contains ( $number , '.' )) {
2024-07-31 13:09:55 +02:00
$post = strlen ( substr ( $number , ( int ) strpos ( $number , '.' ) + 1 ));
$mantis = substr ( $value , ( int ) strpos ( $value , 'E' ) + 1 );
2023-06-21 12:34:58 +02:00
if ( $mantis < 0 ) {
2024-07-31 13:09:55 +02:00
$post += abs (( int ) $mantis );
2023-06-21 12:34:58 +02:00
}
2023-12-20 19:35:52 +01:00
2023-06-21 12:34:58 +02:00
// TODO careless float could break financial math.
2024-07-31 13:09:55 +02:00
return number_format (( float ) $value , $post , '.' , '' );
2023-06-21 12:34:58 +02:00
}
2023-12-20 19:35:52 +01:00
2023-06-21 12:34:58 +02:00
// TODO careless float could break financial math.
2024-07-31 13:09:55 +02:00
return number_format (( float ) $value , 0 , '.' , '' );
2023-06-21 12:34:58 +02:00
}
2024-03-18 20:25:30 +01:00
public function opposite ( ? string $amount = null ) : ? string
2017-06-20 21:04:25 +02:00
{
2018-04-02 14:50:17 +02:00
if ( null === $amount ) {
2018-03-24 14:05:29 +01:00
return null ;
}
2021-01-26 19:28:35 +01:00
2020-10-23 19:11:25 +02:00
return bcmul ( $amount , '-1' );
2017-06-20 21:04:25 +02:00
}
2018-07-15 10:00:08 +02:00
public function phpBytes ( string $string ) : int
2015-07-19 14:30:20 +02:00
{
2021-01-26 19:28:35 +01:00
$string = str_replace ([ 'kb' , 'mb' , 'gb' ], [ 'k' , 'm' , 'g' ], strtolower ( $string ));
2015-07-19 14:30:20 +02:00
2020-10-24 16:59:56 +02:00
if ( false !== stripos ( $string , 'k' )) {
2015-07-19 14:30:20 +02:00
// has a K in it, remove the K and multiply by 1024.
2021-01-26 19:28:35 +01:00
$bytes = bcmul ( rtrim ( $string , 'k' ), '1024' );
2015-07-19 14:30:20 +02:00
2024-07-31 13:09:55 +02:00
return ( int ) $bytes ;
2015-07-19 14:30:20 +02:00
}
2020-10-24 16:59:56 +02:00
if ( false !== stripos ( $string , 'm' )) {
2015-07-19 14:30:20 +02:00
// has a M in it, remove the M and multiply by 1048576.
2021-01-26 19:28:35 +01:00
$bytes = bcmul ( rtrim ( $string , 'm' ), '1048576' );
2015-07-19 14:30:20 +02:00
2024-07-31 13:09:55 +02:00
return ( int ) $bytes ;
2015-07-19 14:30:20 +02:00
}
2020-10-24 16:59:56 +02:00
if ( false !== stripos ( $string , 'g' )) {
2016-08-05 20:54:59 +02:00
// has a G in it, remove the G and multiply by (1024)^3.
2021-01-26 19:28:35 +01:00
$bytes = bcmul ( rtrim ( $string , 'g' ), '1073741824' );
2016-08-05 20:54:59 +02:00
2024-07-31 13:09:55 +02:00
return ( int ) $bytes ;
2016-08-05 20:54:59 +02:00
}
2024-07-31 13:09:55 +02:00
return ( int ) $string ;
2015-07-19 14:30:20 +02:00
}
2017-02-11 09:34:04 +01:00
public function positive ( string $amount ) : string
{
2021-04-05 06:14:13 +02:00
if ( '' === $amount ) {
return '0' ;
}
2023-12-20 19:35:52 +01:00
2022-05-04 20:32:51 +02:00
try {
2023-12-20 19:35:52 +01:00
if ( - 1 === bccomp ( $amount , '0' )) {
2022-05-04 20:32:51 +02:00
$amount = bcmul ( $amount , '-1' );
}
2023-12-20 19:35:52 +01:00
} catch ( \ValueError $e ) {
2023-12-29 09:01:42 +01:00
Log :: error ( sprintf ( 'ValueError in Steam::positive("%s"): %s' , $amount , $e -> getMessage ()));
Log :: error ( $e -> getTraceAsString ());
2023-12-20 19:35:52 +01:00
2022-05-04 20:32:51 +02:00
return '0' ;
2017-02-11 09:34:04 +01:00
}
return $amount ;
}
2015-03-29 08:14:32 +02:00
}