Merge branch 'develop' into js_translations

This commit is contained in:
James Cole 2020-05-29 06:18:22 +02:00 committed by GitHub
commit e0e16489a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
186 changed files with 2556 additions and 1430 deletions

View File

@ -65,6 +65,17 @@ DB_DATABASE=firefly
DB_USERNAME=firefly
DB_PASSWORD=secret_firefly_password
# MySQL supports SSL. You can configure it here.
# If you use Docker or similar, you can set these variables from a file by appending them with _FILE
MYSQL_USE_SSL=false
MYSQL_SSL_VERIFY_SERVER_CERT=true
# You need to set at least of these options
MYSQL_SSL_CAPATH=/etc/ssl/certs/
MYSQL_SSL_CA=
MYSQL_SSL_CERT=
MYSQL_SSL_KEY=
MYSQL_SSL_CIPHER=
# PostgreSQL supports SSL. You can configure it here.
# If you use Docker or similar, you can set these variables from a file by appending them with _FILE
PGSQL_SSL_MODE=prefer
@ -170,8 +181,16 @@ ADLDAP_PORT=389
ADLDAP_TIMEOUT=5
ADLDAP_BASEDN=""
ADLDAP_FOLLOW_REFFERALS=false
# SSL/TLS settings
ADLDAP_USE_SSL=false
ADLDAP_USE_TLS=false
ADLDAP_SSL_CACERTDIR=
ADLDAP_SSL_CACERTFILE=
ADLDAP_SSL_CERTFILE=
ADLDAP_SSL_KEYFILE=
ADLDAP_SSL_CIPHER_SUITE=
ADLDAP_SSL_REQUIRE_CERT=
# You can set the following variables from a file by appending them with _FILE:
ADLDAP_ADMIN_USERNAME=
@ -191,6 +210,7 @@ ADLDAP_AUTH_FIELD=distinguishedname
# Will allow SSO if your server provides an AUTH_USER field.
# You can set the following variables from a file by appending them with _FILE:
WINDOWS_SSO_ENABLED=false
WINDOWS_SSO_DISCOVER=samaccountname
WINDOWS_SSO_KEY=AUTH_USER
@ -218,8 +238,9 @@ TRACKER_SITE_ID=
TRACKER_URL=
#
# Firefly III could (in the future) collect telemetry on how you use Firefly III.
# In order to allow this, change the following variable to true:
# Firefly III can collect telemetry on how you use Firefly III. This is opt-in.
# In order to allow this, change the following variable to true.
# To read more about this feature, go to this page: https://docs.firefly-iii.org/support/telemetry
SEND_TELEMETRY=false
# You can fine tune the start-up of a Docker container by editing these environment variables.
@ -267,7 +288,6 @@ DEMO_USERNAME=
DEMO_PASSWORD=
USE_ENCRYPTION=false
IS_SANDSTORM=false
IS_DOCKER=false
IS_HEROKU=false
BUNQ_USE_SANDBOX=false

View File

@ -269,6 +269,7 @@ class TransactionController extends Controller
*
* @param TransactionStoreRequest $request
*
* @throws FireflyException
* @return JsonResponse
*/
public function store(TransactionStoreRequest $request): JsonResponse
@ -283,7 +284,7 @@ class TransactionController extends Controller
try {
$transactionGroup = $this->groupRepository->store($data);
} catch (DuplicateTransactionException $e) {
Log::warning('Caught a duplicate. Return error message.');
Log::warning('Caught a duplicate transaction. Return error message.');
// return bad validation message.
// TODO use Laravel's internal validation thing to do this.
$response = [
@ -326,7 +327,7 @@ class TransactionController extends Controller
$selectedGroup = $collector->getGroups()->first();
if (null === $selectedGroup) {
throw new NotFoundHttpException(); // @codeCoverageIgnore
throw new FireflyException('Cannot find transaction. Possibly, a rule deleted this transaction after its creation.');
}
/** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class);

View File

@ -83,8 +83,6 @@ class CorrectDatabase extends Command
echo $result;
}
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
}

View File

@ -77,8 +77,6 @@ class CorrectOpeningBalanceCurrencies extends Command
$this->info('There was nothing to fix in the opening balance transactions.');
}
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -78,8 +78,6 @@ class CreateAccessTokens extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verify access tokens in %s seconds.', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
}

View File

@ -79,8 +79,6 @@ class CreateLinkTypes extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified link types in %s seconds', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
}

View File

@ -76,8 +76,6 @@ class DeleteEmptyGroups extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified empty groups in %s seconds', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
}

View File

@ -58,8 +58,6 @@ class DeleteEmptyJournals extends Command
$this->deleteUnevenJournals();
$this->deleteEmptyJournals();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -62,7 +62,6 @@ class DeleteOrphanedTransactions extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified orphans in %s seconds', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -78,8 +78,6 @@ class DeleteZeroAmount extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified zero-amount integrity in %s seconds', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
}

View File

@ -101,8 +101,6 @@ class EnableCurrencies extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified currencies in %s seconds.', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
}

View File

@ -106,7 +106,6 @@ class FixAccountTypes extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verifying account types took %s seconds', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -75,7 +75,6 @@ class FixLongDescriptions extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified all transaction group and journal title lengths in %s seconds.', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
}

View File

@ -98,7 +98,6 @@ class FixPiggies extends Command
$end = round(microtime(true) - $start, 2);
$this->line(sprintf('Verified the content of %d piggy bank events in %s seconds.', $set->count(), $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
}

View File

@ -67,7 +67,6 @@ class FixRecurringTransactions extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Corrected recurring transactions %s seconds.', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -75,7 +75,6 @@ class FixUnevenAmount extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified amount integrity in %s seconds', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -71,7 +71,6 @@ class RemoveBills extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified bills / journals in %s seconds', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
}

View File

@ -83,7 +83,6 @@ class RenameMetaFields extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Renamed meta fields in %s seconds', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -74,7 +74,6 @@ class TransferBudgets extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified budget/journals in %s seconds.', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
}

View File

@ -123,7 +123,6 @@ class DecryptDatabase extends Command
}
$this->info('Done!');
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -137,11 +137,11 @@ class ExportData extends Command
} catch (FireflyException $e) {
$this->error(sprintf('Could not store data: %s', $e->getMessage()));
// app('telemetry')->feature('executed-command-with-error', $this->signature);
app('telemetry')->feature('system.command.errored', $this->signature);
return 1;
}
// app('telemetry')->feature('executed-command', $this->signature);
app('telemetry')->feature('system.command.executed', $this->signature);
return 0;
}

View File

@ -63,279 +63,14 @@ class CreateCSVImport extends Command
{configuration? : The configuration file to use for the import.}
{--user=1 : The user ID that the import should import for.}
{--token= : The user\'s access token.}';
/** @var ImportJob */
private $importJob;
/** @var ImportJobRepositoryInterface */
private $importRepository;
/** @var UserRepositoryInterface */
private $userRepository;
/**
* Run the command.
*/
public function handle(): int
{
$this->stupidLaravel();
// @codeCoverageIgnoreStart
if (!$this->verifyAccessToken()) {
$this->errorLine('Invalid access token.');
return 1;
}
if (!$this->validArguments()) {
$this->errorLine('Invalid arguments.');
return 1;
}
// @codeCoverageIgnoreEnd
/** @var User $user */
$user = $this->userRepository->findNull((int) $this->option('user'));
$file = (string) $this->argument('file');
$configuration = (string) $this->argument('configuration');
$this->importRepository->setUser($user);
$configurationData = json_decode(file_get_contents($configuration), true, 512, JSON_THROW_ON_ERROR);
$this->importJob = $this->importRepository->create('file');
// inform user (and log it)
$this->infoLine(sprintf('Import file : %s', $file));
$this->infoLine(sprintf('Configuration file : %s', $configuration));
$this->infoLine(sprintf('User : #%d (%s)', $user->id, $user->email));
$this->infoLine(sprintf('Job : %s', $this->importJob->key));
try {
$this->storeFile($file);
} catch (FireflyException $e) {
$this->errorLine($e->getMessage());
return 1;
}
// job is ready to go
$this->importRepository->setConfiguration($this->importJob, $configurationData);
$this->importRepository->setStatus($this->importJob, 'ready_to_run');
$this->infoLine('The import routine has started. The process is not visible. Please wait.');
Log::debug('Go for import!');
// keep repeating this call until job lands on "provider_finished"
try {
$this->processFile();
} catch (FireflyException $e) {
$this->errorLine($e->getMessage());
// app('telemetry')->feature('executed-command-with-error', $this->signature);
return 1;
}
// then store data:
try {
$this->storeData();
} catch (FireflyException $e) {
$this->errorLine($e->getMessage());
// app('telemetry')->feature('executed-command-with-error', $this->signature);
return 1;
}
// give feedback:
$this->giveFeedback();
// clear cache for user:
app('preferences')->setForUser($user, 'lastActivity', microtime());
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
$this->error('This command is disabled.');
return 1;
}
/**
* @param string $message
* @param array|null $data
*
* @codeCoverageIgnore
*/
private function errorLine(string $message, array $data = null): void
{
Log::error($message, $data ?? []);
$this->error($message);
}
/**
*
*/
private function giveFeedback(): void
{
$this->infoLine('Job has finished.');
if (null !== $this->importJob->tag) {
$this->infoLine(sprintf('%d transaction(s) have been imported.', $this->importJob->tag->transactionJournals->count()));
$this->infoLine(sprintf('You can find your transactions under tag "%s"', $this->importJob->tag->tag));
}
if (null === $this->importJob->tag) {
$this->errorLine('No transactions have been imported :(.');
}
if (count($this->importJob->errors) > 0) {
$this->infoLine(sprintf('%d error(s) occurred:', count($this->importJob->errors)));
foreach ($this->importJob->errors as $err) {
$this->errorLine('- ' . $err);
}
}
}
/**
* @param string $message
* @param array $data
*
* @codeCoverageIgnore
*/
private function infoLine(string $message, array $data = null): void
{
Log::info($message, $data ?? []);
$this->line($message);
}
/**
* Keep repeating import call until job lands on "provider_finished".
*
* @throws FireflyException
*/
private function processFile(): void
{
$className = config('import.routine.file');
$valid = ['provider_finished'];
$count = 0;
while (!in_array($this->importJob->status, $valid, true) && $count < 6) {
Log::debug(sprintf('Now in loop #%d.', $count + 1));
/** @var RoutineInterface $routine */
$routine = app($className);
$routine->setImportJob($this->importJob);
try {
$routine->run();
} catch (FireflyException|Exception $e) {
$message = 'The import routine crashed: ' . $e->getMessage();
Log::error($message);
Log::error($e->getTraceAsString());
// set job errored out:
$this->importRepository->setStatus($this->importJob, 'error');
throw new FireflyException($message);
}
$count++;
}
$this->importRepository->setStatus($this->importJob, 'provider_finished');
$this->importJob->status = 'provider_finished';
}
/**
*
* @throws FireflyException
*/
private function storeData(): void
{
if ('provider_finished' === $this->importJob->status) {
$this->infoLine('Import has finished. Please wait for storage of data.');
// set job to be storing data:
$this->importRepository->setStatus($this->importJob, 'storing_data');
/** @var ImportArrayStorage $storage */
$storage = app(ImportArrayStorage::class);
$storage->setImportJob($this->importJob);
try {
$storage->store();
} catch (FireflyException|Exception $e) {
$message = 'The import routine crashed: ' . $e->getMessage();
Log::error($message);
Log::error($e->getTraceAsString());
// set job errored out:
$this->importRepository->setStatus($this->importJob, 'error');
throw new FireflyException($message);
}
// set storage to be finished:
$this->importRepository->setStatus($this->importJob, 'storage_finished');
}
}
/**
* Store the supplied file as an attachment to this job.
*
* @param string $file
*
* @throws FireflyException
*/
private function storeFile(string $file): void
{
// store file as attachment.
if ('' !== $file) {
$messages = $this->importRepository->storeCLIUpload($this->importJob, 'import_file', $file);
if ($messages->count() > 0) {
throw new FireflyException($messages->first());
}
}
}
/**
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
* be called from the handle method instead of using the constructor to initialize the command.
*
* @codeCoverageIgnore
*/
private function stupidLaravel(): void
{
$this->userRepository = app(UserRepositoryInterface::class);
$this->importRepository = app(ImportJobRepositoryInterface::class);
}
/**
* Verify user inserts correct arguments.
*
* @noinspection MultipleReturnStatementsInspection
* @return bool
* @codeCoverageIgnore
*/
private function validArguments(): bool
{
$file = (string) $this->argument('file');
$configuration = (string) $this->argument('configuration');
$cwd = getcwd();
$enabled = (bool) config('import.enabled.file');
if (false === $enabled) {
$this->errorLine('CSV Provider is not enabled.');
return false;
}
if (!file_exists($file)) {
$this->errorLine(sprintf('Firefly III cannot find file "%s" (working directory: "%s").', $file, $cwd));
return false;
}
if (!file_exists($configuration)) {
$this->errorLine(sprintf('Firefly III cannot find configuration file "%s" (working directory: "%s").', $configuration, $cwd));
return false;
}
$configurationData = json_decode(file_get_contents($configuration), true, 512, JSON_THROW_ON_ERROR);
if (null === $configurationData) {
$this->errorLine(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
return false;
}
return true;
}
}

View File

@ -64,7 +64,6 @@ class ReportEmptyObjects extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Report on empty objects finished in %s seconds', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -69,7 +69,6 @@ class ReportIntegrity extends Command
echo $result;
}
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
}

