mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Merge branch 'develop' into js_translations
This commit is contained in:
commit
e0e16489a1
26
.env.example
26
.env.example
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -83,8 +83,6 @@ class CorrectDatabase extends Command
|
||||
echo $result;
|
||||
}
|
||||
|
||||
// app('telemetry')->feature('executed-command', $this->signature);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -58,8 +58,6 @@ class DeleteEmptyJournals extends Command
|
||||
$this->deleteUnevenJournals();
|
||||
$this->deleteEmptyJournals();
|
||||
|
||||
// app('telemetry')->feature('executed-command', $this->signature);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,6 @@ class DecryptDatabase extends Command
|
||||
}
|
||||
$this->info('Done!');
|
||||
|
||||
// app('telemetry')->feature('executed-command', $this->signature);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,6 @@ class ReportIntegrity extends Command
|
||||
echo $result;
|
||||
}
|
||||
|
||||
// app('telemetry')->feature('executed-command', $this->signature);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,6 @@ class ReportSum extends Command
|
||||
{
|
||||
$this->reportSum();
|
||||
|
||||
// app('telemetry')->feature('executed-command', $this->signature);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -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.');
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -92,8 +92,6 @@ class BudgetLimitCurrency extends Command
|
||||
|
||||
$this->markAsExecuted();
|
||||
|
||||
// app('telemetry')->feature('executed-command', $this->signature);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -112,8 +112,6 @@ class MigrateToGroups extends Command
|
||||
|
||||
$this->markAsMigrated();
|
||||
|
||||
// app('telemetry')->feature('executed-command', $this->signature);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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'));
|
||||
|
@ -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'));
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,9 @@ class RegisterController extends Controller
|
||||
|
||||
$this->registered($request, $user);
|
||||
|
||||
// telemetry
|
||||
\Telemetry::feature('system.users.count', User::count());
|
||||
|
||||
return redirect($this->redirectPath());
|
||||
}
|
||||
|
||||
|
@ -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'));
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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',
|
||||
|
@ -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'
|
||||
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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'));
|
||||
|
@ -42,6 +42,7 @@ class CronController
|
||||
$results = [];
|
||||
$results[] = $this->runRecurring();
|
||||
$results[] = $this->runAutoBudget();
|
||||
$results[] = $this->runTelemetry();
|
||||
|
||||
return implode("<br>\n", $results);
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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]);
|
||||
|
@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
157
app/Support/ParseDateString.php
Normal file
157
app/Support/ParseDateString.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
81
app/TransactionRules/Actions/DeleteTransaction.php
Normal file
81
app/TransactionRules/Actions/DeleteTransaction.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
107
app/TransactionRules/Triggers/DateAfter.php
Normal file
107
app/TransactionRules/Triggers/DateAfter.php
Normal 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;
|
||||
}
|
||||
}
|
107
app/TransactionRules/Triggers/DateBefore.php
Normal file
107
app/TransactionRules/Triggers/DateBefore.php
Normal 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;
|
||||
}
|
||||
}
|
107
app/TransactionRules/Triggers/DateIs.php
Normal file
107
app/TransactionRules/Triggers/DateIs.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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 */
|
||||
|
33
changelog.md
33
changelog.md
@ -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
|
||||
|
@ -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
547
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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',
|
||||
|
@ -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' => [
|
||||
|
@ -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,
|
||||
],
|
||||
|
||||
],
|
||||
|
@ -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
2
public/v1/js/app.js
vendored
File diff suppressed because one or more lines are too long
@ -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
Loading…
Reference in New Issue
Block a user