Merge remote-tracking branch 'upstream/develop' into fix-transfer-sign

This commit is contained in:
Florian Dupret
2020-07-27 11:06:00 +02:00
850 changed files with 78825 additions and 14247 deletions

View File

@@ -28,6 +28,7 @@ use FireflyIII\User;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Collection;
use Log;
use NumberFormatter;
/**
* Class Amount.
@@ -36,7 +37,6 @@ use Log;
*/
class Amount
{
/**
* bool $sepBySpace is $localeconv['n_sep_by_space']
* int $signPosn = $localeconv['n_sign_posn']
@@ -111,6 +111,58 @@ class Amount
return $format;
}
/**
* This method returns the correct format rules required by accounting.js,
* the library used to format amounts in charts.
*
* Used only in one place.
*
* @return array
*/
public function getJsConfig(): array
{
$config = $this->getLocaleInfo();
$negative = self::getAmountJsConfig($config['n_sep_by_space'], $config['n_sign_posn'], $config['negative_sign'], $config['n_cs_precedes']);
$positive = self::getAmountJsConfig($config['p_sep_by_space'], $config['p_sign_posn'], $config['positive_sign'], $config['p_cs_precedes']);
return [
'mon_decimal_point' => $config['mon_decimal_point'],
'mon_thousands_sep' => $config['mon_thousands_sep'],
'format' => [
'pos' => $positive,
'neg' => $negative,
'zero' => $positive,
],
];
}
/**
* @return array
*/
private function getLocaleInfo(): array
{
// get config from preference, not from translation:
$locale = app('steam')->getLocale();
$array = app('steam')->getLocaleArray($locale);
setlocale(LC_MONETARY, $array);
$info = localeconv();
// correct variables
$info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes');
$info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes');
$info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space');
$info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space');
$fmt = new NumberFormatter( $locale, NumberFormatter::CURRENCY);
$info['mon_decimal_point'] = $fmt->getSymbol(NumberFormatter::MONETARY_SEPARATOR_SYMBOL);
$info['mon_thousands_sep'] = $fmt->getSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL);
return $info;
}
/**
* This method will properly format the given number, in color or "black and white",
* as a currency, given two things: the currency required and the current locale.
@@ -142,14 +194,15 @@ class Amount
*/
public function formatFlat(string $symbol, int $decimalPlaces, string $amount, bool $coloured = null): string
{
$locale = app('steam')->getLocale();
$coloured = $coloured ?? true;
$float = round($amount, 12);
$info = $this->getLocaleInfo();
$formatted = number_format($float, $decimalPlaces, $info['mon_decimal_point'], $info['mon_thousands_sep']);
$precedes = $amount < 0 ? $info['n_cs_precedes'] : $info['p_cs_precedes'];
$separated = $amount < 0 ? $info['n_sep_by_space'] : $info['p_sep_by_space'];
$space = true === $separated ? ' ' : '';
$result = false === $precedes ? $formatted . $space . $symbol : $symbol . $space . $formatted;
$fmt = new NumberFormatter( $locale, NumberFormatter::CURRENCY );
$fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $symbol);
$fmt->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces);
$fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces);
$result = $fmt->format($amount);
if (true === $coloured) {
if ($amount > 0) {
@@ -216,28 +269,7 @@ class Amount
}
/**
* @return string
*/
public function getCurrencySymbol(): string
{
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__));
}
$cache = new CacheProperties;
$cache->addProperty('getCurrencySymbol');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$currencyPreference = app('preferences')->get('currencyPreference', config('firefly.default_currency', 'EUR'));
$currency = TransactionCurrency::where('code', $currencyPreference->data)->first();
$cache->store($currency->symbol);
return $currency->symbol;
}
/**
* @return \FireflyIII\Models\TransactionCurrency
* @return TransactionCurrency
*/
public function getDefaultCurrency(): TransactionCurrency
{
@@ -251,7 +283,7 @@ class Amount
}
/**
* @return \FireflyIII\Models\TransactionCurrency
* @return TransactionCurrency
*/
public function getSystemCurrency(): TransactionCurrency
{
@@ -265,7 +297,7 @@ class Amount
/**
* @param User $user
*
* @return \FireflyIII\Models\TransactionCurrency
* @return TransactionCurrency
*/
public function getDefaultCurrencyByUser(User $user): TransactionCurrency
{
@@ -300,47 +332,6 @@ class Amount
return $currency;
}
/**
* This method returns the correct format rules required by accounting.js,
* the library used to format amounts in charts.
*
* @param array $config
*
* @return array
*/
public function getJsConfig(array $config): array
{
$negative = self::getAmountJsConfig($config['n_sep_by_space'], $config['n_sign_posn'], $config['negative_sign'], $config['n_cs_precedes']);
$positive = self::getAmountJsConfig($config['p_sep_by_space'], $config['p_sign_posn'], $config['positive_sign'], $config['p_cs_precedes']);
return [
'pos' => $positive,
'neg' => $negative,
'zero' => $positive,
];
}
/**
* @return array
*/
public function getLocaleInfo(): array
{
// get config from preference, not from translation:
$locale = app('steam')->getLocale();
$array = app('steam')->getLocaleArray($locale);
setlocale(LC_MONETARY, $array);
$info = localeconv();
// correct variables
$info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes');
$info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes');
$info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space');
$info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space');
return $info;
}
/**
* @param array $info
* @param string $field

View File

@@ -0,0 +1,139 @@
<?php
/**
* RemoteUserGuard.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Authentication;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Foundation\Application;
use Log;
/**
* Class RemoteUserGuard
*/
class RemoteUserGuard implements Guard
{
protected Application $application;
protected $provider;
protected $user;
/**
* Create a new authentication guard.
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
*
* @return void
*/
public function __construct(UserProvider $provider, Application $app)
{
Log::debug('Constructed RemoteUserGuard');
$this->application = $app;
$this->provider = $provider;
$this->user = null;
}
/**
*
*/
public function authenticate(): void
{
Log::debug(sprintf('Now at %s', __METHOD__));
if (!is_null($this->user)) {
Log::debug('No user found.');
return;
}
// Get the user identifier from $_SERVER
$userID = request()->server('REMOTE_USER') ?? null;
if (null === $userID) {
Log::debug('No user in REMOTE_USER.');
throw new FireflyException('The REMOTE_USER header was unexpectedly empty.');
}
// do some basic debugging here:
// $userID = 'test@firefly';
/** @var User $user */
$user = $this->provider->retrieveById($userID);
Log::debug(sprintf('Result of getting user from provider: %s', $user->email));
$this->user = $user;
}
/**
* @inheritDoc
*/
public function check(): bool
{
$result = !is_null($this->user());
Log::debug(sprintf('Now in check(). Will return %s', var_export($result, true)));
return $result;
}
/**
* @inheritDoc
*/
public function guest(): bool
{
return !$this->check();
}
/**
* @inheritDoc
*/
public function id(): ?User
{
return $this->user;
}
/**
* @inheritDoc
*/
public function setUser(Authenticatable $user)
{
$this->user = $user;
}
/**
* @inheritDoc
*/
public function user(): ?User
{
return $this->user;
}
/**
* @inheritDoc
*/
public function validate(array $credentials = [])
{
throw new FireflyException('Did not implement RemoteUserGuard::validate()');
}
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* RemoteUserProvider.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Authentication;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Foundation\Application;
use Log;
use Str;
/**
* Class RemoteUserProvider
*/
class RemoteUserProvider implements UserProvider
{
/**
* @inheritDoc
*/
public function retrieveByCredentials(array $credentials)
{
Log::debug(sprintf('Now at %s', __METHOD__));
throw new FireflyException(sprintf('Did not implement %s', __METHOD__));
}
/**
* @inheritDoc
*/
public function retrieveById($identifier): User
{
Log::debug(sprintf('Now at %s(%s)', __METHOD__, $identifier));
$user = User::where('email', $identifier)->first();
if (null === $user) {
Log::debug(sprintf('User with email "%s" not found. Will be created.', $identifier));
$user = User::create(
[
'blocked' => false,
'blocked_code' => null,
'email' => $identifier,
'password' => bcrypt(Str::random(64)),
]
);
}
Log::debug(sprintf('Going to return user #%d (%s)', $user->id, $user->email));
return $user;
}
/**
* @inheritDoc
*/
public function retrieveByToken($identifier, $token)
{
Log::debug(sprintf('Now at %s', __METHOD__));
throw new FireflyException(sprintf('Did not implement %s', __METHOD__));
}
/**
* @inheritDoc
*/
public function updateRememberToken(Authenticatable $user, $token)
{
Log::debug(sprintf('Now at %s', __METHOD__));
throw new FireflyException(sprintf('Did not implement %s', __METHOD__));
}
/**
* @inheritDoc
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
Log::debug(sprintf('Now at %s', __METHOD__));
throw new FireflyException(sprintf('Did not implement %s', __METHOD__));
}
}

View File

@@ -44,9 +44,7 @@ class AccountList implements BinderInterface
*/
public static function routeBinder(string $value, Route $route): Collection
{
//Log::debug(sprintf('Now in AccountList::routeBinder("%s")', $value));
if (auth()->check()) {
//Log::debug('User is logged in.');
$collection = new Collection;
if ('allAssetAccounts' === $value) {
/** @var Collection $collection */
@@ -65,7 +63,6 @@ class AccountList implements BinderInterface
->whereIn('accounts.id', $list)
->orderBy('accounts.name', 'ASC')
->get(['accounts.*']);
//Log::debug(sprintf('Collection length is %d', $collection->count()));
}
if ($collection->count() > 0) {

View File

@@ -66,7 +66,6 @@ class BudgetList implements BinderInterface
->where('active', 1)
->whereIn('id', $list)
->get();
//Log::debug(sprintf('Found %d active budgets', $collection->count()), $list);
// add empty budget if applicable.
if (in_array(0, $list, true)) {
@@ -75,11 +74,9 @@ class BudgetList implements BinderInterface
}
if ($collection->count() > 0) {
//Log::debug(sprintf('List length is > 0 (%d), so return it.', $collection->count()));
return $collection;
}
//Log::debug('List length is zero, fall back to 404.');
}
Log::warning('BudgetList fallback to 404.');
throw new NotFoundHttpException;

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types=1);
/**
* FrontpageChartGenerator.php
* Copyright (c) 2020 thegrumpydictator@gmail.com
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Chart\Category;
use Carbon\Carbon;
@@ -87,18 +89,16 @@ class FrontpageChartGenerator
foreach ($categories as $category) {
// get expenses
$collection[] = $this->collectExpenses($category, $accounts);
$collection[] = $this->collectIncome($category, $accounts);
}
// collect for no-category:
$collection[] = $this->collectNoCatExpenses($accounts);
$collection[] = $this->collectNoCatIncome($accounts);
$tempData = array_merge(...$collection);
// sort temp array by amount.
$amounts = array_column($tempData, 'sum_float');
array_multisort($amounts, SORT_DESC, $tempData);
array_multisort($amounts, SORT_ASC, $tempData);
$currencyData = $this->createCurrencyGroups($tempData);
@@ -144,29 +144,6 @@ class FrontpageChartGenerator
return $tempData;
}
/**
* @param Category $category
* @param Collection $accounts
*
* @return array
*/
private function collectIncome(Category $category, Collection $accounts): array
{
$spent = $this->opsRepos->sumIncome($this->start, $this->end, $accounts, new Collection([$category]));
$tempData = [];
foreach ($spent as $currency) {
$this->addCurrency($currency);
$tempData[] = [
'name' => $category->name,
'sum' => $currency['sum'],
'sum_float' => round($currency['sum'], $currency['currency_decimal_places']),
'currency_id' => (int) $currency['currency_id'],
];
}
return $tempData;
}
/**
* @param Collection $accounts
*
@@ -182,28 +159,8 @@ class FrontpageChartGenerator
'name' => trans('firefly.no_category'),
'sum' => $currency['sum'],
'sum_float' => round($currency['sum'], $currency['currency_decimal_places'] ?? 2),
'currency_id' => (int) $currency['currency_id'],];
}
return $tempData;
}
/**
* @param Collection $accounts
*
* @return array
*/
private function collectNoCatIncome(Collection $accounts): array
{
$noCatExp = $this->noCatRepos->sumIncome($this->start, $this->end, $accounts);
$tempData = [];
foreach ($noCatExp as $currency) {
$this->addCurrency($currency);
$tempData[] = [
'name' => trans('firefly.no_category'),
'sum' => $currency['sum'],
'sum_float' => round($currency['sum'], $currency['currency_decimal_places'] ?? 2),
'currency_id' => (int) $currency['currency_id'],];
'currency_id' => (int) $currency['currency_id'],
];
}
return $tempData;
@@ -229,14 +186,6 @@ class FrontpageChartGenerator
'currency_symbol' => $currency['currency_symbol'],
'entries' => $names,
];
$key = sprintf('earned-%d', $currencyId);
$return[$key] = [
'label' => sprintf('%s (%s)', (string) trans('firefly.earned'), $currency['currency_name']),
'type' => 'bar',
'currency_symbol' => $currency['currency_symbol'],
'data_type' => 'earned',
'entries' => $names,
];
}
return $return;

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types=1);
/**
* AutoBudgetCronjob.php
* Copyright (c) 2020 thegrumpydictator@gmail.com
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Cronjobs;

View File

@@ -1,5 +1,5 @@
<?php
declare(strict_types=1);
/**
* TelemetryCronjob.php
* Copyright (c) 2020 james@firefly-iii.org
@@ -20,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Cronjobs;
use Carbon\Carbon;

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types=1);
/**
* ExportDataGenerator.php
* Copyright (c) 2019 james@firefly-iii.org
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Export;
use Carbon\Carbon;
@@ -656,11 +658,11 @@ class ExportDataGenerator
// TODO better place for keys?
$header = ['user_id', 'group_id', 'journal_id', 'created_at', 'updated_at', 'group_title', 'type', 'amount', 'foreign_amount', 'currency_code',
'foreign_currency_code', 'description', 'date', 'source_name', 'source_iban', 'source_type', 'destination_name', 'destination_iban',
'destination_type', 'reconciled', 'category', 'budget', 'bill', 'tags',];
'destination_type', 'reconciled', 'category', 'budget', 'bill', 'tags', 'notes'];
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user);
$collector->setRange($this->start, $this->end)->withAccountInformation()->withCategoryInformation()->withBillInformation()
->withBudgetInformation()->withTagInformation();
->withBudgetInformation()->withTagInformation()->withNotes();
$journals = $collector->getExtractedJournals();
$records = [];
@@ -691,6 +693,7 @@ class ExportDataGenerator
$journal['budget_name'],
$journal['bill_name'],
$this->mergeTags($journal['tags']),
$journal['notes'],
];
}

View File

@@ -38,7 +38,6 @@ use Illuminate\Support\Facades\Facade;
* @method string getCurrencySymbol()
* @method TransactionCurrency getDefaultCurrency()
* @method TransactionCurrency getDefaultCurrencyByUser(User $user)
* @method array getJsConfig(array $config)
*/
class Amount extends Facade
{

View File

@@ -24,6 +24,7 @@ namespace FireflyIII\Support;
use Cache;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Configuration;
use Illuminate\Database\QueryException;
use Log;
@@ -67,9 +68,10 @@ class FireflyConfig
/**
* @param string $name
* @param mixed $default
* @param null $default
*
* @return \FireflyIII\Models\Configuration|null
* @throws FireflyException
* @return Configuration|null
*/
public function get(string $name, $default = null): ?Configuration
{
@@ -82,9 +84,12 @@ class FireflyConfig
}
try {
/** @var Configuration $config */
$config = Configuration::where('name', $name)->first(['id', 'name', 'data']);
} catch (QueryException|Exception $e) {
return null;
//Log::error(sprintf('Query exception while polling for config var: %s', $e->getMessage()));
//Log::error($e->getTraceAsString());
throw new FireflyException(sprintf('Could not poll the database: %s', $e->getMessage()));
}
if ($config) {
@@ -151,7 +156,7 @@ class FireflyConfig
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__));
}
Log::debug('Set new value for ', ['name' => $name]);
//Log::debug('Set new value for ', ['name' => $name]);
/** @var Configuration $config */
try {
$config = Configuration::whereName($name)->first();
@@ -163,7 +168,7 @@ class FireflyConfig
return $item;
}
if (null === $config) {
Log::debug('Does not exist yet ', ['name' => $name]);
//Log::debug('Does not exist yet ', ['name' => $name]);
/** @var Configuration $item */
$item = new Configuration;
$item->name = $name;
@@ -174,7 +179,7 @@ class FireflyConfig
return $item;
}
Log::debug('Exists already, overwrite value.', ['name' => $name]);
//Log::debug('Exists already, overwrite value.', ['name' => $name]);
$config->data = $value;
$config->save();
Cache::forget('ff-config-' . $name);