View File

@ -54,7 +54,6 @@ class ReportSum extends Command
{
$this->reportSum();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -24,6 +24,7 @@ namespace FireflyIII\Console\Commands\Integrity;
use FireflyIII\Support\System\OAuthKeys;
use Illuminate\Console\Command;
use Log;
/**
* Class RestoreOAuthKeys
@ -52,7 +53,6 @@ class RestoreOAuthKeys extends Command
{
$this->restoreOAuthKeys();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}
@ -93,7 +93,9 @@ class RestoreOAuthKeys extends Command
*/
private function restoreOAuthKeys(): void
{
Log::debug('Going to restoreOAuthKeys()');
if (!$this->keysInDatabase() && !$this->keysOnDrive()) {
Log::debug('Keys are not in DB and keys are not on the drive.');
$this->generateKeys();
$this->storeKeysInDB();
$this->line('Generated and stored new keys.');
@ -101,12 +103,14 @@ class RestoreOAuthKeys extends Command
return;
}
if ($this->keysInDatabase() && !$this->keysOnDrive()) {
Log::debug('Keys are in DB and keys are not on the drive. Restore.');
$this->restoreKeysFromDB();
$this->line('Restored OAuth keys from database.');
return;
}
if (!$this->keysInDatabase() && $this->keysOnDrive()) {
Log::debug('Keys are not in DB and keys are on the drive. Save in DB.');
$this->storeKeysInDB();
$this->line('Stored OAuth keys in database.');

View File

@ -86,7 +86,7 @@ class ScanAttachments extends Command
$this->line(sprintf('Fixed attachment #%d', $attachment->id));
}
// app('telemetry')->feature('executed-command', $this->signature);
app('telemetry')->feature('system.command.executed', $this->signature);
return 0;
}
}

View File

@ -59,7 +59,7 @@ class SetLatestVersion extends Command
app('fireflyconfig')->set('ff3_version', config('firefly.version'));
$this->line('Updated version.');
// app('telemetry')->feature('executed-command', $this->signature);
app('telemetry')->feature('system.command.executed', $this->signature);
return 0;
}

View File

@ -112,7 +112,7 @@ class ApplyRules extends Command
$result = $this->verifyInput();
if (false === $result) {
// app('telemetry')->feature('executed-command-with-error', $this->signature);
app('telemetry')->feature('system.command.errored', $this->signature);
return 1;
}
@ -131,7 +131,7 @@ class ApplyRules extends Command
$this->warn(' --rule_groups=1,2,...');
$this->warn(' --all_rules');
// app('telemetry')->feature('executed-command-with-error', $this->signature);
app('telemetry')->feature('system.command.errored', $this->signature);
return 1;
}
@ -167,7 +167,7 @@ class ApplyRules extends Command
$this->line('');
$this->line('Done!');
// app('telemetry')->feature('executed-command', $this->signature);
app('telemetry')->feature('system.command.executed', $this->signature);
return 0;
}

View File

@ -94,10 +94,10 @@ class Cron extends Command
}
/*
* Fire telemetry cron job (disabled):
* Fire telemetry cron job
*/
try {
//$this->telemetryCronJob($force, $date);
$this->telemetryCronJob($force, $date);
} catch (FireflyException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
@ -106,7 +106,7 @@ class Cron extends Command
$this->info('More feedback on the cron jobs can be found in the log files.');
// app('telemetry')->feature('executed-command', $this->signature);
app('telemetry')->feature('system.command.executed', $this->signature);
return 0;
}

View File

@ -87,7 +87,6 @@ class AccountCurrencies extends Command
$this->info(sprintf('Verified and fixed account currencies in %s seconds.', $end));
$this->markAsExecuted();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -78,8 +78,6 @@ class BackToJournals extends Command
$this->info(sprintf('Updated category and budget info for all transaction journals in %s seconds.', $end));
$this->markAsExecuted();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -92,8 +92,6 @@ class BudgetLimitCurrency extends Command
$this->markAsExecuted();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -91,8 +91,6 @@ class CCLiabilities extends Command
$this->info(sprintf('Verified credit card liabilities in %s seconds', $end));
$this->markAsExecuted();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -101,8 +101,6 @@ class MigrateAttachments extends Command
$this->info(sprintf('Migrated attachment notes in %s seconds.', $end));
$this->markAsExecuted();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -100,8 +100,6 @@ class MigrateJournalNotes extends Command
$this->info(sprintf('Migrated notes in %s seconds.', $end));
$this->markAsExecuted();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -72,8 +72,6 @@ class MigrateRecurrenceMeta extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Migrated recurrence meta data in %s seconds.', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -65,7 +65,6 @@ class MigrateTagLocations extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Migrated tag locations in %s seconds.', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -112,8 +112,6 @@ class MigrateToGroups extends Command
$this->markAsMigrated();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -99,7 +99,6 @@ class MigrateToRules extends Command
$this->info(sprintf('Verified and fixed bills in %s seconds.', $end));
$this->markAsExecuted();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -90,7 +90,6 @@ class OtherCurrenciesCorrections extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified and fixed transaction currencies in %s seconds.', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -91,7 +91,6 @@ class RenameAccountMeta extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Fixed account meta data in %s seconds.', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -101,7 +101,6 @@ class TransactionIdentifier extends Command
$this->info(sprintf('Verified and fixed transaction identifiers in %s seconds.', $end));
$this->markAsExecuted();
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -111,7 +111,6 @@ class TransferCurrenciesCorrections extends Command
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified and fixed currency information for transfers in %s seconds.', $end));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -117,7 +117,6 @@ class UpgradeDatabase extends Command
// index will set FF3 version.
app('fireflyconfig')->set('ff3_version', (string) config('firefly.version'));
// app('telemetry')->feature('executed-command', $this->signature);
return 0;
}

