Compare commits

..

66 Commits

Author SHA1 Message Date
James Cole
b398924706 Merge branch 'release/5.1.1' 2020-03-13 19:39:59 +01:00
James Cole
d6f46e23c5 update libraries. 2020-03-13 19:36:12 +01:00
James Cole
8b4066a981 Update language strings. 2020-03-13 19:32:58 +01:00
James Cole
f8a695d7e1 update language strings. 2020-03-13 19:32:11 +01:00
James Cole
630b24716d update changelog 2020-03-13 19:25:33 +01:00
James Cole
be41049e72 Remove some debug. 2020-03-13 18:18:07 +01:00
James Cole
b726e7d106 Improved account list for #2768 2020-03-13 18:17:53 +01:00
James Cole
1058bcd31d Add liabilities to home screen #2768 2020-03-13 15:47:26 +01:00
James Cole
8724ba05ca Fix #2672 2020-03-13 12:46:52 +01:00
James Cole
5145707b94 Test facades. 2020-03-13 08:02:38 +01:00
James Cole
3746cb8c71 Some updated translations. 2020-03-13 07:25:27 +01:00
James Cole
624f2d0bfd Update language strings. 2020-03-13 06:39:25 +01:00
James Cole
1c397ad0ad Add revolut import tool. 2020-03-13 06:23:01 +01:00
James Cole
a2c0f0666f Update transaction form 2020-03-13 06:22:50 +01:00
James Cole
72a4e86e2f Throw better exception. 2020-03-12 05:11:19 +01:00
James Cole
5e8d94d16a Better handling of errors. 2020-03-12 05:06:42 +01:00
James Cole
d92b741088 Remove debug 2020-03-11 14:18:20 +01:00
James Cole
2a4107940f Test debug 2020-03-11 14:17:20 +01:00
James Cole
16d5282929 Better count of transactions. 2020-03-11 14:14:51 +01:00
James Cole
f6ecb143fe Catch some more weird exceptions. 2020-03-11 10:39:53 +01:00
James Cole
f375934b41 Prep for a new auto-complete, and enable foreign currency select. 2020-03-11 10:25:38 +01:00
James Cole
9f8bf6d495 A bunch of empty pages as a place-holder for future telemetry efforts. 2020-03-11 08:13:23 +01:00
James Cole
9e9b55da8e Slightly improved help text. 2020-03-11 07:31:37 +01:00
James Cole
c1654f1d04 Fix #3160 2020-03-11 06:50:23 +01:00
James Cole
c526c4ae1a Update language strings. 2020-03-11 06:44:31 +01:00
James Cole
dcf1609c61 Enable Greek again #3176 2020-03-11 06:41:20 +01:00
James Cole
dd84c7f966 Fix #3182 2020-03-11 06:41:02 +01:00
James Cole
bccde86c0c Do some extra validation, fixes #3182 2020-03-11 05:45:37 +01:00
James Cole
2f17c45b3f Update version. 2020-03-10 19:53:56 +01:00
James Cole
b6bab003cd Clean up and optimize fonts. 2020-03-10 19:51:26 +01:00
James Cole
27d7eb4832 Enable file import because 5.1.1 will probably preclude 5.2.0 2020-03-10 19:24:30 +01:00
James Cole
774f7d88c2 Code for #3180 2020-03-10 18:29:27 +01:00
James Cole
086e4d5880 Fix #3177 2020-03-09 18:25:47 +01:00
James Cole
50f4bf568b Auto enable HIBP check 2020-03-09 18:13:20 +01:00
James Cole
c218c70b1f Make modifiers case insensitive. 2020-03-09 18:13:04 +01:00
James Cole
a939a5ba30 Extra code for #3172 2020-03-08 06:20:27 +01:00
James Cole
25be550e6d Disable file import, add links. 2020-03-07 12:47:22 +01:00
James Cole
9331f8985a Fix #3172 2020-03-07 12:09:18 +01:00
James Cole
41e6c8f73e Fix #3173 2020-03-06 18:23:08 +01:00
James Cole
a29f8d5849 Push minimum password length to 16 characters. 2020-03-06 18:22:44 +01:00
James Cole
9817c0807a Add ability to disable running of rules on transactions. 2020-03-06 09:41:27 +01:00
James Cole
4818baee39 Merge tag '5.1.0' into develop
5.1.0
2020-03-06 05:27:28 +01:00
James Cole
379916ee08 Merge branch 'release/5.1.0' 2020-03-06 05:27:27 +01:00
James Cole
60dbd51259 New meta files and translations. 2020-03-06 05:25:36 +01:00
James Cole
0927b0c8ae Accept strings like a good boy. 2020-03-03 19:36:03 +01:00
James Cole
40cc510057 Fix bad location storage. 2020-02-29 14:08:11 +01:00
James Cole
d580bf8f43 Fix untranslatable strings #3159 2020-02-29 13:58:34 +01:00
James Cole
4434ea40ee Add info about timezones. 2020-02-29 13:58:17 +01:00
James Cole
2ba6ffccbc Catch null pointers 2020-02-28 18:56:27 +01:00
James Cole
ec335ae88c Fix possible null pointer. 2020-02-28 18:53:20 +01:00
James Cole
542c64154c Fix for #3154 2020-02-27 17:35:21 +01:00
James Cole
e4fa437f78 Update libraries. 2020-02-26 20:29:04 +01:00
James Cole
61f3f995ad Update changelog, version and strings. 2020-02-26 20:28:54 +01:00
James Cole
8182cf1efa New texts. 2020-02-26 19:50:36 +01:00
James Cole
10d578503c Merge pull request #3146 from rubenverhoef/ING-CSV
Better CSV import for ING (NL)
2020-02-25 19:21:02 +00:00
Ruben Verhoef
75ee331f36 Move "Valutadatum" to own column
The new column has no name right now, need to fix this
2020-02-23 18:46:17 +01:00
Ruben Verhoef
a32c484fd5 fix typo row -> column 2020-02-23 18:45:56 +01:00
Ruben Verhoef
62d74e05f2 Fix saving accounts copy of account number to row 1 and name to row 3 2020-02-23 18:45:36 +01:00
Ruben Verhoef
23ec5cbaa7 Remove "Omschrijving" but not it's value, only the word. 2020-02-23 18:39:39 +01:00
Ruben Verhoef
21fcb6f853 Correctly remove the "Naam" and it's value 2020-02-23 18:38:21 +01:00
Ruben Verhoef
9b0a890531 Add also "Divers" transaction type 2020-02-23 18:37:41 +01:00
James Cole
af503597e6 Fix #3145 2020-02-23 14:01:08 +01:00
James Cole
e0c99fa80c Fix #3145 2020-02-23 13:15:48 +01:00
James Cole
a486d65893 Fix #3145 2020-02-23 12:42:28 +01:00
James Cole
08a6e22556 Fix redirect, courtesy of @SuperSandro2000 2020-02-23 11:11:38 +01:00
James Cole
2d5c4c8101 Merge tag '5.1.0-beta.1' into develop
5.1.0-beta.1
2020-02-23 07:47:40 +01:00
299 changed files with 2877 additions and 1562 deletions

View File

@@ -24,6 +24,7 @@ DEFAULT_LANGUAGE=en_US
# Change this value to your preferred time zone.
# Example: Europe/Amsterdam
# For a list of supported time zones, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
TZ=Europe/Amsterdam
# This variable must match your installation's external address but keep in mind that

View File

@@ -29,6 +29,7 @@ use FireflyIII\Api\V1\Requests\TransactionUpdateRequest;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
@@ -294,10 +295,23 @@ class TransactionController extends Controller
],
];
return response()->json($response, 422);
} catch(FireflyException $e) {
Log::warning('Caught an exception. Return error message.');
Log::error($e->getMessage());
// return bad validation message.
// TODO use Laravel's internal validation thing to do this.
$response = [
'message' => 'The given data was invalid.',
'errors' => [
'transactions.0.description' => [sprintf('Internal exception: %s',$e->getMessage())],
],
];
return response()->json($response, 422);
}
app('preferences')->mark();
event(new StoredTransactionGroup($transactionGroup));
event(new StoredTransactionGroup($transactionGroup, $data['apply_rules'] ?? true));
$manager = $this->getManager();
/** @var User $admin */
@@ -341,7 +355,7 @@ class TransactionController extends Controller
$manager = $this->getManager();
app('preferences')->mark();
event(new UpdatedTransactionGroup($transactionGroup));
event(new UpdatedTransactionGroup($transactionGroup, $data['apply_rules'] ?? true));
/** @var User $admin */
$admin = auth()->user();