View File

@@ -47,17 +47,43 @@ class PiggyBankForm
*/
public function piggyBankList(string $name, $value = null, array $options = null): string
{
// make repositories
/** @var PiggyBankRepositoryInterface $repository */
$repository = app(PiggyBankRepositoryInterface::class);
$piggyBanks = $repository->getPiggyBanksWithAmount();
$array = [
0 => (string)trans('firefly.none_in_select_list'),
$title = (string) trans('firefly.default_group_title_name');
$array = [];
$subList = [
0 => [
'group' => [
'title' => $title,
],
'piggies' => [
(string) trans('firefly.none_in_select_list'),
],
],
];
/** @var PiggyBank $piggy */
foreach ($piggyBanks as $piggy) {
$array[$piggy->id] = $piggy->name;
$group = $piggy->objectGroups->first();
$groupTitle = null;
$groupOrder = 0;
if (null !== $group) {
$groupTitle = $group->title;
$groupOrder = $group->order;
}
$subList[$groupOrder] = $subList[$groupOrder] ?? [
'group' => [
'title' => $groupTitle,
],
'piggies' => [],
];
$subList[$groupOrder]['piggies'][$piggy->id] = $piggy->name;
}
ksort($subList);
foreach ($subList as $info) {
$groupTitle = $info['group']['title'];
$array[$groupTitle] = $info['piggies'];
}
return $this->select($name, $array, $value, $options);

View File

@@ -90,8 +90,8 @@ trait ChartGeneration
$previous = array_values($range)[0];
while ($currentStart <= $end) {
$format = $currentStart->format('Y-m-d');
$label = $currentStart->formatLocalized((string)trans('config.month_and_day', [], $locale));
$balance = isset($range[$format]) ? round($range[$format], 12) : $previous;
$label = trim($currentStart->formatLocalized((string)trans('config.month_and_day', [], $locale)));
$balance = $range[$format] ?? $previous;
$previous = $balance;
$currentStart->addDay();
$currentSet['entries'][$label] = $balance;

View File

@@ -106,12 +106,9 @@ trait GetConfigurationData
// first range is the current range:
$title => [$start, $end],
];
//Log::debug(sprintf('viewRange is %s', $viewRange));
//Log::debug(sprintf('isCustom is %s', var_export($isCustom, true)));
// when current range is a custom range, add the current period as the next range.
if ($isCustom) {
//Log::debug('Custom is true.');
$index = app('navigation')->periodShow($start, $viewRange);
$customPeriodStart = app('navigation')->startOfPeriod($start, $viewRange);
$customPeriodEnd = app('navigation')->endOfPeriod($customPeriodStart, $viewRange);

View File

@@ -166,7 +166,6 @@ trait RequestInformation
// both must be array and either must be > 0
if (count($intro) > 0 || count($specialIntro) > 0) {
$shownDemo = app('preferences')->get($key, false)->data;
//Log::debug(sprintf('Check if user has already seen intro with key "%s". Result is %s', $key, var_export($shownDemo, true)));
}
if (!is_bool($shownDemo)) {
$shownDemo = true; // @codeCoverageIgnore

View File

@@ -40,10 +40,6 @@ use Log;
trait UserNavigation
{
//if (!$this->isEditableAccount($account)) {
// return $this->redirectAccountToAccount($account); // @codeCoverageIgnore
// }
/**
* Will return false if you cant edit this account type.
*

View File

@@ -539,7 +539,6 @@ class Navigation
$subtract = $subtract ?? 1;
$date = clone $theDate;
// 1D 1W 1M 3M 6M 1Y
//Log::debug(sprintf('subtractPeriod: date is %s, repeat frequency is %s and subtract is %d', $date->format('Y-m-d'), $repeatFreq, $subtract));
$functionMap = [
'1D' => 'subDays',
'daily' => 'subDays',
@@ -563,16 +562,12 @@ class Navigation
if (isset($functionMap[$repeatFreq])) {
$function = $functionMap[$repeatFreq];
$date->$function($subtract);
//Log::debug(sprintf('%s is in function map, execute %s with argument %d', $repeatFreq, $function, $subtract));
//Log::debug(sprintf('subtractPeriod: resulting date is %s', $date->format('Y-m-d')));
return $date;
}
if (isset($modifierMap[$repeatFreq])) {
$subtract *= $modifierMap[$repeatFreq];
$date->subMonths($subtract);
//Log::debug(sprintf('%s is in modifier map with value %d, execute subMonths with argument %d', $repeatFreq, $modifierMap[$repeatFreq], $subtract));
//Log::debug(sprintf('subtractPeriod: resulting date is %s', $date->format('Y-m-d')));
return $date;
}
@@ -585,11 +580,8 @@ class Navigation
/** @var Carbon $tEnd */
$tEnd = session('end', Carbon::now()->endOfMonth());
$diffInDays = $tStart->diffInDays($tEnd);
//Log::debug(sprintf('repeatFreq is %s, start is %s and end is %s (session data).', $repeatFreq, $tStart->format('Y-m-d'), $tEnd->format('Y-m-d')));
//Log::debug(sprintf('Diff in days is %d', $diffInDays));
$date->subDays($diffInDays * $subtract);
//Log::debug(sprintf('subtractPeriod: resulting date is %s', $date->format('Y-m-d')));
return $date;
}

View File

@@ -1,4 +1,25 @@
<?php
/**
* ParseDateString.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
@@ -35,6 +56,7 @@ class ParseDateString
*/
public function parseDate(string $date): Carbon
{
$date = strtolower($date);
// parse keywords:
if (in_array($date, $this->keywords, true)) {
return $this->parseKeyword($date);
@@ -48,18 +70,189 @@ class ParseDateString
// if + or -:
if (0 === strpos($date, '+') || 0 === strpos($date, '-')) {
return $this->parseRelativeDate($date);
}
if ('xxxx-xx-xx' === strtolower($date)) {
throw new FireflyException(sprintf('[a]Not a recognised date format: "%s"', $date));
}
// can't do a partial year:
$substrCount = substr_count(substr($date, 0, 4), 'x', 0);
if (10 === strlen($date) && $substrCount > 0 && $substrCount < 4) {
throw new FireflyException(sprintf('[b]Not a recognised date format: "%s"', $date));
}
throw new FireflyException('Not recognised.');
// maybe a date range
if (10 === strlen($date) && (false !== strpos($date, 'xx') || false !== strpos($date, 'xxxx'))) {
Log::debug(sprintf('[c]Detected a date range ("%s"), return a fake date.', $date));
// very lazy way to parse the date without parsing it, because this specific function
// cant handle date ranges.
return new Carbon('1984-09-17');
}
throw new FireflyException(sprintf('[d]Not a recognised date format: "%s"', $date));
}
/**
* @param string $date
*
* @return bool
*/
public function isDateRange(string $date): bool
{
$date = strtolower($date);
// not 10 chars:
if (10 !== strlen($date)) {
return false;
}
// all x'es
if ('xxxx-xx-xx' === strtolower($date)) {
return false;
}
// no x'es
if (false === strpos($date, 'xx') && false === strpos($date, 'xxxx')) {
return false;
}
return true;
}
/**
* @param string $date
* @param Carbon $journalDate
*
* @return array
*/
public function parseRange(string $date, Carbon $journalDate): array
{
// several types of range can be submitted
switch (true) {
default:
break;
case $this->isDayRange($date):
return $this->parseDayRange($date, $journalDate);
case $this->isMonthRange($date):
return $this->parseMonthRange($date, $journalDate);
case $this->isYearRange($date):
return $this->parseYearRange($date, $journalDate);
case $this->isMonthDayRange($date):
return $this->parseMonthDayRange($date, $journalDate);
case $this->isDayYearRange($date):
return $this->parseDayYearRange($date, $journalDate);
case $this->isMonthYearRange($date):
return $this->parseMonthYearRange($date, $journalDate);
}
return [
'start' => new Carbon('1984-09-17'),
'end' => new Carbon('1984-09-17'),
];
}
/**
* @param string $date
*
* @return bool
*/
protected function isDayRange(string $date): bool
{
// if regex for xxxx-xx-DD:
$pattern = '/^xxxx-xx-(0[1-9]|[12][\d]|3[01])$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a day range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a day range.', $date));
return false;
}
/**
* @param string $date
* @param Carbon $journalDate
*
* @return array
*/
protected function parseDayRange(string $date, Carbon $journalDate): array
{
// format of string is xxxx-xx-DD
$validDate = str_replace(['xxxx'], [$journalDate->year], $date);
$validDate = str_replace(['xx'], [$journalDate->format('m')], $validDate);
Log::debug(sprintf('parseDayRange: Parsed "%s" into "%s"', $date, $validDate));
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay();
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay();
return [
'start' => $start,
'end' => $end,
];
}
/**
* @param string $date
*
* @return bool
*/
protected function isMonthRange(string $date): bool
{
// if regex for xxxx-MM-xx:
$pattern = '/^xxxx-(0[1-9]|1[012])-xx$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a month range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a month range.', $date));
return false;
}
/**
* @param string $date
*
* @return bool
*/
protected function isMonthYearRange(string $date): bool
{
// if regex for YYYY-MM-xx:
$pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-xx$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a month/year range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a month/year range.', $date));
return false;
}
/**
* @param string $date
*
* @return bool
*/
protected function isYearRange(string $date): bool
{
// if regex for YYYY-xx-xx:
$pattern = '/^(19|20)\d\d-xx-xx$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a year range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a year range.', $date));
return false;
}
/**
* @param string $date
*
* @return Carbon
*/
private function parseDefaultDate(string $date): Carbon
protected function parseDefaultDate(string $date): Carbon
{
return Carbon::createFromFormat('Y-m-d', $date);
}
@@ -69,7 +262,7 @@ class ParseDateString
*
* @return Carbon
*/
private function parseKeyword(string $keyword): Carbon
protected function parseKeyword(string $keyword): Carbon
{
$today = Carbon::today()->startOfDay();
switch ($keyword) {
@@ -99,12 +292,65 @@ class ParseDateString
}
}
/**
* @param string $date
* @param Carbon $journalDate
*
* @return array
*/
protected function parseMonthRange(string $date, Carbon $journalDate): array
{
// because 31 would turn February into March unexpectedly and the exact day is irrelevant here.
$day = $journalDate->format('d');
if ((int) $day > 28) {
$day = '28';
}
// format of string is xxxx-MM-xx
$validDate = str_replace(['xxxx'], [$journalDate->year], $date);
$validDate = str_replace(['xx'], [$day], $validDate);
Log::debug(sprintf('parseMonthRange: Parsed "%s" into "%s"', $date, $validDate));
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfMonth();
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfMonth();
return [
'start' => $start,
'end' => $end,
];
}
/**
* @param string $date
* @param Carbon $journalDate
*
* @return array
*/
protected function parseMonthYearRange(string $date, Carbon $journalDate): array
{
// because 31 would turn February into March unexpectedly and the exact day is irrelevant here.
$day = $journalDate->format('d');
if ((int) $day > 28) {
$day = '28';
}
// format of string is YYYY-MM-xx
$validDate = str_replace(['xx'], [$day], $date);
Log::debug(sprintf('parseMonthYearRange: Parsed "%s" into "%s"', $date, $validDate));
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfMonth();
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfMonth();
return [
'start' => $start,
'end' => $end,
];
}
/**
* @param string $date
*
* @return Carbon
*/
private function parseRelativeDate(string $date): Carbon
protected function parseRelativeDate(string $date): Carbon
{
Log::debug(sprintf('Now in parseRelativeDate("%s")', $date));
$parts = explode(' ', $date);
@@ -154,4 +400,106 @@ class ParseDateString
return $today;
}
/**
* @param string $date
* @param Carbon $journalDate
*
* @return array
*/
protected function parseYearRange(string $date, Carbon $journalDate): array
{
// format of string is YYYY-xx-xx
// kind of a convulted way of replacing variables but I'm lazy.
$validDate = str_replace(['xx-xx'], [sprintf('%s-xx', $journalDate->format('m'))], $date);
$validDate = str_replace(['xx'], [$journalDate->format('d')], $validDate);
Log::debug(sprintf('parseYearRange: Parsed "%s" into "%s"', $date, $validDate));
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfYear();
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfYear();
return [
'start' => $start,
'end' => $end,
];
}
/**
* @param string $date
*
* @return bool
*/
protected function isMonthDayRange(string $date): bool
{
// if regex for xxxx-MM-DD:
$pattern = '/^xxxx-(0[1-9]|1[012])-(0[1-9]|[12][\d]|3[01])$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a month/day range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a month/day range.', $date));
return false;
}
/**
* @param string $date
*
* @return bool
*/
protected function isDayYearRange(string $date): bool
{
// if regex for YYYY-xx-DD:
$pattern = '/^(19|20)\d\d-xx-(0[1-9]|[12][\d]|3[01])$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a day/year range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a day/year range.', $date));
return false;
}
/**
* @param string $date
* @param Carbon $journalDate
*
* @return array
*/
private function parseMonthDayRange(string $date, Carbon $journalDate): array
{
// Any year.
// format of string is xxxx-MM-DD
$validDate = str_replace(['xxxx'], [$journalDate->year], $date);
Log::debug(sprintf('parseMonthDayRange: Parsed "%s" into "%s"', $date, $validDate));
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay();
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay();
return [
'start' => $start,
'end' => $end,
];
}
/**
* @param string $date
* @param Carbon $journalDate
*
* @return array
*/
private function parseDayYearRange(string $date, Carbon $journalDate): array
{
// Any year.
// format of string is YYYY-xx-DD
$validDate = str_replace(['xx'], [$journalDate->format('m')], $date);
Log::debug(sprintf('parseDayYearRange: Parsed "%s" into "%s"', $date, $validDate));
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay();
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay();
return [
'start' => $start,
'end' => $end,
];
}
}