View File

@ -57,7 +57,12 @@ class UpgradeFireflyInstructions extends Command
$this->installInstructions();
}
// app('telemetry')->feature('executed-command', $this->signature);
// collect system telemetry
$isDocker = true === env('IS_DOCKER', false) ? 'true' : 'false';
app('telemetry')->feature('system.php.version', PHP_VERSION);
app('telemetry')->feature('system.os.version', PHP_OS);
app('telemetry')->feature('system.os.is_docker', $isDocker);
app('telemetry')->feature('system.command.executed', $this->signature);
return 0;
}

View File

@ -87,7 +87,7 @@ class Handler extends ExceptionHandler
);
}
return response()->json(['message' => 'Internal Firefly III Exception. See log files.', 'exception' => get_class($exception)], 500);
return response()->json(['message' => sprintf('Internal Firefly III Exception: %s', $exception->getMessage()), 'exception' => get_class($exception)], 500);
}
if ($exception instanceof NotFoundHttpException) {

View File

@ -428,7 +428,7 @@ class TransactionJournalFactory
->first();
}
if (null !== $result) {
Log::warning('Found a duplicate!');
Log::warning(sprintf('Found a duplicate in errorIfDuplicate because hash %s is not unique!', $hash));
throw new DuplicateTransactionException(sprintf('Duplicate of transaction #%d.', $result->transactionJournal->transaction_group_id));
}
}

View File

@ -118,6 +118,7 @@ class UserEventHandler
if ($repository->hasRole($user, 'demo')) {
// set user back to English.
app('preferences')->setForUser($user, 'language', 'en_US');
app('preferences')->setForUser($user, 'locale', 'equal');
app('preferences')->mark();
}

View File

@ -54,6 +54,7 @@ class VersionCheckEventHandler
$value = (int) $permission->data;
if (1 !== $value) {
Log::info('Update check is not enabled.');
$this->warnToCheckForUpdates($event);
return;
}
@ -85,4 +86,36 @@ class VersionCheckEventHandler
session()->flash($release['level'], $release['message']);
app('fireflyconfig')->set('last_update_check', time());
}
/**
* @param RequestedVersionCheckStatus $event
*/
protected function warnToCheckForUpdates(RequestedVersionCheckStatus $event): void
{
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
/** @var User $user */
$user = $event->user;
if (!$repository->hasRole($user, 'owner')) {
Log::debug('User is not admin, done.');
return;
}
/** @var Configuration $lastCheckTime */
$lastCheckTime = app('fireflyconfig')->get('last_update_warning', time());
$now = time();
$diff = $now - $lastCheckTime->data;
Log::debug(sprintf('Last warning time is %d, current time is %d, difference is %d', $lastCheckTime->data, $now, $diff));
if ($diff < 604800 * 4) {
Log::debug(sprintf('Warned about updates less than four weeks ago (on %s).', date('Y-m-d H:i:s', $lastCheckTime->data)));
return;
}
// last check time was more than a week ago.
Log::debug('Have warned about a new version in four weeks!');
session()->flash('info', (string) trans('firefly.disabled_but_check'));
app('fireflyconfig')->set('last_update_warning', time());
}
}

View File

@ -31,16 +31,11 @@ use FireflyIII\Helpers\Collector\Extensions\AmountCollection;
use FireflyIII\Helpers\Collector\Extensions\CollectorProperties;
use FireflyIII\Helpers\Collector\Extensions\MetaCollection;
use FireflyIII\Helpers\Collector\Extensions\TimeCollection;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
@ -526,7 +521,7 @@ class GroupCollector implements GroupCollectorInterface
}
// or parse the rest.
$journalId = (int) $augumentedJournal->transaction_journal_id;
$groups[$groupId]['count']++;
if (isset($groups[$groupId]['transactions'][$journalId])) {
// append data to existing group + journal (for multiple tags or multiple attachments)
@ -536,6 +531,7 @@ class GroupCollector implements GroupCollectorInterface
if (!isset($groups[$groupId]['transactions'][$journalId])) {
// create second, third, fourth split:
$groups[$groupId]['count']++;
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal);
}
}

View File

@ -109,15 +109,13 @@ class NetWorth implements NetWorthInterface
Log::debug(sprintf('Balance is %s', $balance));
// if the account is a credit card, subtract the virtual balance from the balance,
// to better reflect that this is not money that is actually "yours".
$role = (string) $this->accountRepository->getMetaValue($account, 'account_role');
// always subtract virtual balance.
$virtualBalance = (string) $account->virtual_balance;
if ('ccAsset' === $role && '' !== $virtualBalance && (float) $virtualBalance > 0) {
if ('' !== $virtualBalance) {
$balance = bcsub($balance, $virtualBalance);
}
Log::debug(sprintf('Balance corrected to %s', $balance));
Log::debug(sprintf('Balance corrected to %s because of virtual balance (%s)', $balance, $virtualBalance));
if (!isset($netWorth[$currencyId])) {
$netWorth[$currencyId] = '0';

View File

@ -101,7 +101,8 @@ class TelemetryController extends Controller
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);
$enabled = config('firefly.send_telemetry', false) && config('firefly.feature_flags.telemetry');
$count = $this->repository->count();
return view('admin.telemetry.index', compact('version', 'enabled', 'count'));

View File

@ -82,14 +82,8 @@ class LoginController extends Controller
Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $request->get('email')));
Log::info(sprintf('User is trying to login.'));
if ('ldap' === config('auth.providers.users.driver')) {
/**
* Temporary bug fix for something that doesn't seem to work in
* AdLdap.
*/
$schema = config('ldap.connections.default.schema');
/** @var Adldap\Connections\Provider $provider */
Adldap::getProvider('default')->setSchema(new $schema);
Adldap::getProvider('default');
}
$this->validateLogin($request);
@ -137,6 +131,7 @@ class LoginController extends Controller
return redirect(route('register')); // @codeCoverageIgnore
}
// is allowed to?
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$allowRegistration = true;
@ -154,6 +149,9 @@ class LoginController extends Controller
$email = $request->old('email');
$remember = $request->old('remember');
// todo must forget 2FA if user ends up here.
return view('auth.login', compact('allowRegistration', 'email', 'remember', 'allowReset', 'title'));
}

View File

@ -104,6 +104,9 @@ class RegisterController extends Controller
$this->registered($request, $user);
// telemetry
\Telemetry::feature('system.users.count', User::count());
return redirect($this->redirectPath());
}

View File

@ -157,6 +157,7 @@ class AvailableBudgetController extends Controller
*/
public function edit(AvailableBudget $availableBudget, Carbon $start, Carbon $end)
{
$availableBudget->amount = round($availableBudget->amount, $availableBudget->transactionCurrency->decimal_places);
return view('budgets.available-budgets.edit', compact('availableBudget', 'start', 'end'));
}

View File