View File

@@ -47,6 +47,7 @@ class TransactionStoreRequest extends Request
public function authorize(): bool
{
Log::debug('Authorize TransactionStoreRequest');
// Only allow authenticated users
return auth()->check();
}
@@ -62,6 +63,7 @@ class TransactionStoreRequest extends Request
$data = [
'group_title' => $this->string('group_title'),
'error_if_duplicate_hash' => $this->boolean('error_if_duplicate_hash'),
'apply_rules' => $this->boolean('apply_rules', true),
'transactions' => $this->getTransactionData(),
];
@@ -80,6 +82,7 @@ class TransactionStoreRequest extends Request
// basic fields for group:
'group_title' => 'between:1,1000|nullable',
'error_if_duplicate_hash' => [new IsBoolean],
'apply_rules' => [new IsBoolean],
// transaction rules (in array for splits):
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
@@ -263,11 +266,11 @@ class TransactionStoreRequest extends Request
'tags' => $this->arrayFromValue($object['tags']),
// all custom fields:
'internal_reference' => $this->stringFromValue($object['internal_reference']),
'external_id' => $this->stringFromValue($object['external_id']),
'internal_reference' => $this->stringFromValue((string)$object['internal_reference']),
'external_id' => $this->stringFromValue((string)$object['external_id']),
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
'bunq_payment_id' => $this->stringFromValue($object['bunq_payment_id']),
'bunq_payment_id' => $this->stringFromValue((string)$object['bunq_payment_id']),
'sepa_cc' => $this->stringFromValue($object['sepa_cc']),
'sepa_ct_op' => $this->stringFromValue($object['sepa_ct_op']),

View File