View File

@@ -46,11 +46,11 @@ trait CalculateRangeOccurrences
{
$return = [];
$attempts = 0;
Log::debug('Rep is daily. Start of loop.');
#Log::debug('Rep is daily. Start of loop.');
while ($start <= $end) {
Log::debug(sprintf('Mutator is now: %s', $start->format('Y-m-d')));
#Log::debug(sprintf('Mutator is now: %s', $start->format('Y-m-d')));
if (0 === $attempts % $skipMod) {
Log::debug(sprintf('Attempts modulo skipmod is zero, include %s', $start->format('Y-m-d')));
#Log::debug(sprintf('Attempts modulo skipmod is zero, include %s', $start->format('Y-m-d')));
$return[] = clone $start;
}
$start->addDay();
@@ -77,27 +77,27 @@ trait CalculateRangeOccurrences
$return = [];
$attempts = 0;
$dayOfMonth = (int)$moment;
Log::debug(sprintf('Day of month in repetition is %d', $dayOfMonth));
Log::debug(sprintf('Start is %s.', $start->format('Y-m-d')));
Log::debug(sprintf('End is %s.', $end->format('Y-m-d')));
#Log::debug(sprintf('Day of month in repetition is %d', $dayOfMonth));
#Log::debug(sprintf('Start is %s.', $start->format('Y-m-d')));
#Log::debug(sprintf('End is %s.', $end->format('Y-m-d')));
if ($start->day > $dayOfMonth) {
Log::debug('Add a month.');
#Log::debug('Add a month.');
// day has passed already, add a month.
$start->addMonth();
}
Log::debug(sprintf('Start is now %s.', $start->format('Y-m-d')));
Log::debug('Start loop.');
#Log::debug(sprintf('Start is now %s.', $start->format('Y-m-d')));
#Log::debug('Start loop.');
while ($start < $end) {
Log::debug(sprintf('Mutator is now %s.', $start->format('Y-m-d')));
#Log::debug(sprintf('Mutator is now %s.', $start->format('Y-m-d')));
$domCorrected = min($dayOfMonth, $start->daysInMonth);
Log::debug(sprintf('DoM corrected is %d', $domCorrected));
#Log::debug(sprintf('DoM corrected is %d', $domCorrected));
$start->day = $domCorrected;
Log::debug(sprintf('Mutator is now %s.', $start->format('Y-m-d')));
Log::debug(sprintf('$attempts %% $skipMod === 0 is %s', var_export(0 === $attempts % $skipMod, true)));
Log::debug(sprintf('$start->lte($mutator) is %s', var_export($start->lte($start), true)));
Log::debug(sprintf('$end->gte($mutator) is %s', var_export($end->gte($start), true)));
#Log::debug(sprintf('Mutator is now %s.', $start->format('Y-m-d')));
#Log::debug(sprintf('$attempts %% $skipMod === 0 is %s', var_export(0 === $attempts % $skipMod, true)));
#Log::debug(sprintf('$start->lte($mutator) is %s', var_export($start->lte($start), true)));
#Log::debug(sprintf('$end->gte($mutator) is %s', var_export($end->gte($start), true)));
if (0 === $attempts % $skipMod && $start->lte($start) && $end->gte($start)) {
Log::debug(sprintf('ADD %s to return!', $start->format('Y-m-d')));
#Log::debug(sprintf('ADD %s to return!', $start->format('Y-m-d')));
$return[] = clone $start;
}
$attempts++;

View File

@@ -0,0 +1,153 @@
<?php
/**
* AppendsLocationData.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Request;
use Log;
/**
* Trait AppendsLocationData
*/
trait AppendsLocationData
{
/**
* Abstract method stolen from "InteractsWithInput".
*
* @param null $key
* @param bool $default
*
* @return mixed
*/
abstract public function boolean($key = null, $default = false);
/**
* Abstract method.
*
* @param $key
*
* @return bool
*/
abstract public function has($key);
/**
* Abstract method.
*
* @return string
*/
abstract public function method();
/**
* Abstract method.
*
* @param mixed ...$patterns
*
* @return mixed
*/
abstract public function routeIs(...$patterns);
/**
* Read the submitted Request data and add new or updated Location data to the array.
*
* @param array $data
*
* @param string|null $prefix
*
* @return array
*/
protected function appendLocationData(array $data, ?string $prefix): array
{
Log::debug(sprintf('Now in appendLocationData("%s")', $prefix), $data);
$data['store_location'] = false;
$data['update_location'] = false;
$data['longitude'] = null;
$data['latitude'] = null;
$data['zoom_level'] = null;
$longitudeKey = $this->getLocationKey($prefix, 'longitude');
$latitudeKey = $this->getLocationKey($prefix, 'latitude');
$zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level');
$hasLocationKey = $this->getLocationKey($prefix, 'has_location');
$hasLocation = $this->boolean($hasLocationKey);
// for a POST (store), all fields must be present and accounted for:
if (
('POST' === $this->method() && $this->routeIs('*.store'))
&& ($this->has($longitudeKey) && $this->has($latitudeKey) && $this->has($zoomLevelKey))
) {
Log::debug('Method is POST and all fields present.');
$data['store_location'] = $this->boolean($hasLocationKey);
$data['longitude'] = $this->nullableString($longitudeKey);
$data['latitude'] = $this->nullableString($latitudeKey);
$data['zoom_level'] = $this->nullableString($zoomLevelKey);
}
if (
($this->has($longitudeKey) && $this->has($latitudeKey) && $this->has($zoomLevelKey))
&& (
('PUT' === $this->method() && $this->routeIs('*.update'))
|| ('POST' === $this->method() && $this->routeIs('*.update'))
)
) {
Log::debug('Method is PUT and all fields present.');
$data['update_location'] = true;
$data['longitude'] = $this->nullableString($longitudeKey);
$data['latitude'] = $this->nullableString($latitudeKey);
$data['zoom_level'] = $this->nullableString($zoomLevelKey);
}
if (false === $hasLocation || null === $data['longitude'] || null === $data['latitude'] || null === $data['zoom_level']) {
Log::debug('One of the fields is NULL or hasLocation is false, wont save.');
$data['store_location'] = false;
$data['update_location'] = true; // update is always true, but the values are null:
$data['longitude'] = null;
$data['latitude'] = null;
$data['zoom_level'] = null;
}
Log::debug(sprintf('Returning longitude: "%s", latitude: "%s", zoom level: "%s"', $data['longitude'], $data['latitude'], $data['zoom_level']));
return $data;
}
/**
* Abstract method to ensure filling later.
*
* @param string $field
*
* @return string|null
*/
abstract protected function nullableString(string $field): ?string;
/**
* @param string|null $prefix
* @param string $key
*
* @return string
*/
private function getLocationKey(?string $prefix, string $key): string
{
if (null === $prefix) {
return $key;
}
return sprintf('%s_%s', $prefix, $key);
}
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* ConvertAPIDataTypes.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Request;
/**
* Trait ConvertAPIDataTypes
*/
trait ConvertAPIDataTypes
{
}

View File

@@ -0,0 +1,297 @@
<?php
/**
* ConvertsDataTypes.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Request;
use Carbon\Carbon;
use Exception;
use Log;
/**
* Trait ConvertsDataTypes
*/
trait ConvertsDataTypes
{
/**
* Return date or NULL.
*
* @param string $field
*
* @return Carbon|null
*/
protected function date(string $field): ?Carbon
{
$result = null;
try {
$result = $this->get($field) ? new Carbon($this->get($field)) : null;
} catch (Exception $e) {
Log::debug(sprintf('Exception when parsing date. Not interesting: %s', $e->getMessage()));
}
return $result;
}
/**
* Return integer value.
*
* @param string $field
*
* @return int
*/
protected function integer(string $field): int
{
return (int) $this->get($field);
}
/**
* Return floating value.
*
* @param string $field
*
* @return float|null
*/
protected function float(string $field): ?float
{
$res = $this->get($field);
if (null === $res) {
return null;
}
return (float) $res;
}
/**
* Parse and clean a string, but keep the newlines.
*
* @param string|null $string
*
* @return string|null
*/
protected function nlStringFromValue(?string $string): ?string
{
if (null === $string) {
return null;
}
$result = app('steam')->nlCleanString($string);
return '' === $result ? null : $result;
}
/**
* @param $array
*
* @return array|null
*/
protected function arrayFromValue($array): ?array
{
if (is_array($array)) {
return $array;
}
if (null === $array) {
return null;
}
if (is_string($array)) {
return explode(',', $array);
}
return null;
}
/**
* @param string|null $string
*
* @return Carbon|null
*/
protected function dateFromValue(?string $string): ?Carbon
{
if (null === $string) {
return null;
}
if ('' === $string) {
return null;
}
try {
$carbon = new Carbon($string);
} catch (Exception $e) {
Log::debug(sprintf('Invalid date: %s: %s', $string, $e->getMessage()));
return null;
}
return $carbon;
}
/**
* Parse and clean a string.
*
* @param string|null $string
*
* @return string|null
*/
protected function stringFromValue(?string $string): ?string
{
if (null === $string) {
return null;
}
$result = app('steam')->cleanString($string);
return '' === $result ? null : $result;
}
/**
* Parse to integer
*
* @param string|null $string
*
* @return int|null
*/
protected function integerFromValue(?string $string): ?int
{
if (null === $string) {
return null;
}
if ('' === $string) {
return null;
}
return (int) $string;
}
/**
* Return string value, but keep newlines.
*
* @param string $field
*
* @return string
*/
protected function nlString(string $field): string
{
return app('steam')->nlCleanString((string) ($this->get($field) ?? ''));
}
/**
* Return integer value, or NULL when it's not set.
*
* @param string $field
*
* @return int|null
*/
protected function nullableInteger(string $field): ?int
{
if (!$this->has($field)) {
return null;
}
$value = (string) $this->get($field);
if ('' === $value) {
return null;
}
return (int) $value;
}
/**
* Return string value, but keep newlines, or NULL if empty.
*
* @param string $field
*
* @return string
*/
protected function nullableNlString(string $field): ?string
{
if (!$this->has($field)) {
return null;
}
return app('steam')->nlCleanString((string) ($this->get($field) ?? ''));
}
/**
* @param string $value
*
* @return bool
*/
protected function convertBoolean(?string $value): bool
{
if (null === $value) {
return false;
}
if ('true' === $value) {
return true;
}
if ('yes' === $value) {
return true;
}
if (1 === $value) {
return true;
}
if ('1' === $value) {
return true;
}
if (true === $value) {
return true;
}
return false;
}
/**
* Return string value, or NULL if empty.
*
* @param string $field
*
* @return string|null
*/
protected function nullableString(string $field): ?string
{
if (!$this->has($field)) {
return null;
}
$res = trim(app('steam')->cleanString((string) ($this->get($field) ?? '')));
if ('' === $res) {
return null;
}
return $res;
}
/**
* Return string value.
*
* @param string $field
*
* @return string
*/
protected function string(string $field): string
{
return app('steam')->cleanString((string) ($this->get($field) ?? ''));
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* GetRecurrenceData.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Request;
/**
* Trait GetRecurrenceData
*/
trait GetRecurrenceData
{
/**
* @param array $transaction
*
* @return array
*/
protected function getSingleRecurrenceData(array $transaction): array
{
return [
'amount' => $transaction['amount'],
'currency_id' => isset($transaction['currency_id']) ? (int) $transaction['currency_id'] : null,
'currency_code' => $transaction['currency_code'] ?? null,
'foreign_amount' => $transaction['foreign_amount'] ?? null,
'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? (int) $transaction['foreign_currency_id'] : null,
'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null,
'source_id' => isset($transaction['source_id']) ? (int) $transaction['source_id'] : null,
'source_name' => isset($transaction['source_name']) ? (string) $transaction['source_name'] : null,
'destination_id' => isset($transaction['destination_id']) ? (int) $transaction['destination_id'] : null,
'destination_name' => isset($transaction['destination_name']) ? (string) $transaction['destination_name'] : null,
'description' => $transaction['description'],
'type' => $this->string('type'),
// new and updated fields:
'piggy_bank_id' => isset($transaction['piggy_bank_id']) ? (int) $transaction['piggy_bank_id'] : null,
'piggy_bank_name' => $transaction['piggy_bank_name'] ?? null,
'tags' => $transaction['tags'] ?? [],
'budget_id' => isset($transaction['budget_id']) ? (int) $transaction['budget_id'] : null,
'budget_name' => $transaction['budget_name'] ?? null,
'category_id' => isset($transaction['category_id']) ? (int) $transaction['category_id'] : null,
'category_name' => $transaction['category_name'] ?? null,
];
}
}

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types=1);
/**
* AccountSearch.php
* Copyright (c) 2019 james@firefly-iii.org
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Search;

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types=1);
/**
* GenericSearchInterface.php
* Copyright (c) 2019 james@firefly-iii.org
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Search;

View File

@@ -102,7 +102,7 @@ class Search implements SearchInterface
{
$string = implode(' ', $this->words);
if ('' === $string) {
return \is_string($this->originalQuery) ? $this->originalQuery : '';
return is_string($this->originalQuery) ? $this->originalQuery : '';
}
return $string;
@@ -132,6 +132,11 @@ class Search implements SearchInterface
$filteredQuery = str_replace($match, '', $filteredQuery);
}
$filteredQuery = trim(str_replace(['"', "'"], '', $filteredQuery));
// str replace some stuff:
$search = ['%', '=', '/', '<', '>', '(', ')', ';'];
$filteredQuery = str_replace($search, ' ', $filteredQuery);
if ('' !== $filteredQuery) {
$this->words = array_map('trim', explode(' ', $filteredQuery));
}
@@ -209,7 +214,7 @@ class Search implements SearchInterface
case 'source':
// source can only be asset, liability or revenue account:
$searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE];
$accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes);
$accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes, 25);
if ($accounts->count() > 0) {
$totalAccounts = $accounts->merge($totalAccounts);
}
@@ -218,19 +223,19 @@ class Search implements SearchInterface
case 'destination':
// source can only be asset, liability or expense account:
$searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE];
$accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes);
$accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes, 25);
if ($accounts->count() > 0) {
$totalAccounts = $accounts->merge($totalAccounts);
}
break;
case 'category':
$result = $this->categoryRepository->searchCategory($modifier['value']);
$result = $this->categoryRepository->searchCategory($modifier['value'], 25);
if ($result->count() > 0) {
$collector->setCategories($result);
}
break;
case 'bill':
$result = $this->billRepository->searchBill($modifier['value']);
$result = $this->billRepository->searchBill($modifier['value'], 25);
if ($result->count() > 0) {
$collector->setBills($result);
}
@@ -243,7 +248,7 @@ class Search implements SearchInterface
break;
break;
case 'budget':
$result = $this->budgetRepository->searchBudget($modifier['value']);
$result = $this->budgetRepository->searchBudget($modifier['value'], 25);
if ($result->count() > 0) {
$collector->setBudgets($result);
}
@@ -298,6 +303,12 @@ class Search implements SearchInterface
$updatedAt = new Carbon($modifier['value']);
$collector->setUpdatedAt($updatedAt);
break;
case 'external_id':
$collector->setExternalId($modifier['value']);
break;
case 'internal_reference':
$collector->setInternalReference($modifier['value']);
break;
}
}
$collector->setAccounts($totalAccounts);

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types=1);
/**
* TransactionSearch.php
* Copyright (c) 2019 james@firefly-iii.org
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Search;
use Illuminate\Support\Collection;

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types=1);
/**
* TransferSearch.php
* Copyright (c) 2019 james@firefly-iii.org
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Search;

View File

@@ -234,17 +234,17 @@ class Steam
$modified = null === $entry->modified ? '0' : (string)$entry->modified;
$foreignModified = null === $entry->modified_foreign ? '0' : (string)$entry->modified_foreign;
$amount = '0';
if ($currencyId === (int)$entry->transaction_currency_id || 0 === $currencyId) {
if ($currencyId === (int) $entry->transaction_currency_id || 0 === $currencyId) {
// use normal amount:
$amount = $modified;
}
if ($currencyId === (int)$entry->foreign_currency_id) {
if ($currencyId === (int) $entry->foreign_currency_id) {
// use foreign amount:
$amount = $foreignModified;
}
$currentBalance = bcadd($currentBalance, $amount);
$carbon = new Carbon($entry->date);
$carbon = new Carbon($entry->date, config('app.timezone'));
$date = $carbon->format('Y-m-d');
$balances[$date] = $currentBalance;
}
@@ -362,8 +362,10 @@ class Steam
* Remove weird chars from strings.
*
* @param string $string
* TODO migrate to trait.
*
* @return string
* @deprecated
*/
public function cleanString(string $string): string
{
@@ -425,8 +427,10 @@ class Steam
* Remove weird chars from strings, but keep newlines and tabs.
*
* @param string $string
* TODO migrate to trait.
*
* @return string
* @deprecated
*/
public function nlCleanString(string $string): string
{
@@ -502,7 +506,9 @@ class Steam
->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) AS max_date')]);
foreach ($set as $entry) {
$list[(int)$entry->account_id] = new Carbon($entry->max_date);
$date = new Carbon($entry->max_date,'UTC');
$date->setTimezone(config('app.timezone'));
$list[(int)$entry->account_id] = $date;
}
return $list;
@@ -619,6 +625,7 @@ class Steam
return [
sprintf('%s.utf8', $locale),
sprintf('%s.UTF-8', $locale),
str_replace('_', '-', $locale), // for Windows.
];
}

View File

@@ -1,8 +1,30 @@
<?php
/**
* GeneratesInstallationId.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\System;
use FireflyIII\Exceptions\FireflyException;
use Log;
use Ramsey\Uuid\Uuid;
@@ -16,10 +38,21 @@ trait GeneratesInstallationId
*/
protected function generateInstallationId(): void
{
try {
$config = app('fireflyconfig')->get('installation_id', null);
} catch(FireflyException $e) {
Log::info('Could not create or generate installation ID. Do not continue.');
return;
}
// delete if wrong UUID:
if (null !== $config && 'b2c27d92-be90-5c10-8589-005df5b314e6' === $config->data) {
$config = null;
}
if (null === $config) {
$uuid5 = Uuid::uuid5(Uuid::NAMESPACE_URL, 'firefly-iii.org');
$uniqueId = (string) $uuid5;
$uuid4 = Uuid::uuid4();
$uniqueId = (string) $uuid4;
Log::info(sprintf('Created Firefly III installation ID %s', $uniqueId));
app('fireflyconfig')->set('installation_id', $uniqueId);
}

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types=1);
/**
* OAuthKeys.php
* Copyright (c) 2019 james@firefly-iii.org
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\System;
use Artisan;

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types=1);
/**
* Telemetry.php
* Copyright (c) 2020 thegrumpydictator@gmail.com
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support;
use Carbon\Carbon;

View File

@@ -60,6 +60,7 @@ class General extends AbstractExtension
$this->activeRouteStrict(),
$this->activeRoutePartial(),
$this->activeRoutePartialObjectType(),
$this->menuOpenRoutePartial(),
$this->formatDate(),
$this->getMetaField(),
$this->hasRole(),
@@ -89,9 +90,32 @@ class General extends AbstractExtension
);
}
/**
* Will return "menu-open" when a part of the route matches the argument.
* ie. "accounts" will match "accounts.index".
*
* @return TwigFunction
*/
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 (!(false === strpos($name, $route))) {
return 'menu-open';
}
return '';
}
);
}
/**
* This function will return "active" when the current route matches the first argument (even partly)
* but, the variable $what has been set and matches the second argument.
* but, the variable $objectType has been set and matches the second argument.
*
* @return TwigFunction
*/

View File

@@ -286,6 +286,12 @@ class TransactionGroupTwig extends AbstractExtension
$sourceAccountId = $journal['source_account_id'];
$amount = $this->signAmount($amount, $type, $destinationType, $sourceAccountId, $account->id);
// withdrawals are negative
if ($type !== TransactionType::WITHDRAWAL) {
$amount = bcmul($amount, '-1');
}
if ($type === TransactionType::TRANSFER) {
$colored = false;
}
@@ -345,9 +351,8 @@ class TransactionGroupTwig extends AbstractExtension
$amount = bcmul($amount, '-1');
}
// negative opening balance
if ($type === TransactionType::OPENING_BALANCE)
if (AccountType::INITIAL_BALANCE === $destinationType) {
// opening balance and it goes to initial balance? its expense.
if ($type === TransactionType::OPENING_BALANCE && AccountType::INITIAL_BALANCE === $destinationType) {
$amount = bcmul($amount, '-1');
}
@@ -357,6 +362,11 @@ class TransactionGroupTwig extends AbstractExtension
$amount = bcmul($amount, '-1');
}
// reconciliation and it goes to reconciliation?
if ($type === TransactionType::RECONCILIATION && AccountType::RECONCILIATION === $destinationType) {
$amount = bcmul($amount, '-1');
}
return $amount;
}
}