diff --git a/.env.example b/.env.example index 6974348f2e..2a7503d5cd 100644 --- a/.env.example +++ b/.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 diff --git a/.gitignore b/.gitignore index df15896f8a..0bcb66f5d4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ /storage/*.key /vendor /.vagrant -/.vscode Homestead.json Homestead.yaml npm-debug.log @@ -14,4 +13,4 @@ yarn-error.log public/google*.html report.html composer.phar -app.js.map \ No newline at end of file +app.js.map diff --git a/app/Api/V1/Controllers/TransactionController.php b/app/Api/V1/Controllers/TransactionController.php index 717598f32f..becaa0af87 100644 --- a/app/Api/V1/Controllers/TransactionController.php +++ b/app/Api/V1/Controllers/TransactionController.php @@ -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); diff --git a/app/Console/Commands/Correction/CorrectDatabase.php b/app/Console/Commands/Correction/CorrectDatabase.php index 0c87804883..d2e88f4725 100644 --- a/app/Console/Commands/Correction/CorrectDatabase.php +++ b/app/Console/Commands/Correction/CorrectDatabase.php @@ -83,8 +83,6 @@ class CorrectDatabase extends Command echo $result; } - // app('telemetry')->feature('executed-command', $this->signature); - return 0; } } diff --git a/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php b/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php index 063e40e230..d4cca5e04e 100644 --- a/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php +++ b/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php @@ -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; } diff --git a/app/Console/Commands/Correction/CreateAccessTokens.php b/app/Console/Commands/Correction/CreateAccessTokens.php index f541e626f3..9dd515c17a 100644 --- a/app/Console/Commands/Correction/CreateAccessTokens.php +++ b/app/Console/Commands/Correction/CreateAccessTokens.php @@ -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; } } diff --git a/app/Console/Commands/Correction/CreateLinkTypes.php b/app/Console/Commands/Correction/CreateLinkTypes.php index 7d93025e2a..0bdf1d924d 100644 --- a/app/Console/Commands/Correction/CreateLinkTypes.php +++ b/app/Console/Commands/Correction/CreateLinkTypes.php @@ -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; } } diff --git a/app/Console/Commands/Correction/DeleteEmptyGroups.php b/app/Console/Commands/Correction/DeleteEmptyGroups.php index 083266a65e..41e05013d3 100644 --- a/app/Console/Commands/Correction/DeleteEmptyGroups.php +++ b/app/Console/Commands/Correction/DeleteEmptyGroups.php @@ -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; } } diff --git a/app/Console/Commands/Correction/DeleteEmptyJournals.php b/app/Console/Commands/Correction/DeleteEmptyJournals.php index 07c23d75c2..93d77eb81e 100644 --- a/app/Console/Commands/Correction/DeleteEmptyJournals.php +++ b/app/Console/Commands/Correction/DeleteEmptyJournals.php @@ -58,8 +58,6 @@ class DeleteEmptyJournals extends Command $this->deleteUnevenJournals(); $this->deleteEmptyJournals(); - // app('telemetry')->feature('executed-command', $this->signature); - return 0; } diff --git a/app/Console/Commands/Correction/DeleteOrphanedTransactions.php b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php index f35b35590f..c95007b60f 100644 --- a/app/Console/Commands/Correction/DeleteOrphanedTransactions.php +++ b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php @@ -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; } diff --git a/app/Console/Commands/Correction/DeleteZeroAmount.php b/app/Console/Commands/Correction/DeleteZeroAmount.php index 2e860610b9..f96b426eee 100644 --- a/app/Console/Commands/Correction/DeleteZeroAmount.php +++ b/app/Console/Commands/Correction/DeleteZeroAmount.php @@ -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; } } diff --git a/app/Console/Commands/Correction/EnableCurrencies.php b/app/Console/Commands/Correction/EnableCurrencies.php index 4b85bc8cb9..502d1be977 100644 --- a/app/Console/Commands/Correction/EnableCurrencies.php +++ b/app/Console/Commands/Correction/EnableCurrencies.php @@ -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; } } diff --git a/app/Console/Commands/Correction/FixAccountTypes.php b/app/Console/Commands/Correction/FixAccountTypes.php index 7f6dc372cf..8b6bd26d8e 100644 --- a/app/Console/Commands/Correction/FixAccountTypes.php +++ b/app/Console/Commands/Correction/FixAccountTypes.php @@ -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; } diff --git a/app/Console/Commands/Correction/FixLongDescriptions.php b/app/Console/Commands/Correction/FixLongDescriptions.php index ccc5fae5e4..ec93912784 100644 --- a/app/Console/Commands/Correction/FixLongDescriptions.php +++ b/app/Console/Commands/Correction/FixLongDescriptions.php @@ -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; } } diff --git a/app/Console/Commands/Correction/FixPiggies.php b/app/Console/Commands/Correction/FixPiggies.php index faf61a361a..14243e1aa2 100644 --- a/app/Console/Commands/Correction/FixPiggies.php +++ b/app/Console/Commands/Correction/FixPiggies.php @@ -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; } } diff --git a/app/Console/Commands/Correction/FixRecurringTransactions.php b/app/Console/Commands/Correction/FixRecurringTransactions.php index 6c83c63e45..318fee0304 100644 --- a/app/Console/Commands/Correction/FixRecurringTransactions.php +++ b/app/Console/Commands/Correction/FixRecurringTransactions.php @@ -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; } diff --git a/app/Console/Commands/Correction/FixUnevenAmount.php b/app/Console/Commands/Correction/FixUnevenAmount.php index a8e29d540f..d545d2f11e 100644 --- a/app/Console/Commands/Correction/FixUnevenAmount.php +++ b/app/Console/Commands/Correction/FixUnevenAmount.php @@ -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; } diff --git a/app/Console/Commands/Correction/RemoveBills.php b/app/Console/Commands/Correction/RemoveBills.php index 862d4bd851..4966c1887a 100644 --- a/app/Console/Commands/Correction/RemoveBills.php +++ b/app/Console/Commands/Correction/RemoveBills.php @@ -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; } } diff --git a/app/Console/Commands/Correction/RenameMetaFields.php b/app/Console/Commands/Correction/RenameMetaFields.php index 7a32c509d0..0b1adad4f5 100644 --- a/app/Console/Commands/Correction/RenameMetaFields.php +++ b/app/Console/Commands/Correction/RenameMetaFields.php @@ -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; } diff --git a/app/Console/Commands/Correction/TransferBudgets.php b/app/Console/Commands/Correction/TransferBudgets.php index 1f622aac9b..38b8a5684e 100644 --- a/app/Console/Commands/Correction/TransferBudgets.php +++ b/app/Console/Commands/Correction/TransferBudgets.php @@ -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; } } diff --git a/app/Console/Commands/DecryptDatabase.php b/app/Console/Commands/DecryptDatabase.php index d0f06bdf5e..e1f48cc388 100644 --- a/app/Console/Commands/DecryptDatabase.php +++ b/app/Console/Commands/DecryptDatabase.php @@ -123,7 +123,6 @@ class DecryptDatabase extends Command } $this->info('Done!'); - // app('telemetry')->feature('executed-command', $this->signature); return 0; } diff --git a/app/Console/Commands/Export/ExportData.php b/app/Console/Commands/Export/ExportData.php index 7a18f5ca7e..e187b8ef9c 100644 --- a/app/Console/Commands/Export/ExportData.php +++ b/app/Console/Commands/Export/ExportData.php @@ -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; } diff --git a/app/Console/Commands/Import/CreateCSVImport.php b/app/Console/Commands/Import/CreateCSVImport.php index 537d8bdc96..3da93f17d8 100644 --- a/app/Console/Commands/Import/CreateCSVImport.php +++ b/app/Console/Commands/Import/CreateCSVImport.php @@ -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; - } } diff --git a/app/Console/Commands/Integrity/ReportEmptyObjects.php b/app/Console/Commands/Integrity/ReportEmptyObjects.php index 2607a5ef8c..4cca37288b 100644 --- a/app/Console/Commands/Integrity/ReportEmptyObjects.php +++ b/app/Console/Commands/Integrity/ReportEmptyObjects.php @@ -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; } diff --git a/app/Console/Commands/Integrity/ReportIntegrity.php b/app/Console/Commands/Integrity/ReportIntegrity.php index 5a5db4356e..62e45ed110 100644 --- a/app/Console/Commands/Integrity/ReportIntegrity.php +++ b/app/Console/Commands/Integrity/ReportIntegrity.php @@ -69,7 +69,6 @@ class ReportIntegrity extends Command echo $result; } - // app('telemetry')->feature('executed-command', $this->signature); return 0; } } diff --git a/app/Console/Commands/Integrity/ReportSum.php b/app/Console/Commands/Integrity/ReportSum.php index 6485bddddd..2f24e1f00a 100644 --- a/app/Console/Commands/Integrity/ReportSum.php +++ b/app/Console/Commands/Integrity/ReportSum.php @@ -54,7 +54,6 @@ class ReportSum extends Command { $this->reportSum(); - // app('telemetry')->feature('executed-command', $this->signature); return 0; } diff --git a/app/Console/Commands/Integrity/RestoreOAuthKeys.php b/app/Console/Commands/Integrity/RestoreOAuthKeys.php index ad7fe3fb42..6efce94e4d 100644 --- a/app/Console/Commands/Integrity/RestoreOAuthKeys.php +++ b/app/Console/Commands/Integrity/RestoreOAuthKeys.php @@ -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.'); diff --git a/app/Console/Commands/ScanAttachments.php b/app/Console/Commands/ScanAttachments.php index 9a2c4a4655..21af60f99d 100644 --- a/app/Console/Commands/ScanAttachments.php +++ b/app/Console/Commands/ScanAttachments.php @@ -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; } } diff --git a/app/Console/Commands/SetLatestVersion.php b/app/Console/Commands/SetLatestVersion.php index 893b911941..0c3c1c9e36 100644 --- a/app/Console/Commands/SetLatestVersion.php +++ b/app/Console/Commands/SetLatestVersion.php @@ -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; } diff --git a/app/Console/Commands/Tools/ApplyRules.php b/app/Console/Commands/Tools/ApplyRules.php index 9973a9a8dc..8a9fd90b5b 100644 --- a/app/Console/Commands/Tools/ApplyRules.php +++ b/app/Console/Commands/Tools/ApplyRules.php @@ -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; } diff --git a/app/Console/Commands/Tools/Cron.php b/app/Console/Commands/Tools/Cron.php index 328b473b07..e37676718a 100644 --- a/app/Console/Commands/Tools/Cron.php +++ b/app/Console/Commands/Tools/Cron.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/AccountCurrencies.php b/app/Console/Commands/Upgrade/AccountCurrencies.php index 018a219ebc..f8bfc1410d 100644 --- a/app/Console/Commands/Upgrade/AccountCurrencies.php +++ b/app/Console/Commands/Upgrade/AccountCurrencies.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/BackToJournals.php b/app/Console/Commands/Upgrade/BackToJournals.php index 283f0e0903..bc6a2aead2 100644 --- a/app/Console/Commands/Upgrade/BackToJournals.php +++ b/app/Console/Commands/Upgrade/BackToJournals.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/BudgetLimitCurrency.php b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php index 32e5955043..990b02bbf8 100644 --- a/app/Console/Commands/Upgrade/BudgetLimitCurrency.php +++ b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php @@ -92,8 +92,6 @@ class BudgetLimitCurrency extends Command $this->markAsExecuted(); - // app('telemetry')->feature('executed-command', $this->signature); - return 0; } diff --git a/app/Console/Commands/Upgrade/CCLiabilities.php b/app/Console/Commands/Upgrade/CCLiabilities.php index 0d411bf360..e5904a7198 100644 --- a/app/Console/Commands/Upgrade/CCLiabilities.php +++ b/app/Console/Commands/Upgrade/CCLiabilities.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/MigrateAttachments.php b/app/Console/Commands/Upgrade/MigrateAttachments.php index 595e78aa55..9e2270d7d2 100644 --- a/app/Console/Commands/Upgrade/MigrateAttachments.php +++ b/app/Console/Commands/Upgrade/MigrateAttachments.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/MigrateJournalNotes.php b/app/Console/Commands/Upgrade/MigrateJournalNotes.php index 1a19560810..a234c2948f 100644 --- a/app/Console/Commands/Upgrade/MigrateJournalNotes.php +++ b/app/Console/Commands/Upgrade/MigrateJournalNotes.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php b/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php index 5bccd92bec..bad0d6f1a2 100644 --- a/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php +++ b/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/MigrateTagLocations.php b/app/Console/Commands/Upgrade/MigrateTagLocations.php index bbada0a41d..e5009a60a0 100644 --- a/app/Console/Commands/Upgrade/MigrateTagLocations.php +++ b/app/Console/Commands/Upgrade/MigrateTagLocations.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/MigrateToGroups.php b/app/Console/Commands/Upgrade/MigrateToGroups.php index 7b85398825..29a922cad3 100644 --- a/app/Console/Commands/Upgrade/MigrateToGroups.php +++ b/app/Console/Commands/Upgrade/MigrateToGroups.php @@ -112,8 +112,6 @@ class MigrateToGroups extends Command $this->markAsMigrated(); - // app('telemetry')->feature('executed-command', $this->signature); - return 0; } diff --git a/app/Console/Commands/Upgrade/MigrateToRules.php b/app/Console/Commands/Upgrade/MigrateToRules.php index a29188433c..d751ffa7cd 100644 --- a/app/Console/Commands/Upgrade/MigrateToRules.php +++ b/app/Console/Commands/Upgrade/MigrateToRules.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php index 5c1f5d8187..d021e088ff 100644 --- a/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php +++ b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/RenameAccountMeta.php b/app/Console/Commands/Upgrade/RenameAccountMeta.php index 355e403c41..6a6e369a8d 100644 --- a/app/Console/Commands/Upgrade/RenameAccountMeta.php +++ b/app/Console/Commands/Upgrade/RenameAccountMeta.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/TransactionIdentifier.php b/app/Console/Commands/Upgrade/TransactionIdentifier.php index c8f79c4b6c..a60db0b924 100644 --- a/app/Console/Commands/Upgrade/TransactionIdentifier.php +++ b/app/Console/Commands/Upgrade/TransactionIdentifier.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php b/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php index 64ccbeb9d7..90023cbb92 100644 --- a/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php +++ b/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php @@ -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; } diff --git a/app/Console/Commands/Upgrade/UpgradeDatabase.php b/app/Console/Commands/Upgrade/UpgradeDatabase.php index 28dd57256b..9c330c9554 100644 --- a/app/Console/Commands/Upgrade/UpgradeDatabase.php +++ b/app/Console/Commands/Upgrade/UpgradeDatabase.php @@ -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; } diff --git a/app/Console/Commands/UpgradeFireflyInstructions.php b/app/Console/Commands/UpgradeFireflyInstructions.php index a7ded8703e..ee5013e7f8 100644 --- a/app/Console/Commands/UpgradeFireflyInstructions.php +++ b/app/Console/Commands/UpgradeFireflyInstructions.php @@ -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; } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index ce558d2858..6ffeca74e2 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -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) { diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index e40043e593..58eddad0f9 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -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)); } } diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index 964d0b11e1..7a7941392d 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -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(); } diff --git a/app/Handlers/Events/VersionCheckEventHandler.php b/app/Handlers/Events/VersionCheckEventHandler.php index 2af01ca01f..3c068ffade 100644 --- a/app/Handlers/Events/VersionCheckEventHandler.php +++ b/app/Handlers/Events/VersionCheckEventHandler.php @@ -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()); + } } diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index bde4fee486..4540f41a32 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -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); } } diff --git a/app/Helpers/Report/NetWorth.php b/app/Helpers/Report/NetWorth.php index a906e08619..fc6189ecac 100644 --- a/app/Helpers/Report/NetWorth.php +++ b/app/Helpers/Report/NetWorth.php @@ -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'; diff --git a/app/Http/Controllers/Admin/TelemetryController.php b/app/Http/Controllers/Admin/TelemetryController.php index 7aa60cad8d..2b9163992b 100644 --- a/app/Http/Controllers/Admin/TelemetryController.php +++ b/app/Http/Controllers/Admin/TelemetryController.php @@ -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')); diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 1b6ee69d60..6db9e4f5b1 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -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')); } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index f6671ee5a2..a471c65156 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -104,6 +104,9 @@ class RegisterController extends Controller $this->registered($request, $user); + // telemetry + \Telemetry::feature('system.users.count', User::count()); + return redirect($this->redirectPath()); } diff --git a/app/Http/Controllers/Budget/AvailableBudgetController.php b/app/Http/Controllers/Budget/AvailableBudgetController.php index f48358b0be..4d1047cc53 100644 --- a/app/Http/Controllers/Budget/AvailableBudgetController.php +++ b/app/Http/Controllers/Budget/AvailableBudgetController.php @@ -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')); } diff --git a/app/Http/Controllers/Budget/BudgetLimitController.php b/app/Http/Controllers/Budget/BudgetLimitController.php index 2fa75e527f..7e16f4f793 100644 --- a/app/Http/Controllers/Budget/BudgetLimitController.php +++ b/app/Http/Controllers/Budget/BudgetLimitController.php @@ -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); diff --git a/app/Http/Controllers/Budget/IndexController.php b/app/Http/Controllers/Budget/IndexController.php index c9b81fee8f..5df4354803 100644 --- a/app/Http/Controllers/Budget/IndexController.php +++ b/app/Http/Controllers/Budget/IndexController.php @@ -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', diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index f887a5b833..03eb6b2d83 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -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' + ) ); } diff --git a/app/Http/Controllers/NewUserController.php b/app/Http/Controllers/NewUserController.php index 579cb1d5ee..2d31ec34e0 100644 --- a/app/Http/Controllers/NewUserController.php +++ b/app/Http/Controllers/NewUserController.php @@ -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(); diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php index b3a161cb41..39c3d7460d 100644 --- a/app/Http/Controllers/PreferencesController.php +++ b/app/Http/Controllers/PreferencesController.php @@ -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')); } } diff --git a/app/Http/Controllers/Rule/IndexController.php b/app/Http/Controllers/Rule/IndexController.php index 0233337bc4..0ed7cfa29c 100644 --- a/app/Http/Controllers/Rule/IndexController.php +++ b/app/Http/Controllers/Rule/IndexController.php @@ -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')); diff --git a/app/Http/Controllers/System/CronController.php b/app/Http/Controllers/System/CronController.php index df44ae8a7b..3baa9a8415 100644 --- a/app/Http/Controllers/System/CronController.php +++ b/app/Http/Controllers/System/CronController.php @@ -42,6 +42,7 @@ class CronController $results = []; $results[] = $this->runRecurring(); $results[] = $this->runAutoBudget(); + $results[] = $this->runTelemetry(); return implode("
\n", $results); } diff --git a/app/Http/Middleware/Range.php b/app/Http/Middleware/Range.php index b8a0ca8780..1d273c8124 100644 --- a/app/Http/Middleware/Range.php +++ b/app/Http/Middleware/Range.php @@ -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 } diff --git a/app/Jobs/MailError.php b/app/Jobs/MailError.php index abcc87a586..f76818063a 100644 --- a/app/Jobs/MailError.php +++ b/app/Jobs/MailError.php @@ -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')); } } ); diff --git a/app/Jobs/SubmitTelemetryData.php b/app/Jobs/SubmitTelemetryData.php index f089ce4da6..3071748d8b 100644 --- a/app/Jobs/SubmitTelemetryData.php +++ b/app/Jobs/SubmitTelemetryData.php @@ -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; } -} \ No newline at end of file +} diff --git a/app/Mail/AccessTokenCreatedMail.php b/app/Mail/AccessTokenCreatedMail.php index 584a7b5eee..3837003be1 100644 --- a/app/Mail/AccessTokenCreatedMail.php +++ b/app/Mail/AccessTokenCreatedMail.php @@ -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')); } } diff --git a/app/Mail/AdminTestMail.php b/app/Mail/AdminTestMail.php index 91d65bcba2..565672cde3 100644 --- a/app/Mail/AdminTestMail.php +++ b/app/Mail/AdminTestMail.php @@ -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')); } } diff --git a/app/Mail/ConfirmEmailChangeMail.php b/app/Mail/ConfirmEmailChangeMail.php index 1e7be38f1a..21f60197ca 100644 --- a/app/Mail/ConfirmEmailChangeMail.php +++ b/app/Mail/ConfirmEmailChangeMail.php @@ -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')); } } diff --git a/app/Mail/OAuthTokenCreatedMail.php b/app/Mail/OAuthTokenCreatedMail.php index ad3dbfb474..1d2d4cced8 100644 --- a/app/Mail/OAuthTokenCreatedMail.php +++ b/app/Mail/OAuthTokenCreatedMail.php @@ -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')); } } diff --git a/app/Mail/RegisteredUser.php b/app/Mail/RegisteredUser.php index edda7e6885..1abd4d6caa 100644 --- a/app/Mail/RegisteredUser.php +++ b/app/Mail/RegisteredUser.php @@ -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')); } } diff --git a/app/Mail/ReportNewJournalsMail.php b/app/Mail/ReportNewJournalsMail.php index 26da94db1d..a72321dd0c 100644 --- a/app/Mail/ReportNewJournalsMail.php +++ b/app/Mail/ReportNewJournalsMail.php @@ -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 diff --git a/app/Mail/RequestedNewPassword.php b/app/Mail/RequestedNewPassword.php index f1bb6c9b75..a3d2a83a9a 100644 --- a/app/Mail/RequestedNewPassword.php +++ b/app/Mail/RequestedNewPassword.php @@ -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')); } } diff --git a/app/Mail/UndoEmailChangeMail.php b/app/Mail/UndoEmailChangeMail.php index dd54a573ef..93cc2979c4 100644 --- a/app/Mail/UndoEmailChangeMail.php +++ b/app/Mail/UndoEmailChangeMail.php @@ -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')); } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f16be906f9..3a66b72d3c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -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); + } } /** diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index c68acfa141..a5ba5f1de6 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -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); diff --git a/app/Repositories/RuleGroup/RuleGroupRepository.php b/app/Repositories/RuleGroup/RuleGroupRepository.php index 70da98cd32..1d51301a19 100644 --- a/app/Repositories/RuleGroup/RuleGroupRepository.php +++ b/app/Repositories/RuleGroup/RuleGroupRepository.php @@ -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(); } + } diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepository.php b/app/Repositories/TransactionGroup/TransactionGroupRepository.php index e1444af843..98c489b8b7 100644 --- a/app/Repositories/TransactionGroup/TransactionGroupRepository.php +++ b/app/Repositories/TransactionGroup/TransactionGroupRepository.php @@ -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()); diff --git a/app/Services/Internal/Destroy/JournalDestroyService.php b/app/Services/Internal/Destroy/JournalDestroyService.php index 148eac82b4..219f8657e9 100644 --- a/app/Services/Internal/Destroy/JournalDestroyService.php +++ b/app/Services/Internal/Destroy/JournalDestroyService.php @@ -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 diff --git a/app/Support/Binder/AccountList.php b/app/Support/Binder/AccountList.php index dc6ef92b64..8b10330afd 100644 --- a/app/Support/Binder/AccountList.php +++ b/app/Support/Binder/AccountList.php @@ -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]); diff --git a/app/Support/Cronjobs/TelemetryCronjob.php b/app/Support/Cronjobs/TelemetryCronjob.php index 8efd8c889f..b4f6cc7127 100644 --- a/app/Support/Cronjobs/TelemetryCronjob.php +++ b/app/Support/Cronjobs/TelemetryCronjob.php @@ -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.'); } -} \ No newline at end of file +} diff --git a/app/Support/Http/Controllers/CronRunner.php b/app/Support/Http/Controllers/CronRunner.php index df513b291a..f5fb065f69 100644 --- a/app/Support/Http/Controllers/CronRunner.php +++ b/app/Support/Http/Controllers/CronRunner.php @@ -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 */ diff --git a/app/Support/ParseDateString.php b/app/Support/ParseDateString.php new file mode 100644 index 0000000000..1b45b4defb --- /dev/null +++ b/app/Support/ParseDateString.php @@ -0,0 +1,157 @@ +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; + } + +} diff --git a/app/Support/Telemetry.php b/app/Support/Telemetry.php index 905ca18233..cc9c3aacb6 100644 --- a/app/Support/Telemetry.php +++ b/app/Support/Telemetry.php @@ -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 ); } -} \ No newline at end of file +} diff --git a/app/TransactionRules/Actions/DeleteTransaction.php b/app/TransactionRules/Actions/DeleteTransaction.php new file mode 100644 index 0000000000..394537c59b --- /dev/null +++ b/app/TransactionRules/Actions/DeleteTransaction.php @@ -0,0 +1,81 @@ +. + */ +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; + } +} diff --git a/app/TransactionRules/Triggers/BudgetIs.php b/app/TransactionRules/Triggers/BudgetIs.php index fa6997a281..272ae84ae1 100644 --- a/app/TransactionRules/Triggers/BudgetIs.php +++ b/app/TransactionRules/Triggers/BudgetIs.php @@ -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; diff --git a/app/TransactionRules/Triggers/CategoryIs.php b/app/TransactionRules/Triggers/CategoryIs.php index df09b9bba7..2388139623 100644 --- a/app/TransactionRules/Triggers/CategoryIs.php +++ b/app/TransactionRules/Triggers/CategoryIs.php @@ -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; diff --git a/app/TransactionRules/Triggers/DateAfter.php b/app/TransactionRules/Triggers/DateAfter.php new file mode 100644 index 0000000000..71ac2cdbcf --- /dev/null +++ b/app/TransactionRules/Triggers/DateAfter.php @@ -0,0 +1,107 @@ +. + */ +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; + } +} diff --git a/app/TransactionRules/Triggers/DateBefore.php b/app/TransactionRules/Triggers/DateBefore.php new file mode 100644 index 0000000000..0b6a6e7c40 --- /dev/null +++ b/app/TransactionRules/Triggers/DateBefore.php @@ -0,0 +1,107 @@ +. + */ +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; + } +} diff --git a/app/TransactionRules/Triggers/DateIs.php b/app/TransactionRules/Triggers/DateIs.php new file mode 100644 index 0000000000..6661b4e441 --- /dev/null +++ b/app/TransactionRules/Triggers/DateIs.php @@ -0,0 +1,107 @@ +. + */ +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; + } +} diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index e8c50939d9..208a6c87f8 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -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 */ diff --git a/changelog.md b/changelog.md index 973de9c29e..41d2c640a7 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,51 @@ 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. +- New translations have been added for the emails that Firefly III can send. Despite your preferences, these emails may still be in English. See the bottom of this changelog. +- New translations have been added for error screens that Firefly III may display. Despite your preferences, these error pages may still be in English. See the bottom of this changelog. + +About translating errors and email messages. + +The translated text is generated outside of what's called the user's "session". When Firefly + III operates outside of your session, it can't access your preferences or your data. So it may not be possible for + Firefly III to know which language you would have preferred. You can set the `DEFAULT_LANGUAGE`-environment variable + if you want. But user specific preferences may be ignored. + + +Translation of email and errors. May not be perfect. (1) error does not read user pref. 2: email does not read user pref. 3: stringsnot translated yet. + +## [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 diff --git a/composer.json b/composer.json index f7d59a2599..ad4c983402 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/composer.lock b/composer.lock index 5e8afa915f..93ca3016d5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "63d96a26ef4c9c9e29552e9c45abde06", + "content-hash": "5af5d9774ef0e82cd34d08c63020e85d", "packages": [ { "name": "adldap2/adldap2", @@ -606,33 +606,37 @@ }, { "name": "doctrine/inflector", - "version": "1.3.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1" + "reference": "18b995743e7ec8b15fd6efc594f0fa3de4bfe6d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/ec3a55242203ffa6a4b27c58176da97ff0a7aec1", - "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/18b995743e7ec8b15fd6efc594f0fa3de4bfe6d7", + "reference": "18b995743e7ec8b15fd6efc594f0fa3de4bfe6d7", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.2" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "doctrine/coding-standard": "^7.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -661,15 +665,35 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Common String Manipulations with regard to casing and singular/plural rules.", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", "keywords": [ "inflection", - "pluralize", - "singularize", - "string" + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" ], - "time": "2019-10-30T19:59:35+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2020-05-11T11:25:59+00:00" }, { "name": "doctrine/lexer", @@ -1354,16 +1378,16 @@ }, { "name": "laminas/laminas-zendframework-bridge", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/laminas/laminas-zendframework-bridge.git", - "reference": "bfbbdb6c998d50dbf69d2187cb78a5f1fa36e1e9" + "reference": "fcd87520e4943d968557803919523772475e8ea3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/bfbbdb6c998d50dbf69d2187cb78a5f1fa36e1e9", - "reference": "bfbbdb6c998d50dbf69d2187cb78a5f1fa36e1e9", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/fcd87520e4943d968557803919523772475e8ea3", + "reference": "fcd87520e4943d968557803919523772475e8ea3", "shasum": "" }, "require": { @@ -1402,24 +1426,30 @@ "laminas", "zf" ], - "time": "2020-04-03T16:01:00+00:00" + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-05-20T16:45:56+00:00" }, { "name": "laravel/framework", - "version": "v6.18.11", + "version": "v6.18.15", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "73bc10bb23aab7539c8ffae6d5dc3c4b277de557" + "reference": "a1fa3ddc0bb5285cafa6844b443633f7627d75dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/73bc10bb23aab7539c8ffae6d5dc3c4b277de557", - "reference": "73bc10bb23aab7539c8ffae6d5dc3c4b277de557", + "url": "https://api.github.com/repos/laravel/framework/zipball/a1fa3ddc0bb5285cafa6844b443633f7627d75dc", + "reference": "a1fa3ddc0bb5285cafa6844b443633f7627d75dc", "shasum": "" }, "require": { - "doctrine/inflector": "^1.1", + "doctrine/inflector": "^1.4|^2.0", "dragonmantank/cron-expression": "^2.0", "egulias/email-validator": "^2.1.10", "ext-json": "*", @@ -1440,6 +1470,7 @@ "symfony/finder": "^4.3.4", "symfony/http-foundation": "^4.3.4", "symfony/http-kernel": "^4.3.4", + "symfony/polyfill-php73": "^1.17", "symfony/process": "^4.3.4", "symfony/routing": "^4.3.4", "symfony/var-dumper": "^4.3.4", @@ -1548,20 +1579,20 @@ "framework", "laravel" ], - "time": "2020-04-28T15:18:58+00:00" + "time": "2020-05-19T17:03:02+00:00" }, { "name": "laravel/passport", - "version": "v8.4.4", + "version": "v8.5.0", "source": { "type": "git", "url": "https://github.com/laravel/passport.git", - "reference": "dd4b1d96eb1fe556a6eb2c55c942360364aa02c1" + "reference": "6affa6ed600c5f8909385fbae7cf6f8af3db2d39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/passport/zipball/dd4b1d96eb1fe556a6eb2c55c942360364aa02c1", - "reference": "dd4b1d96eb1fe556a6eb2c55c942360364aa02c1", + "url": "https://api.github.com/repos/laravel/passport/zipball/6affa6ed600c5f8909385fbae7cf6f8af3db2d39", + "reference": "6affa6ed600c5f8909385fbae7cf6f8af3db2d39", "shasum": "" }, "require": { @@ -1621,20 +1652,20 @@ "oauth", "passport" ], - "time": "2020-04-21T19:24:59+00:00" + "time": "2020-05-05T14:25:53+00:00" }, { "name": "laravelcollective/html", - "version": "v6.1.0", + "version": "v6.1.2", "source": { "type": "git", "url": "https://github.com/LaravelCollective/html.git", - "reference": "64f2268bf41bf02b3a9dd3c30f102e934d721664" + "reference": "5ef9a3c9ae2423fe5618996f3cde375d461a3fc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LaravelCollective/html/zipball/64f2268bf41bf02b3a9dd3c30f102e934d721664", - "reference": "64f2268bf41bf02b3a9dd3c30f102e934d721664", + "url": "https://api.github.com/repos/LaravelCollective/html/zipball/5ef9a3c9ae2423fe5618996f3cde375d461a3fc6", + "reference": "5ef9a3c9ae2423fe5618996f3cde375d461a3fc6", "shasum": "" }, "require": { @@ -1689,20 +1720,20 @@ ], "description": "HTML and Form Builders for the Laravel Framework", "homepage": "https://laravelcollective.com", - "time": "2020-03-02T16:41:28+00:00" + "time": "2020-05-19T18:02:16+00:00" }, { "name": "lcobucci/jwt", - "version": "3.3.1", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18" + "reference": "56f10808089e38623345e28af2f2d5e4eb579455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", - "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/56f10808089e38623345e28af2f2d5e4eb579455", + "reference": "56f10808089e38623345e28af2f2d5e4eb579455", "shasum": "" }, "require": { @@ -1744,7 +1775,17 @@ "JWS", "jwt" ], - "time": "2019-05-24T18:30:49+00:00" + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2020-05-22T08:21:12+00:00" }, { "name": "league/commonmark", @@ -1969,16 +2010,16 @@ }, { "name": "league/flysystem", - "version": "1.0.67", + "version": "1.0.69", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "5b1f36c75c4bdde981294c2a0ebdb437ee6f275e" + "reference": "7106f78428a344bc4f643c233a94e48795f10967" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5b1f36c75c4bdde981294c2a0ebdb437ee6f275e", - "reference": "5b1f36c75c4bdde981294c2a0ebdb437ee6f275e", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7106f78428a344bc4f643c233a94e48795f10967", + "reference": "7106f78428a344bc4f643c233a94e48795f10967", "shasum": "" }, "require": { @@ -2049,95 +2090,13 @@ "sftp", "storage" ], - "time": "2020-04-16T13:21:26+00:00" - }, - { - "name": "league/flysystem-replicate-adapter", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem-replicate-adapter.git", - "reference": "864e80409c0918b0ed6921c3941247017d9db77c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-replicate-adapter/zipball/864e80409c0918b0ed6921c3941247017d9db77c", - "reference": "864e80409c0918b0ed6921c3941247017d9db77c", - "shasum": "" - }, - "require": { - "league/flysystem": "~1.0", - "php": ">=5.4.0" - }, - "require-dev": { - "mockery/mockery": "0.9.*", - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Flysystem\\Replicate\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "Frank de Jonge", - "email": "info@frenky.net" + "url": "https://offset.earth/frankdejonge", + "type": "other" } ], - "description": "Flysystem adapter for Replica's", - "time": "2015-08-18T21:07:17+00:00" - }, - { - "name": "league/flysystem-sftp", - "version": "1.0.22", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem-sftp.git", - "reference": "cab59dd2277e02fe46f5f23195672a02ed49774d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-sftp/zipball/cab59dd2277e02fe46f5f23195672a02ed49774d", - "reference": "cab59dd2277e02fe46f5f23195672a02ed49774d", - "shasum": "" - }, - "require": { - "league/flysystem": "~1.0", - "php": ">=5.6.0", - "phpseclib/phpseclib": "~2.0" - }, - "require-dev": { - "mockery/mockery": "0.9.*", - "phpunit/phpunit": "^5.7.25" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\Flysystem\\Sftp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } - ], - "description": "Flysystem adapter for SFTP", - "time": "2019-10-16T20:05:49+00:00" + "time": "2020-05-18T15:13:39+00:00" }, { "name": "league/fractal", @@ -2286,63 +2245,22 @@ ], "time": "2020-04-29T22:14:38+00:00" }, - { - "name": "litipk/flysystem-fallback-adapter", - "version": "0.1.3", - "source": { - "type": "git", - "url": "https://github.com/Litipk/flysystem-fallback-adapter.git", - "reference": "1fdeb9352990deb8cba2a2c57cc94e2ff7e1381b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Litipk/flysystem-fallback-adapter/zipball/1fdeb9352990deb8cba2a2c57cc94e2ff7e1381b", - "reference": "1fdeb9352990deb8cba2a2c57cc94e2ff7e1381b", - "shasum": "" - }, - "require": { - "league/flysystem": "~1.0", - "php": ">=5.4.0" - }, - "require-dev": { - "mockery/mockery": "0.9.*", - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Litipk\\Flysystem\\Fallback\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Andrés Correa Casablanca", - "email": "castarco@litipk.com" - } - ], - "description": "Flysystem adapter for fallback filesystems", - "time": "2016-06-23T00:18:01+00:00" - }, { "name": "monolog/monolog", - "version": "2.0.2", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8" + "reference": "38914429aac460e8e4616c8cb486ecb40ec90bb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c861fcba2ca29404dc9e617eedd9eff4616986b8", - "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/38914429aac460e8e4616c8cb486ecb40ec90bb1", + "reference": "38914429aac460e8e4616c8cb486ecb40ec90bb1", "shasum": "" }, "require": { - "php": "^7.2", + "php": ">=7.2", "psr/log": "^1.0.1" }, "provide": { @@ -2353,11 +2271,11 @@ "doctrine/couchdb": "~1.0@dev", "elasticsearch/elasticsearch": "^6.0", "graylog2/gelf-php": "^1.4.2", - "jakub-onderka/php-parallel-lint": "^0.9", "php-amqplib/php-amqplib": "~2.4", "php-console/php-console": "^3.1.3", + "php-parallel-lint/php-parallel-lint": "^1.0", "phpspec/prophecy": "^1.6.1", - "phpunit/phpunit": "^8.3", + "phpunit/phpunit": "^8.5", "predis/predis": "^1.1", "rollbar/rollbar": "^1.3", "ruflin/elastica": ">=0.90 <3.0", @@ -2406,55 +2324,30 @@ "logging", "psr-3" ], - "time": "2019-12-20T14:22:59+00:00" - }, - { - "name": "mschindler83/fints-hbci-php", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/mschindler83/fints-hbci-php.git", - "reference": "e58cb825c178d4c39a8974a9dd93abbc8094551f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mschindler83/fints-hbci-php/zipball/e58cb825c178d4c39a8974a9dd93abbc8094551f", - "reference": "e58cb825c178d4c39a8974a9dd93abbc8094551f", - "shasum": "" - }, - "require": { - "php": ">=5.3.2", - "psr/log": "~1.0" - }, - "suggest": { - "monolog/monolog": "Allow sending log messages to a variety of different handlers" - }, - "type": "library", - "autoload": { - "psr-0": { - "Fhp": "lib/" + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" ], - "description": "PHP Library for the protocols fints and hbci", - "homepage": "http://fints-hbci-php.markus-schindler.de", - "time": "2017-02-15T13:48:21+00:00" + "time": "2020-05-22T08:12:19+00:00" }, { "name": "nesbot/carbon", - "version": "2.33.0", + "version": "2.34.2", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "4d93cb95a80d9ffbff4018fe58ae3b7dd7f4b99b" + "reference": "3e87404329b8072295ea11d548b47a1eefe5a162" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4d93cb95a80d9ffbff4018fe58ae3b7dd7f4b99b", - "reference": "4d93cb95a80d9ffbff4018fe58ae3b7dd7f4b99b", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/3e87404329b8072295ea11d548b47a1eefe5a162", + "reference": "3e87404329b8072295ea11d548b47a1eefe5a162", "shasum": "" }, "require": { @@ -2478,7 +2371,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "2.x-dev", + "dev-3.x": "3.x-dev" }, "laravel": { "providers": [ @@ -2523,7 +2417,7 @@ "type": "tidelift" } ], - "time": "2020-04-20T15:05:43+00:00" + "time": "2020-05-19T22:14:16+00:00" }, { "name": "nyholm/psr7", @@ -2589,16 +2483,16 @@ }, { "name": "opis/closure", - "version": "3.5.1", + "version": "3.5.2", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969" + "reference": "2e3299cea6f485ca64d19c540f46d7896c512ace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/93ebc5712cdad8d5f489b500c59d122df2e53969", - "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969", + "url": "https://api.github.com/repos/opis/closure/zipball/2e3299cea6f485ca64d19c540f46d7896c512ace", + "reference": "2e3299cea6f485ca64d19c540f46d7896c512ace", "shasum": "" }, "require": { @@ -2646,7 +2540,7 @@ "serialization", "serialize" ], - "time": "2019-11-29T22:36:02+00:00" + "time": "2020-05-21T20:09:36+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -4503,16 +4397,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14" + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14", - "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", "shasum": "" }, "require": { @@ -4524,7 +4418,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4571,20 +4465,20 @@ "type": "tidelift" } ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-12T16:14:59+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8" + "reference": "c4de7601eefbf25f9d47190abe07f79fe0a27424" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/ad6d62792bfbcfc385dd34b424d4fcf9712a32c8", - "reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/c4de7601eefbf25f9d47190abe07f79fe0a27424", + "reference": "c4de7601eefbf25f9d47190abe07f79fe0a27424", "shasum": "" }, "require": { @@ -4596,7 +4490,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4644,20 +4538,20 @@ "type": "tidelift" } ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf" + "reference": "3bff59ea7047e925be6b7f2059d60af31bb46d6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/3bff59ea7047e925be6b7f2059d60af31bb46d6a", + "reference": "3bff59ea7047e925be6b7f2059d60af31bb46d6a", "shasum": "" }, "require": { @@ -4671,7 +4565,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4720,20 +4614,20 @@ "type": "tidelift" } ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c", + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c", "shasum": "" }, "require": { @@ -4745,7 +4639,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4793,20 +4687,20 @@ "type": "tidelift" } ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-php56", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d" + "reference": "e3c8c138280cdfe4b81488441555583aa1984e23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/d51ec491c8ddceae7dca8dd6c7e30428f543f37d", - "reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/e3c8c138280cdfe4b81488441555583aa1984e23", + "reference": "e3c8c138280cdfe4b81488441555583aa1984e23", "shasum": "" }, "require": { @@ -4816,7 +4710,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4863,20 +4757,20 @@ "type": "tidelift" } ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "37b0976c78b94856543260ce09b460a7bc852747" + "reference": "f048e612a3905f34931127360bdd2def19a5e582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", - "reference": "37b0976c78b94856543260ce09b460a7bc852747", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582", + "reference": "f048e612a3905f34931127360bdd2def19a5e582", "shasum": "" }, "require": { @@ -4885,7 +4779,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4932,20 +4826,20 @@ "type": "tidelift" } ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7" + "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a760d8964ff79ab9bf057613a5808284ec852ccc", + "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc", "shasum": "" }, "require": { @@ -4954,7 +4848,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -5004,20 +4898,20 @@ "type": "tidelift" } ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-util", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", - "reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027" + "reference": "4afb4110fc037752cf0ce9869f9ab8162c4e20d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/d8e76c104127675d0ea3df3be0f2ae24a8619027", - "reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4afb4110fc037752cf0ce9869f9ab8162c4e20d7", + "reference": "4afb4110fc037752cf0ce9869f9ab8162c4e20d7", "shasum": "" }, "require": { @@ -5026,7 +4920,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -5070,7 +4964,7 @@ "type": "tidelift" } ], - "time": "2020-03-02T11:55:35+00:00" + "time": "2020-05-12T16:14:59+00:00" }, { "name": "symfony/process", @@ -5586,16 +5480,16 @@ }, { "name": "tightenco/collect", - "version": "v7.9.2", + "version": "v7.11.0", "source": { "type": "git", "url": "https://github.com/tightenco/collect.git", - "reference": "372230e88129364638d2d9809143fafbb993d7d4" + "reference": "ba504e959241f1f408867ffd03159b040f66e00b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tightenco/collect/zipball/372230e88129364638d2d9809143fafbb993d7d4", - "reference": "372230e88129364638d2d9809143fafbb993d7d4", + "url": "https://api.github.com/repos/tightenco/collect/zipball/ba504e959241f1f408867ffd03159b040f66e00b", + "reference": "ba504e959241f1f408867ffd03159b040f66e00b", "shasum": "" }, "require": { @@ -5632,7 +5526,7 @@ "collection", "laravel" ], - "time": "2020-04-29T16:33:30+00:00" + "time": "2020-05-08T22:25:37+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -6160,16 +6054,16 @@ }, { "name": "composer/composer", - "version": "1.10.5", + "version": "1.10.6", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "7a4d5b6aa30d2118af27c04f5e897b57156ccfa9" + "reference": "be81b9c4735362c26876bdbfd3b5bc7e7f711c88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/7a4d5b6aa30d2118af27c04f5e897b57156ccfa9", - "reference": "7a4d5b6aa30d2118af27c04f5e897b57156ccfa9", + "url": "https://api.github.com/repos/composer/composer/zipball/be81b9c4735362c26876bdbfd3b5bc7e7f711c88", + "reference": "be81b9c4735362c26876bdbfd3b5bc7e7f711c88", "shasum": "" }, "require": { @@ -6188,7 +6082,8 @@ "symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0" }, "conflict": { - "symfony/console": "2.8.38" + "symfony/console": "2.8.38", + "symfony/phpunit-bridge": "3.4.40" }, "require-dev": { "phpspec/prophecy": "^1.10", @@ -6246,7 +6141,7 @@ "type": "tidelift" } ], - "time": "2020-04-10T09:44:22+00:00" + "time": "2020-05-06T08:28:10+00:00" }, { "name": "composer/semver", @@ -6565,16 +6460,16 @@ }, { "name": "filp/whoops", - "version": "2.7.1", + "version": "2.7.2", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130" + "reference": "17d0d3f266c8f925ebd035cd36f83cf802b47d4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130", - "reference": "fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130", + "url": "https://api.github.com/repos/filp/whoops/zipball/17d0d3f266c8f925ebd035cd36f83cf802b47d4a", + "reference": "17d0d3f266c8f925ebd035cd36f83cf802b47d4a", "shasum": "" }, "require": { @@ -6622,7 +6517,7 @@ "throwable", "whoops" ], - "time": "2020-01-15T10:00:00+00:00" + "time": "2020-05-05T12:28:07+00:00" }, { "name": "fzaninotto/faker", @@ -6838,30 +6733,33 @@ }, { "name": "mockery/mockery", - "version": "1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be" + "reference": "6c6a7c533469873deacf998237e7649fc6b36223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be", - "reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be", + "url": "https://api.github.com/repos/mockery/mockery/zipball/6c6a7c533469873deacf998237e7649fc6b36223", + "reference": "6c6a7c533469873deacf998237e7649fc6b36223", "shasum": "" }, "require": { "hamcrest/hamcrest-php": "~2.0", "lib-pcre": ">=7.0", - "php": ">=5.6.0" + "php": "^7.3.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0" + "phpunit/phpunit": "^8.0.0 || ^9.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { @@ -6899,7 +6797,7 @@ "test double", "testing" ], - "time": "2019-12-26T09:49:15+00:00" + "time": "2020-05-19T14:25:16+00:00" }, { "name": "myclabs/deep-copy", @@ -7859,16 +7757,16 @@ }, { "name": "phpunit/phpunit", - "version": "8.5.4", + "version": "8.5.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "8474e22d7d642f665084ba5ec780626cbd1efd23" + "reference": "63dda3b212a0025d380a745f91bdb4d8c985adb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8474e22d7d642f665084ba5ec780626cbd1efd23", - "reference": "8474e22d7d642f665084ba5ec780626cbd1efd23", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/63dda3b212a0025d380a745f91bdb4d8c985adb7", + "reference": "63dda3b212a0025d380a745f91bdb4d8c985adb7", "shasum": "" }, "require": { @@ -7948,20 +7846,20 @@ "type": "github" } ], - "time": "2020-04-23T04:39:42+00:00" + "time": "2020-05-22T13:51:52+00:00" }, { "name": "psalm/plugin-laravel", - "version": "1.2.0", + "version": "v1.2.1", "source": { "type": "git", "url": "https://github.com/psalm/psalm-plugin-laravel.git", - "reference": "31b48d7f5863f1b935f65daaea3b9472b4390608" + "reference": "db2f1a2769e383f820eb5e38dd4a0d58fdba069d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psalm/psalm-plugin-laravel/zipball/31b48d7f5863f1b935f65daaea3b9472b4390608", - "reference": "31b48d7f5863f1b935f65daaea3b9472b4390608", + "url": "https://api.github.com/repos/psalm/psalm-plugin-laravel/zipball/db2f1a2769e383f820eb5e38dd4a0d58fdba069d", + "reference": "db2f1a2769e383f820eb5e38dd4a0d58fdba069d", "shasum": "" }, "require": { @@ -7974,7 +7872,7 @@ "illuminate/support": "5.8.* || ^6.0 || ^7.0", "orchestra/testbench": "^3.8 || ^4.0 || ^5.0", "php": "^7.1.3|^8", - "vimeo/psalm": "^3.8.2" + "vimeo/psalm": "^3.8.2 || dev-master" }, "require-dev": { "codeception/codeception": "^4.1", @@ -7982,7 +7880,7 @@ "codeception/module-phpbrowser": "^1.0.0", "slevomat/coding-standard": "^6.2", "squizlabs/php_codesniffer": "*", - "weirdan/codeception-psalm-module": "^0.5.0" + "weirdan/codeception-psalm-module": "^0.7.1" }, "type": "psalm-plugin", "extra": { @@ -8006,7 +7904,7 @@ } ], "description": "A Laravel plugin for Psalm", - "time": "2020-04-21T12:52:20+00:00" + "time": "2020-05-12T05:52:51+00:00" }, { "name": "roave/security-advisories", @@ -8014,12 +7912,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "f46390d28af4fdb07c09d9aabf4c4e35149a7a08" + "reference": "e38de1df609b39d97144514d28b0804ad4daaddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/f46390d28af4fdb07c09d9aabf4c4e35149a7a08", - "reference": "f46390d28af4fdb07c09d9aabf4c4e35149a7a08", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/e38de1df609b39d97144514d28b0804ad4daaddb", + "reference": "e38de1df609b39d97144514d28b0804ad4daaddb", "shasum": "" }, "conflict": { @@ -8032,6 +7930,8 @@ "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", "aws/aws-sdk-php": ">=3,<3.2.1", "bagisto/bagisto": "<0.1.5", + "barrelstrength/sprout-base-email": "<1.2.7", + "barrelstrength/sprout-forms": "<3.9", "bolt/bolt": "<3.6.10", "brightlocal/phpwhois": "<=4.2.5", "buddypress/buddypress": "<5.1.2", @@ -8058,10 +7958,10 @@ "doctrine/mongodb-odm": ">=1,<1.0.2", "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", - "dolibarr/dolibarr": "<=10.0.6", + "dolibarr/dolibarr": "<11.0.4", "dompdf/dompdf": ">=0.6,<0.6.2", - "drupal/core": ">=7,<7.69|>=8,<8.7.12|>=8.8,<8.8.4", - "drupal/drupal": ">=7,<7.69|>=8,<8.7.12|>=8.8,<8.8.4", + "drupal/core": ">=7,<7.70|>=8,<8.7.14|>=8.8,<8.8.6", + "drupal/drupal": ">=7,<7.70|>=8,<8.7.14|>=8.8,<8.8.6", "endroid/qr-code-bundle": "<3.4.2", "enshrined/svg-sanitize": "<0.13.1", "erusev/parsedown": "<1.7.2", @@ -8164,7 +8064,7 @@ "socalnick/scn-social-auth": "<1.15.2", "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", - "ssddanbrown/bookstack": "<0.25.3", + "ssddanbrown/bookstack": "<0.29.2", "stormpath/sdk": ">=0,<9.9.99", "studio-42/elfinder": "<2.1.49", "swiftmailer/swiftmailer": ">=4,<5.4.5", @@ -8202,6 +8102,7 @@ "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", + "t3g/svg-sanitizer": "<1.0.3", "tecnickcom/tcpdf": "<6.2.22", "thelia/backoffice-default-template": ">=2.1,<2.1.2", "thelia/thelia": ">=2.1-beta.1,<2.1.3", @@ -8209,8 +8110,8 @@ "titon/framework": ">=0,<9.9.99", "truckersmp/phpwhois": "<=4.3.1", "twig/twig": "<1.38|>=2,<2.7", - "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", - "typo3/cms-core": ">=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", + "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.30|>=9,<9.5.17|>=10,<10.4.2", + "typo3/cms-core": ">=8,<8.7.30|>=9,<9.5.17|>=10,<10.4.2", "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5", "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", @@ -8283,7 +8184,7 @@ "type": "tidelift" } ], - "time": "2020-05-04T14:37:25+00:00" + "time": "2020-05-22T06:49:22+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -9109,16 +9010,16 @@ }, { "name": "vimeo/psalm", - "version": "3.11.2", + "version": "3.11.4", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "d470903722cfcbc1cd04744c5491d3e6d13ec3d9" + "reference": "58e1d8e68e5098bf4fbfdfb420c38d563f882549" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/d470903722cfcbc1cd04744c5491d3e6d13ec3d9", - "reference": "d470903722cfcbc1cd04744c5491d3e6d13ec3d9", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/58e1d8e68e5098bf4fbfdfb420c38d563f882549", + "reference": "58e1d8e68e5098bf4fbfdfb420c38d563f882549", "shasum": "" }, "require": { @@ -9203,7 +9104,7 @@ "inspection", "php" ], - "time": "2020-04-13T12:47:11+00:00" + "time": "2020-05-11T13:39:25+00:00" }, { "name": "webmozart/assert", diff --git a/config/database.php b/config/database.php index 3ec1f3e55b..ea073f01e5 100644 --- a/config/database.php +++ b/config/database.php @@ -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', diff --git a/config/firefly.php b/config/firefly.php index cc2360cf56..0b4edfb6f9 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -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' => [ diff --git a/config/ldap.php b/config/ldap.php index 5ac78296da..1c2d1b52be 100644 --- a/config/ldap.php +++ b/config/ldap.php @@ -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, ], ], diff --git a/config/ldap_auth.php b/config/ldap_auth.php index 4e730062af..2f395c809b 100644 --- a/config/ldap_auth.php +++ b/config/ldap_auth.php @@ -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'), ], ], diff --git a/public/v1/js/app.js b/public/v1/js/app.js index 62070867c6..e179698448 100644 --- a/public/v1/js/app.js +++ b/public/v1/js/app.js @@ -1,2 +1,2 @@ /*! For license information please see app.js.LICENSE.txt */ -!function(t){var e={};function n(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(i,o,function(e){return t[e]}.bind(null,o));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="/",n(n.s=73)}({73:function(t,e,n){t.exports=n(74)},74:function(t,e,n){try{window.$=window.jQuery=n(75),n(76)}catch(t){}},75:function(t,e,n){var i;!function(e,n){"use strict";"object"==typeof t.exports?t.exports=e.document?n(e,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return n(t)}:n(e)}("undefined"!=typeof window?window:this,(function(n,o){"use strict";var r=[],s=Object.getPrototypeOf,a=r.slice,l=r.flat?function(t){return r.flat.call(t)}:function(t){return r.concat.apply([],t)},u=r.push,c=r.indexOf,f={},p=f.toString,d=f.hasOwnProperty,h=d.toString,g=h.call(Object),v={},m=function(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function(t){return null!=t&&t===t.window},b=n.document,x={type:!0,src:!0,nonce:!0,noModule:!0};function w(t,e,n){var i,o,r=(n=n||b).createElement("script");if(r.text=t,e)for(i in x)(o=e[i]||e.getAttribute&&e.getAttribute(i))&&r.setAttribute(i,o);n.head.appendChild(r).parentNode.removeChild(r)}function T(t){return null==t?t+"":"object"==typeof t||"function"==typeof t?f[p.call(t)]||"object":typeof t}var C=function(t,e){return new C.fn.init(t,e)};function E(t){var e=!!t&&"length"in t&&t.length,n=T(t);return!m(t)&&!y(t)&&("array"===n||0===e||"number"==typeof e&&e>0&&e-1 in t)}C.fn=C.prototype={jquery:"3.5.0",constructor:C,length:0,toArray:function(){return a.call(this)},get:function(t){return null==t?a.call(this):t<0?this[t+this.length]:this[t]},pushStack:function(t){var e=C.merge(this.constructor(),t);return e.prevObject=this,e},each:function(t){return C.each(this,t)},map:function(t){return this.pushStack(C.map(this,(function(e,n){return t.call(e,n,e)})))},slice:function(){return this.pushStack(a.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(C.grep(this,(function(t,e){return(e+1)%2})))},odd:function(){return this.pushStack(C.grep(this,(function(t,e){return e%2})))},eq:function(t){var e=this.length,n=+t+(t<0?e:0);return this.pushStack(n>=0&&n+~]|"+P+")"+P+"*"),z=new RegExp(P+"|>"),V=new RegExp(F),Q=new RegExp("^"+H+"$"),X={ID:new RegExp("^#("+H+")"),CLASS:new RegExp("^\\.("+H+")"),TAG:new RegExp("^("+H+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:new RegExp("^(?:"+q+")$","i"),needsContext:new RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},G=/HTML$/i,Y=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,tt=/[+~]/,et=new RegExp("\\\\[\\da-fA-F]{1,6}"+P+"?|\\\\([^\\r\\n\\f])","g"),nt=function(t,e){var n="0x"+t.slice(1)-65536;return e||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},it=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ot=function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t},rt=function(){p()},st=xt((function(t){return!0===t.disabled&&"fieldset"===t.nodeName.toLowerCase()}),{dir:"parentNode",next:"legend"});try{I.apply(N=L.call(w.childNodes),w.childNodes),N[w.childNodes.length].nodeType}catch(t){I={apply:N.length?function(t,e){O.apply(t,L.call(e))}:function(t,e){for(var n=t.length,i=0;t[n++]=e[i++];);t.length=n-1}}}function at(t,e,i,o){var r,a,u,c,f,h,m,y=e&&e.ownerDocument,w=e?e.nodeType:9;if(i=i||[],"string"!=typeof t||!t||1!==w&&9!==w&&11!==w)return i;if(!o&&(p(e),e=e||d,g)){if(11!==w&&(f=Z.exec(t)))if(r=f[1]){if(9===w){if(!(u=e.getElementById(r)))return i;if(u.id===r)return i.push(u),i}else if(y&&(u=y.getElementById(r))&&b(e,u)&&u.id===r)return i.push(u),i}else{if(f[2])return I.apply(i,e.getElementsByTagName(t)),i;if((r=f[3])&&n.getElementsByClassName&&e.getElementsByClassName)return I.apply(i,e.getElementsByClassName(r)),i}if(n.qsa&&!$[t+" "]&&(!v||!v.test(t))&&(1!==w||"object"!==e.nodeName.toLowerCase())){if(m=t,y=e,1===w&&(z.test(t)||U.test(t))){for((y=tt.test(t)&&mt(e.parentNode)||e)===e&&n.scope||((c=e.getAttribute("id"))?c=c.replace(it,ot):e.setAttribute("id",c=x)),a=(h=s(t)).length;a--;)h[a]=(c?"#"+c:":scope")+" "+bt(h[a]);m=h.join(",")}try{return I.apply(i,y.querySelectorAll(m)),i}catch(e){$(t,!0)}finally{c===x&&e.removeAttribute("id")}}}return l(t.replace(B,"$1"),e,i,o)}function lt(){var t=[];return function e(n,o){return t.push(n+" ")>i.cacheLength&&delete e[t.shift()],e[n+" "]=o}}function ut(t){return t[x]=!0,t}function ct(t){var e=d.createElement("fieldset");try{return!!t(e)}catch(t){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function ft(t,e){for(var n=t.split("|"),o=n.length;o--;)i.attrHandle[n[o]]=e}function pt(t,e){var n=e&&t,i=n&&1===t.nodeType&&1===e.nodeType&&t.sourceIndex-e.sourceIndex;if(i)return i;if(n)for(;n=n.nextSibling;)if(n===e)return-1;return t?1:-1}function dt(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function ht(t){return function(e){var n=e.nodeName.toLowerCase();return("input"===n||"button"===n)&&e.type===t}}function gt(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&st(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function vt(t){return ut((function(e){return e=+e,ut((function(n,i){for(var o,r=t([],n.length,e),s=r.length;s--;)n[o=r[s]]&&(n[o]=!(i[o]=n[o]))}))}))}function mt(t){return t&&void 0!==t.getElementsByTagName&&t}for(e in n=at.support={},r=at.isXML=function(t){var e=t.namespaceURI,n=(t.ownerDocument||t).documentElement;return!G.test(e||n&&n.nodeName||"HTML")},p=at.setDocument=function(t){var e,o,s=t?t.ownerDocument||t:w;return s!=d&&9===s.nodeType&&s.documentElement?(h=(d=s).documentElement,g=!r(d),w!=d&&(o=d.defaultView)&&o.top!==o&&(o.addEventListener?o.addEventListener("unload",rt,!1):o.attachEvent&&o.attachEvent("onunload",rt)),n.scope=ct((function(t){return h.appendChild(t).appendChild(d.createElement("div")),void 0!==t.querySelectorAll&&!t.querySelectorAll(":scope fieldset div").length})),n.attributes=ct((function(t){return t.className="i",!t.getAttribute("className")})),n.getElementsByTagName=ct((function(t){return t.appendChild(d.createComment("")),!t.getElementsByTagName("*").length})),n.getElementsByClassName=K.test(d.getElementsByClassName),n.getById=ct((function(t){return h.appendChild(t).id=x,!d.getElementsByName||!d.getElementsByName(x).length})),n.getById?(i.filter.ID=function(t){var e=t.replace(et,nt);return function(t){return t.getAttribute("id")===e}},i.find.ID=function(t,e){if(void 0!==e.getElementById&&g){var n=e.getElementById(t);return n?[n]:[]}}):(i.filter.ID=function(t){var e=t.replace(et,nt);return function(t){var n=void 0!==t.getAttributeNode&&t.getAttributeNode("id");return n&&n.value===e}},i.find.ID=function(t,e){if(void 0!==e.getElementById&&g){var n,i,o,r=e.getElementById(t);if(r){if((n=r.getAttributeNode("id"))&&n.value===t)return[r];for(o=e.getElementsByName(t),i=0;r=o[i++];)if((n=r.getAttributeNode("id"))&&n.value===t)return[r]}return[]}}),i.find.TAG=n.getElementsByTagName?function(t,e){return void 0!==e.getElementsByTagName?e.getElementsByTagName(t):n.qsa?e.querySelectorAll(t):void 0}:function(t,e){var n,i=[],o=0,r=e.getElementsByTagName(t);if("*"===t){for(;n=r[o++];)1===n.nodeType&&i.push(n);return i}return r},i.find.CLASS=n.getElementsByClassName&&function(t,e){if(void 0!==e.getElementsByClassName&&g)return e.getElementsByClassName(t)},m=[],v=[],(n.qsa=K.test(d.querySelectorAll))&&(ct((function(t){var e;h.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+P+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||v.push("\\["+P+"*(?:value|"+q+")"),t.querySelectorAll("[id~="+x+"-]").length||v.push("~="),(e=d.createElement("input")).setAttribute("name",""),t.appendChild(e),t.querySelectorAll("[name='']").length||v.push("\\["+P+"*name"+P+"*="+P+"*(?:''|\"\")"),t.querySelectorAll(":checked").length||v.push(":checked"),t.querySelectorAll("a#"+x+"+*").length||v.push(".#.+[+~]"),t.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")})),ct((function(t){t.innerHTML="";var e=d.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&v.push("name"+P+"*[*^$|!~]?="),2!==t.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),h.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),v.push(",.*:")}))),(n.matchesSelector=K.test(y=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ct((function(t){n.disconnectedMatch=y.call(t,"*"),y.call(t,"[s!='']:x"),m.push("!=",F)})),v=v.length&&new RegExp(v.join("|")),m=m.length&&new RegExp(m.join("|")),e=K.test(h.compareDocumentPosition),b=e||K.test(h.contains)?function(t,e){var n=9===t.nodeType?t.documentElement:t,i=e&&e.parentNode;return t===i||!(!i||1!==i.nodeType||!(n.contains?n.contains(i):t.compareDocumentPosition&&16&t.compareDocumentPosition(i)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},A=e?function(t,e){if(t===e)return f=!0,0;var i=!t.compareDocumentPosition-!e.compareDocumentPosition;return i||(1&(i=(t.ownerDocument||t)==(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!n.sortDetached&&e.compareDocumentPosition(t)===i?t==d||t.ownerDocument==w&&b(w,t)?-1:e==d||e.ownerDocument==w&&b(w,e)?1:c?R(c,t)-R(c,e):0:4&i?-1:1)}:function(t,e){if(t===e)return f=!0,0;var n,i=0,o=t.parentNode,r=e.parentNode,s=[t],a=[e];if(!o||!r)return t==d?-1:e==d?1:o?-1:r?1:c?R(c,t)-R(c,e):0;if(o===r)return pt(t,e);for(n=t;n=n.parentNode;)s.unshift(n);for(n=e;n=n.parentNode;)a.unshift(n);for(;s[i]===a[i];)i++;return i?pt(s[i],a[i]):s[i]==w?-1:a[i]==w?1:0},d):d},at.matches=function(t,e){return at(t,null,null,e)},at.matchesSelector=function(t,e){if(p(t),n.matchesSelector&&g&&!$[e+" "]&&(!m||!m.test(e))&&(!v||!v.test(e)))try{var i=y.call(t,e);if(i||n.disconnectedMatch||t.document&&11!==t.document.nodeType)return i}catch(t){$(e,!0)}return at(e,d,null,[t]).length>0},at.contains=function(t,e){return(t.ownerDocument||t)!=d&&p(t),b(t,e)},at.attr=function(t,e){(t.ownerDocument||t)!=d&&p(t);var o=i.attrHandle[e.toLowerCase()],r=o&&D.call(i.attrHandle,e.toLowerCase())?o(t,e,!g):void 0;return void 0!==r?r:n.attributes||!g?t.getAttribute(e):(r=t.getAttributeNode(e))&&r.specified?r.value:null},at.escape=function(t){return(t+"").replace(it,ot)},at.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},at.uniqueSort=function(t){var e,i=[],o=0,r=0;if(f=!n.detectDuplicates,c=!n.sortStable&&t.slice(0),t.sort(A),f){for(;e=t[r++];)e===t[r]&&(o=i.push(r));for(;o--;)t.splice(i[o],1)}return c=null,t},o=at.getText=function(t){var e,n="",i=0,r=t.nodeType;if(r){if(1===r||9===r||11===r){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)n+=o(t)}else if(3===r||4===r)return t.nodeValue}else for(;e=t[i++];)n+=o(e);return n},(i=at.selectors={cacheLength:50,createPseudo:ut,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(et,nt),t[3]=(t[3]||t[4]||t[5]||"").replace(et,nt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||at.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&at.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return X.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&V.test(n)&&(e=s(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(et,nt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=E[t+" "];return e||(e=new RegExp("(^|"+P+")"+t+"("+P+"|$)"))&&E(t,(function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")}))},ATTR:function(t,e,n){return function(i){var o=at.attr(i,t);return null==o?"!="===e:!e||(o+="","="===e?o===n:"!="===e?o!==n:"^="===e?n&&0===o.indexOf(n):"*="===e?n&&o.indexOf(n)>-1:"$="===e?n&&o.slice(-n.length)===n:"~="===e?(" "+o.replace(W," ")+" ").indexOf(n)>-1:"|="===e&&(o===n||o.slice(0,n.length+1)===n+"-"))}},CHILD:function(t,e,n,i,o){var r="nth"!==t.slice(0,3),s="last"!==t.slice(-4),a="of-type"===e;return 1===i&&0===o?function(t){return!!t.parentNode}:function(e,n,l){var u,c,f,p,d,h,g=r!==s?"nextSibling":"previousSibling",v=e.parentNode,m=a&&e.nodeName.toLowerCase(),y=!l&&!a,b=!1;if(v){if(r){for(;g;){for(p=e;p=p[g];)if(a?p.nodeName.toLowerCase()===m:1===p.nodeType)return!1;h=g="only"===t&&!h&&"nextSibling"}return!0}if(h=[s?v.firstChild:v.lastChild],s&&y){for(b=(d=(u=(c=(f=(p=v)[x]||(p[x]={}))[p.uniqueID]||(f[p.uniqueID]={}))[t]||[])[0]===T&&u[1])&&u[2],p=d&&v.childNodes[d];p=++d&&p&&p[g]||(b=d=0)||h.pop();)if(1===p.nodeType&&++b&&p===e){c[t]=[T,d,b];break}}else if(y&&(b=d=(u=(c=(f=(p=e)[x]||(p[x]={}))[p.uniqueID]||(f[p.uniqueID]={}))[t]||[])[0]===T&&u[1]),!1===b)for(;(p=++d&&p&&p[g]||(b=d=0)||h.pop())&&((a?p.nodeName.toLowerCase()!==m:1!==p.nodeType)||!++b||(y&&((c=(f=p[x]||(p[x]={}))[p.uniqueID]||(f[p.uniqueID]={}))[t]=[T,b]),p!==e)););return(b-=o)===i||b%i==0&&b/i>=0}}},PSEUDO:function(t,e){var n,o=i.pseudos[t]||i.setFilters[t.toLowerCase()]||at.error("unsupported pseudo: "+t);return o[x]?o(e):o.length>1?(n=[t,t,"",e],i.setFilters.hasOwnProperty(t.toLowerCase())?ut((function(t,n){for(var i,r=o(t,e),s=r.length;s--;)t[i=R(t,r[s])]=!(n[i]=r[s])})):function(t){return o(t,0,n)}):o}},pseudos:{not:ut((function(t){var e=[],n=[],i=a(t.replace(B,"$1"));return i[x]?ut((function(t,e,n,o){for(var r,s=i(t,null,o,[]),a=t.length;a--;)(r=s[a])&&(t[a]=!(e[a]=r))})):function(t,o,r){return e[0]=t,i(e,null,r,n),e[0]=null,!n.pop()}})),has:ut((function(t){return function(e){return at(t,e).length>0}})),contains:ut((function(t){return t=t.replace(et,nt),function(e){return(e.textContent||o(e)).indexOf(t)>-1}})),lang:ut((function(t){return Q.test(t||"")||at.error("unsupported lang: "+t),t=t.replace(et,nt).toLowerCase(),function(e){var n;do{if(n=g?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(n=n.toLowerCase())===t||0===n.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}})),target:function(e){var n=t.location&&t.location.hash;return n&&n.slice(1)===e.id},root:function(t){return t===h},focus:function(t){return t===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:gt(!1),disabled:gt(!0),checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!i.pseudos.empty(t)},header:function(t){return J.test(t.nodeName)},input:function(t){return Y.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:vt((function(){return[0]})),last:vt((function(t,e){return[e-1]})),eq:vt((function(t,e,n){return[n<0?n+e:n]})),even:vt((function(t,e){for(var n=0;ne?e:n;--i>=0;)t.push(i);return t})),gt:vt((function(t,e,n){for(var i=n<0?n+e:n;++i1?function(e,n,i){for(var o=t.length;o--;)if(!t[o](e,n,i))return!1;return!0}:t[0]}function Tt(t,e,n,i,o){for(var r,s=[],a=0,l=t.length,u=null!=e;a-1&&(r[u]=!(s[u]=f))}}else m=Tt(m===s?m.splice(h,m.length):m),o?o(null,s,m,l):I.apply(s,m)}))}function Et(t){for(var e,n,o,r=t.length,s=i.relative[t[0].type],a=s||i.relative[" "],l=s?1:0,c=xt((function(t){return t===e}),a,!0),f=xt((function(t){return R(e,t)>-1}),a,!0),p=[function(t,n,i){var o=!s&&(i||n!==u)||((e=n).nodeType?c(t,n,i):f(t,n,i));return e=null,o}];l1&&wt(p),l>1&&bt(t.slice(0,l-1).concat({value:" "===t[l-2].type?"*":""})).replace(B,"$1"),n,l0,o=t.length>0,r=function(r,s,a,l,c){var f,h,v,m=0,y="0",b=r&&[],x=[],w=u,C=r||o&&i.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,S=C.length;for(c&&(u=s==d||s||c);y!==S&&null!=(f=C[y]);y++){if(o&&f){for(h=0,s||f.ownerDocument==d||(p(f),a=!g);v=t[h++];)if(v(f,s||d,a)){l.push(f);break}c&&(T=E)}n&&((f=!v&&f)&&m--,r&&b.push(f))}if(m+=y,n&&y!==m){for(h=0;v=e[h++];)v(b,x,s,a);if(r){if(m>0)for(;y--;)b[y]||x[y]||(x[y]=j.call(l));x=Tt(x)}I.apply(l,x),c&&!r&&x.length>0&&m+e.length>1&&at.uniqueSort(l)}return c&&(T=E,u=w),b};return n?ut(r):r}(r,o))).selector=t}return a},l=at.select=function(t,e,n,o){var r,l,u,c,f,p="function"==typeof t&&t,d=!o&&s(t=p.selector||t);if(n=n||[],1===d.length){if((l=d[0]=d[0].slice(0)).length>2&&"ID"===(u=l[0]).type&&9===e.nodeType&&g&&i.relative[l[1].type]){if(!(e=(i.find.ID(u.matches[0].replace(et,nt),e)||[])[0]))return n;p&&(e=e.parentNode),t=t.slice(l.shift().value.length)}for(r=X.needsContext.test(t)?0:l.length;r--&&(u=l[r],!i.relative[c=u.type]);)if((f=i.find[c])&&(o=f(u.matches[0].replace(et,nt),tt.test(l[0].type)&&mt(e.parentNode)||e))){if(l.splice(r,1),!(t=o.length&&bt(l)))return I.apply(n,o),n;break}}return(p||a(t,d))(o,e,!g,n,!e||tt.test(t)&&mt(e.parentNode)||e),n},n.sortStable=x.split("").sort(A).join("")===x,n.detectDuplicates=!!f,p(),n.sortDetached=ct((function(t){return 1&t.compareDocumentPosition(d.createElement("fieldset"))})),ct((function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")}))||ft("type|href|height|width",(function(t,e,n){if(!n)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)})),n.attributes&&ct((function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")}))||ft("value",(function(t,e,n){if(!n&&"input"===t.nodeName.toLowerCase())return t.defaultValue})),ct((function(t){return null==t.getAttribute("disabled")}))||ft(q,(function(t,e,n){var i;if(!n)return!0===t[e]?e.toLowerCase():(i=t.getAttributeNode(e))&&i.specified?i.value:null})),at}(n);C.find=S,C.expr=S.selectors,C.expr[":"]=C.expr.pseudos,C.uniqueSort=C.unique=S.uniqueSort,C.text=S.getText,C.isXMLDoc=S.isXML,C.contains=S.contains,C.escapeSelector=S.escape;var k=function(t,e,n){for(var i=[],o=void 0!==n;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(o&&C(t).is(n))break;i.push(t)}return i},$=function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n},A=C.expr.match.needsContext;function D(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()}var N=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(t,e,n){return m(e)?C.grep(t,(function(t,i){return!!e.call(t,i,t)!==n})):e.nodeType?C.grep(t,(function(t){return t===e!==n})):"string"!=typeof e?C.grep(t,(function(t){return c.call(e,t)>-1!==n})):C.filter(e,t,n)}C.filter=function(t,e,n){var i=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===i.nodeType?C.find.matchesSelector(i,t)?[i]:[]:C.find.matches(t,C.grep(e,(function(t){return 1===t.nodeType})))},C.fn.extend({find:function(t){var e,n,i=this.length,o=this;if("string"!=typeof t)return this.pushStack(C(t).filter((function(){for(e=0;e1?C.uniqueSort(n):n},filter:function(t){return this.pushStack(j(this,t||[],!1))},not:function(t){return this.pushStack(j(this,t||[],!0))},is:function(t){return!!j(this,"string"==typeof t&&A.test(t)?C(t):t||[],!1).length}});var O,I=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(C.fn.init=function(t,e,n){var i,o;if(!t)return this;if(n=n||O,"string"==typeof t){if(!(i="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:I.exec(t))||!i[1]&&e)return!e||e.jquery?(e||n).find(t):this.constructor(e).find(t);if(i[1]){if(e=e instanceof C?e[0]:e,C.merge(this,C.parseHTML(i[1],e&&e.nodeType?e.ownerDocument||e:b,!0)),N.test(i[1])&&C.isPlainObject(e))for(i in e)m(this[i])?this[i](e[i]):this.attr(i,e[i]);return this}return(o=b.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):m(t)?void 0!==n.ready?n.ready(t):t(C):C.makeArray(t,this)}).prototype=C.fn,O=C(b);var L=/^(?:parents|prev(?:Until|All))/,R={children:!0,contents:!0,next:!0,prev:!0};function q(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}C.fn.extend({has:function(t){var e=C(t,this),n=e.length;return this.filter((function(){for(var t=0;t-1:1===n.nodeType&&C.find.matchesSelector(n,t))){r.push(n);break}return this.pushStack(r.length>1?C.uniqueSort(r):r)},index:function(t){return t?"string"==typeof t?c.call(C(t),this[0]):c.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(C.uniqueSort(C.merge(this.get(),C(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),C.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return k(t,"parentNode")},parentsUntil:function(t,e,n){return k(t,"parentNode",n)},next:function(t){return q(t,"nextSibling")},prev:function(t){return q(t,"previousSibling")},nextAll:function(t){return k(t,"nextSibling")},prevAll:function(t){return k(t,"previousSibling")},nextUntil:function(t,e,n){return k(t,"nextSibling",n)},prevUntil:function(t,e,n){return k(t,"previousSibling",n)},siblings:function(t){return $((t.parentNode||{}).firstChild,t)},children:function(t){return $(t.firstChild)},contents:function(t){return null!=t.contentDocument&&s(t.contentDocument)?t.contentDocument:(D(t,"template")&&(t=t.content||t),C.merge([],t.childNodes))}},(function(t,e){C.fn[t]=function(n,i){var o=C.map(this,e,n);return"Until"!==t.slice(-5)&&(i=n),i&&"string"==typeof i&&(o=C.filter(i,o)),this.length>1&&(R[t]||C.uniqueSort(o),L.test(t)&&o.reverse()),this.pushStack(o)}}));var P=/[^\x20\t\r\n\f]+/g;function H(t){return t}function M(t){throw t}function F(t,e,n,i){var o;try{t&&m(o=t.promise)?o.call(t).done(e).fail(n):t&&m(o=t.then)?o.call(t,e,n):e.apply(void 0,[t].slice(i))}catch(t){n.apply(void 0,[t])}}C.Callbacks=function(t){t="string"==typeof t?function(t){var e={};return C.each(t.match(P)||[],(function(t,n){e[n]=!0})),e}(t):C.extend({},t);var e,n,i,o,r=[],s=[],a=-1,l=function(){for(o=o||t.once,i=e=!0;s.length;a=-1)for(n=s.shift();++a-1;)r.splice(n,1),n<=a&&a--})),this},has:function(t){return t?C.inArray(t,r)>-1:r.length>0},empty:function(){return r&&(r=[]),this},disable:function(){return o=s=[],r=n="",this},disabled:function(){return!r},lock:function(){return o=s=[],n||e||(r=n=""),this},locked:function(){return!!o},fireWith:function(t,n){return o||(n=[t,(n=n||[]).slice?n.slice():n],s.push(n),e||l()),this},fire:function(){return u.fireWith(this,arguments),this},fired:function(){return!!i}};return u},C.extend({Deferred:function(t){var e=[["notify","progress",C.Callbacks("memory"),C.Callbacks("memory"),2],["resolve","done",C.Callbacks("once memory"),C.Callbacks("once memory"),0,"resolved"],["reject","fail",C.Callbacks("once memory"),C.Callbacks("once memory"),1,"rejected"]],i="pending",o={state:function(){return i},always:function(){return r.done(arguments).fail(arguments),this},catch:function(t){return o.then(null,t)},pipe:function(){var t=arguments;return C.Deferred((function(n){C.each(e,(function(e,i){var o=m(t[i[4]])&&t[i[4]];r[i[1]]((function(){var t=o&&o.apply(this,arguments);t&&m(t.promise)?t.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[i[0]+"With"](this,o?[t]:arguments)}))})),t=null})).promise()},then:function(t,i,o){var r=0;function s(t,e,i,o){return function(){var a=this,l=arguments,u=function(){var n,u;if(!(t=r&&(i!==M&&(a=void 0,l=[n]),e.rejectWith(a,l))}};t?c():(C.Deferred.getStackHook&&(c.stackTrace=C.Deferred.getStackHook()),n.setTimeout(c))}}return C.Deferred((function(n){e[0][3].add(s(0,n,m(o)?o:H,n.notifyWith)),e[1][3].add(s(0,n,m(t)?t:H)),e[2][3].add(s(0,n,m(i)?i:M))})).promise()},promise:function(t){return null!=t?C.extend(t,o):o}},r={};return C.each(e,(function(t,n){var s=n[2],a=n[5];o[n[1]]=s.add,a&&s.add((function(){i=a}),e[3-t][2].disable,e[3-t][3].disable,e[0][2].lock,e[0][3].lock),s.add(n[3].fire),r[n[0]]=function(){return r[n[0]+"With"](this===r?void 0:this,arguments),this},r[n[0]+"With"]=s.fireWith})),o.promise(r),t&&t.call(r,r),r},when:function(t){var e=arguments.length,n=e,i=Array(n),o=a.call(arguments),r=C.Deferred(),s=function(t){return function(n){i[t]=this,o[t]=arguments.length>1?a.call(arguments):n,--e||r.resolveWith(i,o)}};if(e<=1&&(F(t,r.done(s(n)).resolve,r.reject,!e),"pending"===r.state()||m(o[n]&&o[n].then)))return r.then();for(;n--;)F(o[n],s(n),r.reject);return r.promise()}});var W=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;C.Deferred.exceptionHook=function(t,e){n.console&&n.console.warn&&t&&W.test(t.name)&&n.console.warn("jQuery.Deferred exception: "+t.message,t.stack,e)},C.readyException=function(t){n.setTimeout((function(){throw t}))};var B=C.Deferred();function _(){b.removeEventListener("DOMContentLoaded",_),n.removeEventListener("load",_),C.ready()}C.fn.ready=function(t){return B.then(t).catch((function(t){C.readyException(t)})),this},C.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--C.readyWait:C.isReady)||(C.isReady=!0,!0!==t&&--C.readyWait>0||B.resolveWith(b,[C]))}}),C.ready.then=B.then,"complete"===b.readyState||"loading"!==b.readyState&&!b.documentElement.doScroll?n.setTimeout(C.ready):(b.addEventListener("DOMContentLoaded",_),n.addEventListener("load",_));var U=function(t,e,n,i,o,r,s){var a=0,l=t.length,u=null==n;if("object"===T(n))for(a in o=!0,n)U(t,e,a,n[a],!0,r,s);else if(void 0!==i&&(o=!0,m(i)||(s=!0),u&&(s?(e.call(t,i),e=null):(u=e,e=function(t,e,n){return u.call(C(t),n)})),e))for(;a1,null,!0)},removeData:function(t){return this.each((function(){K.remove(this,t)}))}}),C.extend({queue:function(t,e,n){var i;if(t)return e=(e||"fx")+"queue",i=J.get(t,e),n&&(!i||Array.isArray(n)?i=J.access(t,e,C.makeArray(n)):i.push(n)),i||[]},dequeue:function(t,e){e=e||"fx";var n=C.queue(t,e),i=n.length,o=n.shift(),r=C._queueHooks(t,e);"inprogress"===o&&(o=n.shift(),i--),o&&("fx"===e&&n.unshift("inprogress"),delete r.stop,o.call(t,(function(){C.dequeue(t,e)}),r)),!i&&r&&r.empty.fire()},_queueHooks:function(t,e){var n=e+"queueHooks";return J.get(t,n)||J.access(t,n,{empty:C.Callbacks("once memory").add((function(){J.remove(t,[e+"queue",n])}))})}}),C.fn.extend({queue:function(t,e){var n=2;return"string"!=typeof t&&(e=t,t="fx",n--),arguments.length\x20\t\r\n\f]*)/i,mt=/^$|^module$|\/(?:java|ecma)script/i;dt=b.createDocumentFragment().appendChild(b.createElement("div")),(ht=b.createElement("input")).setAttribute("type","radio"),ht.setAttribute("checked","checked"),ht.setAttribute("name","t"),dt.appendChild(ht),v.checkClone=dt.cloneNode(!0).cloneNode(!0).lastChild.checked,dt.innerHTML="",v.noCloneChecked=!!dt.cloneNode(!0).lastChild.defaultValue,dt.innerHTML="",v.option=!!dt.lastChild;var yt={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function bt(t,e){var n;return n=void 0!==t.getElementsByTagName?t.getElementsByTagName(e||"*"):void 0!==t.querySelectorAll?t.querySelectorAll(e||"*"):[],void 0===e||e&&D(t,e)?C.merge([t],n):n}function xt(t,e){for(var n=0,i=t.length;n",""]);var wt=/<|&#?\w+;/;function Tt(t,e,n,i,o){for(var r,s,a,l,u,c,f=e.createDocumentFragment(),p=[],d=0,h=t.length;d-1)o&&o.push(r);else if(u=st(r),s=bt(f.appendChild(r),"script"),u&&xt(s),n)for(c=0;r=s[c++];)mt.test(r.type||"")&&n.push(r);return f}var Ct=/^key/,Et=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,St=/^([^.]*)(?:\.(.+)|)/;function kt(){return!0}function $t(){return!1}function At(t,e){return t===function(){try{return b.activeElement}catch(t){}}()==("focus"===e)}function Dt(t,e,n,i,o,r){var s,a;if("object"==typeof e){for(a in"string"!=typeof n&&(i=i||n,n=void 0),e)Dt(t,a,n,i,e[a],r);return t}if(null==i&&null==o?(o=n,i=n=void 0):null==o&&("string"==typeof n?(o=i,i=void 0):(o=i,i=n,n=void 0)),!1===o)o=$t;else if(!o)return t;return 1===r&&(s=o,(o=function(t){return C().off(t),s.apply(this,arguments)}).guid=s.guid||(s.guid=C.guid++)),t.each((function(){C.event.add(this,e,o,i,n)}))}function Nt(t,e,n){n?(J.set(t,e,!1),C.event.add(t,e,{namespace:!1,handler:function(t){var i,o,r=J.get(this,e);if(1&t.isTrigger&&this[e]){if(r.length)(C.event.special[e]||{}).delegateType&&t.stopPropagation();else if(r=a.call(arguments),J.set(this,e,r),i=n(this,e),this[e](),r!==(o=J.get(this,e))||i?J.set(this,e,!1):o={},r!==o)return t.stopImmediatePropagation(),t.preventDefault(),o.value}else r.length&&(J.set(this,e,{value:C.event.trigger(C.extend(r[0],C.Event.prototype),r.slice(1),this)}),t.stopImmediatePropagation())}})):void 0===J.get(t,e)&&C.event.add(t,e,kt)}C.event={global:{},add:function(t,e,n,i,o){var r,s,a,l,u,c,f,p,d,h,g,v=J.get(t);if(G(t))for(n.handler&&(n=(r=n).handler,o=r.selector),o&&C.find.matchesSelector(rt,o),n.guid||(n.guid=C.guid++),(l=v.events)||(l=v.events=Object.create(null)),(s=v.handle)||(s=v.handle=function(e){return void 0!==C&&C.event.triggered!==e.type?C.event.dispatch.apply(t,arguments):void 0}),u=(e=(e||"").match(P)||[""]).length;u--;)d=g=(a=St.exec(e[u])||[])[1],h=(a[2]||"").split(".").sort(),d&&(f=C.event.special[d]||{},d=(o?f.delegateType:f.bindType)||d,f=C.event.special[d]||{},c=C.extend({type:d,origType:g,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&C.expr.match.needsContext.test(o),namespace:h.join(".")},r),(p=l[d])||((p=l[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,i,h,s)||t.addEventListener&&t.addEventListener(d,s)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),o?p.splice(p.delegateCount++,0,c):p.push(c),C.event.global[d]=!0)},remove:function(t,e,n,i,o){var r,s,a,l,u,c,f,p,d,h,g,v=J.hasData(t)&&J.get(t);if(v&&(l=v.events)){for(u=(e=(e||"").match(P)||[""]).length;u--;)if(d=g=(a=St.exec(e[u])||[])[1],h=(a[2]||"").split(".").sort(),d){for(f=C.event.special[d]||{},p=l[d=(i?f.delegateType:f.bindType)||d]||[],a=a[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=r=p.length;r--;)c=p[r],!o&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||i&&i!==c.selector&&("**"!==i||!c.selector)||(p.splice(r,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(t,c));s&&!p.length&&(f.teardown&&!1!==f.teardown.call(t,h,v.handle)||C.removeEvent(t,d,v.handle),delete l[d])}else for(d in l)C.event.remove(t,d+e[u],n,i,!0);C.isEmptyObject(l)&&J.remove(t,"handle events")}},dispatch:function(t){var e,n,i,o,r,s,a=new Array(arguments.length),l=C.event.fix(t),u=(J.get(this,"events")||Object.create(null))[l.type]||[],c=C.event.special[l.type]||{};for(a[0]=l,e=1;e=1))for(;u!==this;u=u.parentNode||this)if(1===u.nodeType&&("click"!==t.type||!0!==u.disabled)){for(r=[],s={},n=0;n-1:C.find(o,this,null,[u]).length),s[o]&&r.push(i);r.length&&a.push({elem:u,handlers:r})}return u=this,l\s*$/g;function Lt(t,e){return D(t,"table")&&D(11!==e.nodeType?e:e.firstChild,"tr")&&C(t).children("tbody")[0]||t}function Rt(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function qt(t){return"true/"===(t.type||"").slice(0,5)?t.type=t.type.slice(5):t.removeAttribute("type"),t}function Pt(t,e){var n,i,o,r,s,a;if(1===e.nodeType){if(J.hasData(t)&&(a=J.get(t).events))for(o in J.remove(e,"handle events"),a)for(n=0,i=a[o].length;n1&&"string"==typeof h&&!v.checkClone&&Ot.test(h))return t.each((function(o){var r=t.eq(o);g&&(e[0]=h.call(this,o,r.html())),Mt(r,e,n,i)}));if(p&&(r=(o=Tt(e,t[0].ownerDocument,!1,t,i)).firstChild,1===o.childNodes.length&&(o=r),r||i)){for(a=(s=C.map(bt(o,"script"),Rt)).length;f0&&xt(s,!l&&bt(t,"script")),a},cleanData:function(t){for(var e,n,i,o=C.event.special,r=0;void 0!==(n=t[r]);r++)if(G(n)){if(e=n[J.expando]){if(e.events)for(i in e.events)o[i]?C.event.remove(n,i):C.removeEvent(n,i,e.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),C.fn.extend({detach:function(t){return Ft(this,t,!0)},remove:function(t){return Ft(this,t)},text:function(t){return U(this,(function(t){return void 0===t?C.text(this):this.empty().each((function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)}))}),null,t,arguments.length)},append:function(){return Mt(this,arguments,(function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Lt(this,t).appendChild(t)}))},prepend:function(){return Mt(this,arguments,(function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=Lt(this,t);e.insertBefore(t,e.firstChild)}}))},before:function(){return Mt(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this)}))},after:function(){return Mt(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)}))},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(C.cleanData(bt(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map((function(){return C.clone(this,t,e)}))},html:function(t){return U(this,(function(t){var e=this[0]||{},n=0,i=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!jt.test(t)&&!yt[(vt.exec(t)||["",""])[1].toLowerCase()]){t=C.htmlPrefilter(t);try{for(;n3,rt.removeChild(t)),a}}))}();var Qt=["Webkit","Moz","ms"],Xt=b.createElement("div").style,Gt={};function Yt(t){var e=C.cssProps[t]||Gt[t];return e||(t in Xt?t:Gt[t]=function(t){for(var e=t[0].toUpperCase()+t.slice(1),n=Qt.length;n--;)if((t=Qt[n]+e)in Xt)return t}(t)||t)}var Jt=/^(none|table(?!-c[ea]).+)/,Kt=/^--/,Zt={position:"absolute",visibility:"hidden",display:"block"},te={letterSpacing:"0",fontWeight:"400"};function ee(t,e,n){var i=it.exec(e);return i?Math.max(0,i[2]-(n||0))+(i[3]||"px"):e}function ne(t,e,n,i,o,r){var s="width"===e?1:0,a=0,l=0;if(n===(i?"border":"content"))return 0;for(;s<4;s+=2)"margin"===n&&(l+=C.css(t,n+ot[s],!0,o)),i?("content"===n&&(l-=C.css(t,"padding"+ot[s],!0,o)),"margin"!==n&&(l-=C.css(t,"border"+ot[s]+"Width",!0,o))):(l+=C.css(t,"padding"+ot[s],!0,o),"padding"!==n?l+=C.css(t,"border"+ot[s]+"Width",!0,o):a+=C.css(t,"border"+ot[s]+"Width",!0,o));return!i&&r>=0&&(l+=Math.max(0,Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-r-l-a-.5))||0),l}function ie(t,e,n){var i=Bt(t),o=(!v.boxSizingReliable()||n)&&"border-box"===C.css(t,"boxSizing",!1,i),r=o,s=zt(t,e,i),a="offset"+e[0].toUpperCase()+e.slice(1);if(Wt.test(s)){if(!n)return s;s="auto"}return(!v.boxSizingReliable()&&o||!v.reliableTrDimensions()&&D(t,"tr")||"auto"===s||!parseFloat(s)&&"inline"===C.css(t,"display",!1,i))&&t.getClientRects().length&&(o="border-box"===C.css(t,"boxSizing",!1,i),(r=a in t)&&(s=t[a])),(s=parseFloat(s)||0)+ne(t,e,n||(o?"border":"content"),r,i,s)+"px"}function oe(t,e,n,i,o){return new oe.prototype.init(t,e,n,i,o)}C.extend({cssHooks:{opacity:{get:function(t,e){if(e){var n=zt(t,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(t,e,n,i){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var o,r,s,a=X(e),l=Kt.test(e),u=t.style;if(l||(e=Yt(a)),s=C.cssHooks[e]||C.cssHooks[a],void 0===n)return s&&"get"in s&&void 0!==(o=s.get(t,!1,i))?o:u[e];"string"===(r=typeof n)&&(o=it.exec(n))&&o[1]&&(n=ut(t,e,o),r="number"),null!=n&&n==n&&("number"!==r||l||(n+=o&&o[3]||(C.cssNumber[a]?"":"px")),v.clearCloneStyle||""!==n||0!==e.indexOf("background")||(u[e]="inherit"),s&&"set"in s&&void 0===(n=s.set(t,n,i))||(l?u.setProperty(e,n):u[e]=n))}},css:function(t,e,n,i){var o,r,s,a=X(e);return Kt.test(e)||(e=Yt(a)),(s=C.cssHooks[e]||C.cssHooks[a])&&"get"in s&&(o=s.get(t,!0,n)),void 0===o&&(o=zt(t,e,i)),"normal"===o&&e in te&&(o=te[e]),""===n||n?(r=parseFloat(o),!0===n||isFinite(r)?r||0:o):o}}),C.each(["height","width"],(function(t,e){C.cssHooks[e]={get:function(t,n,i){if(n)return!Jt.test(C.css(t,"display"))||t.getClientRects().length&&t.getBoundingClientRect().width?ie(t,e,i):_t(t,Zt,(function(){return ie(t,e,i)}))},set:function(t,n,i){var o,r=Bt(t),s=!v.scrollboxSize()&&"absolute"===r.position,a=(s||i)&&"border-box"===C.css(t,"boxSizing",!1,r),l=i?ne(t,e,i,a,r):0;return a&&s&&(l-=Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-parseFloat(r[e])-ne(t,e,"border",!1,r)-.5)),l&&(o=it.exec(n))&&"px"!==(o[3]||"px")&&(t.style[e]=n,n=C.css(t,e)),ee(0,n,l)}}})),C.cssHooks.marginLeft=Vt(v.reliableMarginLeft,(function(t,e){if(e)return(parseFloat(zt(t,"marginLeft"))||t.getBoundingClientRect().left-_t(t,{marginLeft:0},(function(){return t.getBoundingClientRect().left})))+"px"})),C.each({margin:"",padding:"",border:"Width"},(function(t,e){C.cssHooks[t+e]={expand:function(n){for(var i=0,o={},r="string"==typeof n?n.split(" "):[n];i<4;i++)o[t+ot[i]+e]=r[i]||r[i-2]||r[0];return o}},"margin"!==t&&(C.cssHooks[t+e].set=ee)})),C.fn.extend({css:function(t,e){return U(this,(function(t,e,n){var i,o,r={},s=0;if(Array.isArray(e)){for(i=Bt(t),o=e.length;s1)}}),C.Tween=oe,oe.prototype={constructor:oe,init:function(t,e,n,i,o,r){this.elem=t,this.prop=n,this.easing=o||C.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=i,this.unit=r||(C.cssNumber[n]?"":"px")},cur:function(){var t=oe.propHooks[this.prop];return t&&t.get?t.get(this):oe.propHooks._default.get(this)},run:function(t){var e,n=oe.propHooks[this.prop];return this.options.duration?this.pos=e=C.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):oe.propHooks._default.set(this),this}},oe.prototype.init.prototype=oe.prototype,oe.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=C.css(t.elem,t.prop,""))&&"auto"!==e?e:0},set:function(t){C.fx.step[t.prop]?C.fx.step[t.prop](t):1!==t.elem.nodeType||!C.cssHooks[t.prop]&&null==t.elem.style[Yt(t.prop)]?t.elem[t.prop]=t.now:C.style(t.elem,t.prop,t.now+t.unit)}}},oe.propHooks.scrollTop=oe.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},C.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},C.fx=oe.prototype.init,C.fx.step={};var re,se,ae=/^(?:toggle|show|hide)$/,le=/queueHooks$/;function ue(){se&&(!1===b.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(ue):n.setTimeout(ue,C.fx.interval),C.fx.tick())}function ce(){return n.setTimeout((function(){re=void 0})),re=Date.now()}function fe(t,e){var n,i=0,o={height:t};for(e=e?1:0;i<4;i+=2-e)o["margin"+(n=ot[i])]=o["padding"+n]=t;return e&&(o.opacity=o.width=t),o}function pe(t,e,n){for(var i,o=(de.tweeners[e]||[]).concat(de.tweeners["*"]),r=0,s=o.length;r1)},removeAttr:function(t){return this.each((function(){C.removeAttr(this,t)}))}}),C.extend({attr:function(t,e,n){var i,o,r=t.nodeType;if(3!==r&&8!==r&&2!==r)return void 0===t.getAttribute?C.prop(t,e,n):(1===r&&C.isXMLDoc(t)||(o=C.attrHooks[e.toLowerCase()]||(C.expr.match.bool.test(e)?he:void 0)),void 0!==n?null===n?void C.removeAttr(t,e):o&&"set"in o&&void 0!==(i=o.set(t,n,e))?i:(t.setAttribute(e,n+""),n):o&&"get"in o&&null!==(i=o.get(t,e))?i:null==(i=C.find.attr(t,e))?void 0:i)},attrHooks:{type:{set:function(t,e){if(!v.radioValue&&"radio"===e&&D(t,"input")){var n=t.value;return t.setAttribute("type",e),n&&(t.value=n),e}}}},removeAttr:function(t,e){var n,i=0,o=e&&e.match(P);if(o&&1===t.nodeType)for(;n=o[i++];)t.removeAttribute(n)}}),he={set:function(t,e,n){return!1===e?C.removeAttr(t,n):t.setAttribute(n,n),n}},C.each(C.expr.match.bool.source.match(/\w+/g),(function(t,e){var n=ge[e]||C.find.attr;ge[e]=function(t,e,i){var o,r,s=e.toLowerCase();return i||(r=ge[s],ge[s]=o,o=null!=n(t,e,i)?s:null,ge[s]=r),o}}));var ve=/^(?:input|select|textarea|button)$/i,me=/^(?:a|area)$/i;function ye(t){return(t.match(P)||[]).join(" ")}function be(t){return t.getAttribute&&t.getAttribute("class")||""}function xe(t){return Array.isArray(t)?t:"string"==typeof t&&t.match(P)||[]}C.fn.extend({prop:function(t,e){return U(this,C.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each((function(){delete this[C.propFix[t]||t]}))}}),C.extend({prop:function(t,e,n){var i,o,r=t.nodeType;if(3!==r&&8!==r&&2!==r)return 1===r&&C.isXMLDoc(t)||(e=C.propFix[e]||e,o=C.propHooks[e]),void 0!==n?o&&"set"in o&&void 0!==(i=o.set(t,n,e))?i:t[e]=n:o&&"get"in o&&null!==(i=o.get(t,e))?i:t[e]},propHooks:{tabIndex:{get:function(t){var e=C.find.attr(t,"tabindex");return e?parseInt(e,10):ve.test(t.nodeName)||me.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),v.optSelected||(C.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),C.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],(function(){C.propFix[this.toLowerCase()]=this})),C.fn.extend({addClass:function(t){var e,n,i,o,r,s,a,l=0;if(m(t))return this.each((function(e){C(this).addClass(t.call(this,e,be(this)))}));if((e=xe(t)).length)for(;n=this[l++];)if(o=be(n),i=1===n.nodeType&&" "+ye(o)+" "){for(s=0;r=e[s++];)i.indexOf(" "+r+" ")<0&&(i+=r+" ");o!==(a=ye(i))&&n.setAttribute("class",a)}return this},removeClass:function(t){var e,n,i,o,r,s,a,l=0;if(m(t))return this.each((function(e){C(this).removeClass(t.call(this,e,be(this)))}));if(!arguments.length)return this.attr("class","");if((e=xe(t)).length)for(;n=this[l++];)if(o=be(n),i=1===n.nodeType&&" "+ye(o)+" "){for(s=0;r=e[s++];)for(;i.indexOf(" "+r+" ")>-1;)i=i.replace(" "+r+" "," ");o!==(a=ye(i))&&n.setAttribute("class",a)}return this},toggleClass:function(t,e){var n=typeof t,i="string"===n||Array.isArray(t);return"boolean"==typeof e&&i?e?this.addClass(t):this.removeClass(t):m(t)?this.each((function(n){C(this).toggleClass(t.call(this,n,be(this),e),e)})):this.each((function(){var e,o,r,s;if(i)for(o=0,r=C(this),s=xe(t);e=s[o++];)r.hasClass(e)?r.removeClass(e):r.addClass(e);else void 0!==t&&"boolean"!==n||((e=be(this))&&J.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===t?"":J.get(this,"__className__")||""))}))},hasClass:function(t){var e,n,i=0;for(e=" "+t+" ";n=this[i++];)if(1===n.nodeType&&(" "+ye(be(n))+" ").indexOf(e)>-1)return!0;return!1}});var we=/\r/g;C.fn.extend({val:function(t){var e,n,i,o=this[0];return arguments.length?(i=m(t),this.each((function(n){var o;1===this.nodeType&&(null==(o=i?t.call(this,n,C(this).val()):t)?o="":"number"==typeof o?o+="":Array.isArray(o)&&(o=C.map(o,(function(t){return null==t?"":t+""}))),(e=C.valHooks[this.type]||C.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,o,"value")||(this.value=o))}))):o?(e=C.valHooks[o.type]||C.valHooks[o.nodeName.toLowerCase()])&&"get"in e&&void 0!==(n=e.get(o,"value"))?n:"string"==typeof(n=o.value)?n.replace(we,""):null==n?"":n:void 0}}),C.extend({valHooks:{option:{get:function(t){var e=C.find.attr(t,"value");return null!=e?e:ye(C.text(t))}},select:{get:function(t){var e,n,i,o=t.options,r=t.selectedIndex,s="select-one"===t.type,a=s?null:[],l=s?r+1:o.length;for(i=r<0?l:s?r:0;i-1)&&(n=!0);return n||(t.selectedIndex=-1),r}}}}),C.each(["radio","checkbox"],(function(){C.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=C.inArray(C(t).val(),e)>-1}},v.checkOn||(C.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})})),v.focusin="onfocusin"in n;var Te=/^(?:focusinfocus|focusoutblur)$/,Ce=function(t){t.stopPropagation()};C.extend(C.event,{trigger:function(t,e,i,o){var r,s,a,l,u,c,f,p,h=[i||b],g=d.call(t,"type")?t.type:t,v=d.call(t,"namespace")?t.namespace.split("."):[];if(s=p=a=i=i||b,3!==i.nodeType&&8!==i.nodeType&&!Te.test(g+C.event.triggered)&&(g.indexOf(".")>-1&&(v=g.split("."),g=v.shift(),v.sort()),u=g.indexOf(":")<0&&"on"+g,(t=t[C.expando]?t:new C.Event(g,"object"==typeof t&&t)).isTrigger=o?2:3,t.namespace=v.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+v.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),e=null==e?[t]:C.makeArray(e,[t]),f=C.event.special[g]||{},o||!f.trigger||!1!==f.trigger.apply(i,e))){if(!o&&!f.noBubble&&!y(i)){for(l=f.delegateType||g,Te.test(l+g)||(s=s.parentNode);s;s=s.parentNode)h.push(s),a=s;a===(i.ownerDocument||b)&&h.push(a.defaultView||a.parentWindow||n)}for(r=0;(s=h[r++])&&!t.isPropagationStopped();)p=s,t.type=r>1?l:f.bindType||g,(c=(J.get(s,"events")||Object.create(null))[t.type]&&J.get(s,"handle"))&&c.apply(s,e),(c=u&&s[u])&&c.apply&&G(s)&&(t.result=c.apply(s,e),!1===t.result&&t.preventDefault());return t.type=g,o||t.isDefaultPrevented()||f._default&&!1!==f._default.apply(h.pop(),e)||!G(i)||u&&m(i[g])&&!y(i)&&((a=i[u])&&(i[u]=null),C.event.triggered=g,t.isPropagationStopped()&&p.addEventListener(g,Ce),i[g](),t.isPropagationStopped()&&p.removeEventListener(g,Ce),C.event.triggered=void 0,a&&(i[u]=a)),t.result}},simulate:function(t,e,n){var i=C.extend(new C.Event,n,{type:t,isSimulated:!0});C.event.trigger(i,null,e)}}),C.fn.extend({trigger:function(t,e){return this.each((function(){C.event.trigger(t,e,this)}))},triggerHandler:function(t,e){var n=this[0];if(n)return C.event.trigger(t,e,n,!0)}}),v.focusin||C.each({focus:"focusin",blur:"focusout"},(function(t,e){var n=function(t){C.event.simulate(e,t.target,C.event.fix(t))};C.event.special[e]={setup:function(){var i=this.ownerDocument||this.document||this,o=J.access(i,e);o||i.addEventListener(t,n,!0),J.access(i,e,(o||0)+1)},teardown:function(){var i=this.ownerDocument||this.document||this,o=J.access(i,e)-1;o?J.access(i,e,o):(i.removeEventListener(t,n,!0),J.remove(i,e))}}}));var Ee=n.location,Se={guid:Date.now()},ke=/\?/;C.parseXML=function(t){var e;if(!t||"string"!=typeof t)return null;try{e=(new n.DOMParser).parseFromString(t,"text/xml")}catch(t){e=void 0}return e&&!e.getElementsByTagName("parsererror").length||C.error("Invalid XML: "+t),e};var $e=/\[\]$/,Ae=/\r?\n/g,De=/^(?:submit|button|image|reset|file)$/i,Ne=/^(?:input|select|textarea|keygen)/i;function je(t,e,n,i){var o;if(Array.isArray(e))C.each(e,(function(e,o){n||$e.test(t)?i(t,o):je(t+"["+("object"==typeof o&&null!=o?e:"")+"]",o,n,i)}));else if(n||"object"!==T(e))i(t,e);else for(o in e)je(t+"["+o+"]",e[o],n,i)}C.param=function(t,e){var n,i=[],o=function(t,e){var n=m(e)?e():e;i[i.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==n?"":n)};if(null==t)return"";if(Array.isArray(t)||t.jquery&&!C.isPlainObject(t))C.each(t,(function(){o(this.name,this.value)}));else for(n in t)je(n,t[n],e,o);return i.join("&")},C.fn.extend({serialize:function(){return C.param(this.serializeArray())},serializeArray:function(){return this.map((function(){var t=C.prop(this,"elements");return t?C.makeArray(t):this})).filter((function(){var t=this.type;return this.name&&!C(this).is(":disabled")&&Ne.test(this.nodeName)&&!De.test(t)&&(this.checked||!gt.test(t))})).map((function(t,e){var n=C(this).val();return null==n?null:Array.isArray(n)?C.map(n,(function(t){return{name:e.name,value:t.replace(Ae,"\r\n")}})):{name:e.name,value:n.replace(Ae,"\r\n")}})).get()}});var Oe=/%20/g,Ie=/#.*$/,Le=/([?&])_=[^&]*/,Re=/^(.*?):[ \t]*([^\r\n]*)$/gm,qe=/^(?:GET|HEAD)$/,Pe=/^\/\//,He={},Me={},Fe="*/".concat("*"),We=b.createElement("a");function Be(t){return function(e,n){"string"!=typeof e&&(n=e,e="*");var i,o=0,r=e.toLowerCase().match(P)||[];if(m(n))for(;i=r[o++];)"+"===i[0]?(i=i.slice(1)||"*",(t[i]=t[i]||[]).unshift(n)):(t[i]=t[i]||[]).push(n)}}function _e(t,e,n,i){var o={},r=t===Me;function s(a){var l;return o[a]=!0,C.each(t[a]||[],(function(t,a){var u=a(e,n,i);return"string"!=typeof u||r||o[u]?r?!(l=u):void 0:(e.dataTypes.unshift(u),s(u),!1)})),l}return s(e.dataTypes[0])||!o["*"]&&s("*")}function Ue(t,e){var n,i,o=C.ajaxSettings.flatOptions||{};for(n in e)void 0!==e[n]&&((o[n]?t:i||(i={}))[n]=e[n]);return i&&C.extend(!0,t,i),t}We.href=Ee.href,C.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ee.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Ee.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Fe,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":C.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?Ue(Ue(t,C.ajaxSettings),e):Ue(C.ajaxSettings,t)},ajaxPrefilter:Be(He),ajaxTransport:Be(Me),ajax:function(t,e){"object"==typeof t&&(e=t,t=void 0),e=e||{};var i,o,r,s,a,l,u,c,f,p,d=C.ajaxSetup({},e),h=d.context||d,g=d.context&&(h.nodeType||h.jquery)?C(h):C.event,v=C.Deferred(),m=C.Callbacks("once memory"),y=d.statusCode||{},x={},w={},T="canceled",E={readyState:0,getResponseHeader:function(t){var e;if(u){if(!s)for(s={};e=Re.exec(r);)s[e[1].toLowerCase()+" "]=(s[e[1].toLowerCase()+" "]||[]).concat(e[2]);e=s[t.toLowerCase()+" "]}return null==e?null:e.join(", ")},getAllResponseHeaders:function(){return u?r:null},setRequestHeader:function(t,e){return null==u&&(t=w[t.toLowerCase()]=w[t.toLowerCase()]||t,x[t]=e),this},overrideMimeType:function(t){return null==u&&(d.mimeType=t),this},statusCode:function(t){var e;if(t)if(u)E.always(t[E.status]);else for(e in t)y[e]=[y[e],t[e]];return this},abort:function(t){var e=t||T;return i&&i.abort(e),S(0,e),this}};if(v.promise(E),d.url=((t||d.url||Ee.href)+"").replace(Pe,Ee.protocol+"//"),d.type=e.method||e.type||d.method||d.type,d.dataTypes=(d.dataType||"*").toLowerCase().match(P)||[""],null==d.crossDomain){l=b.createElement("a");try{l.href=d.url,l.href=l.href,d.crossDomain=We.protocol+"//"+We.host!=l.protocol+"//"+l.host}catch(t){d.crossDomain=!0}}if(d.data&&d.processData&&"string"!=typeof d.data&&(d.data=C.param(d.data,d.traditional)),_e(He,d,e,E),u)return E;for(f in(c=C.event&&d.global)&&0==C.active++&&C.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!qe.test(d.type),o=d.url.replace(Ie,""),d.hasContent?d.data&&d.processData&&0===(d.contentType||"").indexOf("application/x-www-form-urlencoded")&&(d.data=d.data.replace(Oe,"+")):(p=d.url.slice(o.length),d.data&&(d.processData||"string"==typeof d.data)&&(o+=(ke.test(o)?"&":"?")+d.data,delete d.data),!1===d.cache&&(o=o.replace(Le,"$1"),p=(ke.test(o)?"&":"?")+"_="+Se.guid+++p),d.url=o+p),d.ifModified&&(C.lastModified[o]&&E.setRequestHeader("If-Modified-Since",C.lastModified[o]),C.etag[o]&&E.setRequestHeader("If-None-Match",C.etag[o])),(d.data&&d.hasContent&&!1!==d.contentType||e.contentType)&&E.setRequestHeader("Content-Type",d.contentType),E.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+Fe+"; q=0.01":""):d.accepts["*"]),d.headers)E.setRequestHeader(f,d.headers[f]);if(d.beforeSend&&(!1===d.beforeSend.call(h,E,d)||u))return E.abort();if(T="abort",m.add(d.complete),E.done(d.success),E.fail(d.error),i=_e(Me,d,e,E)){if(E.readyState=1,c&&g.trigger("ajaxSend",[E,d]),u)return E;d.async&&d.timeout>0&&(a=n.setTimeout((function(){E.abort("timeout")}),d.timeout));try{u=!1,i.send(x,S)}catch(t){if(u)throw t;S(-1,t)}}else S(-1,"No Transport");function S(t,e,s,l){var f,p,b,x,w,T=e;u||(u=!0,a&&n.clearTimeout(a),i=void 0,r=l||"",E.readyState=t>0?4:0,f=t>=200&&t<300||304===t,s&&(x=function(t,e,n){for(var i,o,r,s,a=t.contents,l=t.dataTypes;"*"===l[0];)l.shift(),void 0===i&&(i=t.mimeType||e.getResponseHeader("Content-Type"));if(i)for(o in a)if(a[o]&&a[o].test(i)){l.unshift(o);break}if(l[0]in n)r=l[0];else{for(o in n){if(!l[0]||t.converters[o+" "+l[0]]){r=o;break}s||(s=o)}r=r||s}if(r)return r!==l[0]&&l.unshift(r),n[r]}(d,E,s)),!f&&C.inArray("script",d.dataTypes)>-1&&(d.converters["text script"]=function(){}),x=function(t,e,n,i){var o,r,s,a,l,u={},c=t.dataTypes.slice();if(c[1])for(s in t.converters)u[s.toLowerCase()]=t.converters[s];for(r=c.shift();r;)if(t.responseFields[r]&&(n[t.responseFields[r]]=e),!l&&i&&t.dataFilter&&(e=t.dataFilter(e,t.dataType)),l=r,r=c.shift())if("*"===r)r=l;else if("*"!==l&&l!==r){if(!(s=u[l+" "+r]||u["* "+r]))for(o in u)if((a=o.split(" "))[1]===r&&(s=u[l+" "+a[0]]||u["* "+a[0]])){!0===s?s=u[o]:!0!==u[o]&&(r=a[0],c.unshift(a[1]));break}if(!0!==s)if(s&&t.throws)e=s(e);else try{e=s(e)}catch(t){return{state:"parsererror",error:s?t:"No conversion from "+l+" to "+r}}}return{state:"success",data:e}}(d,x,E,f),f?(d.ifModified&&((w=E.getResponseHeader("Last-Modified"))&&(C.lastModified[o]=w),(w=E.getResponseHeader("etag"))&&(C.etag[o]=w)),204===t||"HEAD"===d.type?T="nocontent":304===t?T="notmodified":(T=x.state,p=x.data,f=!(b=x.error))):(b=T,!t&&T||(T="error",t<0&&(t=0))),E.status=t,E.statusText=(e||T)+"",f?v.resolveWith(h,[p,T,E]):v.rejectWith(h,[E,T,b]),E.statusCode(y),y=void 0,c&&g.trigger(f?"ajaxSuccess":"ajaxError",[E,d,f?p:b]),m.fireWith(h,[E,T]),c&&(g.trigger("ajaxComplete",[E,d]),--C.active||C.event.trigger("ajaxStop")))}return E},getJSON:function(t,e,n){return C.get(t,e,n,"json")},getScript:function(t,e){return C.get(t,void 0,e,"script")}}),C.each(["get","post"],(function(t,e){C[e]=function(t,n,i,o){return m(n)&&(o=o||i,i=n,n=void 0),C.ajax(C.extend({url:t,type:e,dataType:o,data:n,success:i},C.isPlainObject(t)&&t))}})),C.ajaxPrefilter((function(t){var e;for(e in t.headers)"content-type"===e.toLowerCase()&&(t.contentType=t.headers[e]||"")})),C._evalUrl=function(t,e,n){return C.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(t){C.globalEval(t,e,n)}})},C.fn.extend({wrapAll:function(t){var e;return this[0]&&(m(t)&&(t=t.call(this[0])),e=C(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map((function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t})).append(this)),this},wrapInner:function(t){return m(t)?this.each((function(e){C(this).wrapInner(t.call(this,e))})):this.each((function(){var e=C(this),n=e.contents();n.length?n.wrapAll(t):e.append(t)}))},wrap:function(t){var e=m(t);return this.each((function(n){C(this).wrapAll(e?t.call(this,n):t)}))},unwrap:function(t){return this.parent(t).not("body").each((function(){C(this).replaceWith(this.childNodes)})),this}}),C.expr.pseudos.hidden=function(t){return!C.expr.pseudos.visible(t)},C.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},C.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(t){}};var ze={0:200,1223:204},Ve=C.ajaxSettings.xhr();v.cors=!!Ve&&"withCredentials"in Ve,v.ajax=Ve=!!Ve,C.ajaxTransport((function(t){var e,i;if(v.cors||Ve&&!t.crossDomain)return{send:function(o,r){var s,a=t.xhr();if(a.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(s in t.xhrFields)a[s]=t.xhrFields[s];for(s in t.mimeType&&a.overrideMimeType&&a.overrideMimeType(t.mimeType),t.crossDomain||o["X-Requested-With"]||(o["X-Requested-With"]="XMLHttpRequest"),o)a.setRequestHeader(s,o[s]);e=function(t){return function(){e&&(e=i=a.onload=a.onerror=a.onabort=a.ontimeout=a.onreadystatechange=null,"abort"===t?a.abort():"error"===t?"number"!=typeof a.status?r(0,"error"):r(a.status,a.statusText):r(ze[a.status]||a.status,a.statusText,"text"!==(a.responseType||"text")||"string"!=typeof a.responseText?{binary:a.response}:{text:a.responseText},a.getAllResponseHeaders()))}},a.onload=e(),i=a.onerror=a.ontimeout=e("error"),void 0!==a.onabort?a.onabort=i:a.onreadystatechange=function(){4===a.readyState&&n.setTimeout((function(){e&&i()}))},e=e("abort");try{a.send(t.hasContent&&t.data||null)}catch(t){if(e)throw t}},abort:function(){e&&e()}}})),C.ajaxPrefilter((function(t){t.crossDomain&&(t.contents.script=!1)})),C.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return C.globalEval(t),t}}}),C.ajaxPrefilter("script",(function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")})),C.ajaxTransport("script",(function(t){var e,n;if(t.crossDomain||t.scriptAttrs)return{send:function(i,o){e=C("