@ -132,6 +132,7 @@ class BudgetLimitController extends Controller
*/
public function store(Request $request)
{
Log::debug('Going to store new budget-limit.', $request->all());
// first search for existing one and update it if necessary.
$currency = $this->currencyRepos->find((int) $request->get('transaction_currency_id'));
$budget = $this->repository->findNull((int) $request->get('budget_id'));
@ -143,7 +144,6 @@ class BudgetLimitController extends Controller
$start->startOfDay();
$end->endOfDay();
Log::debug(sprintf('Start: %s, end: %s', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
$limit = $this->blRepository->find($budget, $currency, $start, $end);

View File

@ -97,6 +97,7 @@ class IndexController extends Controller
*/
public function index(Request $request, Carbon $start = null, Carbon $end = null)
{
Log::debug('Start of IndexController::index()');
// collect some basic vars:
$range = app('preferences')->get('viewRange', '1M')->data;
$start = $start ?? session('start', Carbon::now()->startOfMonth());
@ -104,15 +105,18 @@ class IndexController extends Controller
$defaultCurrency = app('amount')->getDefaultCurrency();
$budgeted = '0';
$spent = '0';
Log::debug(sprintf('1) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
// new period stuff:
$periodTitle = app('navigation')->periodShow($start, $range);
$prevLoop = $this->getPreviousPeriods($start, $range);
$nextLoop = $this->getNextPeriods($start, $range);
Log::debug(sprintf('2) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
// get all available budgets.
$ab = $this->abRepository->get($start, $end);
$availableBudgets = [];
Log::debug(sprintf('3) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
// for each, complement with spent amount:
/** @var AvailableBudget $entry */
foreach ($ab as $entry) {
@ -129,6 +133,7 @@ class IndexController extends Controller
$array['budgeted'] = $budgeted;
$availableBudgets[] = $array;
unset($spentArr);
Log::debug(sprintf('4) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
}
if (0 === count($availableBudgets)) {
@ -137,6 +142,7 @@ class IndexController extends Controller
$spentArr = $this->opsRepository->sumExpenses($start, $end, null, null, $defaultCurrency);
$spent = $spentArr[$defaultCurrency->id]['sum'] ?? '0';
unset($spentArr);
Log::debug(sprintf('5) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
}
// count the number of enabled currencies. This determines if we display a "+" button.
@ -146,11 +152,12 @@ class IndexController extends Controller
// number of days for consistent budgeting.
$activeDaysPassed = $this->activeDaysPassed($start, $end); // see method description.
$activeDaysLeft = $this->activeDaysLeft($start, $end); // see method description.
Log::debug(sprintf('Start: %s, end: %s', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
Log::debug(sprintf('6) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
// get all budgets, and paginate them into $budgets.
$collection = $this->repository->getActiveBudgets();
$budgets = [];
Log::debug(sprintf('7) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
// complement budget with budget limits in range, and expenses in currency X in range.
/** @var Budget $current */
@ -161,18 +168,22 @@ class IndexController extends Controller
$array['attachments'] = $this->repository->getAttachments($current);
$array['auto_budget'] = $this->repository->getAutoBudget($current);
$budgetLimits = $this->blRepository->getBudgetLimits($current, $start, $end);
Log::debug(sprintf('8) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
/** @var BudgetLimit $limit */
foreach ($budgetLimits as $limit) {
$currency = $limit->transactionCurrency ?? $defaultCurrency;
$array['budgeted'][] = [
'id' => $limit->id,
'amount' => round($limit->amount, $currency->decimal_places),
'start_date' => $limit->start_date->formatLocalized($this->monthAndDayFormat),
'end_date' => $limit->end_date->formatLocalized($this->monthAndDayFormat),
'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end),
'currency_id' => $currency->id,
'currency_symbol' => $currency->symbol,
'currency_name' => $currency->name,
'currency_decimal_places' => $currency->decimal_places,
];
Log::debug(sprintf('9) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
}
/** @var TransactionCurrency $currency */
@ -183,6 +194,7 @@ class IndexController extends Controller
$array['spent'][$currency->id]['currency_id'] = $currency->id;
$array['spent'][$currency->id]['currency_symbol'] = $currency->symbol;
$array['spent'][$currency->id]['currency_decimal_places'] = $currency->decimal_places;
Log::debug(sprintf('10) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
}
}
$budgets[] = $array;
@ -190,7 +202,7 @@ class IndexController extends Controller
// get all inactive budgets, and simply list them:
$inactive = $this->repository->getInactiveBudgets();
Log::debug(sprintf('11) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
return view(
'budgets.index',

View File

@ -121,11 +121,11 @@ class DebugController extends Controller
$search = ['~', '#'];
$replace = ['\~', '# '];
$now = Carbon::now()->format('Y-m-d H:i:s e');
$installationId = app('fireflyconfig')->get('installation_id', '')->data;
$phpVersion = str_replace($search, $replace, PHP_VERSION);
$phpOs = str_replace($search, $replace, PHP_OS);
$interface = PHP_SAPI;
$now = Carbon::now()->format('Y-m-d H:i:s e');
$drivers = implode(', ', DB::availableDrivers());
$currentDriver = DB::getDriverName();
$userAgent = $request->header('user-agent');
@ -137,14 +137,23 @@ class DebugController extends Controller
$logChannel = config('logging.default');
$appLogLevel = config('logging.level');
$cacheDriver = config('cache.default');
$loginProvider = config('auth.driver');
$loginProvider = config('auth.providers.users.driver');
// some new vars.
$telemetry = true === config('firefly.send_telemetry') && true === config('firefly.feature_flags.telemetry');
$defaultLanguage = (string) config('firefly.default_language');
$defaultLocale = (string) config('firefly.default_locale');
$userLanguage = app('steam')->getLanguage();
$userLocale = app('steam')->getLocale();
$isDocker = env('IS_DOCKER', false);
// set languages, see what happens:
$original = setlocale(LC_ALL, 0);
$localeAttempts = [];
$parts = app('steam')->getLocaleArray(app('steam')->getLocale());
foreach ($parts as $code) {
$code = trim($code);
$code = trim($code);
Log::debug(sprintf('Trying to set %s', $code));
$localeAttempts[$code] = var_export(setlocale(LC_ALL, $code), true);
}
setlocale(LC_ALL, $original);
@ -194,7 +203,14 @@ class DebugController extends Controller
'interface',
'logContent',
'cacheDriver',
'trustedProxies'
'trustedProxies',
'telemetry',
'userLanguage',
'userLocale',
'defaultLanguage',
'defaultLocale',
'isDocker'
)
);
}

View File

@ -124,6 +124,11 @@ class NewUserController extends Controller
'invoice_date' => false, 'internal_reference' => false, 'notes' => true, 'attachments' => true,];
app('preferences')->set('transaction_journal_optional_fields', $visibleFields);
// telemetry: user language preference + default language.
app('telemetry')->feature('config.firefly.default_language', config('firefly.default_language', 'en_US'));
app('telemetry')->feature('user.preferences.language', app('steam')->getLanguage());
app('telemetry')->feature('user.preferences.locale', app('steam')->getLocale());
session()->flash('success', (string) trans('firefly.stored_new_accounts_new_user'));
app('preferences')->mark();

View File

@ -186,9 +186,11 @@ class PreferencesController extends Controller
}
// same for locale:
/** @var Preference $currentLocale */
$locale = $request->get('locale');
app('preferences')->set('locale', $locale);
if (!auth()->user()->hasRole('demo')) {
/** @var Preference $currentLocale */
$locale = $request->get('locale');
app('preferences')->set('locale', $locale);
}
// optional fields for transactions:
$setOptions = $request->get('tj');
@ -208,6 +210,11 @@ class PreferencesController extends Controller
session()->flash('success', (string) trans('firefly.saved_preferences'));
app('preferences')->mark();
// telemetry: user language preference + default language.
app('telemetry')->feature('config.firefly.default_language', config('firefly.default_language', 'en_US'));
app('telemetry')->feature('user.preferences.language', app('steam')->getLanguage());
app('telemetry')->feature('user.preferences.locale', app('steam')->getLocale());
return redirect(route('preferences.index'));
}
}

View File

@ -91,6 +91,7 @@ class IndexController extends Controller
$user = auth()->user();
$this->createDefaultRuleGroup();
$this->createDefaultRule();
$this->ruleGroupRepos->resetRuleGroupOrder();
$ruleGroups = $this->ruleGroupRepos->getRuleGroupsWithRules($user);
return view('rules.index', compact('ruleGroups'));

View File

@ -42,6 +42,7 @@ class CronController
$results = [];
$results[] = $this->runRecurring();
$results[] = $this->runAutoBudget();
$results[] = $this->runTelemetry();
return implode("<br>\n", $results);
}

View File

@ -28,6 +28,7 @@ use Closure;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\Http\Controllers\RequestInformation;
use Illuminate\Http\Request;
use Log;
/**
* Class SessionFilter.
@ -87,6 +88,7 @@ class Range
// send error to view if could not set money format
if (false === $moneyResult) {
Log::error('Could not set locale. The following array doesnt work: ', $localeArray);
app('view')->share('invalidMonetaryLocale', true); // @codeCoverageIgnore
}

View File

@ -84,7 +84,7 @@ class MailError extends Job implements ShouldQueue
$args,
function (Message $message) use ($email) {
if ('mail@example.com' !== $email) {
$message->to($email, $email)->subject('Caught an error in Firefly III');
$message->to($email, $email)->subject((string) trans('email.error_subject'));
}
}
);

View File

@ -24,6 +24,7 @@ namespace FireflyIII\Jobs;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Telemetry;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
@ -33,7 +34,9 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use JsonException;
use Log;
use Exception;
/**
* Class SubmitTelemetryData
@ -61,7 +64,7 @@ class SubmitTelemetryData implements ShouldQueue
}
/**
*
* @throws FireflyException
*/
public function handle(): void
{
@ -77,9 +80,17 @@ class SubmitTelemetryData implements ShouldQueue
}
$json = $this->parseJson($telemetry);
try {
$body = json_encode($json, JSON_THROW_ON_ERROR, 512);
} catch (JsonException $e) {
Log::error($e->getMessage());
Log::error('Could not parse JSON.');
throw new FireflyException(sprintf('Could not parse telemetry JSON: %s', $e->getMessage()));
}
$client = new Client;
$options = [
'body' => json_encode($json, JSON_THROW_ON_ERROR, 512),
'body' => $body,
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
@ -89,11 +100,11 @@ class SubmitTelemetryData implements ShouldQueue
];
try {
$result = $client->post($url, $options);
} catch (GuzzleException $e) {
} catch (GuzzleException|Exception $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
Log::error('Could not submit telemetry.');
return;
throw new FireflyException(sprintf('Could not submit telemetry: %s', $e->getMessage()));
}
$body = (string) $result->getBody();
$statusCode = $result->getStatusCode();
@ -156,6 +167,7 @@ class SubmitTelemetryData implements ShouldQueue
foreach ($telemetry as $entry) {
$array[] = [
'installation_id' => $entry->installation_id,
'collected_at' => $entry->created_at->format('r'),
'type' => $entry->type,
'key' => $entry->key,
'value' => $entry->value,
@ -165,4 +177,4 @@ class SubmitTelemetryData implements ShouldQueue
return $array;
}
}
}

View File

@ -63,6 +63,6 @@ class AccessTokenCreatedMail extends Mailable
public function build(): self
{
return $this->view('emails.access-token-created-html')->text('emails.access-token-created-text')
->subject('A new access token was created');
->subject((string) trans('email.access_token_created_subject'));
}
}

View File

@ -62,6 +62,6 @@ class AdminTestMail extends Mailable
public function build(): self
{
return $this->view('emails.admin-test-html')->text('emails.admin-test-text')
->subject('A test message from your Firefly III installation');
->subject((string) trans('email.admin_test_subject'));
}
}

View File

@ -70,6 +70,6 @@ class ConfirmEmailChangeMail extends Mailable
public function build(): self
{
return $this->view('emails.confirm-email-change-html')->text('emails.confirm-email-change-text')
->subject('Your Firefly III email address has changed');
->subject((string) trans('email.email_change_subject'));
}
}

View File

@ -67,6 +67,6 @@ class OAuthTokenCreatedMail extends Mailable
public function build(): self
{
return $this->view('emails.oauth-client-created-html')->text('emails.oauth-client-created-text')
->subject('A new OAuth client has been created');
->subject((string) trans('email.oauth_created_subject'));
}
}

View File

@ -61,6 +61,6 @@ class RegisteredUser extends Mailable
*/
public function build(): self
{
return $this->view('emails.registered-html')->text('emails.registered-text')->subject('Welcome to Firefly III!');
return $this->view('emails.registered-html')->text('emails.registered-text')->subject((string) trans('email.registered_subject'));
}
}

View File

@ -79,7 +79,7 @@ class ReportNewJournalsMail extends Mailable
$this->transform();
return $this->view('emails.report-new-journals-html')->text('emails.report-new-journals-text')
->subject($subject);
->subject((string) trans_choice('email.new_journals_subject', $this->groups->count()));
}
private function transform(): void

View File

@ -60,6 +60,6 @@ class RequestedNewPassword extends Mailable
*/
public function build(): self
{
return $this->view('emails.password-html')->text('emails.password-text')->subject('Your password reset request');
return $this->view('emails.password-html')->text('emails.password-text')->subject((string) trans('email.reset_pw_subject'));
}
}

View File

@ -68,6 +68,6 @@ class UndoEmailChangeMail extends Mailable
public function build(): self
{
return $this->view('emails.undo-email-change-html')->text('emails.undo-email-change-text')
->subject('Your Firefly III email address has changed');
->subject((string) trans('email.email_change_subject'));
}
}

View File

@ -26,6 +26,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
use Laravel\Passport\Passport;
use URL;
use Adldap\Laravel\Middleware\WindowsAuthenticate;
/**
* @codeCoverageIgnore
@ -44,6 +45,9 @@ class AppServiceProvider extends ServiceProvider
if ('heroku' === config('app.env')) {
URL::forceScheme('https');
}
if (config('ldap_auth.identifiers.windows.enabled', false)) {
$this->app['router']->pushMiddlewareToGroup('web', WindowsAuthenticate::class);
}
}
/**

View File

@ -330,7 +330,7 @@ class BudgetRepository implements BudgetRepositoryInterface
// create initial budget limit.
$today = new Carbon;
$start = app('navigation')->startOfPeriod($today, $autoBudget->period);
$end = app('navigation')->startOfPeriod($start, $autoBudget->period);
$end = app('navigation')->endOfPeriod($start, $autoBudget->period);
$limitRepos = app(BudgetLimitRepositoryInterface::class);
$limitRepos->setUser($this->user);

View File

@ -56,7 +56,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
}
/**
* @param RuleGroup $ruleGroup
* @param RuleGroup $ruleGroup
* @param RuleGroup|null $moveTo
*
* @return bool
@ -92,12 +92,18 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
{
$this->user->ruleGroups()->whereNotNull('deleted_at')->update(['order' => 0]);
$set = $this->user->ruleGroups()->where('active', 1)->orderBy('order', 'ASC')->get();
$set = $this->user
->ruleGroups()
->orderBy('order', 'ASC')->get();
$count = 1;
/** @var RuleGroup $entry */
foreach ($set as $entry) {
$entry->order = $count;
$entry->save();
// also update rules in group.
$this->resetRulesInGroupOrder($entry);
++$count;
}
@ -209,18 +215,16 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
public function getRuleGroupsWithRules(User $user): Collection
{
return $user->ruleGroups()
->orderBy('active', 'DESC')
->orderBy('order', 'ASC')
->with(
[
'rules' => function (HasMany $query) {
$query->orderBy('active', 'DESC');
'rules' => static function (HasMany $query) {
$query->orderBy('order', 'ASC');
},
'rules.ruleTriggers' => function (HasMany $query) {
'rules.ruleTriggers' => static function (HasMany $query) {
$query->orderBy('order', 'ASC');
},
'rules.ruleActions' => function (HasMany $query) {
'rules.ruleActions' => static function (HasMany $query) {
$query->orderBy('order', 'ASC');
},
]
@ -328,7 +332,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
/**
* @param RuleGroup $ruleGroup
* @param array $data
* @param array $data
*
* @return RuleGroup
*/
@ -353,4 +357,5 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
{
return $this->user->ruleGroups()->where('title', $title)->first();
}
}

View File

@ -346,6 +346,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
Log::warning('Group repository caught group factory with a duplicate exception!');
throw new DuplicateTransactionException($e->getMessage());
} catch(FireflyException $e) {
Log::warning('Group repository caught group factory with an exception!');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage());

View File

@ -95,8 +95,6 @@ class JournalDestroyService
// update events
$journal->piggyBankEvents()->update(['transaction_journal_id' => null]);
$journal->delete();
} catch (Exception $e) {
Log::error(sprintf('Could not delete bill: %s', $e->getMessage())); // @codeCoverageIgnore

View File

@ -52,11 +52,10 @@ class AccountList implements BinderInterface
/** @var Collection $collection */
$collection = auth()->user()->accounts()
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->where('account_types.type', AccountType::ASSET)
->whereIn('account_types.type', [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE])
->orderBy('accounts.name', 'ASC')
->get(['accounts.*']);
}
if ('allAssetAccounts' !== $value) {
$incoming = array_map('\intval', explode(',', $value));
$list = array_merge(array_unique($incoming), [0]);

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Support\Cronjobs;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Jobs\SubmitTelemetryData;
use FireflyIII\Models\Configuration;
use Log;
@ -35,9 +36,17 @@ class TelemetryCronjob extends AbstractCronjob
/**
* @inheritDoc
* @throws FireflyException
*/
public function fire(): bool
{
// do not fire if telemetry is disabled.
if (false === config('firefly.send_telemetry') || false === config('firefly.feature_flags.telemetry')) {
Log::warning('Telemetry is disabled. The cron job will do nothing.');
return false;
}
/** @var Configuration $config */
$config = app('fireflyconfig')->get('last_tm_job', 0);
$lastTime = (int) $config->data;
@ -46,8 +55,8 @@ class TelemetryCronjob extends AbstractCronjob
if (0 === $lastTime) {
Log::info('Telemetry cron-job has never fired before.');
}
// less than half a day ago:
if ($lastTime > 0 && $diff <= 43200) {
// less than a week ago:
if ($lastTime > 0 && $diff <= 604800) {
Log::info(sprintf('It has been %s since the telemetry cron-job has fired.', $diffForHumans));
if (false === $this->force) {
Log::info('The cron-job will not fire now.');
@ -60,8 +69,8 @@ class TelemetryCronjob extends AbstractCronjob
Log::info('Execution of the telemetry cron-job has been FORCED.');
}
}
if ($lastTime > 0 && $diff > 43200) {
// more than a week ago.
if ($lastTime > 0 && $diff > 604799) {
Log::info(sprintf('It has been %s since the telemetry cron-job has fired. It will fire now!', $diffForHumans));
}
@ -74,7 +83,7 @@ class TelemetryCronjob extends AbstractCronjob
/**
*
* @throws FireflyException
*/
private function fireTelemetry(): void
{
@ -84,7 +93,10 @@ class TelemetryCronjob extends AbstractCronjob
$job->setDate($this->date);
$job->setForce($this->force);
$job->handle();
// TODO remove old, submitted telemetry data.
app('fireflyconfig')->set('last_tm_job', (int) $this->date->format('U'));
Log::info('Done with telemetry cron job task.');
}
}
}

View File

@ -26,6 +26,7 @@ namespace FireflyIII\Support\Http\Controllers;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Support\Cronjobs\AutoBudgetCronjob;
use FireflyIII\Support\Cronjobs\RecurringCronjob;
use FireflyIII\Support\Cronjobs\TelemetryCronjob;
/**
* Trait CronRunner
@ -51,6 +52,24 @@ trait CronRunner
return 'The recurring transaction cron job fired successfully.';
}
/**
* @return string
*/
protected function runTelemetry(): string {
/** @var TelemetryCronjob $telemetry */
$telemetry = app(TelemetryCronjob::class);
try {
$result = $telemetry->fire();
} catch (FireflyException $e) {
return $e->getMessage();
}
if (false === $result) {
return 'The telemetry cron job did not fire.';
}
return 'The telemetry cron job fired successfully.';
}
/**
* @return string
*/

View File

@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Support;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use Log;
/**
* Class ParseDateString
*/
class ParseDateString
{
private $keywords
= [
'today',
'yesterday',
'tomorrow',
'start of this week',
'end of this week',
'start of this month',
'end of this month',
'start of this quarter',
'end of this quarter',
'start of this year',
'end of this year',
];
/**
* @param string $date
*
* @return Carbon
*/
public function parseDate(string $date): Carbon
{
// parse keywords:
if (in_array($date, $this->keywords, true)) {
return $this->parseKeyword($date);
}
// if regex for YYYY-MM-DD:
$pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][\d]|3[01])$/';
if (preg_match($pattern, $date)) {
return $this->parseDefaultDate($date);
}
// if + or -:
if (0 === strpos($date, '+') || 0 === strpos($date, '-')) {
return $this->parseRelativeDate($date);
}
throw new FireflyException('Not recognised.');
}
/**
* @param string $date
*
* @return Carbon
*/
private function parseDefaultDate(string $date): Carbon
{
return Carbon::createFromFormat('Y-m-d', $date);
}
/**
* @param string $keyword
*
* @return Carbon
*/
private function parseKeyword(string $keyword): Carbon
{
$today = Carbon::today()->startOfDay();
switch ($keyword) {
default:
case 'today':
return $today;
case 'yesterday':
return $today->subDay();
case 'tomorrow':
return $today->addDay();
case 'start of this week':
return $today->startOfWeek();
case 'end of this week':
return $today->endOfWeek();
case 'start of this month':
return $today->startOfMonth();
case 'end of this month':
return $today->endOfMonth();
case 'start of this quarter':
return $today->startOfQuarter();
case 'end of this quarter':
return $today->endOfQuarter();
case 'start of this year':
return $today->startOfYear();
case 'end of this year':
return $today->endOfYear();
}
}
/**
* @param string $date
*
* @return Carbon
*/
private function parseRelativeDate(string $date): Carbon
{
Log::debug(sprintf('Now in parseRelativeDate("%s")', $date));
$parts = explode(' ', $date);
$today = Carbon::today()->startOfDay();
$functions = [
[
'd' => 'subDays',
'w' => 'subWeeks',
'm' => 'subMonths',
'q' => 'subQuarters',
'y' => 'subYears',
], [
'd' => 'addDays',
'w' => 'addWeeks',
'm' => 'addMonths',
'q' => 'addQuarters',
'y' => 'addYears',
],
];
/** @var string $part */
foreach ($parts as $part) {
Log::debug(sprintf('Now parsing part "%s"', $part));
$part = trim($part);
// verify if correct
$pattern = '/[+-]\d+[wqmdy]/';
$res = preg_match($pattern, $part);
if (0 === $res || false === $res) {
Log::error(sprintf('Part "%s" does not match regular expression. Will be skipped.', $part));
continue;
}
$direction = 0 === strpos($part, '+') ? 1 : 0;
$period = $part[strlen($part) - 1];
$number = (int) substr($part, 1, -1);
if (!isset($functions[$direction][$period])) {
Log::error(sprintf('No method for direction %d and period "%s".', $direction, $period));
continue;
}
$func = $functions[$direction][$period];
Log::debug(sprintf('Will now do %s(%d) on %s', $func, $number, $today->format('Y-m-d')));
$today->$func($number);
Log::debug(sprintf('Resulting date is %s', $today->format('Y-m-d')));
}
return $today;
}
}

View File

@ -22,7 +22,9 @@ declare(strict_types=1);
namespace FireflyIII\Support;
use Carbon\Carbon;
use FireflyIII\Models\Telemetry as TelemetryModel;
use JsonException;
use Log;
/**
@ -65,6 +67,25 @@ class Telemetry
}
}
/**
* @param string $key
* @param string $value
* @param int $days
*/
public function recurring(string $key, string $value, int $days): void
{
if (false === config('firefly.send_telemetry') || false === config('firefly.feature_flags.telemetry')) {
// hard stop if not allowed to do telemetry.
// do nothing!
return;
}
$cutoffDate = Carbon::today()->subDays($days);
if (!$this->hasRecentEntry('recurring', $key, $value, $cutoffDate)) {
$this->storeEntry('recurring', $key, $value);
}
}
/**
* String telemetry stores a string value as a telemetry entry. Values could include:
*
@ -85,7 +106,6 @@ class Telemetry
}
Log::info(sprintf('Logged telemetry string "%s" with value "%s".', $name, $value));
// no storage backend yet, do nothing.
$this->storeEntry('string', $name, $value);
}
@ -98,16 +118,49 @@ class Telemetry
*/
private function hasEntry(string $type, string $key, string $value): bool
{
try {
$jsonEncoded = json_encode($value, JSON_THROW_ON_ERROR, 512);
} catch (JsonException $e) {
Log::error(sprintf('JSON Exception encoding the following value: %s: %s', $value, $e->getMessage()));
$jsonEncoded = [];
}
return TelemetryModel
::where('type', $type)
->where('key', $key)
->where('value', json_encode($value, JSON_THROW_ON_ERROR, 512))
->where('value', $jsonEncoded)
->count() > 0;
}
/**
* @param string $type
* @param string $key
* @param string $value
* @param Carbon $date
*
* @return bool
*/
private function hasRecentEntry(string $type, string $key, string $value, Carbon $date): bool
{
try {
$jsonEncoded = json_encode($value, JSON_THROW_ON_ERROR, 512);
} catch (JsonException $e) {
Log::error(sprintf('JSON Exception encoding the following value: %s: %s', $value, $e->getMessage()));
$jsonEncoded = [];
}
return TelemetryModel
::where('type', $type)
->where('key', $key)
->where('created_at', '>=', $date->format('Y-m-d H:i:s'))
->where('value', $jsonEncoded)
->count() > 0;
}
/**
* Store new entry in DB.
*
* @param string $type
* @param string $name
* @param string $value
*/
@ -123,4 +176,4 @@ class Telemetry
);
}
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
* DeleteTransaction.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\TransactionRules\Actions;
use Exception;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
use FireflyIII\Services\Internal\Destroy\TransactionGroupDestroyService;
use Log;
/**
* Class DeleteTransaction.
*/
class DeleteTransaction implements ActionInterface
{
/**
* TriggerInterface constructor.
*
* @param RuleAction $action
*/
public function __construct(RuleAction $action)
{
}
/**
* Will delete transaction journal. Also the group if no other journals are in the group.
*
* @param TransactionJournal $journal
*
* @throws Exception
* @return bool
*/
public function act(TransactionJournal $journal): bool
{
$count = $journal->transactionGroup->transactionJournals()->count();
// destroy entire group.
if (1 === $count) {
Log::debug(
sprintf(
'RuleAction DeleteTransaction DELETED the entire transaction group of journal #%d ("%s").',
$journal->id, $journal->description
)
);
$service = app(TransactionGroupDestroyService::class);
$service->destroy($journal->transactionGroup);
return true;
}
Log::debug(sprintf('RuleAction DeleteTransaction DELETED transaction journal #%d ("%s").', $journal->id, $journal->description));
// trigger delete factory:
/** @var JournalDestroyService $service */
$service = app(JournalDestroyService::class);
$service->destroy($journal);
return true;
}
}

View File

@ -22,7 +22,6 @@ declare(strict_types=1);
namespace FireflyIII\TransactionRules\Triggers;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Log;
@ -76,31 +75,6 @@ final class BudgetIs extends AbstractTrigger implements TriggerInterface
return true;
}
}
if (null === $budget) {
// perhaps transactions have this budget?
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
$budget = $transaction->budgets()->first();
if (null !== $budget) {
$name = strtolower($budget->name);
if ($name === strtolower($this->triggerValue)) {
Log::debug(
sprintf(
'RuleTrigger BudgetIs for journal #%d (transaction #%d): "%s" is "%s", return true.',
$journal->id,
$transaction->id,
$name,
$this->triggerValue
)
);
return true;
}
}
}
}
Log::debug(sprintf('RuleTrigger BudgetIs for journal #%d: does not have budget "%s", return false.', $journal->id, $this->triggerValue));
return false;

View File

@ -22,7 +22,6 @@ declare(strict_types=1);
namespace FireflyIII\TransactionRules\Triggers;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Log;
@ -76,31 +75,6 @@ final class CategoryIs extends AbstractTrigger implements TriggerInterface
return true;
}
}
if (null === $category) {
// perhaps transactions have this category?
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
$category = $transaction->categories()->first();
if (null !== $category) {
$name = strtolower($category->name);
if ($name === strtolower($this->triggerValue)) {
Log::debug(
sprintf(
'RuleTrigger CategoryIs for journal #%d (transaction #%d): "%s" is "%s", return true.',
$journal->id,
$transaction->id,
$name,
$this->triggerValue
)
);
return true;
}
}
}
}
Log::debug(sprintf('RuleTrigger CategoryIs for journal #%d: does not have category "%s", return false.', $journal->id, $this->triggerValue));
return false;

View File

@ -0,0 +1,107 @@
<?php
/**
* DateAfter.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\TransactionRules\Triggers;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\ParseDateString;
use Log;
/**
* Class DateAfter.
*/
final class DateAfter extends AbstractTrigger implements TriggerInterface
{
/**
* A trigger is said to "match anything", or match any given transaction,
* when the trigger value is very vague or has no restrictions. Easy examples
* are the "AmountMore"-trigger combined with an amount of 0: any given transaction
* has an amount of more than zero! Other examples are all the "Description"-triggers
* which have hard time handling empty trigger values such as "" or "*" (wild cards).
*
* If the user tries to create such a trigger, this method MUST return true so Firefly III
* can stop the storing / updating the trigger. If the trigger is in any way restrictive
* (even if it will still include 99.9% of the users transactions), this method MUST return
* false.
*
* @param mixed $value
*
* @return bool
*/
public static function willMatchEverything($value = null): bool
{
if (null !== $value) {
return false;
}
Log::error(sprintf('Cannot use %s with a null value.', self::class));
return true;
}
/**
* Returns true when category is X.
*
* @param TransactionJournal $journal
*
* @return bool
*/
public function triggered(TransactionJournal $journal): bool
{
/** @var Carbon $date */
$date = $journal->date;
Log::debug(sprintf('Found date on journal: %s', $date->format('Y-m-d')));
$dateParser = new ParseDateString();
try {
$ruleDate = $dateParser->parseDate($this->triggerValue);
} catch (FireflyException $e) {
Log::error('Cannot execute rule trigger.');
Log::error($e->getMessage());
return false;
}
if ($date->isAfter($ruleDate)) {
Log::debug(
sprintf(
'%s is after %s, so return true.',
$date->format('Y-m-d H:i:s'),
$ruleDate->format('Y-m-d H:i:s'),
)
);
return true;
}
Log::debug(
sprintf(
'%s is NOT after %s, so return true.',
$date->format('Y-m-d H:i:s'),
$ruleDate->format('Y-m-d H:i:s'),
)
);
return false;
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* DateBefore.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\TransactionRules\Triggers;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\ParseDateString;
use Log;
/**
* Class DateBefore.
*/
final class DateBefore extends AbstractTrigger implements TriggerInterface
{
/**
* A trigger is said to "match anything", or match any given transaction,
* when the trigger value is very vague or has no restrictions. Easy examples
* are the "AmountMore"-trigger combined with an amount of 0: any given transaction
* has an amount of more than zero! Other examples are all the "Description"-triggers
* which have hard time handling empty trigger values such as "" or "*" (wild cards).
*
* If the user tries to create such a trigger, this method MUST return true so Firefly III
* can stop the storing / updating the trigger. If the trigger is in any way restrictive
* (even if it will still include 99.9% of the users transactions), this method MUST return
* false.
*
* @param mixed $value
*
* @return bool
*/
public static function willMatchEverything($value = null): bool
{
if (null !== $value) {
return false;
}
Log::error(sprintf('Cannot use %s with a null value.', self::class));
return true;
}
/**
* Returns true when category is X.
*
* @param TransactionJournal $journal
*
* @return bool
*/
public function triggered(TransactionJournal $journal): bool
{
/** @var Carbon $date */
$date = $journal->date;
Log::debug(sprintf('Found date on journal: %s', $date->format('Y-m-d')));
$dateParser = new ParseDateString();
try {
$ruleDate = $dateParser->parseDate($this->triggerValue);
} catch (FireflyException $e) {
Log::error('Cannot execute rule trigger.');
Log::error($e->getMessage());
return false;
}
if ($date->isBefore($ruleDate)) {
Log::debug(
sprintf(
'%s is before %s, so return true.',
$date->format('Y-m-d H:i:s'),
$ruleDate->format('Y-m-d H:i:s'),
)
);
return true;
}
Log::debug(
sprintf(
'%s is NOT before %s, so return true.',
$date->format('Y-m-d H:i:s'),
$ruleDate->format('Y-m-d H:i:s'),
)
);
return false;
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* DateIs.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\TransactionRules\Triggers;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\ParseDateString;
use Log;
/**
* Class DateIs.
*/
final class DateIs extends AbstractTrigger implements TriggerInterface
{
/**
* A trigger is said to "match anything", or match any given transaction,
* when the trigger value is very vague or has no restrictions. Easy examples
* are the "AmountMore"-trigger combined with an amount of 0: any given transaction
* has an amount of more than zero! Other examples are all the "Description"-triggers
* which have hard time handling empty trigger values such as "" or "*" (wild cards).
*
* If the user tries to create such a trigger, this method MUST return true so Firefly III
* can stop the storing / updating the trigger. If the trigger is in any way restrictive
* (even if it will still include 99.9% of the users transactions), this method MUST return
* false.
*
* @param mixed $value
*
* @return bool
*/
public static function willMatchEverything($value = null): bool
{
if (null !== $value) {
return false;
}
Log::error(sprintf('Cannot use %s with a null value.', self::class));
return true;
}
/**
* Returns true when category is X.
*
* @param TransactionJournal $journal
*
* @return bool
*/
public function triggered(TransactionJournal $journal): bool
{
/** @var Carbon $date */
$date = $journal->date;
Log::debug(sprintf('Found date on journal: %s', $date->format('Y-m-d')));
$dateParser = new ParseDateString();
try {
$ruleDate = $dateParser->parseDate($this->triggerValue);
} catch (FireflyException $e) {
Log::error('Cannot execute rule trigger.');
Log::error($e->getMessage());
return false;
}
if ($ruleDate->isSameDay($date)) {
Log::debug(
sprintf(
'%s is on the same day as %s, so return true.',
$date->format('Y-m-d H:i:s'),
$ruleDate->format('Y-m-d H:i:s'),
)
);
return true;
}
Log::debug(
sprintf(
'%s is NOT on the same day as %s, so return true.',
$date->format('Y-m-d H:i:s'),
$ruleDate->format('Y-m-d H:i:s'),
)
);
return false;
}
}

View File

@ -24,6 +24,7 @@ namespace FireflyIII\Validation;
use Config;
use DB;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
@ -34,11 +35,13 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Services\Password\Verifier;
use FireflyIII\Support\ParseDateString;
use FireflyIII\TransactionRules\Triggers\TriggerInterface;
use FireflyIII\User;
use Google2FA;
use Illuminate\Support\Collection;
use Illuminate\Validation\Validator;
use Log;
/**
* Class FireflyValidator.
@ -333,6 +336,20 @@ class FireflyValidator extends Validator
return 1 === $count;
}
// if the type is date, the simply try to parse it and throw error when it's bad.
if (in_array($triggerType, ['date_is'], true)) {
/** @var ParseDateString $parser */
$parser = app(ParseDateString::class);
try {
$parser->parseDate($value);
} catch (FireflyException $e) {
Log::error($e->getMessage());
return false;
}
}
// and finally a "will match everything check":
$classes = app('config')->get('firefly.rule-triggers');
/** @var TriggerInterface $class */

View File

@ -2,6 +2,39 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [5.2.7 (API 1.1.0) - 2020-06-01
Firefly III v5.2.7 is the first version of Firefly III with the ability to opt-in to usage telemetry. This entirely optional of course.
## [5.2.6 (API 1.1.0)] - 2020-05-22
### Added
- [Issue 3049](https://github.com/firefly-iii/firefly-iii/issues/3049) New transaction triggers for dates.
- Warning if recurring transactions no longer run.
- View fixed for recurring transactions.
- A new rule action that will DELETE transactions.
- Four-week reminder to check for updates if you disabled updates.
### Changed
- [Issue 3011](https://github.com/firefly-iii/firefly-iii/issues/3011) Reconciliation page has "select all"-button and remembers checkboxes even when you refresh the page.
- [Issue 3348](https://github.com/firefly-iii/firefly-iii/issues/3348) Smarter menu for accounts on the dashboard
- Demo user can't upload attachments.
- Demo user can't set locale.
### Fixed
- [Issue 3339](https://github.com/firefly-iii/firefly-iii/issues/3339) Could not mass-delete reconciliation transactions.
- [Issue 3344](https://github.com/firefly-iii/firefly-iii/issues/3344) Could not attach files to accounts.
- [Issue 3335](https://github.com/firefly-iii/firefly-iii/issues/3335) Fix reconciliation currency account, thanks to @maksimkurb
- [Issue 3350](https://github.com/firefly-iii/firefly-iii/issues/3350) Better charts in account overview
- [Issue 3355](https://github.com/firefly-iii/firefly-iii/issues/3355) Better sorting for bills in reports.
- [Issue 3363](https://github.com/firefly-iii/firefly-iii/issues/3363) New strings translated, thanks to @sephrat
- [Issue 3367](https://github.com/firefly-iii/firefly-iii/issues/3367) Error in views when uploading > 1 attachments
- [Issue 3368](https://github.com/firefly-iii/firefly-iii/issues/3368) Wrong hover-text
- [Issue 3374](https://github.com/firefly-iii/firefly-iii/issues/3374) Inconsistent net worth calculation. You may seem to lose money.
- [Issue 3376](https://github.com/firefly-iii/firefly-iii/issues/3376) Better rule ordering when cloning rules.
- [Issue 3381](https://github.com/firefly-iii/firefly-iii/issues/3381) Fix for LDAP
- Better rounding for budget amounts.
## [5.2.5 (API 1.1.0)] - 2020-05-04
### Added

View File

@ -94,11 +94,7 @@
"laravelcollective/html": "6.*",
"league/commonmark": "1.*",
"league/csv": "9.*",
"league/flysystem-replicate-adapter": "1.*",
"league/flysystem-sftp": "1.*",
"league/fractal": "0.*",
"litipk/flysystem-fallback-adapter": "0.*",
"mschindler83/fints-hbci-php": "1.*",
"pragmarx/google2fa": "^7.0",
"pragmarx/recovery": "^0.1.0",
"predis/predis": "^1.1",

547
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -39,6 +39,38 @@ if (!(false === $databaseUrl)) {
$database = substr($options['path'] ?? '/firefly', 1);
}
/*
* Get SSL parameters from .env file.
*/
$mysql_ssl_ca_dir = envNonEmpty('MYSQL_SSL_CAPATH', null);
$mysql_ssl_ca_file = envNonEmpty('MYSQL_SSL_CA', null);
$mysql_ssl_cert = envNonEmpty('MYSQL_SSL_CERT', null);
$mysql_ssl_key = envNonEmpty('MYSQL_SSL_KEY', null);
$mysql_ssl_ciphers = envNonEmpty('MYSQL_SSL_CIPHER', null);
$mysql_ssl_verify = envNonEmpty('MYSQL_SSL_VERIFY_SERVER_CERT', null);
$mySqlSSLOptions = [];
if (false !== envNonEmpty('MYSQL_USE_SSL', false)) {
if (null !== $mysql_ssl_ca_dir) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_CAPATH] = $mysql_ssl_ca_dir;
}
if (null !== $mysql_ssl_ca_file) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_CA] = $mysql_ssl_ca_file;
}
if (null !== $mysql_ssl_cert) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_CERT] = $mysql_ssl_cert;
}
if (null !== $mysql_ssl_key) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_KEY] = $mysql_ssl_key;
}
if (null !== $mysql_ssl_ciphers) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_CIPHER] = $mysql_ssl_ciphers;
}
if (null !== $mysql_ssl_verify) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $mysql_ssl_verify;
}
}
return [
'default' => envNonEmpty('DB_CONNECTION', 'pgsql'),
'connections' => [
@ -60,6 +92,7 @@ return [
'prefix' => '',
'strict' => true,
'engine' => 'InnoDB',
'options' => $mySqlSSLOptions,
],
'pgsql' => [
'driver' => 'pgsql',

View File

@ -66,24 +66,28 @@ use FireflyIII\TransactionRules\Actions\ClearNotes;
use FireflyIII\TransactionRules\Actions\ConvertToDeposit;
use FireflyIII\TransactionRules\Actions\ConvertToTransfer;
use FireflyIII\TransactionRules\Actions\ConvertToWithdrawal;
use FireflyIII\TransactionRules\Actions\DeleteTransaction;
use FireflyIII\TransactionRules\Actions\LinkToBill;
use FireflyIII\TransactionRules\Actions\PrependDescription;
use FireflyIII\TransactionRules\Actions\PrependNotes;
use FireflyIII\TransactionRules\Actions\RemoveAllTags;
use FireflyIII\TransactionRules\Actions\RemoveTag;
use FireflyIII\TransactionRules\Actions\SetBudget;
use FireflyIII\TransactionRules\Actions\UpdatePiggybank;
use FireflyIII\TransactionRules\Actions\SetCategory;
use FireflyIII\TransactionRules\Actions\SetDescription;
use FireflyIII\TransactionRules\Actions\SetDestinationAccount;
use FireflyIII\TransactionRules\Actions\SetNotes;
use FireflyIII\TransactionRules\Actions\SetSourceAccount;
use FireflyIII\TransactionRules\Actions\UpdatePiggybank;
use FireflyIII\TransactionRules\Triggers\AmountExactly;
use FireflyIII\TransactionRules\Triggers\AmountLess;
use FireflyIII\TransactionRules\Triggers\AmountMore;
use FireflyIII\TransactionRules\Triggers\BudgetIs;
use FireflyIII\TransactionRules\Triggers\CategoryIs;
use FireflyIII\TransactionRules\Triggers\CurrencyIs;
use FireflyIII\TransactionRules\Triggers\DateIs;
use FireflyIII\TransactionRules\Triggers\DateBefore;
use FireflyIII\TransactionRules\Triggers\DateAfter;
use FireflyIII\TransactionRules\Triggers\DescriptionContains;
use FireflyIII\TransactionRules\Triggers\DescriptionEnds;
use FireflyIII\TransactionRules\Triggers\DescriptionIs;
@ -135,11 +139,11 @@ return [
],
'feature_flags' => [
'export' => true,
'telemetry' => false,
'telemetry' => true,
],
'encryption' => null === env('USE_ENCRYPTION') || true === env('USE_ENCRYPTION'),
'version' => '5.2.5',
'version' => '5.2.6',
'api_version' => '1.1.0',
'db_version' => 13,
'maxUploadSize' => 15242880,
@ -467,6 +471,9 @@ return [
'description_ends' => DescriptionEnds::class,
'description_contains' => DescriptionContains::class,
'description_is' => DescriptionIs::class,
'date_is' => DateIs::class,
'date_before' => DateBefore::class,
'date_after' => DateAfter::class,
'transaction_type' => TransactionType::class,
'category_is' => CategoryIs::class,
'budget_is' => BudgetIs::class,
@ -508,6 +515,7 @@ return [
'convert_deposit' => ConvertToDeposit::class,
'convert_transfer' => ConvertToTransfer::class,
'update_piggy' => UpdatePiggybank::class,
'delete_transaction' => DeleteTransaction::class,
],
'context-rule-actions' => [
'set_category',
@ -552,6 +560,9 @@ return [
'notes_start',
'notes_end',
'notes_are',
'date_is',
'date_before',
'date_after',
],
'test-triggers' => [

View File

@ -38,6 +38,36 @@ if ('ActiveDirectory' === envNonEmpty('ADLDAP_CONNECTION_SCHEME', 'OpenLDAP')) {
$schema = ActiveDirectory::class;
}
/*
* Get SSL parameters from .env file.
*/
$ssl_ca_dir = envNonEmpty('ADLDAP_SSL_CACERTDIR', null);
$ssl_ca_file = envNonEmpty('ADLDAP_SSL_CACERTFILE', null);
$ssl_cert = envNonEmpty('ADLDAP_SSL_CERTFILE', null);
$ssl_key = envNonEmpty('ADLDAP_SSL_KEYFILE', null);
$ssl_ciphers = envNonEmpty('ADLDAP_SSL_CIPHER_SUITE', null);
$ssl_require = envNonEmpty('ADLDAP_SSL_REQUIRE_CERT', null);
$sslOptions = [];
if (null !== $ssl_ca_dir) {
$sslOptions[LDAP_OPT_X_TLS_CACERTDIR] = $ssl_ca_dir;
}
if (null !== $ssl_ca_file) {
$sslOptions[LDAP_OPT_X_TLS_CACERTFILE] = $ssl_ca_file;
}
if (null !== $ssl_cert) {
$sslOptions[LDAP_OPT_X_TLS_CERTFILE] = $ssl_cert;
}
if (null !== $ssl_key) {
$sslOptions[LDAP_OPT_X_TLS_KEYFILE] = $ssl_key;
}
if (null !== $ssl_ciphers) {
$sslOptions[LDAP_OPT_X_TLS_CIPHER_SUITE] = $ssl_ciphers;
}
if (null !== $ssl_require) {
$sslOptions[LDAP_OPT_X_TLS_REQUIRE_CERT] = $ssl_require;
}
return [
/*
|--------------------------------------------------------------------------
@ -87,29 +117,6 @@ return [
'connection' => Adldap\Connections\Ldap::class,
/*
|--------------------------------------------------------------------------
| Schema
|--------------------------------------------------------------------------
|
| The schema class to use for retrieving attributes and generating models.
|
| You can also set this option to `null` to use the default schema class.
|
| For OpenLDAP, you must use the schema:
|
| Adldap\Schemas\OpenLDAP::class
|
| For FreeIPA, you must use the schema:
|
| Adldap\Schemas\FreeIPA::class
|
| Custom schema classes must implement Adldap\Schemas\SchemaInterface
|
*/
'schema' => $schema,
/*
|--------------------------------------------------------------------------
| Connection Settings
@ -123,6 +130,29 @@ return [
'settings' => [
/*
|--------------------------------------------------------------------------
| Schema
|--------------------------------------------------------------------------
|
| The schema class to use for retrieving attributes and generating models.
|
| You can also set this option to `null` to use the default schema class.
|
| For OpenLDAP, you must use the schema:
|
| Adldap\Schemas\OpenLDAP::class
|
| For FreeIPA, you must use the schema:
|
| Adldap\Schemas\FreeIPA::class
|
| Custom schema classes must implement Adldap\Schemas\SchemaInterface
|
*/
'schema' => $schema,
/*
|--------------------------------------------------------------------------
| Account Prefix
@ -254,6 +284,7 @@ return [
'use_ssl' => env('ADLDAP_USE_SSL', false),
'use_tls' => env('ADLDAP_USE_TLS', false),
'custom_options' => $sslOptions,
],
],

View File

@ -217,10 +217,16 @@ return [
| Windows Authentication Middleware (SSO)
|--------------------------------------------------------------------------
|
| Discover:
| Enabled:
|
| The 'discover' value is the users attribute you would
| like to locate LDAP users by in your directory.
| The middleware will be registered only if enabled is set to true.
| If you update this file, beware, this is not a standard
| AdLdap2-Laravel configuration key.
|
| Locate Users By:
|
| This value is the users attribute you would like to locate LDAP
| users by in your directory.
|
| For example, if 'samaccountname' is the value, then your LDAP server is
| queried for a user with the 'samaccountname' equal to the value of
@ -229,9 +235,9 @@ return [
| If a user is found, they are imported (if using the DatabaseUserProvider)
| into your local database, then logged in.
|
| Key:
| Server Key:
|
| The 'key' value represents the 'key' of the $_SERVER
| This value represents the 'key' of the $_SERVER
| array to pull the users account name from.
|
| For example, $_SERVER['AUTH_USER'].
@ -239,8 +245,9 @@ return [
*/
'windows' => [
'discover' => envNonEmpty('WINDOWS_SSO_DISCOVER', 'samaccountname'),
'key' => envNonEmpty('WINDOWS_SSO_KEY', 'AUTH_USER'),
'enabled' => envNonEmpty('WINDOWS_SSO_ENABLED', false),
'locate_users_by' => envNonEmpty('WINDOWS_SSO_DISCOVER', 'samaccountname'),
'server_key' => envNonEmpty('WINDOWS_SSO_KEY', 'AUTH_USER'),
],
],

2
public/v1/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,7 @@
*/
/*!
* jQuery JavaScript Library v3.5.0
* jQuery JavaScript Library v3.5.1
* https://jquery.com/
*
* Includes Sizzle.js
@ -26,5 +26,5 @@
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2020-04-10T15:07Z
* Date: 2020-05-04T22:49Z
*/

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