@@ -138,6 +138,7 @@ class TransactionUpdateRequest extends Request
$data = [
'transactions' => $this->getTransactionData(),
'apply_rules' => $this->boolean('apply_rules', true),
];
if ($this->has('group_title')) {
$data['group_title'] = $this->string('group_title');
@@ -156,6 +157,7 @@ class TransactionUpdateRequest extends Request
$rules = [
// basic fields for group:
'group_title' => 'between:1,1000',
'apply_rules' => [new IsBoolean],
// transaction rules (in array for splits):
'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation',

View File

@@ -25,6 +25,9 @@ namespace FireflyIII\Console\Commands;
use Illuminate\Console\Command;
/**
* Class SetLatestVersion
*/
class SetLatestVersion extends Command
{
/**
@@ -53,19 +56,22 @@ class SetLatestVersion extends Command
/**
* Execute the console command.
*
* @return mixed
* @return int
*/
public function handle()
public function handle(): int
{
if (!$this->option('james-is-cool')) {
$this->error('Am too!');
return;
return 0;
}
app('fireflyconfig')->set('db_version', config('firefly.db_version'));
app('fireflyconfig')->set('ff3_version', config('firefly.version'));
$this->line('Updated version.');
//Telemetry::string('db_version', config('firefly.db_version'));
//Telemetry::string('ff3_version', config('firefly.version'));
return 0;
}
}

View File

@@ -36,6 +36,7 @@ class StoredTransactionGroup extends Event
{
use SerializesModels;
/** @var bool */
public $applyRules;
/** @var TransactionGroup The group that was stored. */
public $transactionGroup;

View File

@@ -37,6 +37,8 @@ class UpdatedTransactionGroup extends Event
{
use SerializesModels;
/** @var bool */
public $applyRules;
/** @var TransactionGroup The group that was stored. */
public $transactionGroup;
@@ -45,8 +47,9 @@ class UpdatedTransactionGroup extends Event
*
* @param TransactionGroup $transactionGroup
*/
public function __construct(TransactionGroup $transactionGroup)
public function __construct(TransactionGroup $transactionGroup, bool $applyRules = true)
{
$this->transactionGroup = $transactionGroup;
$this->applyRules = $applyRules;
}
}

View File

@@ -30,6 +30,7 @@ use FireflyIII\Models\Attachment;
use FireflyIII\Models\Bill;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\User;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Request;
@@ -225,6 +226,10 @@ class GracefulNotFoundHandler extends ExceptionHandler
$type = $journal->transactionType->type;
$request->session()->reflash();
if (TransactionType::RECONCILIATION === $type) {
return redirect(route('accounts.index', ['asset']));
}
return redirect(route('transactions.index', [strtolower($type)]));
}

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Factory;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
@@ -102,12 +103,17 @@ class TransactionFactory
/**
* Create transaction with negative amount (for source accounts).
*
* @param string $amount
* @param string $amount
* @param string|null $foreignAmount
* @return Transaction|null
*
* @return Transaction
* @throws FireflyException
*/
public function createNegative(string $amount, ?string $foreignAmount): ?Transaction
public function createNegative(string $amount, ?string $foreignAmount): Transaction
{
if ('' === $foreignAmount) {
$foreignAmount = null;
}
if (null !== $foreignAmount) {
$foreignAmount = app('steam')->negative($foreignAmount);
}
@@ -118,16 +124,20 @@ class TransactionFactory
/**
* Create transaction with positive amount (for destination accounts).
*
* @param string $amount
* @param string $amount
* @param string|null $foreignAmount
* @return Transaction|null
*
* @return Transaction
* @throws FireflyException
*/
public function createPositive(string $amount, ?string $foreignAmount): ?Transaction
public function createPositive(string $amount, ?string $foreignAmount): Transaction
{
if ('' === $foreignAmount) {
$foreignAmount = null;
}
if (null !== $foreignAmount) {
$foreignAmount = app('steam')->positive($foreignAmount);
}
return $this->create(app('steam')->positive($amount), $foreignAmount);
}
@@ -151,14 +161,19 @@ class TransactionFactory
}
/**
* @param string $amount
* @param string $amount
* @param string|null $foreignAmount
* @return Transaction|null
*
* @return Transaction
* @throws FireflyException
*/
private function create(string $amount, ?string $foreignAmount): ?Transaction
private function create(string $amount, ?string $foreignAmount): Transaction
{
$result = null;
$data = [
if ('' === $foreignAmount) {
$foreignAmount = null;
}
$data = [
'reconciled' => $this->reconciled,
'account_id' => $this->account->id,
'transaction_journal_id' => $this->journal->id,
@@ -174,6 +189,12 @@ class TransactionFactory
// @codeCoverageIgnoreStart
} catch (QueryException $e) {
Log::error(sprintf('Could not create transaction: %s', $e->getMessage()), $data);
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException('Query exception when creating transaction.');
}
if (null === $result) {
throw new FireflyException('Transaction is NULL.');
}
// @codeCoverageIgnoreEnd
if (null !== $result) {
@@ -185,7 +206,7 @@ class TransactionFactory
);
// do foreign currency thing: add foreign currency info to $one and $two if necessary.
if (null !== $this->foreignCurrency && null !== $foreignAmount && $this->foreignCurrency->id !== $this->currency->id) {
if (null !== $this->foreignCurrency && null !== $foreignAmount && $this->foreignCurrency->id !== $this->currency->id && '' !== $foreignAmount) {
$result->foreign_currency_id = $this->foreignCurrency->id;
$result->foreign_amount = $foreignAmount;

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Factory;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\User;
use Log;
@@ -60,7 +61,6 @@ class TransactionGroupFactory
{
$this->journalFactory->setUser($this->user);
$this->journalFactory->setErrorOnHash($data['error_if_duplicate_hash'] ?? false);
try {
$collection = $this->journalFactory->create($data);
} catch(DuplicateTransactionException $e) {

View File

@@ -29,6 +29,7 @@ use Exception;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalMeta;
@@ -40,6 +41,7 @@ use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\User;
@@ -125,6 +127,7 @@ class TransactionJournalFactory
*
* @return Collection
* @throws DuplicateTransactionException
* @throws FireflyException
*/
public function create(array $data): Collection
{
@@ -139,24 +142,30 @@ class TransactionJournalFactory
return new Collection;
}
/** @var array $row */
foreach ($transactions as $index => $row) {
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
Log::debug('Going to call createJournal', $row);
try {
try {
/** @var array $row */
foreach ($transactions as $index => $row) {
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
$journal = $this->createJournal(new NullArrayObject($row));
} catch (DuplicateTransactionException|Exception $e) {
Log::warning('TransactionJournalFactory::create() caught a duplicate journal in createJournal()');
throw new DuplicateTransactionException($e->getMessage());
}
if (null !== $journal) {
$collection->push($journal);
}
if (null === $journal) {
Log::error('The createJournal() method returned NULL. This may indicate an error.');
if (null !== $journal) {
$collection->push($journal);
}
if (null === $journal) {
Log::error('The createJournal() method returned NULL. This may indicate an error.');
}
}
} catch (DuplicateTransactionException $e) {
Log::warning('TransactionJournalFactory::create() caught a duplicate journal in createJournal()');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->forceDeleteOnError($collection);
throw new DuplicateTransactionException($e->getMessage());
} catch (FireflyException $e) {
Log::warning('TransactionJournalFactory::create() caught an exception.');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->forceDeleteOnError($collection);
throw new FireflyException($e->getMessage());
}
return $collection;
@@ -204,7 +213,7 @@ class TransactionJournalFactory
* @param NullArrayObject $row
*
* @return TransactionJournal|null
* @throws Exception
* @throws FireflyException
* @throws DuplicateTransactionException
*/
private function createJournal(NullArrayObject $row): ?TransactionJournal
@@ -232,36 +241,33 @@ class TransactionJournalFactory
try {
// validate source and destination using a new Validator.
$this->validateAccounts($row);
/** create or get source and destination accounts */
$sourceInfo = [
'id' => (int)$row['source_id'],
'name' => $row['source_name'],
'iban' => $row['source_iban'],
'number' => $row['source_number'],
'bic' => $row['source_bic'],
];
$destInfo = [
'id' => (int)$row['destination_id'],
'name' => $row['destination_name'],
'iban' => $row['destination_iban'],
'number' => $row['destination_number'],
'bic' => $row['destination_bic'],
];
Log::debug('Source info:', $sourceInfo);
Log::debug('Destination info:', $destInfo);
$sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
// @codeCoverageIgnoreStart
} catch (FireflyException $e) {
Log::error('Could not validate source or destination.');
Log::error($e->getMessage());
return null;
}
// @codeCoverageIgnoreEnd
/** create or get source and destination accounts */
$sourceInfo = [
'id' => (int)$row['source_id'],
'name' => $row['source_name'],
'iban' => $row['source_iban'],
'number' => $row['source_number'],
'bic' => $row['source_bic'],
];
$destInfo = [
'id' => (int)$row['destination_id'],
'name' => $row['destination_name'],
'iban' => $row['destination_iban'],
'number' => $row['destination_number'],
'bic' => $row['destination_bic'],
];
Log::debug('Source info:', $sourceInfo);
Log::debug('Destination info:', $destInfo);
$sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
// TODO AFTER 4.8,0 better handling below:
@@ -337,7 +343,15 @@ class TransactionJournalFactory
$transactionFactory->setCurrency($sourceCurrency);
$transactionFactory->setForeignCurrency($sourceForeignCurrency);
$transactionFactory->setReconciled($row['reconciled'] ?? false);
$transactionFactory->createNegative((string)$row['amount'], (string)$row['foreign_amount']);
try {
$negative = $transactionFactory->createNegative((string)$row['amount'], (string)$row['foreign_amount']);
} catch (FireflyException $e) {
Log::error('Exception creating negative transaction.');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->forceDeleteOnError(new Collection([$journal]));
throw new FireflyException($e->getMessage());
}
// and the destination one:
/** @var TransactionFactory $transactionFactory */
@@ -348,7 +362,18 @@ class TransactionJournalFactory
$transactionFactory->setCurrency($destCurrency);
$transactionFactory->setForeignCurrency($destForeignCurrency);
$transactionFactory->setReconciled($row['reconciled'] ?? false);
$transactionFactory->createPositive((string)$row['amount'], (string)$row['foreign_amount']);
try {
$transactionFactory->createPositive((string)$row['amount'], (string)$row['foreign_amount']);
} catch (FireflyException $e) {
Log::error('Exception creating positive transaction.');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
Log::warning('Delete negative transaction.');
$this->forceTrDelete($negative);
$this->forceDeleteOnError(new Collection([$journal]));
throw new FireflyException($e->getMessage());
}
// verify that journal has two transactions. Otherwise, delete and cancel.
// TODO this can't be faked so it can't be tested.
@@ -418,6 +443,37 @@ class TransactionJournalFactory
}
}
/**
* Force the deletion of an entire set of transaction journals and their meta object in case of
* an error creating a group.
*
* @param Collection $collection
*/
private function forceDeleteOnError(Collection $collection): void
{
Log::debug(sprintf('forceDeleteOnError on collection size %d item(s)', $collection->count()));
$service = app(JournalDestroyService::class);
/** @var TransactionJournal $journal */
foreach ($collection as $journal) {
Log::debug(sprintf('forceDeleteOnError on journal #%d', $journal->id));
$service->destroy($journal);
}
}
/**
* @param Transaction $transaction
*/
private function forceTrDelete(Transaction $transaction): void
{
try {
$transaction->delete();
} catch (Exception $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
Log::error('Could not delete negative transaction.');
}
}
/**
* @param TransactionCurrency|null $currency
* @param Account $account

View File

@@ -35,21 +35,23 @@ class StoredGroupEventHandler
/**
* This method grabs all the users rules and processes them.
*
* @param StoredTransactionGroup $storedJournalEvent
* @param StoredTransactionGroup $storedGroupEvent
*/
public function processRules(StoredTransactionGroup $storedJournalEvent): void
public function processRules(StoredTransactionGroup $storedGroupEvent): void
{
if (false === $storedJournalEvent->applyRules) {
if (false === $storedGroupEvent->applyRules) {
Log::info(sprintf('Will not run rules on group #%d', $storedGroupEvent->transactionGroup->id));
return;
}
Log::debug('Now in StoredGroupEventHandler::processRules()');
/** @var RuleEngine $ruleEngine */
$ruleEngine = app(RuleEngine::class);
$ruleEngine->setUser($storedJournalEvent->transactionGroup->user);
$ruleEngine->setUser($storedGroupEvent->transactionGroup->user);
$ruleEngine->setAllRules(true);
$ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE);
$journals = $storedJournalEvent->transactionGroup->transactionJournals;
$journals = $storedGroupEvent->transactionGroup->transactionJournals;
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {

View File

@@ -35,16 +35,22 @@ class UpdatedGroupEventHandler
/**
* This method will check all the rules when a journal is updated.
*
* @param UpdatedTransactionGroup $updatedJournalEvent
* @param UpdatedTransactionGroup $updatedGroupEvent
*/
public function processRules(UpdatedTransactionGroup $updatedJournalEvent): void
public function processRules(UpdatedTransactionGroup $updatedGroupEvent): void
{
if (false === $updatedGroupEvent->applyRules) {
Log::info(sprintf('Will not run rules on group #%d', $updatedGroupEvent->transactionGroup->id));
return;
}
/** @var RuleEngine $ruleEngine */
$ruleEngine = app(RuleEngine::class);
$ruleEngine->setUser($updatedJournalEvent->transactionGroup->user);
$ruleEngine->setUser($updatedGroupEvent->transactionGroup->user);
$ruleEngine->setAllRules(true);
$ruleEngine->setTriggerMode(RuleEngine::TRIGGER_UPDATE);
$journals = $updatedJournalEvent->transactionGroup->transactionJournals;
$journals = $updatedGroupEvent->transactionGroup->transactionJournals;
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {

View File

@@ -0,0 +1,78 @@
<?php
/**
* TelemetryController.php
* Copyright (c) 2020 thegrumpydictator@gmail.com
*
* 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/>.
*/
namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Http\Controllers\Controller;
/**
* Class TelemetryController
*/
class TelemetryController extends Controller
{
public function __construct()
{
parent::__construct();
$this->middleware(
static function ($request, $next) {
app('view')->share('title', (string)trans('firefly.administration'));
app('view')->share('mainTitleIcon', 'fa-hand-spock-o');
return $next($request);
}
);
}
/**
* @return string
*/
public function delete()
{
session()->flash('info', 'No telemetry to delete. Does not work yet.');
return redirect(route('admin.telemetry.index'));
}
/**
*
*/
public function index()
{
app('view')->share('subTitleIcon', 'fa-eye');
app('view')->share('subTitle', (string)trans('firefly.telemetry_admin_index'));
$version = config('firefly.version');
$enabled = config('firefly.telemetry', false);
$count = 1;
return view('admin.telemetry.index', compact('version', 'enabled', 'count'));
}
/**
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function view()
{
return view('admin.telemetry.view');
}
}

View File

@@ -78,7 +78,7 @@ class ResetPasswordController extends Controller
$rules = [
'token' => 'required',
'email' => 'required|email',
'password' => 'required|confirmed|min:6|secure_password',
'password' => 'required|confirmed|min:16|secure_password',
];
$this->validate($request, $rules, $this->validationErrorMessages());

View File

@@ -34,6 +34,20 @@ use Preferences;
*/
class TwoFactorController extends Controller
{
/**
* What to do if 2FA lost?
*
* @return mixed
*/
public function lostTwoFactor()
{
/** @var User $user */
$user = auth()->user();
$siteOwner = config('firefly.site_owner');
$title = (string)trans('firefly.two_factor_forgot_title');
return view('auth.lost-two-factor', compact('user', 'siteOwner', 'title'));
}
/**
* @param Request $request
*
@@ -118,27 +132,6 @@ class TwoFactorController extends Controller
Preferences::set('mfa_history', $newHistory);
}
/**
* What to do if 2FA lost?
*
* @return mixed
*/
public function lostTwoFactor()
{
/** @var User $user */
$user = auth()->user();
$siteOwner = config('firefly.site_owner');
$title = (string)trans('firefly.two_factor_forgot_title');
Log::info(
'To reset the two factor authentication for user #' . $user->id .
' (' . $user->email . '), simply open the "preferences" table and delete the entries with the names "twoFactorAuthEnabled" and' .
' "twoFactorAuthSecret" for user_id ' . $user->id . '. That will take care of it.'
);
return view('auth.lost-two-factor', compact('user', 'siteOwner', 'title'));
}
/**
* Each MFA history has a timestamp and a code, saving the MFA entries for 5 minutes. So if the
* submitted MFA code has been submitted in the last 5 minutes, it won't work despite being valid.

View File

@@ -114,7 +114,7 @@ class CategoryController extends Controller
$cache->addProperty($end);
$cache->addProperty('chart.category.frontpage');
if ($cache->has()) {
return response()->json($cache->get()); // @codeCoverageIgnore
// return response()->json($cache->get()); // @codeCoverageIgnore
}
// currency repos:
@@ -163,22 +163,24 @@ class CategoryController extends Controller
// no category per currency:
$noCategory = $noCatRepository->sumExpenses($start, $end);
if (0 !== bccomp($noCategory[0]['sum'] ?? '0', '0')) {
foreach ($noCategory as $currency) {
$currencyId = $currency['currency_id'];
$currencies[$currencyId] = $currencies[$currencyId] ?? [
'currency_id' => $currency['currency_id'],
'currency_name' => $currency['currency_name'],
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_decimal_places' => $currency['currency_decimal_places'],
foreach ($noCategory as $currency) {
$currencyId = $currency['currency_id'];
$currencies[$currencyId] = $currencies[$currencyId] ?? [
'currency_id' => $currency['currency_id'],
'currency_name' => $currency['currency_name'],
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_decimal_places' => $currency['currency_decimal_places'],
];
$tempData[] = [
'name' => trans('firefly.no_category'),
'sum' => $currency['sum'],
'sum_float' => round($currency['sum'], $currency['currency_decimal_places'] ?? 2),
'currency_id' => $currency['currency_id'],
];
$tempData[] = [
'name' => trans('firefly.no_category'),
'sum' => $currency['sum'],
'sum_float' => round($currency['sum'], $currency['currency_decimal_places']),
'currency_id' => $currency['currency_id'],
];
}
}
// sort temp array by amount.
@@ -203,6 +205,7 @@ class CategoryController extends Controller
$name = $entry['name'];
$chartData[$currencyId]['entries'][$name] = bcmul($entry['sum'], '-1');
}
$data = $this->generator->multiSet($chartData);
$cache->store($data);

View File

@@ -59,8 +59,6 @@ class BoxController extends Controller
*/
public function available(): JsonResponse
{
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
/** @var OperationsRepositoryInterface $opsRepository */
$opsRepository = app(OperationsRepositoryInterface::class);
/** @var AvailableBudgetRepositoryInterface $abRepository */
@@ -102,13 +100,12 @@ class BoxController extends Controller
$spent = $opsRepository->sumExpenses($start, $end, null, null, $currency);
$spentAmount = $spent[(int)$currency->id]['sum'] ?? '0';
$spentPerDay = '-1';
if (1 === $availableBudgets->count()) {
if ($availableBudgets->count() > 0) {
$display = 0; // assume user overspent
$boxTitle = (string)trans('firefly.overspent');
/** @var AvailableBudget $availableBudget */
$availableBudget = $availableBudgets->first();
$totalAvailableSum = (string)$availableBudgets->sum('amount');
// calculate with available budget.
$leftToSpendAmount = bcadd($availableBudget->amount, $spentAmount);
$leftToSpendAmount = bcadd($totalAvailableSum, $spentAmount);
if (1 === bccomp($leftToSpendAmount, '0')) {
$boxTitle = (string)trans('firefly.left_to_spend');
$days = $today->diffInDays($end) + 1;
@@ -185,7 +182,7 @@ class BoxController extends Controller
foreach ($set as $journal) {
$currencyId = (int)$journal['currency_id'];
$expenses[$currencyId] = $expenses[$currencyId] ?? '0';
$expenses[$currencyId] = bcadd($expenses[$currencyId], $journal['amount']);
$expenses[$currencyId] = bcadd($expenses[$currencyId], $journal['amount'] ?? '0');
$sums[$currencyId] = $sums[$currencyId] ?? '0';
$sums[$currencyId] = bcadd($sums[$currencyId], $journal['amount']);
}

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Preference;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@@ -59,10 +60,29 @@ class PreferencesController extends Controller
*/
public function index(AccountRepositoryInterface $repository)
{
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
// group accounts
$groupedAccounts = [];
/** @var Account $account */
foreach ($accounts as $account) {
$type = $account->accountType->type;
$role = sprintf('opt_group_%s', $repository->getMetaValue($account, 'account_role'));
if (in_array($type, [AccountType::MORTGAGE, AccountType::DEBT, AccountType::LOAN], true)) {
$role = sprintf('opt_group_l_%s',$type);
}
if ('' === $role || 'opt_group_' === $role) {
$role = 'opt_group_defaultAsset';
}
$groupedAccounts[trans(sprintf('firefly.%s',$role))][$account->id] = $account->name;
}
ksort($groupedAccounts);
$accountIds = $accounts->pluck('id')->toArray();
$viewRangePref = app('preferences')->get('viewRange', '1M');
/** @noinspection NullPointerExceptionInspection */
$viewRange = $viewRangePref->data;
$frontPageAccounts = app('preferences')->get('frontPageAccounts', $accountIds);
$language = app('preferences')->get('language', config('firefly.default_language', 'en_US'))->data;
@@ -82,7 +102,7 @@ class PreferencesController extends Controller
'preferences.index',
compact(
'language',
'accounts',
'groupedAccounts',
'frontPageAccounts',
'tjOptionalFields',
'viewRange',
@@ -135,7 +155,7 @@ class PreferencesController extends Controller
// language:
/** @var Preference $currentLang */
$currentLang = app('preferences')->get('language', 'en_US');
$lang = $request->get('language');
$lang = $request->get('language');
if (array_key_exists($lang, config('firefly.languages'))) {
app('preferences')->set('language', $lang);
}

View File

@@ -27,6 +27,7 @@ use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorFactory;
use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Http\Requests\ReportFormRequest;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
@@ -262,11 +263,32 @@ class ReportController extends Controller
$start = clone session('first');
$months = $this->helper->listOfMonths($start);
$customFiscalYear = app('preferences')->get('customFiscalYear', 0)->data;
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$accounts = $repository->getAccountsByType(
[AccountType::DEFAULT, AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE]
);
// group accounts by role:
$groupedAccounts = [];
/** @var Account $account */
foreach ($accounts as $account) {
$type = $account->accountType->type;
$role = sprintf('opt_group_%s', $repository->getMetaValue($account, 'account_role'));
if (in_array($type, [AccountType::MORTGAGE, AccountType::DEBT, AccountType::LOAN], true)) {
$role = sprintf('opt_group_l_%s',$type);
}
if ('' === $role || 'opt_group_' === $role) {
$role = 'opt_group_defaultAsset';
}
$groupedAccounts[trans(sprintf('firefly.%s',$role))][$account->id] = $account;
}
ksort($groupedAccounts);
$accountList = implode(',', $accounts->pluck('id')->toArray());
$this->repository->cleanupBudgets();
return view('reports.index', compact('months', 'accounts', 'start', 'accountList', 'customFiscalYear'));
return view('reports.index', compact('months', 'accounts', 'start', 'accountList','groupedAccounts', 'customFiscalYear'));
}
/**

View File

@@ -90,6 +90,9 @@ class CreateController extends Controller
{
app('preferences')->mark();
$sourceId = (int)request()->get('source');
$destinationId = (int)request()->get('destination');
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$cash = $repository->getCashAccount();
@@ -112,7 +115,7 @@ class CreateController extends Controller
'transactions.create', compact(
'subTitleIcon', 'cash', 'objectType', 'subTitle', 'defaultCurrency', 'previousUri', 'optionalFields', 'preFilled',
'allowedOpposingTypes',
'accountToTypes'
'accountToTypes','sourceId','destinationId'
)
);
}

View File

@@ -81,14 +81,14 @@ class ShowController extends Controller
public function show(Request $request, TransactionGroup $transactionGroup)
{
/** @var TransactionJournal $first */
$first = $transactionGroup->transactionJournals->first();
$splits = $transactionGroup->transactionJournals->count();
$first = $transactionGroup->transactionJournals()->first(['transaction_journals.*']);
$splits = $transactionGroup->transactionJournals()->count();
if(null === $first) {
throw new FireflyException('This transaction is broken :(.');
}
$type = $first->transactionType->type;
$type = (string)trans(sprintf('firefly.%s',$first->transactionType->type));
$title = 1 === $splits ? $first->description : $transactionGroup->title;
$subTitle = sprintf('%s: "%s"', $type, $title);

View File

@@ -347,6 +347,7 @@ class Request extends FormRequest
$longitudeKey = null === $prefix ? 'longitude' : sprintf('%s_longitude', $prefix);
$latitudeKey = null === $prefix ? 'latitude' : sprintf('%s_latitude', $prefix);
$zoomLevelKey = null === $prefix ? 'zoom_level' : sprintf('%s_zoom_level', $prefix);
$hasLocationKey = null === $prefix ? 'has_location' : sprintf('%s_has_location', $prefix);
// for a POST (store, all fields must be present and accounted for:
if (
@@ -354,7 +355,8 @@ class Request extends FormRequest
&& ($this->has($longitudeKey) && $this->has($latitudeKey) && $this->has($zoomLevelKey))
) {
Log::debug('Method is POST and all fields present.');
$data['store_location'] = true;
$data['store_location'] = $this->boolean($hasLocationKey);
$data['longitude'] = '' === $this->string($longitudeKey) ? null : $this->string($longitudeKey);
$data['latitude'] = '' === $this->string($latitudeKey) ? null : $this->string($latitudeKey);
$data['zoom_level'] = '' === $this->string($zoomLevelKey) ? null : $this->integer($zoomLevelKey);

View File

@@ -70,18 +70,22 @@ class IngDescription implements SpecificInterface
public function run(array $row): array
{
$this->row = array_values($row);
array_push($this->row); // New column for "Valutadatum"
if (count($this->row) >= 8) { // check if the array is correct
switch ($this->row[4]) { // Get value for the mutation type
case 'GT': // InternetBankieren
case 'OV': // Overschrijving
case 'VZ': // Verzamelbetaling
case 'IC': // Incasso
$this->removeIBANIngDescription();
$this->removeNameIngDescription();
// if "tegenrekening" empty, copy the description. Primitive, but it works.
$this->copyDescriptionToOpposite();
case 'DV': // Divers
$this->removeIBANIngDescription(); // Remove "IBAN:", because it is already at "Tegenrekening"
$this->removeNameIngDescription(); // Remove "Naam:", because it is already at "Naam/ Omschrijving"
$this->removeIngDescription(); // Remove "Omschrijving", but not the value from description
$this->moveValutadatumDescription(); // Move "Valutadatum" from description to new column
$this->MoveSavingsAccount(); // Move savings account number and name
break;
case 'BA': // Betaalautomaat
$this->moveValutadatumDescription(); // Move "Valutadatum" from description to new column
$this->addNameIngDescription();
break;
}
@@ -99,33 +103,60 @@ class IngDescription implements SpecificInterface
$this->row[8] = $this->row[1] . ' ' . $this->row[8];
}
/**
* Remove "Omschrijving" (and NOT its value) from the description.
*/
protected function removeIngDescription(): void
{
$this->row[8] = preg_replace('/Omschrijving: /', '', $this->row[8]);
}
/**
* Remove IBAN number out of the description
* Default description of Description is: Naam: <OPPOS NAME> Omschrijving: <DESCRIPTION> IBAN: <OPPOS IBAN NR>.
*/
protected function removeIBANIngDescription(): void
{
// Try replace the iban number with nothing. The IBAN nr is found in the third row
// Try replace the iban number with nothing. The IBAN nr is found in the third column
$this->row[8] = preg_replace('/\sIBAN:\s' . $this->row[3] . '/', '', $this->row[8]);
}
/**
* Remove name from the description (Remove everything before the description incl the word 'Omschrijving' ).
* Remove "Naam" (and its value) from the description.
*/
protected function removeNameIngDescription(): void
{
// Try remove everything before the 'Omschrijving'
$this->row[8] = preg_replace('/.+Omschrijving: /', '', $this->row[8]);
$this->row[8] = preg_replace('/Naam:.*?([a-zA-Z\/]+:)/', '$1', $this->row[8]);
}
/**
* Copy description to name of opposite account.
* Move "Valutadatum" from the description to new column.
*/
private function copyDescriptionToOpposite(): void
protected function moveValutadatumDescription(): void
{
$search = ['Naar Oranje Spaarrekening ', 'Afschrijvingen'];
$matches = array();
preg_match('/Valutadatum: ([0-9-]+)/', $this->row[8], $matches);
$this->row[9] = date("Ymd", strtotime($matches[1]));
$this->row[8] = preg_replace('/Valutadatum: [0-9-]+/', '', $this->row[8]);
}
/**
* Move savings account number to column 1 and name to column 3.
*/
private function MoveSavingsAccount(): void
{
$matches = array();
if ('' === (string)$this->row[3]) {
$this->row[3] = trim(str_ireplace($search, '', $this->row[8]));
if (preg_match('/(Naar|Van) (.*rekening) ([0-9]+)/', $this->row[8], $matches)) {
$matches[3] = sprintf("%010d", $matches[3]);
$this->row[1] = $matches[2]; // Savings account name
$this->row[3] = $matches[3]; // Savings account number
$this->row[8] = preg_replace('/(Naar|Van) (.*rekening) ([0-9]+)/', '', $this->row[8]); // Remove the savings account content from description
} elseif (preg_match('/(Naar|Van) (.*rekening) ([0-9]+)/', $this->row[1], $matches)) {
$matches[3] = sprintf("%010d", $matches[3]);
$this->row[1] = $matches[2]; // Savings account name
$this->row[3] = $matches[3]; // Savings account number
}
}
}
}

View File

@@ -58,6 +58,7 @@ use FireflyIII\Support\Form\RuleForm;
use FireflyIII\Support\Navigation;
use FireflyIII\Support\Preferences;
use FireflyIII\Support\Steam;
use FireflyIII\Support\Telemetry;
use FireflyIII\Validation\FireflyValidator;
use Illuminate\Support\ServiceProvider;
use Validator;
@@ -91,33 +92,33 @@ class FireflyServiceProvider extends ServiceProvider
{
$this->app->bind(
'preferences',
function () {
static function () {
return new Preferences;
}
);
$this->app->bind(
'fireflyconfig',
function () {
static function () {
return new FireflyConfig;
}
);
$this->app->bind(
'navigation',
function () {
static function () {
return new Navigation;
}
);
$this->app->bind(
'amount',
function () {
static function () {
return new Amount;
}
);
$this->app->bind(
'steam',
function () {
static function () {
return new Steam;
}
);
@@ -155,6 +156,13 @@ class FireflyServiceProvider extends ServiceProvider
}
);
$this->app->bind(
'telemetry',
static function () {
return new Telemetry;
}
);
// chart generator:
$this->app->bind(GeneratorInterface::class, ChartJsGenerator::class);

View File

@@ -473,7 +473,9 @@ class AccountRepository implements AccountRepositoryInterface
if (AccountType::ASSET !== $account->accountType->type) {
throw new FireflyException(sprintf('%s is not an asset account.', $account->name));
}
$name = $account->name . ' reconciliation';
$name = trans('firefly.reconciliation_account_name', ['name' => $account->name]);
/** @var AccountType $type */
$type = AccountType::where('type', AccountType::RECONCILIATION)->first();
$accounts = $this->user->accounts()->where('account_type_id', $type->id)->get();

View File

@@ -198,7 +198,7 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => $journal['currency_decimal_places'],
];
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount']));
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount'] ?? '0'));
}
return $array;

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Journal;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Note;
use FireflyIII\Models\Transaction;
@@ -386,6 +387,9 @@ class JournalRepository implements JournalRepositoryInterface
{
/** @var Transaction $transaction */
$transaction = $journal->transactions()->with('account')->where('amount', '<', 0)->first();
if (null === $transaction) {
throw new FireflyException(sprintf('Your administration is broken. Transaction journal #%d has no source transaction.', $journal->id));
}
return $transaction->account;
}
@@ -397,6 +401,9 @@ class JournalRepository implements JournalRepositoryInterface
{
/** @var Transaction $transaction */
$transaction = $journal->transactions()->with('account')->where('amount', '>', 0)->first();
if (null === $transaction) {
throw new FireflyException(sprintf('Your administration is broken. Transaction journal #%d has no destination transaction.', $journal->id));
}
return $transaction->account;
}

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Journal;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
@@ -114,7 +115,9 @@ interface JournalRepositoryInterface
* Returns the source account of the journal.
*
* @param TransactionJournal $journal
*
* @return Account
* @throws FireflyException
*/
public function getSourceAccount(TransactionJournal $journal): Account;
@@ -123,6 +126,7 @@ interface JournalRepositoryInterface
*
* @param TransactionJournal $journal
* @return Account
* @throws FireflyException
*/
public function getDestinationAccount(TransactionJournal $journal): Account;

View File

@@ -332,6 +332,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
*
* @return TransactionGroup
* @throws DuplicateTransactionException
* @throws FireflyException
*/
public function store(array $data): TransactionGroup
{
@@ -343,6 +344,10 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
} catch (DuplicateTransactionException $e) {
Log::warning('Group repository caught group factory with a duplicate exception!');
throw new DuplicateTransactionException($e->getMessage());
} catch(FireflyException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage());
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Amount.php
* Copyright (c) 2019 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\Facades;
use Illuminate\Support\Facades\Facade;
/**
* Class Telemetry
*/
class Telemetry extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor(): string
{
return 'telemetry';
}
}

View File

@@ -97,8 +97,8 @@ trait UserNavigation
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$type = $transaction->account->accountType->type;
if (!in_array($type, $ignore)) {
return redirect(route('accounts.show', [$transaction->account_id]));
if (!in_array($type, $ignore, true)) {
return redirect(route('accounts.edit', [$transaction->account_id]));
}
}

View File

@@ -312,7 +312,7 @@ class Search implements SearchInterface
{
$parts = explode(':', $string);
if (2 === count($parts) && '' !== trim((string)$parts[1]) && '' !== trim((string)$parts[0])) {
$type = trim((string)$parts[0]);
$type = strtolower(trim((string)$parts[0]));
$value = trim((string)$parts[1]);
$value = trim(trim($value, '"\''));
if (in_array($type, $this->validModifiers, true)) {

View File

@@ -76,8 +76,15 @@ class Steam
->where('transactions.transaction_currency_id', '!=', $currency->id)
->sum('transactions.foreign_amount');
// check:
Log::debug(sprintf('Steam::balance. Native balance is "%s"', $nativeBalance));
Log::debug(sprintf('Steam::balance. Foreign balance is "%s"', $foreignBalance));
$balance = bcadd($nativeBalance, $foreignBalance);
$virtual = null === $account->virtual_balance ? '0' : (string)$account->virtual_balance;
Log::debug(sprintf('Steam::balance. Virtual balance is "%s"', $virtual));
$balance = bcadd($balance, $virtual);
$cache->store($balance);

81
app/Support/Telemetry.php Normal file
View File

@@ -0,0 +1,81 @@
<?php
/**
* Telemetry.php
* Copyright (c) 2020 thegrumpydictator@gmail.com
*
* 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/>.
*/
namespace FireflyIII\Support;
use Log;
/**
* Class Telemetry
*/
class Telemetry
{
/**
* Feature telemetry stores a boolean "true" for the given $flag.
*
* Examples:
* - use-help-pages
* - has-created-bill
* - do-big-import
* - first-time-install
* - more
*
* Its use should be limited to exotic and strange use cases in Firefly III.
* Because time and date are logged as well, useful to track users' evolution in Firefly III.
*
* Any meta-data stored is strictly non-financial.
*
* @param string $flag
*/
public function feature(string $flag): void
{
if (false === config('firefly.send_telemetry')) {
// hard stop if not allowed to do telemetry.
// do nothing!
return;
}
Log::info(sprintf('Logged telemetry feature flag "%s".', $flag));
// no storage backend yet, do nothing.
}
/**
* String telemetry stores a string value as a telemetry entry. Values could include:
*
* - "php-version", "php7.3"
* - "os-version", "linux"
*
* Any meta-data stored is strictly non-financial.
*
* @param string $name
* @param string $value
*/
public function string(string $name, string $value): void
{
if (false === config('firefly.send_telemetry')) {
// hard stop if not allowed to do telemetry.
// do nothing!
return;
}
Log::info(sprintf('Logged telemetry string "%s" with value "%s".', $name, $value));
}
}

View File

@@ -147,7 +147,7 @@ class General extends AbstractExtension
'balance',
static function (?Account $account): string {
if (null === $account) {
return 'NULL';
return '0';
}
/** @var Carbon $date */
$date = session('end', Carbon::now()->endOfMonth());

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Transformers;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Transaction;
@@ -32,6 +33,7 @@ use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Support\NullArrayObject;
use Illuminate\Support\Collection;
use Log;
/**
* Class TransactionGroupTransformer
@@ -98,9 +100,11 @@ class TransactionGroupTransformer extends AbstractTransformer
* @param TransactionGroup $group
*
* @return array
* @throws FireflyException
*/
public function transformObject(TransactionGroup $group): array
{
try {
$result = [
'id' => (int)$group->id,
'created_at' => $group->created_at->toAtomString(),
@@ -115,7 +119,11 @@ class TransactionGroupTransformer extends AbstractTransformer
],
],
];
} catch(FireflyException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException(sprintf('Transaction group #%d is broken. Please check out your log files.', $group->id));
}
// do something else.
return $result;
@@ -125,36 +133,47 @@ class TransactionGroupTransformer extends AbstractTransformer
* @param TransactionJournal $journal
*
* @return Transaction
* @throws FireflyException
*/
private function getDestinationTransaction(TransactionJournal $journal): Transaction
{
return $journal->transactions->first(
$result = $journal->transactions->first(
static function (Transaction $transaction) {
return (float)$transaction->amount > 0;
}
);
if (null === $result) {
throw new FireflyException(sprintf('Journal #%d unexpectedly has no destination transaction.', $journal->id));
}
return $result;
}
/**
* @param TransactionJournal $journal
*
* @return Transaction
* @throws FireflyException
*/
private function getSourceTransaction(TransactionJournal $journal): Transaction
{
return $journal->transactions->first(
$result = $journal->transactions->first(
static function (Transaction $transaction) {
return (float)$transaction->amount < 0;
}
);
if (null === $result) {
throw new FireflyException(sprintf('Journal #%d unexpectedly has no source transaction.', $journal->id));
}
return $result;
}
/**
* @param Collection $transactionJournals
*
* @return array
* @throws FireflyException
*/
private function transformJournals(Collection $transactionJournals): array
{

View File

@@ -2,7 +2,31 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [5.1.0 (API 1.0.0)] - 2020-03-xx
## [5.1.1 (API 1.0.2)] - 2020-03-xx
### Added
- [Issue 2672](https://github.com/firefly-iii/firefly-iii/issues/2672) Buttons to create transactions from the list of accounts is back.
### Changed
- [Issue 3176](https://github.com/firefly-iii/firefly-iii/issues/3176) Greek language support is enabled again!
### Fixed
- [Issue 3160](https://github.com/firefly-iii/firefly-iii/issues/3160) Deleting a reconciliation won't send you to a 404.
- [Issue 3172](https://github.com/firefly-iii/firefly-iii/issues/3172) Remaining amount left calculation is wrong over multiple months.
- [Issue 3173](https://github.com/firefly-iii/firefly-iii/issues/3173) Amount is invisible when viewing transactions.
- [Issue 3177](https://github.com/firefly-iii/firefly-iii/issues/3177) Fix attachment breadcrumb.
- [Issue 3180](https://github.com/firefly-iii/firefly-iii/issues/3180) Improve instructions for when the user loses MFA info.
- [Issue 3182](https://github.com/firefly-iii/firefly-iii/issues/3182) Better fallback when transaction errors out.
- Search modifiers are now case insensitive.
### Security
- The minimal password length for new users is now 16 characters.
- Have I Been Pwnd check is now enabled by default.
### API
- A new call to `apply_rules` is available when submitting transactions.
## [5.1.0 (API 1.0.1)] - 2020-03-06
Before this release came out, several alpha and beta versions had been released already:
@@ -45,6 +69,11 @@ Before this release came out, several alpha and beta versions had been released
- [Issue 3137](https://github.com/firefly-iii/firefly-iii/issues/3137) Fix mis-alignment in table rows.
- [Issue 3140](https://github.com/firefly-iii/firefly-iii/issues/3140) New user email message had a broken link.
- [Issue 3141](https://github.com/firefly-iii/firefly-iii/issues/3141) Cache issue
- [Issue 3145](https://github.com/firefly-iii/firefly-iii/issues/3145) Issue with empty charts.
- [Issue 3146](https://github.com/firefly-iii/firefly-iii/issues/3146) Better handling of CSV imports from ING.
- [Issue 3154](https://github.com/firefly-iii/firefly-iii/issues/3154) Problem with bcadd() in PHP 7.4
- [Issue 3159](https://github.com/firefly-iii/firefly-iii/issues/3159) Fixed some untranslatable strings.
- Bad redirect when editing opening balances.
### API
- [Issue 3097](https://github.com/firefly-iii/firefly-iii/issues/3097) Unifying API models

554
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -134,11 +134,12 @@ return [
],
'feature_flags' => [
'export' => true,
'telemetry' => false,
],
'encryption' => null === env('USE_ENCRYPTION') || true === env('USE_ENCRYPTION'),
'version' => '5.1.0-beta.1',
'api_version' => '1.0.1',
'version' => '5.1.1',
'api_version' => '1.0.2',
'db_version' => 12,
'maxUploadSize' => 15242880,
'send_error_message' => env('SEND_ERROR_MESSAGE', true),
@@ -160,6 +161,7 @@ return [
'login_provider' => envNonEmpty('LOGIN_PROVIDER', 'eloquent'),
'cer_provider' => envNonEmpty('CER_PROVIDER', 'fixer'),
'update_endpoint' => 'https://version.firefly-iii.org/index.json',
'send_telemetry' => env('SEND_TELEMETRY', false),
'update_minimum_age' => 6,
'default_location' => [
'longitude' => env('MAP_DEFAULT_LONG', '5.916667'),
@@ -317,6 +319,7 @@ return [
// currently enabled languages
'en_US' => ['name_locale' => 'English', 'name_english' => 'English'],
'cs_CZ' => ['name_locale' => 'Czech', 'name_english' => 'Czech'],
'el_GR' => ['name_locale' => 'Ελληνικά', 'name_english' => 'Greek'],
'es_ES' => ['name_locale' => 'Español', 'name_english' => 'Spanish'],
'de_DE' => ['name_locale' => 'Deutsch', 'name_english' => 'German'],
'fr_FR' => ['name_locale' => 'Français', 'name_english' => 'French'],
@@ -345,7 +348,7 @@ return [
// 'pt_PT' => ['name_locale' => 'Portuguese', 'name_english' => 'Portuguese'],
// 'sl_SI' => ['name_locale' => 'Slovenian', 'name_english' => 'Slovenian'],
// 'tlh_AA' => ['name_locale' => 'tlhIngan Hol', 'name_english' => 'Klingon'],
// 'el_GR' => ['name_locale' => 'Ελληνικά', 'name_english' => 'Greek'],
//
// 'tr_TR' => ['name_locale' => 'Türkçe', 'name_english' => 'Turkish'],
// 'sr_CS' => ['name_locale' => 'Serbian (Latin)', 'name_english' => 'Serbian (Latin)'],
// 'uk_UA' => ['name_locale' => 'Ukranian', 'name_english' => 'Ukranian'],

View File

@@ -70,7 +70,7 @@ return [
'allowed_for_user' => [
'fake' => false,
'file' => true,
'bunq' => true,
'bunq' => false,
'spectre' => true,
'ynab' => true,
'plaid' => true,
@@ -82,7 +82,7 @@ return [
'has_prereq' => [
'fake' => true,
'file' => false,
'bunq' => true,
'bunq' => false,
'spectre' => true,
'ynab' => true,
'plaid' => true,

View File

@@ -19,10 +19,8 @@
"laravel-mix": "^4.1.2",
"laravel-mix-bundle-analyzer": "^1.0.5",
"uiv": "^0.31.5",
"vue-i18n": "^8.14.1",
"vue": "^2.6.10",
"vue-i18n": "^8.14.1",
"vue-template-compiler": "^2.6.10"
},
"dependencies": {
}
}

View File

@@ -1,523 +1,59 @@
/*
* gf-source.css
* Copyright (c) 2019 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/>.
*/
/* cyrillic-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightItalic'), url('../fonts/SourceSansPro-LightItalic-cyrillic-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-LightItalic-cyrillic-ext.woff') format('woff');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightItalic'), url('../fonts/SourceSansPro-LightItalic-cyrillic.woff2') format('woff2'), url('../fonts/SourceSansPro-LightItalic-cyrillic.woff') format('woff');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightItalic'), url('../fonts/SourceSansPro-LightItalic-greek-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-LightItalic-greek-ext.woff') format('woff');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightItalic'), url('../fonts/SourceSansPro-LightItalic-greek.woff2') format('woff2'), url('../fonts/SourceSansPro-LightItalic-greek.woff') format('woff');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightItalic'), url('../fonts/SourceSansPro-LightItalic-vietnamese.woff2') format('woff2'), url('../fonts/SourceSansPro-LightItalic-vietnamese.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightItalic'), url('../fonts/SourceSansPro-LightItalic-latin-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-LightItalic-latin-ext.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightItalic'), url('../fonts/SourceSansPro-LightItalic-latin.woff2') format('woff2'), url('../fonts/SourceSansPro-LightItalic-latin.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url('../fonts/SourceSansPro-Italic-cyrillic-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Italic-cyrillic-ext.woff') format('woff');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url('../fonts/SourceSansPro-Italic-cyrillic.woff2') format('woff2'), url('../fonts/SourceSansPro-Italic-cyrillic.woff') format('woff');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url('../fonts/SourceSansPro-Italic-greek-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Italic-greek-ext.woff') format('woff');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url('../fonts/SourceSansPro-Italic-greek.woff2') format('woff2'), url('../fonts/SourceSansPro-Italic-greek.woff') format('woff');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url('../fonts/SourceSansPro-Italic-vietnamese.woff2') format('woff2'), url('../fonts/SourceSansPro-Italic-vietnamese.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url('../fonts/SourceSansPro-Italic-latin-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Italic-latin-ext.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url('../fonts/SourceSansPro-Italic-latin.woff2') format('woff2'), url('../fonts/SourceSansPro-Italic-latin.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 600;
src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url('../fonts/SourceSansPro-SemiBoldItalic-cyrillic-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBoldItalic-cyrillic-ext.woff') format('woff');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 600;
src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url('../fonts/SourceSansPro-SemiBoldItalic-cyrillic.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBoldItalic-cyrillic.woff') format('woff');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 600;
src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url('../fonts/SourceSansPro-SemiBoldItalic-greek-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBoldItalic-greek-ext.woff') format('woff');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 600;
src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url('../fonts/SourceSansPro-SemiBoldItalic-greek.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBoldItalic-greek.woff') format('woff');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 600;
src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url('../fonts/SourceSansPro-SemiBoldItalic-vietnamese.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBoldItalic-vietnamese.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 600;
src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url('../fonts/SourceSansPro-SemiBoldItalic-latin-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBoldItalic-latin-ext.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 600;
src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url('../fonts/SourceSansPro-SemiBoldItalic-latin.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBoldItalic-latin.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url('../fonts/SourceSansPro-BoldItalic-cyrillic-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-BoldItalic-cyrillic-ext.woff') format('woff');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url('../fonts/SourceSansPro-BoldItalic-cyrillic.woff2') format('woff2'), url('../fonts/SourceSansPro-BoldItalic-cyrillic.woff') format('woff');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url('../fonts/SourceSansPro-BoldItalic-greek-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-BoldItalic-greek-ext.woff') format('woff');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url('../fonts/SourceSansPro-BoldItalic-greek.woff2') format('woff2'), url('../fonts/SourceSansPro-BoldItalic-greek.woff') format('woff');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url('../fonts/SourceSansPro-BoldItalic-vietnamese.woff2') format('woff2'), url('../fonts/SourceSansPro-BoldItalic-vietnamese.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url('../fonts/SourceSansPro-BoldItalic-latin-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-BoldItalic-latin-ext.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url('../fonts/SourceSansPro-BoldItalic-latin.woff2') format('woff2'), url('../fonts/SourceSansPro-BoldItalic-latin.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
/* source-sans-pro-300 - greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('../fonts/SourceSansPro-Light-cyrillic-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Light-cyrillic-ext.woff') format('woff');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'),
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('../fonts/SourceSansPro-Light-cyrillic.woff2') format('woff2'), url('../fonts/SourceSansPro-Light-cyrillic.woff') format('woff');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('../fonts/SourceSansPro-Light-greek-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Light-greek-ext.woff') format('woff');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('../fonts/SourceSansPro-Light-greek.woff2') format('woff2'), url('../fonts/SourceSansPro-Light-greek.woff') format('woff');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('../fonts/SourceSansPro-Light-vietnamese.woff2') format('woff2'), url('../fonts/SourceSansPro-Light-vietnamese.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('../fonts/SourceSansPro-Light-latin-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Light-latin-ext.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url('../fonts/SourceSansPro-Light-latin.woff2') format('woff2'), url('../fonts/SourceSansPro-Light-latin.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('../fonts/SourceSansPro-Regular-cyrillic-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Regular-cyrillic-ext.woff') format('woff');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('../fonts/SourceSansPro-Regular-cyrillic.woff2') format('woff2'), url('../fonts/SourceSansPro-Regular-cyrillic.woff') format('woff');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('../fonts/SourceSansPro-Regular-greek-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Regular-greek-ext.woff') format('woff');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('../fonts/SourceSansPro-Regular-greek.woff2') format('woff2'), url('../fonts/SourceSansPro-Regular-greek.woff') format('woff');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('../fonts/SourceSansPro-Regular-vietnamese.woff2') format('woff2'), url('../fonts/SourceSansPro-Regular-vietnamese.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('../fonts/SourceSansPro-Regular-latin-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Regular-latin-ext.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('../fonts/SourceSansPro-Regular-latin.woff2') format('woff2'), url('../fonts/SourceSansPro-Regular-latin.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
/* source-sans-pro-600 - greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url('../fonts/SourceSansPro-SemiBold-cyrillic-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBold-cyrillic-ext.woff') format('woff');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'),
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* cyrillic */
/* source-sans-pro-regular - greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'),
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* source-sans-pro-italic - greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'),
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* source-sans-pro-600italic - greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 600;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url('../fonts/SourceSansPro-SemiBold-cyrillic.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBold-cyrillic.woff') format('woff');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'),
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-600italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-600italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url('../fonts/SourceSansPro-SemiBold-greek-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBold-greek-ext.woff') format('woff');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url('../fonts/SourceSansPro-SemiBold-greek.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBold-greek.woff') format('woff');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url('../fonts/SourceSansPro-SemiBold-vietnamese.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBold-vietnamese.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url('../fonts/SourceSansPro-SemiBold-latin-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBold-latin-ext.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url('../fonts/SourceSansPro-SemiBold-latin.woff2') format('woff2'), url('../fonts/SourceSansPro-SemiBold-latin.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* cyrillic-ext */
/* source-sans-pro-700 - greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url('../fonts/SourceSansPro-Bold-cyrillic-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Bold-cyrillic-ext.woff') format('woff');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url('../fonts/SourceSansPro-Bold-cyrillic.woff2') format('woff2'), url('../fonts/SourceSansPro-Bold-cyrillic.woff') format('woff');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url('../fonts/SourceSansPro-Bold-greek-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Bold-greek-ext.woff') format('woff');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url('../fonts/SourceSansPro-Bold-greek.woff2') format('woff2'), url('../fonts/SourceSansPro-Bold-greek.woff') format('woff');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url('../fonts/SourceSansPro-Bold-vietnamese.woff2') format('woff2'), url('../fonts/SourceSansPro-Bold-vietnamese.woff') format('woff');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url('../fonts/SourceSansPro-Bold-latin-ext.woff2') format('woff2'), url('../fonts/SourceSansPro-Bold-latin-ext.woff') format('woff');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url('../fonts/SourceSansPro-Bold-latin.woff2') format('woff2'), url('../fonts/SourceSansPro-Bold-latin.woff') format('woff');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'),
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro-v13-greek_cyrillic-ext_vietnamese_greek-ext_latin-ext_cyrillic_latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}

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