diff --git a/.deploy/docker/entrypoint.sh b/.deploy/docker/entrypoint.sh index 17d62270c3..e88cc8cf85 100755 --- a/.deploy/docker/entrypoint.sh +++ b/.deploy/docker/entrypoint.sh @@ -1,8 +1,23 @@ #!/bin/bash +# make sure the correct directories exists (suggested by @chrif): +mkdir -p $FIREFLY_PATH/storage/app +mkdir -p $FIREFLY_PATH/storage/app/public +mkdir -p $FIREFLY_PATH/storage/build +mkdir -p $FIREFLY_PATH/storage/database +mkdir -p $FIREFLY_PATH/storage/debugbar +mkdir -p $FIREFLY_PATH/storage/export +mkdir -p $FIREFLY_PATH/storage/framework/cache +mkdir -p $FIREFLY_PATH/storage/framework/sessions +mkdir -p $FIREFLY_PATH/storage/framework/testing +mkdir -p $FIREFLY_PATH/storage/framework/views +mkdir -p $FIREFLY_PATH/storage/logs +mkdir -p $FIREFLY_PATH/storage/upload + + # make sure we own the volumes: -chown -R www-data:www-data -R $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/cache -chmod -R 775 $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/cache +chown -R www-data:www-data -R $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/framework/cache +chmod -R 775 $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/framework/cache # remove any lingering files that may break upgrades: rm -f $FIREFLY_PATH/storage/logs/laravel.log diff --git a/.env.docker b/.env.docker index 20f4436f77..502b81ef49 100644 --- a/.env.docker +++ b/.env.docker @@ -50,6 +50,7 @@ COOKIE_DOMAIN= COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings +# For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html MAIL_DRIVER=${MAIL_DRIVER} MAIL_HOST=${MAIL_HOST} MAIL_PORT=${MAIL_PORT} @@ -58,6 +59,12 @@ MAIL_USERNAME=${MAIL_USERNAME} MAIL_PASSWORD=${MAIL_PASSWORD} MAIL_ENCRYPTION=${MAIL_ENCRYPTION} +# Other mail drivers: +MAILGUN_DOMAIN=${MAILGUN_DOMAIN} +MAILGUN_SECRET=${MAILGUN_SECRET} +MANDRILL_SECRET=${MANDRILL_SECRET} +SPARKPOST_SECRET=${SPARKPOST_SECRET} + # Firefly III can send you the following messages SEND_REGISTRATION_MAIL=true SEND_ERROR_MESSAGE=false diff --git a/.env.example b/.env.example index e45753a2ca..73afaf0470 100644 --- a/.env.example +++ b/.env.example @@ -54,6 +54,7 @@ COOKIE_DOMAIN= COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings +# For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html MAIL_DRIVER=log MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 @@ -62,6 +63,12 @@ MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null +# Other mail drivers: +MAILGUN_DOMAIN= +MAILGUN_SECRET= +MANDRILL_SECRET= +SPARKPOST_SECRET= + # Firefly III can send you the following messages SEND_REGISTRATION_MAIL=true SEND_ERROR_MESSAGE=true @@ -100,3 +107,5 @@ IS_DOCKER=false IS_SANDSTORM=false BUNQ_USE_SANDBOX=false IS_HEROKU=false +MAILGUN_DOMAIN= +MAILGUN_SECRET= diff --git a/.env.heroku b/.env.heroku index 91e273659a..55237bd98b 100644 --- a/.env.heroku +++ b/.env.heroku @@ -54,6 +54,7 @@ COOKIE_DOMAIN= COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings +# For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html MAIL_DRIVER=log MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 @@ -62,6 +63,12 @@ MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null +# Other mail drivers: +MAILGUN_DOMAIN= +MAILGUN_SECRET= +MANDRILL_SECRET= +SPARKPOST_SECRET= + # Firefly III can send you the following messages SEND_REGISTRATION_MAIL=true SEND_ERROR_MESSAGE=true @@ -100,3 +107,5 @@ IS_DOCKER=false IS_SANDSTORM=false BUNQ_USE_SANDBOX=false IS_HEROKU=true +MAILGUN_DOMAIN= +MAILGUN_SECRET= diff --git a/.env.sandstorm b/.env.sandstorm index c72676704c..5895f63467 100755 --- a/.env.sandstorm +++ b/.env.sandstorm @@ -54,6 +54,7 @@ COOKIE_DOMAIN= COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings +# For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html MAIL_DRIVER=log MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 @@ -62,6 +63,12 @@ MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null +# Other mail drivers: +MAILGUN_DOMAIN= +MAILGUN_SECRET= +MANDRILL_SECRET= +SPARKPOST_SECRET= + # Firefly III can send you the following messages SEND_REGISTRATION_MAIL=true SEND_ERROR_MESSAGE=true @@ -100,3 +107,5 @@ IS_DOCKER=false IS_SANDSTORM=true BUNQ_USE_SANDBOX=false IS_HEROKU=false +MAILGUN_DOMAIN= +MAILGUN_SECRET= diff --git a/.env.testing b/.env.testing index 943ab13502..6eec082239 100644 --- a/.env.testing +++ b/.env.testing @@ -49,6 +49,7 @@ COOKIE_DOMAIN= COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings +# For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html MAIL_DRIVER=log MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 @@ -57,9 +58,11 @@ MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null -# Firefly III can send you the following messages -SEND_REGISTRATION_MAIL=true -SEND_ERROR_MESSAGE=false +# Other mail drivers: +MAILGUN_DOMAIN= +MAILGUN_SECRET= +MANDRILL_SECRET= +SPARKPOST_SECRET= # Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places. @@ -96,3 +99,5 @@ IS_DOCKER=false IS_SANDSTORM=false BUNQ_USE_SANDBOX=true IS_HEROKU=false +MAILGUN_DOMAIN= +MAILGUN_SECRET= diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 1312de943a..12ba47695e 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -13,7 +13,7 @@ I am running Firefly III version x.x.x What do you need to do to trigger this bug? **Extra info** -Please add extra info here, such as OS, browser, and the output from the `/debug`-page of your Firefly III installation (click the version at the bottom). +Please add extra info here, such as OS, browser, and the output from the /debug page of your Firefly III installation (click the version at the bottom). **Bonus points** Earn bonus points by: diff --git a/.github/ISSUE_TEMPLATE/Custom.md b/.github/ISSUE_TEMPLATE/Custom.md index f943c935cb..0db426cdf3 100644 --- a/.github/ISSUE_TEMPLATE/Custom.md +++ b/.github/ISSUE_TEMPLATE/Custom.md @@ -1,25 +1,27 @@ --- name: I have a question or a problem -about: Ask away +about: Ask away! --- I am running Firefly III version x.x.x -**Description of my issue:** +**Description** + **Steps to reproduce** +(if relevant of course) + -(please include if this problem also exists on the demo site: https://demo.firefly-iii.org/ ) **Extra info** - Please add extra info here, such as OS, browser, and the output from the `/debug`-page of your Firefly III installation (click the version at the bottom). + + **Bonus points** Earn bonus points by: -- Post a stacktrace from your log files - Add a screenshot -- Post nginx or Apache configuration \ No newline at end of file +- Replicate the problem on the demo site https://demo.firefly-iii.org/ \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index 7dfae270ae..3bdd3d861e 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -8,7 +8,8 @@ about: Suggest an idea or feature for Firefly III Please describe your feature request: - I would like Firefly III to do X. -- What if you would add Y? +- What if you would add feature Y? +- Firefly III doesn't do Z. **Solution** Describe what your feature would add to Firefly III. diff --git a/.github/stale.yml b/.github/stale.yml index f9e5be7b89..8f9da31fcd 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 30 +daysUntilStale: 14 # Number of days of inactivity before a stale Issue or Pull Request is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. @@ -12,6 +12,7 @@ daysUntilClose: 7 exemptLabels: - enhancement - feature + - bug # Set to true to ignore issues in a project (defaults to false) exemptProjects: false diff --git a/.sandstorm/changelog.md b/.sandstorm/changelog.md index c46086da90..c6415e473d 100644 --- a/.sandstorm/changelog.md +++ b/.sandstorm/changelog.md @@ -1,3 +1,22 @@ +# 4.7.5 +- A new feature called "recurring transactions" that will make Firefly III automatically create transactions for you. +- New API end points for attachments, available budgets, budgets, budget limits, categories, configuration, currency exchange rates, journal links, link types, piggy banks, preferences, recurring transactions, rules, rule groups and tags. +- Added support for YunoHost. +- The 2FA secret is visible so you can type it into 2FA apps. +- Bunq and Spectre imports will now ask to apply rules. +- Sandstorm users can now make API keys. +- Various typo's in the English translations. [issue 1493](https://github.com/firefly-iii/firefly-iii/issues/1493) +- Bug where Spectre was never called [issue 1492](https://github.com/firefly-iii/firefly-iii/issues/1492) +- Clear cache after journal is created through API [issue 1483](https://github.com/firefly-iii/firefly-iii/issues/1483) +- Make sure docker directories exist [issue 1500](https://github.com/firefly-iii/firefly-iii/issues/1500) +- Broken link to bill edit [issue 1505](https://github.com/firefly-iii/firefly-iii/issues/1505) +- Several bugs in the editing of split transactions [issue 1509](https://github.com/firefly-iii/firefly-iii/issues/1509) +- Import routine ignored formatting of several date fields [issue 1510](https://github.com/firefly-iii/firefly-iii/issues/1510) +- Piggy bank events now show the correct currency [issue 1446](https://github.com/firefly-iii/firefly-iii/issues/1446) +- Inactive accounts are no longer suggested [issue 1463](https://github.com/firefly-iii/firefly-iii/issues/1463) +- Some income / expense charts are less confusing [issue 1518](https://github.com/firefly-iii/firefly-iii/issues/1518) +- Validation bug in multi-currency create view [issue 1521](https://github.com/firefly-iii/firefly-iii/issues/1521) + # 4.7.4 - [Issue 1409](https://github.com/firefly-iii/firefly-iii/issues/1409), add Indian Rupee and explain that users can do this themselves [issue 1413](https://github.com/firefly-iii/firefly-iii/issues/1413) - [Issue 1445](https://github.com/firefly-iii/firefly-iii/issues/1445), upgrade Curl in Docker image. diff --git a/.sandstorm/sandstorm-files.list b/.sandstorm/sandstorm-files.list index 06a302593d..d311a31fcf 100644 --- a/.sandstorm/sandstorm-files.list +++ b/.sandstorm/sandstorm-files.list @@ -244,10 +244,12 @@ opt/app/app/Api/V1/Controllers/AboutController.php opt/app/app/Api/V1/Controllers/AccountController.php opt/app/app/Api/V1/Controllers/BillController.php opt/app/app/Api/V1/Controllers/Controller.php +opt/app/app/Api/V1/Controllers/CurrencyController.php opt/app/app/Api/V1/Controllers/TransactionController.php opt/app/app/Api/V1/Controllers/UserController.php opt/app/app/Api/V1/Requests/AccountRequest.php opt/app/app/Api/V1/Requests/BillRequest.php +opt/app/app/Api/V1/Requests/CurrencyRequest.php opt/app/app/Api/V1/Requests/Request.php opt/app/app/Api/V1/Requests/TransactionRequest.php opt/app/app/Api/V1/Requests/UserRequest.php @@ -321,6 +323,7 @@ opt/app/app/Generator/Report/Support.php opt/app/app/Generator/Report/Tag/MonthReportGenerator.php opt/app/app/Generator/Report/Tag/MultiYearReportGenerator.php opt/app/app/Generator/Report/Tag/YearReportGenerator.php +opt/app/app/Handlers/Events/APIEventHandler.php opt/app/app/Handlers/Events/AdminEventHandler.php opt/app/app/Handlers/Events/StoredJournalEventHandler.php opt/app/app/Handlers/Events/UpdatedJournalEventHandler.php @@ -348,6 +351,7 @@ opt/app/app/Helpers/Filter/NegativeAmountFilter.php opt/app/app/Helpers/Filter/OpposingAccountFilter.php opt/app/app/Helpers/Filter/PositiveAmountFilter.php opt/app/app/Helpers/Filter/SplitIndicatorFilter.php +opt/app/app/Helpers/Filter/TransactionViewFilter.php opt/app/app/Helpers/Filter/TransferFilter.php opt/app/app/Helpers/FiscalHelper.php opt/app/app/Helpers/FiscalHelperInterface.php @@ -393,10 +397,10 @@ opt/app/app/Http/Controllers/DebugController.php opt/app/app/Http/Controllers/ExportController.php opt/app/app/Http/Controllers/HelpController.php opt/app/app/Http/Controllers/HomeController.php -opt/app/app/Http/Controllers/Import/ConfigurationController.php opt/app/app/Http/Controllers/Import/IndexController.php +opt/app/app/Http/Controllers/Import/JobConfigurationController.php +opt/app/app/Http/Controllers/Import/JobStatusController.php opt/app/app/Http/Controllers/Import/PrerequisitesController.php -opt/app/app/Http/Controllers/Import/StatusController.php opt/app/app/Http/Controllers/JavascriptController.php opt/app/app/Http/Controllers/Json/AutoCompleteController.php opt/app/app/Http/Controllers/Json/BoxController.php @@ -480,17 +484,17 @@ opt/app/app/Http/Requests/UserFormRequest.php opt/app/app/Http/Requests/UserRegistrationRequest.php opt/app/app/Import/Configuration/BunqConfigurator.php opt/app/app/Import/Configuration/ConfiguratorInterface.php -opt/app/app/Import/Configuration/FileConfigurator.php -opt/app/app/Import/Configuration/SpectreConfigurator.php opt/app/app/Import/Converter/Amount.php opt/app/app/Import/Converter/AmountCredit.php opt/app/app/Import/Converter/AmountDebit.php opt/app/app/Import/Converter/ConverterInterface.php opt/app/app/Import/Converter/INGDebitCredit.php opt/app/app/Import/Converter/RabobankDebitCredit.php -opt/app/app/Import/FileProcessor/CsvProcessor.php -opt/app/app/Import/FileProcessor/FileProcessorInterface.php -opt/app/app/Import/Logging/CommandHandler.php +opt/app/app/Import/JobConfiguration/BunqJobConfiguration.php +opt/app/app/Import/JobConfiguration/FakeJobConfiguration.php +opt/app/app/Import/JobConfiguration/FileJobConfiguration.php +opt/app/app/Import/JobConfiguration/JobConfigurationInterface.php +opt/app/app/Import/JobConfiguration/SpectreJobConfiguration.php opt/app/app/Import/Mapper/AssetAccountIbans.php opt/app/app/Import/Mapper/AssetAccounts.php opt/app/app/Import/Mapper/Bills.php @@ -511,10 +515,12 @@ opt/app/app/Import/Object/ImportCategory.php opt/app/app/Import/Object/ImportCurrency.php opt/app/app/Import/Object/ImportJournal.php opt/app/app/Import/Prerequisites/BunqPrerequisites.php +opt/app/app/Import/Prerequisites/FakePrerequisites.php opt/app/app/Import/Prerequisites/FilePrerequisites.php opt/app/app/Import/Prerequisites/PrerequisitesInterface.php opt/app/app/Import/Prerequisites/SpectrePrerequisites.php opt/app/app/Import/Routine/BunqRoutine.php +opt/app/app/Import/Routine/FakeRoutine.php opt/app/app/Import/Routine/FileRoutine.php opt/app/app/Import/Routine/RoutineInterface.php opt/app/app/Import/Routine/SpectreRoutine.php @@ -524,14 +530,15 @@ opt/app/app/Import/Specifics/PresidentsChoice.php opt/app/app/Import/Specifics/RabobankDescription.php opt/app/app/Import/Specifics/SnsDescription.php opt/app/app/Import/Specifics/SpecificInterface.php -opt/app/app/Import/Storage/ImportStorage.php -opt/app/app/Import/Storage/ImportSupport.php +opt/app/app/Import/Storage/ImportArrayStorage.php opt/app/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php opt/app/app/Jobs/ExecuteRuleOnExistingTransactions.php opt/app/app/Jobs/Job.php opt/app/app/Jobs/MailError.php +opt/app/app/Mail/AccessTokenCreatedMail.php opt/app/app/Mail/AdminTestMail.php opt/app/app/Mail/ConfirmEmailChangeMail.php +opt/app/app/Mail/OAuthTokenCreatedMail.php opt/app/app/Mail/RegisteredUser.php opt/app/app/Mail/RequestedNewPassword.php opt/app/app/Mail/UndoEmailChangeMail.php @@ -627,46 +634,17 @@ opt/app/app/Repositories/User/UserRepositoryInterface.php opt/app/app/Rules/BelongsUser.php opt/app/app/Rules/UniqueIban.php opt/app/app/Rules/ValidTransactions.php -opt/app/app/Services/Bunq/Id/BunqId.php -opt/app/app/Services/Bunq/Id/DeviceServerId.php -opt/app/app/Services/Bunq/Id/DeviceSessionId.php -opt/app/app/Services/Bunq/Id/InstallationId.php -opt/app/app/Services/Bunq/Object/Alias.php -opt/app/app/Services/Bunq/Object/Amount.php -opt/app/app/Services/Bunq/Object/Avatar.php -opt/app/app/Services/Bunq/Object/BunqObject.php -opt/app/app/Services/Bunq/Object/DeviceServer.php -opt/app/app/Services/Bunq/Object/Image.php -opt/app/app/Services/Bunq/Object/LabelMonetaryAccount.php -opt/app/app/Services/Bunq/Object/LabelUser.php -opt/app/app/Services/Bunq/Object/MonetaryAccountBank.php -opt/app/app/Services/Bunq/Object/MonetaryAccountProfile.php -opt/app/app/Services/Bunq/Object/MonetaryAccountSetting.php -opt/app/app/Services/Bunq/Object/NotificationFilter.php -opt/app/app/Services/Bunq/Object/Payment.php -opt/app/app/Services/Bunq/Object/ServerPublicKey.php -opt/app/app/Services/Bunq/Object/UserCompany.php -opt/app/app/Services/Bunq/Object/UserLight.php -opt/app/app/Services/Bunq/Object/UserPerson.php -opt/app/app/Services/Bunq/Request/BunqRequest.php -opt/app/app/Services/Bunq/Request/DeleteDeviceSessionRequest.php -opt/app/app/Services/Bunq/Request/DeviceServerRequest.php -opt/app/app/Services/Bunq/Request/DeviceSessionRequest.php -opt/app/app/Services/Bunq/Request/InstallationTokenRequest.php -opt/app/app/Services/Bunq/Request/ListDeviceServerRequest.php -opt/app/app/Services/Bunq/Request/ListMonetaryAccountRequest.php -opt/app/app/Services/Bunq/Request/ListPaymentRequest.php -opt/app/app/Services/Bunq/Request/ListUserRequest.php -opt/app/app/Services/Bunq/Token/BunqToken.php -opt/app/app/Services/Bunq/Token/InstallationToken.php -opt/app/app/Services/Bunq/Token/SessionToken.php +opt/app/app/Services/Bunq/ApiContext.php +opt/app/app/Services/Bunq/MonetaryAccount.php +opt/app/app/Services/Bunq/Payment.php opt/app/app/Services/Currency/ExchangeRateInterface.php -opt/app/app/Services/Currency/FixerIO.php opt/app/app/Services/Currency/FixerIOv2.php opt/app/app/Services/Github/Object/GithubObject.php opt/app/app/Services/Github/Object/Release.php opt/app/app/Services/Github/Request/GithubRequest.php opt/app/app/Services/Github/Request/UpdateRequest.php +opt/app/app/Services/IP/IPRetrievalInterface.php +opt/app/app/Services/IP/IpifyOrg.php opt/app/app/Services/Internal/Destroy/AccountDestroyService.php opt/app/app/Services/Internal/Destroy/BillDestroyService.php opt/app/app/Services/Internal/Destroy/CategoryDestroyService.php @@ -683,11 +661,11 @@ opt/app/app/Services/Internal/Update/CategoryUpdateService.php opt/app/app/Services/Internal/Update/CurrencyUpdateService.php opt/app/app/Services/Internal/Update/JournalUpdateService.php opt/app/app/Services/Internal/Update/TransactionUpdateService.php -opt/app/app/Services/Password/PwndVerifier.php opt/app/app/Services/Password/PwndVerifierV2.php opt/app/app/Services/Password/Verifier.php opt/app/app/Services/Spectre/Exception/DuplicatedCustomerException.php opt/app/app/Services/Spectre/Exception/SpectreException.php +opt/app/app/Services/Spectre/Exception/WrongRequestFormatException.php opt/app/app/Services/Spectre/Object/Account.php opt/app/app/Services/Spectre/Object/Attempt.php opt/app/app/Services/Spectre/Object/Customer.php @@ -711,13 +689,14 @@ opt/app/app/Support/Binder/BudgetList.php opt/app/app/Support/Binder/CategoryList.php opt/app/app/Support/Binder/CurrencyCode.php opt/app/app/Support/Binder/Date.php +opt/app/app/Support/Binder/ImportProvider.php opt/app/app/Support/Binder/JournalList.php +opt/app/app/Support/Binder/SimpleJournalList.php opt/app/app/Support/Binder/TagList.php opt/app/app/Support/Binder/UnfinishedJournal.php opt/app/app/Support/CacheProperties.php opt/app/app/Support/ChartColour.php opt/app/app/Support/Domain.php -opt/app/app/Support/Events/BillScanner.php opt/app/app/Support/ExpandedForm.php opt/app/app/Support/Facades/Amount.php opt/app/app/Support/Facades/ExpandedForm.php @@ -726,15 +705,44 @@ opt/app/app/Support/Facades/Navigation.php opt/app/app/Support/Facades/Preferences.php opt/app/app/Support/Facades/Steam.php opt/app/app/Support/FireflyConfig.php -opt/app/app/Support/Import/Configuration/Bunq/HaveAccounts.php -opt/app/app/Support/Import/Configuration/ConfigurationInterface.php -opt/app/app/Support/Import/Configuration/File/Initial.php -opt/app/app/Support/Import/Configuration/File/Map.php -opt/app/app/Support/Import/Configuration/File/Roles.php -opt/app/app/Support/Import/Configuration/File/UploadConfig.php -opt/app/app/Support/Import/Configuration/Spectre/HaveAccounts.php opt/app/app/Support/Import/Information/BunqInformation.php +opt/app/app/Support/Import/Information/GetSpectreCustomerTrait.php +opt/app/app/Support/Import/Information/GetSpectreTokenTrait.php opt/app/app/Support/Import/Information/InformationInterface.php +opt/app/app/Support/Import/JobConfiguration/Bunq/BunqJobConfigurationInterface.php +opt/app/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php +opt/app/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php +opt/app/app/Support/Import/JobConfiguration/File/ConfigureMappingHandler.php +opt/app/app/Support/Import/JobConfiguration/File/ConfigureRolesHandler.php +opt/app/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php +opt/app/app/Support/Import/JobConfiguration/File/FileConfigurationInterface.php +opt/app/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/AuthenticatedHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/DoAuthenticateHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/NewSpectreJobHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/SpectreJobConfigurationInterface.php +opt/app/app/Support/Import/Placeholder/ColumnValue.php +opt/app/app/Support/Import/Placeholder/ImportTransaction.php +opt/app/app/Support/Import/Routine/Bunq/StageImportDataHandler.php +opt/app/app/Support/Import/Routine/Bunq/StageNewHandler.php +opt/app/app/Support/Import/Routine/Fake/StageAhoyHandler.php +opt/app/app/Support/Import/Routine/Fake/StageFinalHandler.php +opt/app/app/Support/Import/Routine/Fake/StageNewHandler.php +opt/app/app/Support/Import/Routine/File/AssetAccountMapper.php +opt/app/app/Support/Import/Routine/File/CSVProcessor.php +opt/app/app/Support/Import/Routine/File/CurrencyMapper.php +opt/app/app/Support/Import/Routine/File/FileProcessorInterface.php +opt/app/app/Support/Import/Routine/File/ImportableConverter.php +opt/app/app/Support/Import/Routine/File/ImportableCreator.php +opt/app/app/Support/Import/Routine/File/LineReader.php +opt/app/app/Support/Import/Routine/File/MappedValuesValidator.php +opt/app/app/Support/Import/Routine/File/MappingConverger.php +opt/app/app/Support/Import/Routine/File/OpposingAccountMapper.php +opt/app/app/Support/Import/Routine/Spectre/StageAuthenticatedHandler.php +opt/app/app/Support/Import/Routine/Spectre/StageImportDataHandler.php +opt/app/app/Support/Import/Routine/Spectre/StageNewHandler.php opt/app/app/Support/Models/TransactionJournalTrait.php opt/app/app/Support/Navigation.php opt/app/app/Support/Preferences.php @@ -743,10 +751,12 @@ opt/app/app/Support/Search/Search.php opt/app/app/Support/Search/SearchInterface.php opt/app/app/Support/Steam.php opt/app/app/Support/Twig/AmountFormat.php +opt/app/app/Support/Twig/Extension/Account.php opt/app/app/Support/Twig/Extension/Transaction.php opt/app/app/Support/Twig/Extension/TransactionJournal.php opt/app/app/Support/Twig/General.php opt/app/app/Support/Twig/Journal.php +opt/app/app/Support/Twig/Loader/AccountLoader.php opt/app/app/Support/Twig/Loader/TransactionJournalLoader.php opt/app/app/Support/Twig/Loader/TransactionLoader.php opt/app/app/Support/Twig/PiggyBank.php @@ -760,6 +770,7 @@ opt/app/app/TransactionRules/Actions/AppendNotes.php opt/app/app/TransactionRules/Actions/ClearBudget.php opt/app/app/TransactionRules/Actions/ClearCategory.php opt/app/app/TransactionRules/Actions/ClearNotes.php +opt/app/app/TransactionRules/Actions/LinkToBill.php opt/app/app/TransactionRules/Actions/PrependDescription.php opt/app/app/TransactionRules/Actions/PrependNotes.php opt/app/app/TransactionRules/Actions/RemoveAllTags.php @@ -780,6 +791,7 @@ opt/app/app/TransactionRules/Triggers/AmountLess.php opt/app/app/TransactionRules/Triggers/AmountMore.php opt/app/app/TransactionRules/Triggers/BudgetIs.php opt/app/app/TransactionRules/Triggers/CategoryIs.php +opt/app/app/TransactionRules/Triggers/CurrencyIs.php opt/app/app/TransactionRules/Triggers/DescriptionContains.php opt/app/app/TransactionRules/Triggers/DescriptionEnds.php opt/app/app/TransactionRules/Triggers/DescriptionIs.php @@ -814,6 +826,7 @@ opt/app/app/Transformers/AttachmentTransformer.php opt/app/app/Transformers/BillTransformer.php opt/app/app/Transformers/BudgetTransformer.php opt/app/app/Transformers/CategoryTransformer.php +opt/app/app/Transformers/CurrencyTransformer.php opt/app/app/Transformers/JournalMetaTransformer.php opt/app/app/Transformers/PiggyBankEventTransformer.php opt/app/app/Transformers/PiggyBankTransformer.php @@ -852,7 +865,6 @@ opt/app/config/twigbridge.php opt/app/config/upgrade.php opt/app/config/view.php opt/app/database/factories/ModelFactory.php -opt/app/database/migrations opt/app/database/migrations/2016_06_16_000000_create_support_tables.php opt/app/database/migrations/2016_06_16_000001_create_users_table.php opt/app/database/migrations/2016_06_16_000002_create_main_tables.php @@ -873,6 +885,8 @@ opt/app/database/migrations/2018_01_01_000003_create_oauth_refresh_tokens_table. opt/app/database/migrations/2018_01_01_000004_create_oauth_clients_table.php opt/app/database/migrations/2018_01_01_000005_create_oauth_personal_access_clients_table.php opt/app/database/migrations/2018_03_19_141348_changes_for_v472.php +opt/app/database/migrations/2018_04_07_210913_changes_for_v473.php +opt/app/database/migrations/2018_04_29_174524_changes_for_v474.php opt/app/database/seeds/AccountTypeSeeder.php opt/app/database/seeds/ConfigSeeder.php opt/app/database/seeds/DatabaseSeeder.php @@ -884,6 +898,7 @@ opt/app/docker-compose.yml opt/app/index.php opt/app/package-lock.json opt/app/public/.htaccess +opt/app/public/.well-known/security.txt opt/app/public/android-chrome-192x192.png opt/app/public/android-chrome-512x512.png opt/app/public/apple-touch-icon.png @@ -1038,9 +1053,12 @@ opt/app/public/images/loading-small.gif opt/app/public/images/loading-wide.gif opt/app/public/images/logos/bunq.png opt/app/public/images/logos/csv.png +opt/app/public/images/logos/fake.png opt/app/public/images/logos/file.png opt/app/public/images/logos/plaid.png +opt/app/public/images/logos/quovo.png opt/app/public/images/logos/spectre.png +opt/app/public/images/logos/yodlee.png opt/app/public/images/page_green.png opt/app/public/images/page_white_acrobat.png opt/app/public/index.php @@ -1065,10 +1083,23 @@ opt/app/public/js/ff/export/index.js opt/app/public/js/ff/firefly.js opt/app/public/js/ff/guest.js opt/app/public/js/ff/help.js +opt/app/public/js/ff/import/file/configure-upload.js opt/app/public/js/ff/import/status.js +opt/app/public/js/ff/import/status_v2.js opt/app/public/js/ff/index.js opt/app/public/js/ff/install/index.js opt/app/public/js/ff/intro/intro.js +opt/app/public/js/ff/moment/de_DE.js +opt/app/public/js/ff/moment/en_US.js +opt/app/public/js/ff/moment/es_ES.js +opt/app/public/js/ff/moment/fr_FR.js +opt/app/public/js/ff/moment/id_ID.js +opt/app/public/js/ff/moment/it_IT.js +opt/app/public/js/ff/moment/nl_NL.js +opt/app/public/js/ff/moment/pl_PL.js +opt/app/public/js/ff/moment/pt_BR.js +opt/app/public/js/ff/moment/ru_RU.js +opt/app/public/js/ff/moment/tr_TR.js opt/app/public/js/ff/piggy-banks/create.js opt/app/public/js/ff/piggy-banks/edit.js opt/app/public/js/ff/piggy-banks/index.js @@ -1349,6 +1380,7 @@ opt/app/resources/views/admin/users/index.twig opt/app/resources/views/admin/users/show.twig opt/app/resources/views/attachments/delete.twig opt/app/resources/views/attachments/edit.twig +opt/app/resources/views/attachments/index.twig opt/app/resources/views/auth/login.twig opt/app/resources/views/auth/lost-two-factor.twig opt/app/resources/views/auth/passwords/email.twig @@ -1389,6 +1421,8 @@ opt/app/resources/views/demo/no-demo-text.twig opt/app/resources/views/demo/piggy-banks/index.twig opt/app/resources/views/demo/reports/index.twig opt/app/resources/views/demo/transactions/index.twig +opt/app/resources/views/emails/access-token-created-html.twig +opt/app/resources/views/emails/access-token-created-text.twig opt/app/resources/views/emails/admin-test-html.twig opt/app/resources/views/emails/admin-test-text.twig opt/app/resources/views/emails/confirm-account-html.twig @@ -1401,6 +1435,8 @@ opt/app/resources/views/emails/footer-html.twig opt/app/resources/views/emails/footer-text.twig opt/app/resources/views/emails/header-html.twig opt/app/resources/views/emails/header-text.twig +opt/app/resources/views/emails/oauth-client-created-html.twig +opt/app/resources/views/emails/oauth-client-created-text.twig opt/app/resources/views/emails/password-html.twig opt/app/resources/views/emails/password-text.twig opt/app/resources/views/emails/registered-html.twig @@ -1413,8 +1449,10 @@ opt/app/resources/views/errors/500.twig opt/app/resources/views/errors/503.twig opt/app/resources/views/errors/FireflyException.twig opt/app/resources/views/export/index.twig +opt/app/resources/views/form/amount-no-currency.twig opt/app/resources/views/form/amount-small.twig opt/app/resources/views/form/amount.twig +opt/app/resources/views/form/assetAccountCheckList.twig opt/app/resources/views/form/balance.twig opt/app/resources/views/form/checkbox.twig opt/app/resources/views/form/date.twig @@ -1423,7 +1461,6 @@ opt/app/resources/views/form/file.twig opt/app/resources/views/form/help.twig opt/app/resources/views/form/integer.twig opt/app/resources/views/form/location.twig -opt/app/resources/views/form/multiCheckbox.twig opt/app/resources/views/form/multiRadio.twig opt/app/resources/views/form/non-selectable-amount.twig opt/app/resources/views/form/number.twig @@ -1435,14 +1472,20 @@ opt/app/resources/views/form/tags.twig opt/app/resources/views/form/text.twig opt/app/resources/views/form/textarea.twig opt/app/resources/views/import/bank/form.twig -opt/app/resources/views/import/bunq/accounts.twig +opt/app/resources/views/import/bunq/choose-accounts.twig opt/app/resources/views/import/bunq/prerequisites.twig -opt/app/resources/views/import/file/initial.twig +opt/app/resources/views/import/fake/apply-rules.twig +opt/app/resources/views/import/fake/enter-album.twig +opt/app/resources/views/import/fake/enter-artist.twig +opt/app/resources/views/import/fake/enter-song.twig +opt/app/resources/views/import/fake/prerequisites.twig +opt/app/resources/views/import/file/configure-upload.twig opt/app/resources/views/import/file/map.twig +opt/app/resources/views/import/file/new.twig opt/app/resources/views/import/file/roles.twig -opt/app/resources/views/import/file/upload-config.twig opt/app/resources/views/import/index.twig opt/app/resources/views/import/spectre/accounts.twig +opt/app/resources/views/import/spectre/choose-login.twig opt/app/resources/views/import/spectre/prerequisites.twig opt/app/resources/views/import/spectre/redirect.twig opt/app/resources/views/import/status.twig @@ -1561,7 +1604,6 @@ opt/app/routes/breadcrumbs.php opt/app/routes/channels.php opt/app/routes/console.php opt/app/routes/web.php -opt/app/security.txt opt/app/server.php opt/app/storage opt/app/vendor/autoload.php @@ -1625,9 +1667,339 @@ opt/app/vendor/bacon/bacon-qr-code/tests/BaconQrCode/Encoder/MatrixUtilTest.php opt/app/vendor/bacon/bacon-qr-code/tests/BaconQrCode/Renderer/Text/HtmlTest.php opt/app/vendor/bacon/bacon-qr-code/tests/BaconQrCode/Renderer/Text/TextTest.php opt/app/vendor/bacon/bacon-qr-code/tests/bootstrap.php +opt/app/vendor/bin/bunq-install opt/app/vendor/bin/commonmark opt/app/vendor/bin/doctrine-dbal opt/app/vendor/bin/generate-defuse-key +opt/app/vendor/bin/var-dump-server +opt/app/vendor/bunq/sdk_php/.github/ISSUE_TEMPLATE.md +opt/app/vendor/bunq/sdk_php/.github/PULL_REQUEST_TEMPLATE.md +opt/app/vendor/bunq/sdk_php/.gitmodules +opt/app/vendor/bunq/sdk_php/.zappr.yaml +opt/app/vendor/bunq/sdk_php/LICENSE.md +opt/app/vendor/bunq/sdk_php/bin/bunq-install +opt/app/vendor/bunq/sdk_php/composer.json +opt/app/vendor/bunq/sdk_php/src/Context/ApiContext.php +opt/app/vendor/bunq/sdk_php/src/Context/BunqContext.php +opt/app/vendor/bunq/sdk_php/src/Context/InstallationContext.php +opt/app/vendor/bunq/sdk_php/src/Context/SessionContext.php +opt/app/vendor/bunq/sdk_php/src/Context/UserContext.php +opt/app/vendor/bunq/sdk_php/src/Exception/ApiException.php +opt/app/vendor/bunq/sdk_php/src/Exception/BadRequestException.php +opt/app/vendor/bunq/sdk_php/src/Exception/BunqException.php +opt/app/vendor/bunq/sdk_php/src/Exception/EXCEPTION.md +opt/app/vendor/bunq/sdk_php/src/Exception/ExceptionFactory.php +opt/app/vendor/bunq/sdk_php/src/Exception/ForbiddenException.php +opt/app/vendor/bunq/sdk_php/src/Exception/MethodNotAllowedException.php +opt/app/vendor/bunq/sdk_php/src/Exception/NotFoundException.php +opt/app/vendor/bunq/sdk_php/src/Exception/PleaseContactBunqException.php +opt/app/vendor/bunq/sdk_php/src/Exception/SecurityException.php +opt/app/vendor/bunq/sdk_php/src/Exception/TooManyRequestsException.php +opt/app/vendor/bunq/sdk_php/src/Exception/UnauthorizedException.php +opt/app/vendor/bunq/sdk_php/src/Exception/UnknownApiErrorException.php +opt/app/vendor/bunq/sdk_php/src/Http/ApiClient.php +opt/app/vendor/bunq/sdk_php/src/Http/BunqResponse.php +opt/app/vendor/bunq/sdk_php/src/Http/BunqResponseRaw.php +opt/app/vendor/bunq/sdk_php/src/Http/Certificate/api.bunq.com.pubkey.pem +opt/app/vendor/bunq/sdk_php/src/Http/Certificate/sandbox.public.api.bunq.com.pubkey.pem +opt/app/vendor/bunq/sdk_php/src/Http/Handler/HandlerBase.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/HandlerUtil.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/RequestHandlerAuthentication.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/RequestHandlerBase.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/RequestHandlerEncryption.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/RequestHandlerSignature.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/ResponseHandlerBase.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/ResponseHandlerError.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/ResponseHandlerSignature.php +opt/app/vendor/bunq/sdk_php/src/Http/Pagination.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/AnchorObjectInterface.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/BunqModel.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/BunqResponseInstallation.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/BunqResponseSessionServer.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/DeviceServerInternal.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/Id.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/Installation.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/SandboxUserInternal.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/ServerPublicKey.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/SessionServer.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/Token.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/Uuid.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentConversationContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentMonetaryAccount.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentPublic.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentPublicContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentTabContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Avatar.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BillingContractSubscription.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeFundraiserProfile.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeFundraiserResult.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeTabEntry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeTabResultInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeTabResultResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseAttachmentPublic.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseAttachmentTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseAvatar.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseBillingContractSubscriptionList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseBunqMeTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseBunqMeTabList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCard.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardDebit.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardGeneratedCvc2.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardGeneratedCvc2List.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardNameList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardPinChange.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardPinChangeList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardResult.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardResultList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCashRegister.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCashRegisterList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCashRegisterQrCode.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCashRegisterQrCodeList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCertificatePinned.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCertificatePinnedList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseChatConversation.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseChatConversationList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseChatMessageList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCustomer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCustomerLimitList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCustomerList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCustomerStatementExport.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCustomerStatementExportList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDevice.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDeviceList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDeviceServer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDeviceServerList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftPayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftPaymentList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftShareInviteApiKey.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftShareInviteApiKeyList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftShareInviteBank.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftShareInviteBankList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseExportAnnualOverview.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseExportAnnualOverviewList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseIdealMerchantTransaction.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseIdealMerchantTransactionList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInstallationServerPublicKeyList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInt.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInvoice.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInvoiceByUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInvoiceByUserList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInvoiceList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMasterCardAction.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMasterCardActionList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccount.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountBank.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountBankList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountJoint.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountJointList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountLight.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountLightList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseNull.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePaymentBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePaymentBatchList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePaymentChatList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePaymentList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePermittedIp.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePermittedIpList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePromotionDisplay.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestInquiryBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestInquiryBatchList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestInquiryChatList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestInquiryList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestResponseChatList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestResponseList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseSandboxUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseSchedule.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseScheduleInstance.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseScheduleInstanceList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseScheduleList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseSchedulePayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseSchedulePaymentList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseScheduleUserList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseShareInviteBankInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseShareInviteBankInquiryList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseShareInviteBankResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseShareInviteBankResponseList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseString.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabAttachmentTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabItemShop.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabItemShopList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabResultInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabResultInquiryList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabResultResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabResultResponseList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabUsageMultiple.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabUsageMultipleList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabUsageSingle.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabUsageSingleList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTokenQrRequestIdeal.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTokenQrRequestSofort.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserCompany.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserCredentialPasswordIp.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserCredentialPasswordIpList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserLight.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserPerson.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Card.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardDebit.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardGeneratedCvc2.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardName.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardPinChange.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardReplace.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardResult.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CashRegister.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CashRegisterQrCode.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CashRegisterQrCodeContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CertificatePinned.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatConversation.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatConversationReference.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatConversationSupportExternal.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessage.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessageAnnouncement.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessageAttachment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessageStatus.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessageText.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessageUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Customer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CustomerLimit.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CustomerStatementExport.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CustomerStatementExportContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Device.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DeviceServer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DraftPayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DraftShareInviteApiKey.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DraftShareInviteApiKeyQrCodeContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DraftShareInviteBank.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DraftShareInviteBankQrCodeContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ExportAnnualOverview.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ExportAnnualOverviewContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/IdealMerchantTransaction.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/InstallationServerPublicKey.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Invoice.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/InvoiceByUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MasterCardAction.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MonetaryAccount.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MonetaryAccountBank.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MonetaryAccountJoint.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MonetaryAccountLight.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MonetaryAccountProfile.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Payment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/PaymentBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/PaymentChat.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/PermittedIp.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/PromotionDisplay.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/RequestInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/RequestInquiryBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/RequestInquiryChat.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/RequestResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/RequestResponseChat.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/SandboxUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Schedule.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ScheduleInstance.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/SchedulePayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/SchedulePaymentBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ScheduleUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Session.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ShareInviteBankAmountUsed.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ShareInviteBankInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ShareInviteBankResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Tab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabAttachmentTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabAttachmentTabContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabItem.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabItemShop.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabItemShopBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabQrCodeContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabResultInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabResultResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabUsageMultiple.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabUsageSingle.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TokenQrRequestIdeal.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TokenQrRequestSofort.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/User.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/UserCompany.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/UserCredentialPasswordIp.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/UserLight.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/UserPerson.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Whitelist.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/WhitelistResult.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Address.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Amount.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/AnchoredObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Attachment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/AttachmentMonetaryAccountPayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/AttachmentPublic.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/AttachmentTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Avatar.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/BudgetRestriction.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/BunqId.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/BunqMeMerchantAvailable.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/CardCountryPermission.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/CardLimit.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/CardMagStripePermission.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/CardPinAssignment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Certificate.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentAnchorEvent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentAttachment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentGeolocation.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentStatusConversation.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentStatusConversationTitle.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentStatusMembership.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentText.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/CoOwner.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/DraftPaymentAnchorObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/DraftPaymentEntry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/DraftPaymentResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/DraftShareInviteEntry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Error.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Geolocation.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Image.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/InvoiceItem.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/InvoiceItemGroup.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Issuer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/LabelCard.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/LabelMonetaryAccount.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/LabelUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/MonetaryAccountProfileDrain.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/MonetaryAccountProfileFill.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/MonetaryAccountSetting.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/NotificationAnchorObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/NotificationFilter.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/NotificationUrl.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/PermittedDevice.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Pointer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/RequestInquiryReference.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/RequestReferenceSplitTheBillAnchorObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ScheduleAnchorObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ScheduleInstanceAnchorObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/SchedulePaymentEntry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ShareDetail.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ShareDetailDraftPayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ShareDetailPayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ShareDetailReadOnly.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/TabTextWaitingScreen.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/TabVisibility.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/TaxResident.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Ubo.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/WhitelistResultViewAnchoredObject.php +opt/app/vendor/bunq/sdk_php/src/Security/KeyPair.php +opt/app/vendor/bunq/sdk_php/src/Security/PrivateKey.php +opt/app/vendor/bunq/sdk_php/src/Security/PublicKey.php +opt/app/vendor/bunq/sdk_php/src/Util/BunqEnum.php +opt/app/vendor/bunq/sdk_php/src/Util/BunqEnumApiEnvironmentType.php +opt/app/vendor/bunq/sdk_php/src/Util/FileUtil.php +opt/app/vendor/bunq/sdk_php/src/Util/InstallationUtil.php +opt/app/vendor/bunq/sdk_php/src/Util/ModelUtil.php opt/app/vendor/composer/ClassLoader.php opt/app/vendor/composer/LICENSE opt/app/vendor/composer/autoload_classmap.php @@ -2179,7 +2551,6 @@ opt/app/vendor/egulias/email-validator/EmailValidator/Warning/Warning.php opt/app/vendor/egulias/email-validator/LICENSE opt/app/vendor/egulias/email-validator/README.md opt/app/vendor/egulias/email-validator/composer.json -opt/app/vendor/egulias/email-validator/composer.lock opt/app/vendor/egulias/email-validator/phpunit.xml.dist opt/app/vendor/erusev/parsedown/LICENSE.txt opt/app/vendor/erusev/parsedown/Parsedown.php @@ -2476,6 +2847,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/Queue.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/QueueableCollection.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/QueueableEntity.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/ShouldQueue.php +opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Redis/Connection.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Redis/Factory.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Redis/LimiterTimeoutException.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Routing/BindingRegistrar.php @@ -2682,6 +3054,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/ListenerMakeC opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/MailMakeCommand.php opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/ModelMakeCommand.php opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/NotificationMakeCommand.php +opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/ObserverMakeCommand.php opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/PackageDiscoverCommand.php opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/PolicyMakeCommand.php opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/PresetCommand.php @@ -2733,6 +3106,8 @@ opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/markdow opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/markdown.stub opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/model.stub opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/notification.stub +opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/observer.plain.stub +opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/observer.stub opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/pivot.model.stub opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/policy.plain.stub opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/policy.stub @@ -4141,109 +4516,6 @@ opt/app/vendor/rcrowe/twigbridge/src/ServiceProvider.php opt/app/vendor/rcrowe/twigbridge/src/Twig/Globals.php opt/app/vendor/rcrowe/twigbridge/src/Twig/Loader.php opt/app/vendor/rcrowe/twigbridge/src/Twig/Template.php -opt/app/vendor/rmccue/requests/.coveralls.yml -opt/app/vendor/rmccue/requests/CHANGELOG.md -opt/app/vendor/rmccue/requests/LICENSE -opt/app/vendor/rmccue/requests/README.md -opt/app/vendor/rmccue/requests/bin/create_pear_package.php -opt/app/vendor/rmccue/requests/composer.json -opt/app/vendor/rmccue/requests/docs/README.md -opt/app/vendor/rmccue/requests/docs/authentication-custom.md -opt/app/vendor/rmccue/requests/docs/authentication.md -opt/app/vendor/rmccue/requests/docs/goals.md -opt/app/vendor/rmccue/requests/docs/hooks.md -opt/app/vendor/rmccue/requests/docs/proxy.md -opt/app/vendor/rmccue/requests/docs/usage-advanced.md -opt/app/vendor/rmccue/requests/docs/usage.md -opt/app/vendor/rmccue/requests/docs/why-requests.md -opt/app/vendor/rmccue/requests/examples/basic-auth.php -opt/app/vendor/rmccue/requests/examples/cookie.php -opt/app/vendor/rmccue/requests/examples/cookie_jar.php -opt/app/vendor/rmccue/requests/examples/get.php -opt/app/vendor/rmccue/requests/examples/multiple.php -opt/app/vendor/rmccue/requests/examples/post.php -opt/app/vendor/rmccue/requests/examples/proxy.php -opt/app/vendor/rmccue/requests/examples/session.php -opt/app/vendor/rmccue/requests/examples/timeout.php -opt/app/vendor/rmccue/requests/library/Requests.php -opt/app/vendor/rmccue/requests/library/Requests/Auth.php -opt/app/vendor/rmccue/requests/library/Requests/Auth/Basic.php -opt/app/vendor/rmccue/requests/library/Requests/Cookie.php -opt/app/vendor/rmccue/requests/library/Requests/Cookie/Jar.php -opt/app/vendor/rmccue/requests/library/Requests/Exception.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/304.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/305.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/306.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/400.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/401.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/402.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/403.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/404.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/405.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/406.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/407.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/408.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/409.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/410.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/411.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/412.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/413.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/414.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/415.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/416.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/417.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/418.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/428.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/429.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/431.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/500.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/501.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/502.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/503.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/504.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/505.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/511.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/Unknown.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/Transport.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/Transport/cURL.php -opt/app/vendor/rmccue/requests/library/Requests/Hooker.php -opt/app/vendor/rmccue/requests/library/Requests/Hooks.php -opt/app/vendor/rmccue/requests/library/Requests/IDNAEncoder.php -opt/app/vendor/rmccue/requests/library/Requests/IPv6.php -opt/app/vendor/rmccue/requests/library/Requests/IRI.php -opt/app/vendor/rmccue/requests/library/Requests/Proxy.php -opt/app/vendor/rmccue/requests/library/Requests/Proxy/HTTP.php -opt/app/vendor/rmccue/requests/library/Requests/Response.php -opt/app/vendor/rmccue/requests/library/Requests/Response/Headers.php -opt/app/vendor/rmccue/requests/library/Requests/SSL.php -opt/app/vendor/rmccue/requests/library/Requests/Session.php -opt/app/vendor/rmccue/requests/library/Requests/Transport.php -opt/app/vendor/rmccue/requests/library/Requests/Transport/cURL.php -opt/app/vendor/rmccue/requests/library/Requests/Transport/cacert.pem -opt/app/vendor/rmccue/requests/library/Requests/Transport/fsockopen.php -opt/app/vendor/rmccue/requests/library/Requests/Utility/CaseInsensitiveDictionary.php -opt/app/vendor/rmccue/requests/library/Requests/Utility/FilteredIterator.php -opt/app/vendor/rmccue/requests/package.xml.tpl -opt/app/vendor/rmccue/requests/tests/Auth/Basic.php -opt/app/vendor/rmccue/requests/tests/ChunkedEncoding.php -opt/app/vendor/rmccue/requests/tests/Cookies.php -opt/app/vendor/rmccue/requests/tests/Encoding.php -opt/app/vendor/rmccue/requests/tests/IDNAEncoder.php -opt/app/vendor/rmccue/requests/tests/IRI.php -opt/app/vendor/rmccue/requests/tests/Proxy/HTTP.php -opt/app/vendor/rmccue/requests/tests/Requests.php -opt/app/vendor/rmccue/requests/tests/Response/Headers.php -opt/app/vendor/rmccue/requests/tests/SSL.php -opt/app/vendor/rmccue/requests/tests/Session.php -opt/app/vendor/rmccue/requests/tests/Transport/Base.php -opt/app/vendor/rmccue/requests/tests/Transport/cURL.php -opt/app/vendor/rmccue/requests/tests/Transport/fsockopen.php -opt/app/vendor/rmccue/requests/tests/bootstrap.php -opt/app/vendor/rmccue/requests/tests/phpunit.xml.dist -opt/app/vendor/rmccue/requests/tests/utils/proxy/proxy.py -opt/app/vendor/rmccue/requests/tests/utils/proxy/start.sh -opt/app/vendor/rmccue/requests/tests/utils/proxy/stop.sh opt/app/vendor/swiftmailer/swiftmailer/.gitattributes opt/app/vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md opt/app/vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md @@ -4595,6 +4867,7 @@ opt/app/vendor/symfony/console/Exception/ExceptionInterface.php opt/app/vendor/symfony/console/Exception/InvalidArgumentException.php opt/app/vendor/symfony/console/Exception/InvalidOptionException.php opt/app/vendor/symfony/console/Exception/LogicException.php +opt/app/vendor/symfony/console/Exception/NamespaceNotFoundException.php opt/app/vendor/symfony/console/Exception/RuntimeException.php opt/app/vendor/symfony/console/Formatter/OutputFormatter.php opt/app/vendor/symfony/console/Formatter/OutputFormatterInterface.php @@ -4615,6 +4888,7 @@ opt/app/vendor/symfony/console/Helper/QuestionHelper.php opt/app/vendor/symfony/console/Helper/SymfonyQuestionHelper.php opt/app/vendor/symfony/console/Helper/Table.php opt/app/vendor/symfony/console/Helper/TableCell.php +opt/app/vendor/symfony/console/Helper/TableRows.php opt/app/vendor/symfony/console/Helper/TableSeparator.php opt/app/vendor/symfony/console/Helper/TableStyle.php opt/app/vendor/symfony/console/Input/ArgvInput.php @@ -4632,6 +4906,7 @@ opt/app/vendor/symfony/console/Logger/ConsoleLogger.php opt/app/vendor/symfony/console/Output/BufferedOutput.php opt/app/vendor/symfony/console/Output/ConsoleOutput.php opt/app/vendor/symfony/console/Output/ConsoleOutputInterface.php +opt/app/vendor/symfony/console/Output/ConsoleSectionOutput.php opt/app/vendor/symfony/console/Output/NullOutput.php opt/app/vendor/symfony/console/Output/Output.php opt/app/vendor/symfony/console/Output/OutputInterface.php @@ -4647,6 +4922,7 @@ opt/app/vendor/symfony/console/Style/SymfonyStyle.php opt/app/vendor/symfony/console/Terminal.php opt/app/vendor/symfony/console/Tester/ApplicationTester.php opt/app/vendor/symfony/console/Tester/CommandTester.php +opt/app/vendor/symfony/console/Tester/TesterTrait.php opt/app/vendor/symfony/console/Tests/ApplicationTest.php opt/app/vendor/symfony/console/Tests/Command/CommandTest.php opt/app/vendor/symfony/console/Tests/Command/HelpCommandTest.php @@ -4686,6 +4962,7 @@ opt/app/vendor/symfony/console/Tests/Fixtures/FooSameCaseLowercaseCommand.php opt/app/vendor/symfony/console/Tests/Fixtures/FooSameCaseUppercaseCommand.php opt/app/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced1Command.php opt/app/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced2Command.php +opt/app/vendor/symfony/console/Tests/Fixtures/FooWithoutAliasCommand.php opt/app/vendor/symfony/console/Tests/Fixtures/FoobarCommand.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_0.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_1.php @@ -4700,6 +4977,7 @@ opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_2.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_3.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_4.php +opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_4_with_iterators.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_5.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_6.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_7.php @@ -4720,6 +4998,7 @@ opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_1 opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_2.txt opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_3.txt opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_4.txt +opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_4_with_iterators.txt opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_5.txt opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_7.txt @@ -4863,6 +5142,7 @@ opt/app/vendor/symfony/console/Tests/Input/InputTest.php opt/app/vendor/symfony/console/Tests/Input/StringInputTest.php opt/app/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.php opt/app/vendor/symfony/console/Tests/Output/ConsoleOutputTest.php +opt/app/vendor/symfony/console/Tests/Output/ConsoleSectionOutputTest.php opt/app/vendor/symfony/console/Tests/Output/NullOutputTest.php opt/app/vendor/symfony/console/Tests/Output/OutputTest.php opt/app/vendor/symfony/console/Tests/Output/StreamOutputTest.php @@ -5107,8 +5387,15 @@ opt/app/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php opt/app/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php opt/app/vendor/symfony/http-foundation/ExpressionRequestMatcher.php opt/app/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php +opt/app/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php +opt/app/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php opt/app/vendor/symfony/http-foundation/File/Exception/FileException.php opt/app/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php +opt/app/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php +opt/app/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php +opt/app/vendor/symfony/http-foundation/File/Exception/NoFileException.php +opt/app/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php +opt/app/vendor/symfony/http-foundation/File/Exception/PartialFileException.php opt/app/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php opt/app/vendor/symfony/http-foundation/File/Exception/UploadException.php opt/app/vendor/symfony/http-foundation/File/File.php @@ -5123,6 +5410,7 @@ opt/app/vendor/symfony/http-foundation/File/Stream.php opt/app/vendor/symfony/http-foundation/File/UploadedFile.php opt/app/vendor/symfony/http-foundation/FileBag.php opt/app/vendor/symfony/http-foundation/HeaderBag.php +opt/app/vendor/symfony/http-foundation/HeaderUtils.php opt/app/vendor/symfony/http-foundation/IpUtils.php opt/app/vendor/symfony/http-foundation/JsonResponse.php opt/app/vendor/symfony/http-foundation/LICENSE @@ -5148,10 +5436,12 @@ opt/app/vendor/symfony/http-foundation/Session/SessionBagProxy.php opt/app/vendor/symfony/http-foundation/Session/SessionInterface.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php +opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php +opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php opt/app/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -5178,7 +5468,21 @@ opt/app/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif opt/app/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php opt/app/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php opt/app/vendor/symfony/http-foundation/Tests/FileBagTest.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/common.inc +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.php opt/app/vendor/symfony/http-foundation/Tests/HeaderBagTest.php +opt/app/vendor/symfony/http-foundation/Tests/HeaderUtilsTest.php opt/app/vendor/symfony/http-foundation/Tests/IpUtilsTest.php opt/app/vendor/symfony/http-foundation/Tests/JsonResponseTest.php opt/app/vendor/symfony/http-foundation/Tests/ParameterBagTest.php @@ -5186,6 +5490,7 @@ opt/app/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php opt/app/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php opt/app/vendor/symfony/http-foundation/Tests/RequestStackTest.php opt/app/vendor/symfony/http-foundation/Tests/RequestTest.php +opt/app/vendor/symfony/http-foundation/Tests/ResponseFunctionalTest.php opt/app/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php opt/app/vendor/symfony/http-foundation/Tests/ResponseTest.php opt/app/vendor/symfony/http-foundation/Tests/ResponseTestCase.php @@ -5195,6 +5500,7 @@ opt/app/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttribu opt/app/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/SessionTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected @@ -5210,10 +5516,15 @@ opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/wi opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MigratingSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisSessionHandlerTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php @@ -5245,6 +5556,7 @@ opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeV opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php +opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php opt/app/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php @@ -5369,6 +5681,7 @@ opt/app/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php opt/app/vendor/symfony/http-kernel/Tests/ClientTest.php opt/app/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php opt/app/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolver/ServiceValueResolverTest.php +opt/app/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolver/TraceableValueResolverTest.php opt/app/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php opt/app/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php opt/app/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php @@ -5488,6 +5801,11 @@ opt/app/vendor/symfony/http-kernel/Tests/UriSignerTest.php opt/app/vendor/symfony/http-kernel/UriSigner.php opt/app/vendor/symfony/http-kernel/composer.json opt/app/vendor/symfony/http-kernel/phpunit.xml.dist +opt/app/vendor/symfony/polyfill-ctype/Ctype.php +opt/app/vendor/symfony/polyfill-ctype/LICENSE +opt/app/vendor/symfony/polyfill-ctype/README.md +opt/app/vendor/symfony/polyfill-ctype/bootstrap.php +opt/app/vendor/symfony/polyfill-ctype/composer.json opt/app/vendor/symfony/polyfill-mbstring/LICENSE opt/app/vendor/symfony/polyfill-mbstring/Mbstring.php opt/app/vendor/symfony/polyfill-mbstring/README.md @@ -5519,6 +5837,7 @@ opt/app/vendor/symfony/process/Exception/ExceptionInterface.php opt/app/vendor/symfony/process/Exception/InvalidArgumentException.php opt/app/vendor/symfony/process/Exception/LogicException.php opt/app/vendor/symfony/process/Exception/ProcessFailedException.php +opt/app/vendor/symfony/process/Exception/ProcessSignaledException.php opt/app/vendor/symfony/process/Exception/ProcessTimedOutException.php opt/app/vendor/symfony/process/Exception/RuntimeException.php opt/app/vendor/symfony/process/ExecutableFinder.php @@ -5596,8 +5915,6 @@ opt/app/vendor/symfony/routing/Loader/PhpFileLoader.php opt/app/vendor/symfony/routing/Loader/XmlFileLoader.php opt/app/vendor/symfony/routing/Loader/YamlFileLoader.php opt/app/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd -opt/app/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php -opt/app/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php opt/app/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php opt/app/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php opt/app/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php @@ -5626,6 +5943,24 @@ opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/AbstractClassController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/ActionPathController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/DefaultValueController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/ExplicitLocalizedActionPathController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/InvokableController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/InvokableLocalizedController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedActionPathController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedMethodActionControllers.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedPrefixLocalizedActionController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedPrefixMissingLocaleActionController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedPrefixMissingRouteLocaleActionController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedPrefixWithRouteWithoutLocale.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/MethodActionControllers.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/MissingRouteNameController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/NothingButNameController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/PrefixedActionLocalizedRouteController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/PrefixedActionPathController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/RouteWithPrefixController.php opt/app/vendor/symfony/routing/Tests/Fixtures/CustomCompiledRoute.php opt/app/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php opt/app/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php @@ -5652,12 +5987,18 @@ opt/app/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml opt/app/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher0.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher10.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher11.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher12.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher13.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher8.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher9.php opt/app/vendor/symfony/routing/Tests/Fixtures/empty.yml opt/app/vendor/symfony/routing/Tests/Fixtures/file_resource.yml opt/app/vendor/symfony/routing/Tests/Fixtures/foo.xml @@ -5673,11 +6014,31 @@ opt/app/vendor/symfony/routing/Tests/Fixtures/glob/import_single.yml opt/app/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl.php opt/app/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_bar.php opt/app/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_baz.php +opt/app/vendor/symfony/routing/Tests/Fixtures/import_with_name_prefix/routing.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/import_with_name_prefix/routing.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/import_with_no_trailing_slash/routing.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/import_with_no_trailing_slash/routing.yml opt/app/vendor/symfony/routing/Tests/Fixtures/incomplete.yml opt/app/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/imported-with-locale-but-not-localized.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/imported-with-locale-but-not-localized.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/imported-with-locale.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/imported-with-locale.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importer-with-controller-default.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importer-with-locale-imports-non-localized-route.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importer-with-locale-imports-non-localized-route.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importer-with-locale.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importer-with-locale.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importing-localized-route.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/localized-route.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/missing-locale-in-importer.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/not-localized.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/officially_formatted_locales.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/route-without-path-or-locales.yml opt/app/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml @@ -5695,7 +6056,11 @@ opt/app/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml opt/app/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml opt/app/vendor/symfony/routing/Tests/Fixtures/null_values.xml opt/app/vendor/symfony/routing/Tests/Fixtures/php_dsl.php +opt/app/vendor/symfony/routing/Tests/Fixtures/php_dsl_i18n.php opt/app/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub.php +opt/app/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub_i18n.php +opt/app/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub_root.php +opt/app/vendor/symfony/routing/Tests/Fixtures/php_object_dsl.php opt/app/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml opt/app/vendor/symfony/routing/Tests/Fixtures/validpattern.php @@ -5714,6 +6079,7 @@ opt/app/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php +opt/app/vendor/symfony/routing/Tests/Loader/FileLocatorStub.php opt/app/vendor/symfony/routing/Tests/Loader/GlobFileLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php @@ -5721,7 +6087,6 @@ opt/app/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php opt/app/vendor/symfony/routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php opt/app/vendor/symfony/routing/Tests/Matcher/DumpedUrlMatcherTest.php -opt/app/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php opt/app/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php opt/app/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php opt/app/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php @@ -5805,6 +6170,7 @@ opt/app/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd opt/app/vendor/symfony/translation/Tests/Catalogue/AbstractOperationTest.php opt/app/vendor/symfony/translation/Tests/Catalogue/MergeOperationTest.php opt/app/vendor/symfony/translation/Tests/Catalogue/TargetOperationTest.php +opt/app/vendor/symfony/translation/Tests/Command/XliffLintCommandTest.php opt/app/vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php opt/app/vendor/symfony/translation/Tests/DataCollectorTranslatorTest.php opt/app/vendor/symfony/translation/Tests/DependencyInjection/TranslationDumperPassTest.php @@ -5921,6 +6287,7 @@ opt/app/vendor/symfony/var-dumper/Caster/DoctrineCaster.php opt/app/vendor/symfony/var-dumper/Caster/EnumStub.php opt/app/vendor/symfony/var-dumper/Caster/ExceptionCaster.php opt/app/vendor/symfony/var-dumper/Caster/FrameStub.php +opt/app/vendor/symfony/var-dumper/Caster/GmpCaster.php opt/app/vendor/symfony/var-dumper/Caster/LinkStub.php opt/app/vendor/symfony/var-dumper/Caster/PdoCaster.php opt/app/vendor/symfony/var-dumper/Caster/PgSqlCaster.php @@ -5940,18 +6307,32 @@ opt/app/vendor/symfony/var-dumper/Cloner/Data.php opt/app/vendor/symfony/var-dumper/Cloner/DumperInterface.php opt/app/vendor/symfony/var-dumper/Cloner/Stub.php opt/app/vendor/symfony/var-dumper/Cloner/VarCloner.php +opt/app/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php +opt/app/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php +opt/app/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php +opt/app/vendor/symfony/var-dumper/Command/ServerDumpCommand.php opt/app/vendor/symfony/var-dumper/Dumper/AbstractDumper.php opt/app/vendor/symfony/var-dumper/Dumper/CliDumper.php +opt/app/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php +opt/app/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php +opt/app/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php +opt/app/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php opt/app/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php opt/app/vendor/symfony/var-dumper/Dumper/HtmlDumper.php +opt/app/vendor/symfony/var-dumper/Dumper/ServerDumper.php opt/app/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php opt/app/vendor/symfony/var-dumper/LICENSE opt/app/vendor/symfony/var-dumper/README.md +opt/app/vendor/symfony/var-dumper/Resources/bin/var-dump-server +opt/app/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css opt/app/vendor/symfony/var-dumper/Resources/functions/dump.php +opt/app/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js +opt/app/vendor/symfony/var-dumper/Server/DumpServer.php opt/app/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php opt/app/vendor/symfony/var-dumper/Tests/Caster/CasterTest.php opt/app/vendor/symfony/var-dumper/Tests/Caster/DateCasterTest.php opt/app/vendor/symfony/var-dumper/Tests/Caster/ExceptionCasterTest.php +opt/app/vendor/symfony/var-dumper/Tests/Caster/GmpCasterTest.php opt/app/vendor/symfony/var-dumper/Tests/Caster/PdoCasterTest.php opt/app/vendor/symfony/var-dumper/Tests/Caster/RedisCasterTest.php opt/app/vendor/symfony/var-dumper/Tests/Caster/ReflectionCasterTest.php @@ -5962,11 +6343,13 @@ opt/app/vendor/symfony/var-dumper/Tests/Cloner/DataTest.php opt/app/vendor/symfony/var-dumper/Tests/Cloner/VarClonerTest.php opt/app/vendor/symfony/var-dumper/Tests/Dumper/CliDumperTest.php opt/app/vendor/symfony/var-dumper/Tests/Dumper/HtmlDumperTest.php +opt/app/vendor/symfony/var-dumper/Tests/Dumper/ServerDumperTest.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/FooInterface.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/GeneratorDemo.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/NotLoadableClass.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/Twig.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/dumb-var.php +opt/app/vendor/symfony/var-dumper/Tests/Fixtures/dump_server.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/xml_reader.xml opt/app/vendor/symfony/var-dumper/Tests/Test/VarDumperTestTraitTest.php opt/app/vendor/symfony/var-dumper/VarDumper.php diff --git a/.sandstorm/sandstorm-pkgdef.capnp b/.sandstorm/sandstorm-pkgdef.capnp index 6f5c532bd3..3f86b4e979 100644 --- a/.sandstorm/sandstorm-pkgdef.capnp +++ b/.sandstorm/sandstorm-pkgdef.capnp @@ -15,8 +15,8 @@ const pkgdef :Spk.PackageDefinition = ( manifest = ( appTitle = (defaultText = "Firefly III"), - appVersion = 13, - appMarketingVersion = (defaultText = "4.7.4"), + appVersion = 14, + appMarketingVersion = (defaultText = "4.7.5"), actions = [ # Define your "new document" handlers here. @@ -65,9 +65,9 @@ const pkgdef :Spk.PackageDefinition = ( # Sizes are given in device-independent pixels, so if you took these # screenshots on a Retina-style high DPI screen, divide each dimension by two. - (width = 1291, height = 800, png = embed "screenshots/screenshot-1.png"), - (width = 1291, height = 800, png = embed "screenshots/screenshot-2.png"), - (width = 1291, height = 800, png = embed "screenshots/screenshot-3.png"), + (width = 1290, height = 800, png = embed "screenshots/screenshot-1.png"), + (width = 1290, height = 800, png = embed "screenshots/screenshot-2.png"), + (width = 1290, height = 800, png = embed "screenshots/screenshot-3.png"), ], changeLog = (defaultText = embed "changelog.md"), diff --git a/.sandstorm/screenshots/screenshot-1.png b/.sandstorm/screenshots/screenshot-1.png index c8a49481c0..5f786ad2ff 100644 Binary files a/.sandstorm/screenshots/screenshot-1.png and b/.sandstorm/screenshots/screenshot-1.png differ diff --git a/.sandstorm/screenshots/screenshot-2.png b/.sandstorm/screenshots/screenshot-2.png index 15dbbd4122..fe0bfb0218 100644 Binary files a/.sandstorm/screenshots/screenshot-2.png and b/.sandstorm/screenshots/screenshot-2.png differ diff --git a/.sandstorm/screenshots/screenshot-3.png b/.sandstorm/screenshots/screenshot-3.png index c52941aa7b..f68d5de0c1 100644 Binary files a/.sandstorm/screenshots/screenshot-3.png and b/.sandstorm/screenshots/screenshot-3.png differ diff --git a/.travis.yml b/.travis.yml index 4543dc5049..0771080d51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - 7.1 - - 7.2 + - 7.1.18 cache: directories: @@ -14,7 +13,6 @@ install: - cp .env.testing .env - php artisan clear-compiled - php artisan env - - cp .env.testing .env - wget -q https://github.com/firefly-iii/test-data/raw/master/storage/database.sqlite -O storage/database/database.sqlite - mkdir -p build/logs diff --git a/Dockerfile b/Dockerfile index e9619b7ee4..4eb3f9cb1d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,7 @@ RUN apt-get update -y && \ libpq-dev \ libbz2-dev \ gettext-base \ + cron \ locales && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -47,6 +48,13 @@ RUN cd /tmp && \ make && \ make install + +# Create the log file to be able to run tail +RUN touch /var/log/cron.log + +# Setup cron job +RUN (crontab -l ; echo "* * * * * root $FIREFLY_PATH/artisan schedule:run >> /var/log/cron.log") | crontab + # Install PHP exentions. RUN docker-php-ext-install -j$(nproc) curl gd intl json readline tidy zip bcmath xml mbstring pdo_sqlite pdo_mysql bz2 pdo_pgsql @@ -77,6 +85,9 @@ RUN composer install --prefer-dist --no-dev --no-scripts --no-suggest # Expose port 80 EXPOSE 80 +# Run the command on container startup +CMD cron + # Run entrypoint thing ENTRYPOINT [".deploy/docker/entrypoint.sh"] diff --git a/app/Api/V1/Controllers/AboutController.php b/app/Api/V1/Controllers/AboutController.php index 77e63b7c64..2f7795d05c 100644 --- a/app/Api/V1/Controllers/AboutController.php +++ b/app/Api/V1/Controllers/AboutController.php @@ -26,20 +26,25 @@ namespace FireflyIII\Api\V1\Controllers; use DB; use FireflyIII\Transformers\UserTransformer; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use League\Fractal\Manager; use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; /** + * Returns basic information about this installation. + * * Class AboutController */ class AboutController extends Controller { /** - * @return \Illuminate\Http\JsonResponse + * Returns system information. + * + * @return JsonResponse */ - public function about() + public function about(): JsonResponse { $search = ['~', '#']; $replace = ['\~', '# ']; @@ -59,11 +64,13 @@ class AboutController extends Controller } /** + * Returns information about the user. + * * @param Request $request * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ - public function user(Request $request) + public function user(Request $request): JsonResponse { $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; diff --git a/app/Api/V1/Controllers/AccountController.php b/app/Api/V1/Controllers/AccountController.php index 2f951fed3b..f7bd0580f2 100644 --- a/app/Api/V1/Controllers/AccountController.php +++ b/app/Api/V1/Controllers/AccountController.php @@ -30,6 +30,8 @@ use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Transformers\AccountTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use League\Fractal\Manager; @@ -37,7 +39,6 @@ use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; -use Preferences; /** * Class AccountController @@ -51,20 +52,20 @@ class AccountController extends Controller /** * AccountController constructor. - * - * @throws \FireflyIII\Exceptions\FireflyException */ public function __construct() { parent::__construct(); $this->middleware( function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); // @var AccountRepositoryInterface repository $this->repository = app(AccountRepositoryInterface::class); - $this->repository->setUser(auth()->user()); + $this->repository->setUser($user); $this->currencyRepository = app(CurrencyRepositoryInterface::class); - $this->currencyRepository->setUser(auth()->user()); + $this->currencyRepository->setUser($user); return $next($request); } @@ -76,9 +77,9 @@ class AccountController extends Controller * * @param \FireflyIII\Models\Account $account * - * @return \Illuminate\Http\Response + * @return JsonResponse */ - public function delete(Account $account) + public function delete(Account $account): JsonResponse { $this->repository->destroy($account, null); @@ -90,12 +91,12 @@ class AccountController extends Controller * * @param Request $request * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ - public function index(Request $request) + public function index(Request $request): JsonResponse { // create some objects: - $manager = new Manager(); + $manager = new Manager; $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; // read type from URI @@ -104,7 +105,7 @@ class AccountController extends Controller // types to get, page size: $types = $this->mapTypes($this->parameters->get('type')); - $pageSize = (int)Preferences::getForUser(auth()->user(), 'listPageSize', 50)->data; + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; // get list of accounts. Count it and split it. $collection = $this->repository->getAccountsByType($types); @@ -129,9 +130,9 @@ class AccountController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function show(Request $request, Account $account) + public function show(Request $request, Account $account): JsonResponse { - $manager = new Manager(); + $manager = new Manager; // add include parameter: $include = $request->get('include') ?? ''; @@ -149,7 +150,7 @@ class AccountController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function store(AccountRequest $request) + public function store(AccountRequest $request): JsonResponse { $data = $request->getAll(); // if currency ID is 0, find the currency by the code: @@ -158,7 +159,7 @@ class AccountController extends Controller $data['currency_id'] = null === $currency ? 0 : $currency->id; } $account = $this->repository->store($data); - $manager = new Manager(); + $manager = new Manager; $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); @@ -175,7 +176,7 @@ class AccountController extends Controller * * @return \Illuminate\Http\JsonResponse */ - public function update(AccountRequest $request, Account $account) + public function update(AccountRequest $request, Account $account): JsonResponse { $data = $request->getAll(); // if currency ID is 0, find the currency by the code: @@ -186,7 +187,7 @@ class AccountController extends Controller // set correct type: $data['type'] = config('firefly.shortNamesByFullName.' . $account->accountType->type); $this->repository->update($account, $data); - $manager = new Manager(); + $manager = new Manager; $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); diff --git a/app/Api/V1/Controllers/AttachmentController.php b/app/Api/V1/Controllers/AttachmentController.php new file mode 100644 index 0000000000..8dae717b38 --- /dev/null +++ b/app/Api/V1/Controllers/AttachmentController.php @@ -0,0 +1,229 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\AttachmentRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; +use FireflyIII\Models\Attachment; +use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; +use FireflyIII\Transformers\AttachmentTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Http\Response as LaravelResponse; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * Class AttachmentController + */ +class AttachmentController extends Controller +{ + /** @var AttachmentRepositoryInterface */ + private $repository; + + /** + * AccountController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(AttachmentRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Remove the specified resource from storage. + * + * @param Attachment $attachment + * + * @return JsonResponse + */ + public function delete(Attachment $attachment): JsonResponse + { + $this->repository->destroy($attachment); + + return response()->json([], 204); + } + + /** + * @param Attachment $attachment + * + * @return LaravelResponse + * @throws FireflyException + */ + public function download(Attachment $attachment): LaravelResponse + { + if ($attachment->uploaded === false) { + throw new FireflyException('No file has been uploaded for this attachment (yet).'); + } + if ($this->repository->exists($attachment)) { + $content = $this->repository->getContent($attachment); + $quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\')); + + /** @var LaravelResponse $response */ + $response = response($content, 200); + $response + ->header('Content-Description', 'File Transfer') + ->header('Content-Type', 'application/octet-stream') + ->header('Content-Disposition', 'attachment; filename=' . $quoted) + ->header('Content-Transfer-Encoding', 'binary') + ->header('Connection', 'Keep-Alive') + ->header('Expires', '0') + ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->header('Pragma', 'public') + ->header('Content-Length', \strlen($content)); + + return $response; + } + throw new FireflyException('Could not find the indicated attachment. The file is no longer there.'); + } + + /** + * Display a listing of the resource. + * + * @param Request $request + * + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of accounts. Count it and split it. + $collection = $this->repository->get(); + $count = $collection->count(); + $attachments = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($attachments, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.attachments.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($attachments, new AttachmentTransformer($this->parameters), 'attachments'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Display the specified resource. + * + * @param Request $request + * @param Attachment $attachment + * + * @return JsonResponse + */ + public function show(Request $request, Attachment $attachment): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($attachment, new AttachmentTransformer($this->parameters), 'attachments'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Store a newly created resource in storage. + * + * @param AttachmentRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(AttachmentRequest $request): JsonResponse + { + $data = $request->getAll(); + $attachment = $this->repository->store($data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($attachment, new AttachmentTransformer($this->parameters), 'attachments'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Update the specified resource in storage. + * + * @param AttachmentRequest $request + * @param Attachment $attachment + * + * @return JsonResponse + */ + public function update(AttachmentRequest $request, Attachment $attachment): JsonResponse + { + $data = $request->getAll(); + $this->repository->update($attachment, $data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($attachment, new AttachmentTransformer($this->parameters), 'attachments'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param Request $request + * @param Attachment $attachment + * + * @return JsonResponse + */ + public function upload(Request $request, Attachment $attachment): JsonResponse + { + /** @var AttachmentHelperInterface $helper */ + $helper = app(AttachmentHelperInterface::class); + $body = $request->getContent(); + $helper->saveAttachmentFromApi($attachment, $body); + + return response()->json([], 204); + } + +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/AvailableBudgetController.php b/app/Api/V1/Controllers/AvailableBudgetController.php new file mode 100644 index 0000000000..ee182bb457 --- /dev/null +++ b/app/Api/V1/Controllers/AvailableBudgetController.php @@ -0,0 +1,183 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\AvailableBudgetRequest; +use FireflyIII\Models\AvailableBudget; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Transformers\AvailableBudgetTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * Class AvailableBudgetController + */ +class AvailableBudgetController extends Controller +{ + /** @var CurrencyRepositoryInterface */ + private $currencyRepository; + /** @var BudgetRepositoryInterface */ + private $repository; + + /** + * AccountController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(BudgetRepositoryInterface::class); + $this->currencyRepository = app(CurrencyRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Remove the specified resource from storage. + * + * @param AvailableBudget $availableBudget + * + * @return JsonResponse + */ + public function delete(AvailableBudget $availableBudget): JsonResponse + { + $this->repository->destroyAvailableBudget($availableBudget); + + return response()->json([], 204); + } + + /** + * Display a listing of the resource. + * + * @param Request $request + * + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of available budgets. Count it and split it. + $collection = $this->repository->getAvailableBudgets(); + $count = $collection->count(); + $availableBudgets = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($availableBudgets, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.available_budgets.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($availableBudgets, new AvailableBudgetTransformer($this->parameters), 'available_budgets'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Display the specified resource. + * + * @param Request $request + * @param AvailableBudget $availableBudget + * + * @return JsonResponse + */ + public function show(Request $request, AvailableBudget $availableBudget): JsonResponse + { + + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($availableBudget, new AvailableBudgetTransformer($this->parameters), 'available_budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Store a newly created resource in storage. + * + * @param AvailableBudgetRequest $request + * + * @return JsonResponse + */ + public function store(AvailableBudgetRequest $request): JsonResponse + { + $data = $request->getAll(); + $currency = $this->currencyRepository->findNull($data['transaction_currency_id']); + $availableBudget = $this->repository->setAvailableBudget($currency, $data['start_date'], $data['end_date'], $data['amount']); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($availableBudget, new AvailableBudgetTransformer($this->parameters), 'available_budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Update the specified resource in storage. + * + * @param AvailableBudgetRequest $request + * @param AvailableBudget $availableBudget + * + * @return JsonResponse + */ + public function update(AvailableBudgetRequest $request, AvailableBudget $availableBudget): JsonResponse + { + $data = $request->getAll(); + $this->repository->updateAvailableBudget($availableBudget, $data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($availableBudget, new AvailableBudgetTransformer($this->parameters), 'available_budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/BillController.php b/app/Api/V1/Controllers/BillController.php index 420b01dc16..c06b060df9 100644 --- a/app/Api/V1/Controllers/BillController.php +++ b/app/Api/V1/Controllers/BillController.php @@ -37,7 +37,6 @@ use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; -use Preferences; /** * Class BillController @@ -49,8 +48,6 @@ class BillController extends Controller /** * BillController constructor. - * - * @throws FireflyException */ public function __construct() { @@ -89,7 +86,7 @@ class BillController extends Controller */ public function index(Request $request): JsonResponse { - $pageSize = (int)Preferences::getForUser(auth()->user(), 'listPageSize', 50)->data; + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; $paginator = $this->repository->getPaginator($pageSize); /** @var Collection $bills */ $bills = $paginator->getCollection(); diff --git a/app/Api/V1/Controllers/BudgetController.php b/app/Api/V1/Controllers/BudgetController.php new file mode 100644 index 0000000000..d2a2ba2041 --- /dev/null +++ b/app/Api/V1/Controllers/BudgetController.php @@ -0,0 +1,176 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\BudgetRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Budget; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Transformers\BudgetTransformer; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * Class BudgetController + */ +class BudgetController extends Controller +{ + /** @var BudgetRepositoryInterface */ + private $repository; + + /** + * BudgetController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var BudgetRepositoryInterface repository */ + $this->repository = app(BudgetRepositoryInterface::class); + $this->repository->setUser(auth()->user()); + + return $next($request); + } + ); + } + + /** + * Remove the specified resource from storage. + * + * @param Budget $budget + * + * @return JsonResponse + */ + public function delete(Budget $budget): JsonResponse + { + $this->repository->destroy($budget); + + return response()->json([], 204); + } + + /** + * Display a listing of the resource. + * + * @param Request $request + * + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budgets. Count it and split it. + $collection = $this->repository->getBudgets(); + $count = $collection->count(); + $budgets = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($budgets, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.budgets.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($budgets, new BudgetTransformer($this->parameters), 'budgets'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + + /** + * @param Request $request + * @param Budget $budget + * + * @return JsonResponse + */ + public function show(Request $request, Budget $budget): JsonResponse + { + $manager = new Manager(); + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($budget, new BudgetTransformer($this->parameters), 'budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param BudgetRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(BudgetRequest $request): JsonResponse + { + $budget = $this->repository->store($request->getAll()); + if (null !== $budget) { + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($budget, new BudgetTransformer($this->parameters), 'budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + throw new FireflyException('Could not store new budget.'); // @codeCoverageIgnore + } + + + /** + * @param BudgetRequest $request + * @param Budget $budget + * + * @return JsonResponse + */ + public function update(BudgetRequest $request, Budget $budget): JsonResponse + { + $data = $request->getAll(); + $budget = $this->repository->update($budget, $data); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($budget, new BudgetTransformer($this->parameters), 'budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/BudgetLimitController.php b/app/Api/V1/Controllers/BudgetLimitController.php new file mode 100644 index 0000000000..fe07060205 --- /dev/null +++ b/app/Api/V1/Controllers/BudgetLimitController.php @@ -0,0 +1,232 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use Carbon\Carbon; +use Exception; +use FireflyIII\Api\V1\Requests\AvailableBudgetRequest; +use FireflyIII\Api\V1\Requests\BudgetLimitRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\BudgetLimit; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Transformers\BudgetLimitTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; +use InvalidArgumentException; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; +use Log; +use Throwable; + +/** + * Class BudgetLimitController + */ +class BudgetLimitController extends Controller +{ + ///** @var CurrencyRepositoryInterface */ + //private $currencyRepository; + /** @var BudgetRepositoryInterface */ + private $repository; + + /** + * AccountController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(BudgetRepositoryInterface::class); + //$this->currencyRepository = app(CurrencyRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Remove the specified resource from storage. + * + * @param BudgetLimit $budgetLimit + * + * @return JsonResponse + */ + public function delete(BudgetLimit $budgetLimit): JsonResponse + { + $this->repository->destroyBudgetLimit($budgetLimit); + + return response()->json([], 204); + } + + /** + * Display a listing of the resource. + * + * @param Request $request + * + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // read budget from request + $budgetId = (int)($request->get('budget_id') ?? 0); + $budget = null; + if ($budgetId > 0) { + $budget = $this->repository->findNull($budgetId); + } + // read start date from request + $start = null; + try { + $start = Carbon::createFromFormat('Y-m-d', $request->get('start')); + $this->parameters->set('start', $start->format('Y-m-d')); + } catch (InvalidArgumentException $e) { + Log::debug(sprintf('Could not parse start date "%s": %s', $request->get('start'), $e->getMessage())); + + } + + // read end date from request + $end = null; + try { + $end = Carbon::createFromFormat('Y-m-d', $request->get('end')); + $this->parameters->set('end', $end->format('Y-m-d')); + } catch (InvalidArgumentException $e) { + Log::debug(sprintf('Could not parse end date "%s": %s', $request->get('end'), $e->getMessage())); + } + $this->parameters->set('budget_id', $budgetId); + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budget limits. Count it and split it. + $collection = new Collection; + if (null === $budget) { + $collection = $this->repository->getAllBudgetLimits($start, $end); + } + if (null !== $budget) { + $collection = $this->repository->getBudgetLimits($budget, $start, $end); + } + + $count = $collection->count(); + $budgetLimits = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($budgetLimits, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.budget_limits.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($budgetLimits, new BudgetLimitTransformer($this->parameters), 'budget_limits'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Display the specified resource. + * + * @param Request $request + * @param BudgetLimit $budgetLimit + * + * @return JsonResponse + */ + public function show(Request $request, BudgetLimit $budgetLimit): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($budgetLimit, new BudgetLimitTransformer($this->parameters), 'budget_limits'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Store a newly created resource in storage. + * + * @param BudgetLimitRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(BudgetLimitRequest $request): JsonResponse + { + $data = $request->getAll(); + $budget = $this->repository->findNull($data['budget_id']); + if (null === $budget) { + throw new FireflyException('Unknown budget.'); + } + $data['budget'] = $budget; + $budgetLimit = $this->repository->storeBudgetLimit($data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($budgetLimit, new BudgetLimitTransformer($this->parameters), 'budget_limits'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Update the specified resource in storage. + * + * @param AvailableBudgetRequest $request + * @param BudgetLimit $budgetLimit + * + * @return JsonResponse + */ + public function update(BudgetLimitRequest $request, BudgetLimit $budgetLimit): JsonResponse + { + $data = $request->getAll(); + $budget = $this->repository->findNull($data['budget_id']); + if (null === $budget) { + $budget = $budgetLimit->budget; + } + $data['budget'] = $budget; + $budgetLimit = $this->repository->updateBudgetLimit($budgetLimit, $data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($budgetLimit, new BudgetLimitTransformer($this->parameters), 'budget_limits'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/CategoryController.php b/app/Api/V1/Controllers/CategoryController.php new file mode 100644 index 0000000000..fc33a6fe16 --- /dev/null +++ b/app/Api/V1/Controllers/CategoryController.php @@ -0,0 +1,176 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\CategoryRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Category; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Transformers\CategoryTransformer; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * Class CategoryController + */ +class CategoryController extends Controller +{ + /** @var CategoryRepositoryInterface */ + private $repository; + + /** + * CategoryController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var CategoryRepositoryInterface repository */ + $this->repository = app(CategoryRepositoryInterface::class); + $this->repository->setUser(auth()->user()); + + return $next($request); + } + ); + } + + /** + * Remove the specified resource from storage. + * + * @param Category $category + * + * @return JsonResponse + */ + public function delete(Category $category): JsonResponse + { + $this->repository->destroy($category); + + return response()->json([], 204); + } + + /** + * Display a listing of the resource. + * + * @param Request $request + * + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budgets. Count it and split it. + $collection = $this->repository->getCategories(); + $count = $collection->count(); + $categories = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($categories, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.categories.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($categories, new CategoryTransformer($this->parameters), 'categories'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + + /** + * @param Request $request + * @param Category $category + * + * @return JsonResponse + */ + public function show(Request $request, Category $category): JsonResponse + { + $manager = new Manager(); + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($category, new CategoryTransformer($this->parameters), 'categories'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param CategoryRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(CategoryRequest $request): JsonResponse + { + $category = $this->repository->store($request->getAll()); + if (null !== $category) { + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($category, new CategoryTransformer($this->parameters), 'categories'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + throw new FireflyException('Could not store new category.'); // @codeCoverageIgnore + } + + + /** + * @param CategoryRequest $request + * @param Category $category + * + * @return JsonResponse + */ + public function update(CategoryRequest $request, Category $category): JsonResponse + { + $data = $request->getAll(); + $category = $this->repository->update($category, $data); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($category, new CategoryTransformer($this->parameters), 'categories'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/ConfigurationController.php b/app/Api/V1/Controllers/ConfigurationController.php new file mode 100644 index 0000000000..9bedd0a459 --- /dev/null +++ b/app/Api/V1/Controllers/ConfigurationController.php @@ -0,0 +1,105 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Configuration; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; + +/** + * Class ConfigurationController + */ +class ConfigurationController extends Controller +{ + + /** + * @throws FireflyException + */ + public function index() + { + if (!auth()->user()->hasRole('owner')) { + throw new FireflyException('No access to method.'); // @codeCoverageIgnore + } + $configData = $this->getConfigData(); + + return response()->json(['data' => $configData], 200)->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param Request $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function update(Request $request): JsonResponse + { + if (!auth()->user()->hasRole('owner')) { + throw new FireflyException('No access to method.'); // @codeCoverageIgnore + } + $name = $request->get('name'); + $value = $request->get('value'); + $valid = ['is_demo_site', 'permission_update_check', 'single_user_mode']; + if (!\in_array($name, $valid, true)) { + throw new FireflyException('You cannot edit this configuration value.'); + } + $configValue = ''; + switch ($name) { + case 'is_demo_site': + case 'single_user_mode': + $configValue = $value === 'true'; + break; + case 'permission_update_check': + $configValue = (int)$value >= -1 && (int)$value <= 1 ? (int)$value : -1; + break; + } + app('fireflyconfig')->set($name, $configValue); + $configData = $this->getConfigData(); + + return response()->json(['data' => $configData], 200)->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @return array + */ + private function getConfigData(): array + { + /** @var Configuration $isDemoSite */ + $isDemoSite = app('fireflyconfig')->get('is_demo_site'); + /** @var Configuration $updateCheck */ + $updateCheck = app('fireflyconfig')->get('permission_update_check'); + /** @var Configuration $lastCheck */ + $lastCheck = app('fireflyconfig')->get('last_update_check'); + /** @var Configuration $singleUser */ + $singleUser = app('fireflyconfig')->get('single_user_mode'); + $data = [ + 'is_demo_site' => null === $isDemoSite ? null : $isDemoSite->data, + 'permission_update_check' => null === $updateCheck ? null : (int)$updateCheck->data, + 'last_update_check' => null === $lastCheck ? null : (int)$lastCheck->data, + 'single_user_mode' => null === $singleUser ? null : $singleUser->data, + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php index a212ad3029..3b5b3d23b0 100644 --- a/app/Api/V1/Controllers/Controller.php +++ b/app/Api/V1/Controllers/Controller.php @@ -26,8 +26,6 @@ namespace FireflyIII\Api\V1\Controllers; use Carbon\Carbon; use Carbon\Exceptions\InvalidDateException; -use FireflyConfig; -use FireflyIII\Exceptions\FireflyException; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; diff --git a/app/Api/V1/Controllers/CurrencyController.php b/app/Api/V1/Controllers/CurrencyController.php index 176986afb8..174c4ff261 100644 --- a/app/Api/V1/Controllers/CurrencyController.php +++ b/app/Api/V1/Controllers/CurrencyController.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; -use FireflyIII\Api\V1\Requests\BillRequest; use FireflyIII\Api\V1\Requests\CurrencyRequest; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionCurrency; @@ -39,7 +38,6 @@ use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; -use Preferences; /** * Class CurrencyController @@ -102,7 +100,7 @@ class CurrencyController extends Controller */ public function index(Request $request): JsonResponse { - $pageSize = (int)Preferences::getForUser(auth()->user(), 'listPageSize', 50)->data; + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; $collection = $this->repository->get(); $count = $collection->count(); // slice them: @@ -158,8 +156,8 @@ class CurrencyController extends Controller $currency = $this->repository->store($request->getAll()); if ($request->boolean('default') === true) { - Preferences::set('currencyPreference', $currency->code); - Preferences::mark(); + app('preferences')->set('currencyPreference', $currency->code); + app('preferences')->mark(); } if (null !== $currency) { $manager = new Manager(); @@ -178,7 +176,7 @@ class CurrencyController extends Controller /** - * @param CurrencyRequest $request + * @param CurrencyRequest $request * @param TransactionCurrency $currency * * @return JsonResponse @@ -189,8 +187,8 @@ class CurrencyController extends Controller $currency = $this->repository->update($currency, $data); if ($request->boolean('default') === true) { - Preferences::set('currencyPreference', $currency->code); - Preferences::mark(); + app('preferences')->set('currencyPreference', $currency->code); + app('preferences')->mark(); } $manager = new Manager(); diff --git a/app/Api/V1/Controllers/CurrencyExchangeRateController.php b/app/Api/V1/Controllers/CurrencyExchangeRateController.php new file mode 100644 index 0000000000..54d8672b0e --- /dev/null +++ b/app/Api/V1/Controllers/CurrencyExchangeRateController.php @@ -0,0 +1,115 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Services\Currency\ExchangeRateInterface; +use FireflyIII\Transformers\CurrencyExchangeRateTransformer; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use InvalidArgumentException; +use League\Fractal\Manager; +use League\Fractal\Resource\Item; +use Log; + +/** + * + * Class CurrencyExchangeRateController + */ +class CurrencyExchangeRateController extends Controller +{ + /** @var CurrencyRepositoryInterface */ + private $repository; + + /** + * CurrencyExchangeRateController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + $this->repository = app(CurrencyRepositoryInterface::class); + $this->repository->setUser(auth()->user()); + + return $next($request); + } + ); + + } + + /** + * @param Request $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // currencies + $fromCurrency = $this->repository->findByCodeNull($request->get('from') ?? 'EUR'); + $toCurrency = $this->repository->findByCodeNull($request->get('to') ?? 'USD'); + + if (null === $fromCurrency) { + throw new FireflyException('Unknown source currency.'); + } + if (null === $toCurrency) { + throw new FireflyException('Unknown destination currency.'); + } + + $dateObj = new Carbon; + try { + $dateObj = Carbon::createFromFormat('Y-m-d', $request->get('date') ?? date('Y-m-d')); + } catch (InvalidArgumentException $e) { + Log::debug($e->getMessage()); + } + + + $this->parameters->set('from', $fromCurrency->code); + $this->parameters->set('to', $toCurrency->code); + $this->parameters->set('date', $dateObj->format('Y-m-d')); + + // get the exchange rate. + $rate = $this->repository->getExchangeRate($fromCurrency, $toCurrency, $dateObj); + if (null === $rate) { + // create service: + /** @var ExchangeRateInterface $service */ + $service = app(ExchangeRateInterface::class); + $service->setUser(auth()->user()); + + // get rate: + $rate = $service->getRate($fromCurrency, $toCurrency, $dateObj); + } + + $resource = new Item($rate, new CurrencyExchangeRateTransformer($this->parameters), 'currency_exchange_rates'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/JournalLinkController.php b/app/Api/V1/Controllers/JournalLinkController.php new file mode 100644 index 0000000000..0dde9742be --- /dev/null +++ b/app/Api/V1/Controllers/JournalLinkController.php @@ -0,0 +1,207 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\JournalLinkRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\TransactionJournalLink; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; +use FireflyIII\Transformers\JournalLinkTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +class JournalLinkController extends Controller +{ + /** @var JournalRepositoryInterface */ + private $journalRepository; + /** @var LinkTypeRepositoryInterface */ + private $repository; + + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + $this->repository = app(LinkTypeRepositoryInterface::class); + $this->journalRepository = app(JournalRepositoryInterface::class); + + $this->repository->setUser($user); + $this->journalRepository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param TransactionJournalLink $link + * + * @return JsonResponse + */ + public function delete(TransactionJournalLink $link): JsonResponse + { + $this->repository->destroyLink($link); + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // read type from URI + $name = $request->get('name') ?? null; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + $linkType = $this->repository->findByName($name); + + // get list of accounts. Count it and split it. + $collection = $this->repository->getJournalLinks($linkType); + $count = $collection->count(); + $journalLinks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($journalLinks, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.journal_links.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($journalLinks, new JournalLinkTransformer($this->parameters), 'journal_links'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * List single resource. + * + * @param Request $request + * @param TransactionJournalLink $journalLink + * + * @return JsonResponse + */ + public function show(Request $request, TransactionJournalLink $journalLink): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($journalLink, new JournalLinkTransformer($this->parameters), 'journal_links'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * Store new object. + * + * @param JournalLinkRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(JournalLinkRequest $request): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $data = $request->getAll(); + $inward = $this->journalRepository->findNull($data['inward_id'] ?? 0); + $outward = $this->journalRepository->findNull($data['outward_id'] ?? 0); + if (null === $inward || null === $outward) { + throw new FireflyException('Source or destination is NULL.'); + } + $data['direction'] = 'inward'; + + $journalLink = $this->repository->storeLink($data, $inward, $outward); + + $resource = new Item($journalLink, new JournalLinkTransformer($this->parameters), 'journal_links'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * @param JournalLinkRequest $request + * @param TransactionJournalLink $journalLink + * + * @return JsonResponse + * @throws FireflyException + */ + public function update(JournalLinkRequest $request, TransactionJournalLink $journalLink): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + + $data = $request->getAll(); + $data['inward'] = $this->journalRepository->findNull($data['inward_id'] ?? 0); + $data['outward'] = $this->journalRepository->findNull($data['outward_id'] ?? 0); + if (null === $data['inward'] || null === $data['outward']) { + throw new FireflyException('Source or destination is NULL.'); + } + $data['direction'] = 'inward'; + $journalLink = $this->repository->updateLink($journalLink, $data); + + $resource = new Item($journalLink, new JournalLinkTransformer($this->parameters), 'journal_links'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/LinkTypeController.php b/app/Api/V1/Controllers/LinkTypeController.php new file mode 100644 index 0000000000..31215cd5a3 --- /dev/null +++ b/app/Api/V1/Controllers/LinkTypeController.php @@ -0,0 +1,197 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\LinkTypeRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\LinkType; +use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\Transformers\LinkTypeTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * + * Class LinkTypeController + */ +class LinkTypeController extends Controller +{ + /** @var LinkTypeRepositoryInterface */ + private $repository; + + /** @var UserRepositoryInterface */ + private $userRepository; + + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(LinkTypeRepositoryInterface::class); + $this->userRepository = app(UserRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param LinkType $linkType + * + * @return JsonResponse + * @throws FireflyException + */ + public function delete(LinkType $linkType): JsonResponse + { + if ($linkType->editable === false) { + throw new FireflyException(sprintf('You cannot delete this link type (#%d, "%s")', $linkType->id, $linkType->name)); + } + $this->repository->destroy($linkType, null); + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of accounts. Count it and split it. + $collection = $this->repository->get(); + $count = $collection->count(); + $linkTypes = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($linkTypes, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.link_types.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($linkTypes, new LinkTypeTransformer($this->parameters), 'link_types'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * List single resource. + * + * @param Request $request + * @param LinkType $linkType + * + * @return JsonResponse + */ + public function show(Request $request, LinkType $linkType): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($linkType, new LinkTypeTransformer($this->parameters), 'link_types'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * Store new object. + * + * @param LinkTypeRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(LinkTypeRequest $request): JsonResponse + { + if (!$this->userRepository->hasRole(auth()->user(), 'owner')) { + throw new FireflyException('You need the "owner"-role to do this.'); + } + $data = $request->getAll(); + // if currency ID is 0, find the currency by the code: + $linkType = $this->repository->store($data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($linkType, new LinkTypeTransformer($this->parameters), 'link_types'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * @param LinkTypeRequest $request + * @param LinkType $linkType + * + * @return JsonResponse + * @throws FireflyException + */ + public function update(LinkTypeRequest $request, LinkType $linkType): JsonResponse + { + if ($linkType->editable === false) { + throw new FireflyException(sprintf('You cannot edit this link type (#%d, "%s")', $linkType->id, $linkType->name)); + } + if (!$this->userRepository->hasRole(auth()->user(), 'owner')) { + throw new FireflyException('You need the "owner"-role to do this.'); + } + + $data = $request->getAll(); + $this->repository->update($linkType, $data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($linkType, new LinkTypeTransformer($this->parameters), 'link_types'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/PiggyBankController.php b/app/Api/V1/Controllers/PiggyBankController.php new file mode 100644 index 0000000000..2dd1f0f8a6 --- /dev/null +++ b/app/Api/V1/Controllers/PiggyBankController.php @@ -0,0 +1,180 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\PiggyBankRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\PiggyBank; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use FireflyIII\Transformers\PiggyBankTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * TODO order up and down. + * Class PiggyBankController + */ +class PiggyBankController extends Controller +{ + + /** @var PiggyBankRepositoryInterface */ + private $repository; + + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + $this->repository = app(PiggyBankRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param PiggyBank $piggyBank + * + * @return JsonResponse + */ + public function delete(PiggyBank $piggyBank): JsonResponse + { + $this->repository->destroy($piggyBank); + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budgets. Count it and split it. + $collection = $this->repository->getPiggyBanks(); + $count = $collection->count(); + $piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.piggy_banks.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($piggyBanks, new PiggyBankTransformer($this->parameters), 'piggy_banks'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * List single resource. + * + * @param Request $request + * @param PiggyBank $piggyBank + * + * @return JsonResponse + */ + public function show(Request $request, PiggyBank $piggyBank): JsonResponse + { + $manager = new Manager(); + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($piggyBank, new PiggyBankTransformer($this->parameters), 'piggy_banks'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * Store new object. + * + * @param PiggyBankRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(PiggyBankRequest $request): JsonResponse + { + $piggyBank = $this->repository->store($request->getAll()); + if (null !== $piggyBank) { + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($piggyBank, new PiggyBankTransformer($this->parameters), 'piggy_banks'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + throw new FireflyException('Could not store new piggy bank.'); // @codeCoverageIgnore + + } + + /** + * @param PiggyBankRequest $request + * @param PiggyBank $piggyBank + * + * @return JsonResponse + */ + public function update(PiggyBankRequest $request, PiggyBank $piggyBank): JsonResponse + { + $piggyBank = $this->repository->update($piggyBank, $request->getAll()); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($piggyBank, new PiggyBankTransformer($this->parameters), 'piggy_banks'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/PreferenceController.php b/app/Api/V1/Controllers/PreferenceController.php new file mode 100644 index 0000000000..3efe8fa35a --- /dev/null +++ b/app/Api/V1/Controllers/PreferenceController.php @@ -0,0 +1,157 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\PreferenceRequest; +use FireflyIII\Models\Preference; +use FireflyIII\Transformers\PreferenceTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Collection; +use League\Fractal\Manager; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; +use Preferences; + +/** + * + * Class PreferenceController + */ +class PreferenceController extends Controller +{ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + // todo add local repositories. + return $next($request); + } + ); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + /** @var User $user */ + $user = auth()->user(); + $available = [ + 'language', 'customFiscalYear', 'fiscalYearStart', 'currencyPreference', + 'transaction_journal_optional_fields', 'frontPageAccounts', 'viewRange', + 'listPageSize, twoFactorAuthEnabled', + ]; + $preferences = new Collection; + foreach ($available as $name) { + $pref = Preferences::getForUser($user, $name); + if (null !== $pref) { + $preferences->push($pref); + } + } + + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($preferences, new PreferenceTransformer($this->parameters), 'preferences'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + + } + + /** + * List single resource. + * + * @param Request $request + * @param Preference $preference + * + * @return JsonResponse + */ + public function show(Request $request, Preference $preference): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($preference, new PreferenceTransformer($this->parameters), 'preferences'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * @param PreferenceRequest $request + * @param Preference $preference + * + * @return JsonResponse + */ + public function update(PreferenceRequest $request, Preference $preference): JsonResponse + { + + $data = $request->getAll(); + $newValue = $data['data']; + switch ($preference->name) { + default: + break; + case 'transaction_journal_optional_fields': + case 'frontPageAccounts': + $newValue = explode(',', $data['data']); + break; + case 'listPageSize': + $newValue = (int)$data['data']; + break; + case 'customFiscalYear': + case 'twoFactorAuthEnabled': + $newValue = (int)$data['data'] === 1; + break; + } + $result = Preferences::set($preference->name, $newValue); + + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($result, new PreferenceTransformer($this->parameters), 'preferences'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/RecurrenceController.php b/app/Api/V1/Controllers/RecurrenceController.php new file mode 100644 index 0000000000..a5ec34e9c6 --- /dev/null +++ b/app/Api/V1/Controllers/RecurrenceController.php @@ -0,0 +1,179 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\RecurrenceRequest; +use FireflyIII\Models\Recurrence; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Transformers\RecurrenceTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * + * Class RecurrenceController + */ +class RecurrenceController extends Controller +{ + /** @var RecurringRepositoryInterface */ + private $repository; + + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + /** @var RecurringRepositoryInterface repository */ + $this->repository = app(RecurringRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param Recurrence $recurrence + * + * @return JsonResponse + */ + public function delete(Recurrence $recurrence): JsonResponse + { + $this->repository->destroy($recurrence); + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budgets. Count it and split it. + $collection = $this->repository->getAll(); + $count = $collection->count(); + $piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.recurrences.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($piggyBanks, new RecurrenceTransformer($this->parameters), 'recurrences'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * List single resource. + * + * @param Request $request + * @param Recurrence $recurrence + * + * @return JsonResponse + */ + public function show(Request $request, Recurrence $recurrence): JsonResponse + { + $manager = new Manager(); + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($recurrence, new RecurrenceTransformer($this->parameters), 'recurrences'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + + } + + /** + * Store new object. + * + * @param RecurrenceRequest $request + * + * @return JsonResponse + */ + public function store(RecurrenceRequest $request): JsonResponse + { + $recurrence = $this->repository->store($request->getAll()); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($recurrence, new RecurrenceTransformer($this->parameters), 'recurrences'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param RecurrenceRequest $request + * @param Recurrence $recurrence + * + * @return JsonResponse + */ + public function update(RecurrenceRequest $request, Recurrence $recurrence): JsonResponse + { + $data = $request->getAll(); + + // + + $category = $this->repository->update($recurrence, $data); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($category, new RecurrenceTransformer($this->parameters), 'recurrences'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/RuleController.php b/app/Api/V1/Controllers/RuleController.php new file mode 100644 index 0000000000..bd581dcfb2 --- /dev/null +++ b/app/Api/V1/Controllers/RuleController.php @@ -0,0 +1,173 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\RuleRequest; +use FireflyIII\Models\Rule; +use FireflyIII\Repositories\Rule\RuleRepositoryInterface; +use FireflyIII\Transformers\RuleTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * Class RuleController + */ +class RuleController extends Controller +{ + /** @var RuleRepositoryInterface */ + private $ruleRepository; + + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + $this->ruleRepository = app(RuleRepositoryInterface::class); + $this->ruleRepository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param Rule $rule + * + * @return JsonResponse + */ + public function delete(Rule $rule): JsonResponse + { + $this->ruleRepository->destroy($rule); + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budgets. Count it and split it. + $collection = $this->ruleRepository->getAll(); + $count = $collection->count(); + $rules = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($rules, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.piggy_banks.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($rules, new RuleTransformer($this->parameters), 'rules'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * List single resource. + * + * @param Request $request + * @param Rule $rule + * + * @return JsonResponse + */ + public function show(Request $request, Rule $rule): JsonResponse + { + $manager = new Manager(); + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($rule, new RuleTransformer($this->parameters), 'rules'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * Store new object. + * + * @param Request $request + * + * @return JsonResponse + */ + public function store(RuleRequest $request): JsonResponse + { + $rule = $this->ruleRepository->store($request->getAll()); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($rule, new RuleTransformer($this->parameters), 'rules'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param RuleRequest $request + * @param Rule $rule + * + * @return JsonResponse + */ + public function update(RuleRequest $request, Rule $rule): JsonResponse + { + $rule = $this->ruleRepository->update($rule, $request->getAll()); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($rule, new RuleTransformer($this->parameters), 'rules'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/RuleGroupController.php b/app/Api/V1/Controllers/RuleGroupController.php new file mode 100644 index 0000000000..9085594c69 --- /dev/null +++ b/app/Api/V1/Controllers/RuleGroupController.php @@ -0,0 +1,171 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\RuleGroupRequest; +use FireflyIII\Models\RuleGroup; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use FireflyIII\Transformers\RuleGroupTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + + +class RuleGroupController extends Controller +{ + /** @var RuleGroupRepositoryInterface */ + private $ruleGroupRepository; + + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + $this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class); + $this->ruleGroupRepository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param string $object + * + * @return JsonResponse + */ + public function delete(RuleGroup $ruleGroup): JsonResponse + { + $this->ruleGroupRepository->destroy($ruleGroup, null); + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budgets. Count it and split it. + $collection = $this->ruleGroupRepository->get(); + $count = $collection->count(); + $ruleGroups = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($ruleGroups, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.rule_groups.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($ruleGroups, new RuleGroupTransformer($this->parameters), 'rule_groups'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * List single resource. + * + * @param Request $request + * @param RuleGroup $ruleGroup + * + * @return JsonResponse + */ + public function show(Request $request, RuleGroup $ruleGroup): JsonResponse + { + $manager = new Manager(); + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($ruleGroup, new RuleGroupTransformer($this->parameters), 'rule_groups'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * Store new object. + * + * @param RuleGroupRequest $request + * + * @return JsonResponse + */ + public function store(RuleGroupRequest $request): JsonResponse + { + $ruleGroup = $this->ruleGroupRepository->store($request->getAll()); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($ruleGroup, new RuleGroupTransformer($this->parameters), 'rule_groups'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function update(RuleGroupRequest $request, RuleGroup $ruleGroup): JsonResponse + { + $ruleGroup = $this->ruleGroupRepository->update($ruleGroup, $request->getAll()); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($ruleGroup, new RuleGroupTransformer($this->parameters), 'rule_groups'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/TransactionController.php b/app/Api/V1/Controllers/TransactionController.php index f0dbc2b246..a03b594f42 100644 --- a/app/Api/V1/Controllers/TransactionController.php +++ b/app/Api/V1/Controllers/TransactionController.php @@ -40,7 +40,6 @@ use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Serializer\JsonApiSerializer; use Log; -use Preferences; /** * Class TransactionController @@ -92,7 +91,7 @@ class TransactionController extends Controller */ public function index(Request $request) { - $pageSize = (int)Preferences::getForUser(auth()->user(), 'listPageSize', 50)->data; + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; // read type from URI $type = $request->get('type') ?? 'default'; @@ -136,17 +135,19 @@ class TransactionController extends Controller /** * @param Request $request * @param Transaction $transaction + * @param string $include * * @return \Illuminate\Http\JsonResponse */ - public function show(Request $request, Transaction $transaction) + public function show(Request $request, Transaction $transaction, string $include = null) { $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); // add include parameter: - $include = $request->get('include') ?? ''; + $include = $include ?? ''; + $include = $request->get('include') ?? $include; $manager->parseIncludes($include); // collect transactions using the journal collector diff --git a/app/Api/V1/Controllers/UserController.php b/app/Api/V1/Controllers/UserController.php index f2759f9e71..e8466818f9 100644 --- a/app/Api/V1/Controllers/UserController.php +++ b/app/Api/V1/Controllers/UserController.php @@ -36,7 +36,6 @@ use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; -use Preferences; /** @@ -94,7 +93,7 @@ class UserController extends Controller public function index(Request $request) { // user preferences - $pageSize = (int)Preferences::getForUser(auth()->user(), 'listPageSize', 50)->data; + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; // make manager $manager = new Manager(); diff --git a/app/Api/V1/Requests/AttachmentRequest.php b/app/Api/V1/Requests/AttachmentRequest.php new file mode 100644 index 0000000000..30da4d2683 --- /dev/null +++ b/app/Api/V1/Requests/AttachmentRequest.php @@ -0,0 +1,92 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\Bill; +use FireflyIII\Models\ImportJob; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Rules\IsBase64; +use FireflyIII\Rules\IsValidAttachmentModel; + +/** + * Class AttachmentRequest + */ +class AttachmentRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'filename' => $this->string('filename'), + 'title' => $this->string('title'), + 'notes' => $this->string('notes'), + 'model' => $this->string('model'), + 'model_id' => $this->integer('model_id'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $models = implode( + ',', [ + Bill::class, + ImportJob::class, + TransactionJournal::class, + ] + ); + $model = $this->string('model'); + $rules = [ + 'filename' => 'required|between:1,255', + 'title' => 'between:1,255', + 'notes' => 'between:1,65000', + 'model' => sprintf('required|in:%s', $models), + 'model_id' => ['required', 'numeric', new IsValidAttachmentModel($model)], + ]; + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + unset($rules['model'], $rules['model_id']); + $rules['filename'] = 'between:1,255'; + break; + } + + return $rules; + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/AvailableBudgetRequest.php b/app/Api/V1/Requests/AvailableBudgetRequest.php new file mode 100644 index 0000000000..5631f5acc1 --- /dev/null +++ b/app/Api/V1/Requests/AvailableBudgetRequest.php @@ -0,0 +1,69 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +/** + * Class AvailableBudgetRequest + */ +class AvailableBudgetRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'transaction_currency_id' => $this->integer('transaction_currency_id'), + 'amount' => $this->string('amount'), + 'start_date' => $this->date('start_date'), + 'end_date' => $this->date('end_date'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'transaction_currency_id' => 'required|numeric|exists:transaction_currencies,id', + 'amount' => 'required|numeric|more:0', + 'start_date' => 'required|date|before:end_date', + 'end_date' => 'required|date|after:start_date', + ]; + + return $rules; + } + + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/BillRequest.php b/app/Api/V1/Requests/BillRequest.php index 9f68a656cd..f58c977737 100644 --- a/app/Api/V1/Requests/BillRequest.php +++ b/app/Api/V1/Requests/BillRequest.php @@ -86,8 +86,8 @@ class BillRequest extends Request break; case 'PUT': case 'PATCH': - $bill = $this->route()->parameter('bill'); - $rules['name'] .= ',' . $bill->id; + $bill = $this->route()->parameter('bill'); + $rules['name'] .= ',' . $bill->id; break; } diff --git a/app/Api/V1/Requests/BudgetLimitRequest.php b/app/Api/V1/Requests/BudgetLimitRequest.php new file mode 100644 index 0000000000..8ede47cfce --- /dev/null +++ b/app/Api/V1/Requests/BudgetLimitRequest.php @@ -0,0 +1,77 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + + +/** + * Class BudgetLimitRequest + */ +class BudgetLimitRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'budget_id' => $this->integer('budget_id'), + 'start_date' => $this->date('start_date'), + 'end_date' => $this->date('end_date'), + 'amount' => $this->string('amount'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'budget_id' => 'required|exists:budgets,id|belongsToUser:budgets,id', + 'start_date' => 'required|before:end_date|date', + 'end_date' => 'required|after:start_date|date', + 'amount' => 'required|more:0', + ]; + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + $rules['budget_id'] = 'required|exists:budgets,id|belongsToUser:budgets,id'; + break; + } + + return $rules; + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/BudgetRequest.php b/app/Api/V1/Requests/BudgetRequest.php new file mode 100644 index 0000000000..e90866d505 --- /dev/null +++ b/app/Api/V1/Requests/BudgetRequest.php @@ -0,0 +1,76 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\Budget; + +/** + * Class BudgetRequest + */ +class BudgetRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'name' => $this->string('name'), + 'active' => $this->boolean('active'), + 'order' => 0, + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'name' => 'required|between:1,100|uniqueObjectForUser:budgets,name', + 'active' => 'required|boolean', + ]; + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + /** @var Budget $budget */ + $budget = $this->route()->parameter('budget'); + $rules['name'] = sprintf('required|between:1,100|uniqueObjectForUser:budgets,name,%d', $budget->id); + break; + } + + return $rules; + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/CategoryRequest.php b/app/Api/V1/Requests/CategoryRequest.php new file mode 100644 index 0000000000..b910674e54 --- /dev/null +++ b/app/Api/V1/Requests/CategoryRequest.php @@ -0,0 +1,75 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\Category; + +/** + * Class CategoryRequest + */ +class CategoryRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'name' => $this->string('name'), + 'active' => $this->boolean('active'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'name' => 'required|between:1,100|uniqueObjectForUser:categories,name', + 'active' => 'required|boolean', + ]; + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + /** @var Category $category */ + $category = $this->route()->parameter('category'); + $rules['name'] = sprintf('required|between:1,100|uniqueObjectForUser:categories,name,%d', $category->id); + break; + } + + return $rules; + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/CurrencyRequest.php b/app/Api/V1/Requests/CurrencyRequest.php index 8e2edfeda9..6c63c83ddc 100644 --- a/app/Api/V1/Requests/CurrencyRequest.php +++ b/app/Api/V1/Requests/CurrencyRequest.php @@ -41,7 +41,7 @@ class CurrencyRequest extends Request /** * @return array */ - public function getAll() + public function getAll(): array { return [ 'name' => $this->string('name'), diff --git a/app/Api/V1/Requests/JournalLinkRequest.php b/app/Api/V1/Requests/JournalLinkRequest.php new file mode 100644 index 0000000000..e52d5b7484 --- /dev/null +++ b/app/Api/V1/Requests/JournalLinkRequest.php @@ -0,0 +1,68 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + + +/** + * + * Class JournalLinkRequest + */ +class JournalLinkRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'link_type_id' => $this->integer('link_type_id'), + 'inward_id' => $this->integer('inward_id'), + 'outward_id' => $this->integer('outward_id'), + 'notes' => $this->string('notes'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'link_type_id' => 'required|exists:link_types,id', + 'inward_id' => 'required|belongsToUser:transaction_journals,id', + 'outward_id' => 'required|belongsToUser:transaction_journals,id', + 'notes' => 'between:0,65000', + ]; + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/LinkTypeRequest.php b/app/Api/V1/Requests/LinkTypeRequest.php new file mode 100644 index 0000000000..c44dbcb145 --- /dev/null +++ b/app/Api/V1/Requests/LinkTypeRequest.php @@ -0,0 +1,86 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\LinkType; +use Illuminate\Validation\Rule; + +/** + * + * Class LinkTypeRequest + */ +class LinkTypeRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'name' => $this->string('name'), + 'outward' => $this->string('outward'), + 'inward' => $this->string('inward'), + ]; + + + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'name' => 'required|unique:link_types,name|min:1', + 'outward' => 'required|unique:link_types,outward|min:1|different:inward', + 'inward' => 'required|unique:link_types,inward|min:1|different:outward', + ]; + // Rule::unique('users')->ignore($user->id), + + + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + /** @var LinkType $linkType */ + $linkType = $this->route()->parameter('linkType'); + $rules['name'] = ['required', Rule::unique('link_types', 'name')->ignore($linkType->id), 'min:1']; + $rules['outward'] = ['required', 'different:inward', Rule::unique('link_types', 'outward')->ignore($linkType->id), 'min:1']; + $rules['inward'] = ['required', 'different:outward', Rule::unique('link_types', 'inward')->ignore($linkType->id), 'min:1']; + break; + } + + return $rules; + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/PiggyBankRequest.php b/app/Api/V1/Requests/PiggyBankRequest.php new file mode 100644 index 0000000000..15a41bda01 --- /dev/null +++ b/app/Api/V1/Requests/PiggyBankRequest.php @@ -0,0 +1,90 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\PiggyBank; +use FireflyIII\Rules\IsAssetAccountId; + +/** + * + * Class PiggyBankRequest + */ +class PiggyBankRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'name' => $this->string('name'), + 'account_id' => $this->integer('account_id'), + 'targetamount' => $this->string('target_amount'), + 'current_amount' => $this->string('current_amount'), + 'start_date' => $this->date('start_date'), + 'target_date' => $this->date('target_date'), + 'note' => $this->string('notes'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'name' => 'required|between:1,255|uniquePiggyBankForUser', + 'account_id' => ['required', 'belongsToUser:accounts', new IsAssetAccountId], + 'target_amount' => 'required|numeric|more:0', + 'current_amount' => 'numeric|more:0|lte:target_amount', + 'start_date' => 'date|nullable', + 'target_date' => 'date|nullable', + 'notes' => 'max:65000', + ]; + + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + /** @var PiggyBank $piggyBank */ + $piggyBank = $this->route()->parameter('piggyBank'); + $rules['name'] = 'required|between:1,255|uniquePiggyBankForUser:' . $piggyBank->id; + break; + } + + + return $rules; + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/PreferenceRequest.php b/app/Api/V1/Requests/PreferenceRequest.php new file mode 100644 index 0000000000..38c3a39e11 --- /dev/null +++ b/app/Api/V1/Requests/PreferenceRequest.php @@ -0,0 +1,57 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +/** + * + * Class PreferenceRequest + */ +class PreferenceRequest extends Request +{ + + + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + public function getAll(): array + { + return [ + 'data' => $this->get('data'), + ]; + } + + public function rules(): array + { + return [ + 'data' => 'required|between:1,65000', + ]; + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/RecurrenceRequest.php b/app/Api/V1/Requests/RecurrenceRequest.php new file mode 100644 index 0000000000..3aa948722a --- /dev/null +++ b/app/Api/V1/Requests/RecurrenceRequest.php @@ -0,0 +1,482 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use Carbon\Carbon; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Rules\BelongsUser; +use Illuminate\Validation\Validator; +use InvalidArgumentException; +use Log; + +/** + * Class RecurrenceRequest + */ +class RecurrenceRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + $return = [ + 'recurrence' => [ + 'type' => $this->string('type'), + 'title' => $this->string('title'), + 'description' => $this->string('description'), + 'first_date' => $this->date('first_date'), + 'repeat_until' => $this->date('repeat_until'), + 'repetitions' => $this->integer('nr_of_repetitions'), + 'apply_rules' => $this->boolean('apply_rules'), + 'active' => $this->boolean('active'), + ], + 'meta' => [ + 'piggy_bank_id' => $this->integer('piggy_bank_id'), + 'piggy_bank_name' => $this->string('piggy_bank_name'), + 'tags' => explode(',', $this->string('tags')), + ], + 'transactions' => [], + 'repetitions' => [], + ]; + + // repetition data: + /** @var array $repetitions */ + $repetitions = $this->get('repetitions'); + /** @var array $repetition */ + foreach ($repetitions as $repetition) { + $return['repetitions'][] = [ + 'type' => $repetition['type'], + 'moment' => $repetition['moment'], + 'skip' => (int)$repetition['skip'], + 'weekend' => (int)$repetition['weekend'], + ]; + } + // transaction data: + /** @var array $transactions */ + $transactions = $this->get('transactions'); + /** @var array $transaction */ + foreach ($transactions as $transaction) { + $return['transactions'][] = [ + 'amount' => $transaction['amount'], + + 'currency_id' => isset($transaction['currency_id']) ? (int)$transaction['currency_id'] : null, + 'currency_code' => $transaction['currency_code'] ?? null, + + 'foreign_amount' => $transaction['foreign_amount'] ?? null, + 'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? (int)$transaction['foreign_currency_id'] : null, + 'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null, + + 'budget_id' => isset($transaction['budget_id']) ? (int)$transaction['budget_id'] : null, + 'budget_name' => $transaction['budget_name'] ?? null, + 'category_id' => isset($transaction['category_id']) ? (int)$transaction['category_id'] : null, + 'category_name' => $transaction['category_name'] ?? null, + + 'source_id' => isset($transaction['source_id']) ? (int)$transaction['source_id'] : null, + 'source_name' => isset($transaction['source_name']) ? (string)$transaction['source_name'] : null, + 'destination_id' => isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null, + 'destination_name' => isset($transaction['destination_name']) ? (string)$transaction['destination_name'] : null, + + 'description' => $transaction['description'], + ]; + } + + return $return; + } + + /** + * @return array + */ + public function rules(): array + { + $today = new Carbon; + $today->addDay(); + + return [ + 'type' => 'required|in:withdrawal,transfer,deposit', + 'title' => 'required|between:1,255|uniqueObjectForUser:recurrences,title', + 'description' => 'between:1,65000', + 'first_date' => sprintf('required|date|after:%s', $today->format('Y-m-d')), + 'repeat_until' => sprintf('date|after:%s', $today->format('Y-m-d')), + 'nr_of_repetitions' => 'numeric|between:1,31', + 'apply_rules' => 'required|boolean', + 'active' => 'required|boolean', + + // rules for meta values: + 'tags' => 'between:1,64000', + 'piggy_bank_id' => 'numeric', + + // rules for repetitions. + 'repetitions.*.type' => 'required|in:daily,weekly,ndom,monthly,yearly', + 'repetitions.*.moment' => 'between:0,10', + 'repetitions.*.skip' => 'required|numeric|between:0,31', + 'repetitions.*.weekend' => 'required|numeric|min:1|max:4', + + // rules for transactions. + 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|required_without:transactions.*.currency_code', + 'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code|required_without:transactions.*.currency_id', + 'transactions.*.foreign_amount' => 'numeric|more:0', + 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id', + 'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser], + 'transactions.*.category_name' => 'between:1,255|nullable', + 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.source_name' => 'between:1,255|nullable', + 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.destination_name' => 'between:1,255|nullable', + 'transactions.*.amount' => 'required|numeric|more:0', + 'transactions.*.description' => 'required|between:1,255', + ]; + } + + /** + * Configure the validator instance. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + $this->atLeastOneTransaction($validator); + $this->atLeastOneRepetition($validator); + $this->validRepeatsUntil($validator); + $this->validRepetitionMoment($validator); + $this->foreignCurrencyInformation($validator); + $this->validateAccountInformation($validator); + } + ); + } + + /** + * Throws an error when this asset account is invalid. + * + * @noinspection MoreThanThreeArgumentsInspection + * + * @param Validator $validator + * @param int|null $accountId + * @param null|string $accountName + * @param string $idField + * @param string $nameField + * + * @return null|Account + */ + protected function assetAccountExists(Validator $validator, ?int $accountId, ?string $accountName, string $idField, string $nameField): ?Account + { + $accountId = (int)$accountId; + $accountName = (string)$accountName; + // both empty? hard exit. + if ($accountId < 1 && '' === $accountName) { + $validator->errors()->add($idField, trans('validation.filled', ['attribute' => $idField])); + + return null; + } + // ID belongs to user and is asset account: + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $repository->setUser(auth()->user()); + $set = $repository->getAccountsById([$accountId]); + Log::debug(sprintf('Count of accounts found by ID %d is: %d', $accountId, $set->count())); + if ($set->count() === 1) { + /** @var Account $first */ + $first = $set->first(); + if ($first->accountType->type !== AccountType::ASSET) { + $validator->errors()->add($idField, trans('validation.belongs_user')); + + return null; + } + + // we ignore the account name at this point. + return $first; + } + + $account = $repository->findByName($accountName, [AccountType::ASSET]); + if (null === $account) { + $validator->errors()->add($nameField, trans('validation.belongs_user')); + + return null; + } + + return $account; + } + + /** + * Adds an error to the validator when there are no repetitions in the array of data. + * + * @param Validator $validator + */ + protected function atLeastOneRepetition(Validator $validator): void + { + $data = $validator->getData(); + $repetitions = $data['repetitions'] ?? []; + // need at least one transaction + if (\count($repetitions) === 0) { + $validator->errors()->add('description', trans('validation.at_least_one_repetition')); + } + } + + /** + * Adds an error to the validator when there are no transactions in the array of data. + * + * @param Validator $validator + */ + protected function atLeastOneTransaction(Validator $validator): void + { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + // need at least one transaction + if (\count($transactions) === 0) { + $validator->errors()->add('description', trans('validation.at_least_one_transaction')); + } + } + + /** + * TODO can be made a rule? + * If the transactions contain foreign amounts, there must also be foreign currency information. + * + * @param Validator $validator + */ + protected function foreignCurrencyInformation(Validator $validator): void + { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + foreach ($transactions as $index => $transaction) { + // must have currency info. + if (isset($transaction['foreign_amount']) + && !(isset($transaction['foreign_currency_id']) + || isset($transaction['foreign_currency_code']))) { + $validator->errors()->add( + 'transactions.' . $index . '.foreign_amount', + trans('validation.require_currency_info') + ); + } + } + } + + /** + * Throws an error when the given opposing account (of type $type) is invalid. + * Empty data is allowed, system will default to cash. + * + * @noinspection MoreThanThreeArgumentsInspection + * + * @param Validator $validator + * @param string $type + * @param int|null $accountId + * @param null|string $accountName + * @param string $idField + * + * @return null|Account + */ + protected function opposingAccountExists(Validator $validator, string $type, ?int $accountId, ?string $accountName, string $idField): ?Account + { + $accountId = (int)$accountId; + $accountName = (string)$accountName; + // both empty? done! + if ($accountId < 1 && \strlen($accountName) === 0) { + return null; + } + if ($accountId !== 0) { + // ID belongs to user and is $type account: + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $repository->setUser(auth()->user()); + $set = $repository->getAccountsById([$accountId]); + if ($set->count() === 1) { + /** @var Account $first */ + $first = $set->first(); + if ($first->accountType->type !== $type) { + $validator->errors()->add($idField, trans('validation.belongs_user')); + + return null; + } + + // we ignore the account name at this point. + return $first; + } + } + + // not having an opposing account by this name is NOT a problem. + return null; + } + + /** + * TODO can be a rule? + * + * Validates the given account information. Switches on given transaction type. + * + * @param Validator $validator + */ + protected function validateAccountInformation(Validator $validator): void + { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + $idField = 'description'; + $transactionType = $data['type'] ?? 'false'; + foreach ($transactions as $index => $transaction) { + $sourceId = isset($transaction['source_id']) ? (int)$transaction['source_id'] : null; + $sourceName = $transaction['source_name'] ?? null; + $destinationId = isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null; + $destinationName = $transaction['destination_name'] ?? null; + $sourceAccount = null; + $destinationAccount = null; + switch ($transactionType) { + case 'withdrawal': + $idField = 'transactions.' . $index . '.source_id'; + $nameField = 'transactions.' . $index . '.source_name'; + $sourceAccount = $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField); + $idField = 'transactions.' . $index . '.destination_id'; + $destinationAccount = $this->opposingAccountExists($validator, AccountType::EXPENSE, $destinationId, $destinationName, $idField); + break; + case 'deposit': + $idField = 'transactions.' . $index . '.source_id'; + $sourceAccount = $this->opposingAccountExists($validator, AccountType::REVENUE, $sourceId, $sourceName, $idField); + + $idField = 'transactions.' . $index . '.destination_id'; + $nameField = 'transactions.' . $index . '.destination_name'; + $destinationAccount = $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField); + break; + case 'transfer': + $idField = 'transactions.' . $index . '.source_id'; + $nameField = 'transactions.' . $index . '.source_name'; + $sourceAccount = $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField); + + $idField = 'transactions.' . $index . '.destination_id'; + $nameField = 'transactions.' . $index . '.destination_name'; + $destinationAccount = $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField); + break; + default: + $validator->errors()->add($idField, trans('validation.invalid_account_info')); + + return; + + } + // add some errors in case of same account submitted: + if (null !== $sourceAccount && null !== $destinationAccount && $sourceAccount->id === $destinationAccount->id) { + $validator->errors()->add($idField, trans('validation.source_equals_destination')); + } + } + } + + /** + * @param Validator $validator + */ + private function validRepeatsUntil(Validator $validator): void + { + $data = $validator->getData(); + $repetitions = $data['nr_of_repetitions'] ?? null; + $repeatUntil = $data['repeat_until'] ?? null; + if (null !== $repetitions && null !== $repeatUntil) { + // expect a date OR count: + $validator->errors()->add('repeat_until', trans('validation.require_repeat_until')); + $validator->errors()->add('nr_of_repetitions', trans('validation.require_repeat_until')); + + return; + } + } + + /** + * TODO merge this in a rule somehow. + * + * @param Validator $validator + */ + private function validRepetitionMoment(Validator $validator): void + { + $data = $validator->getData(); + $repetitions = $data['repetitions'] ?? []; + /** + * @var int $index + * @var array $repetition + */ + foreach ($repetitions as $index => $repetition) { + switch ($repetition['type']) { + default: + $validator->errors()->add(sprintf('repetitions.%d.type', $index), trans('validation.valid_recurrence_rep_type')); + + return; + case 'daily': + if ('' !== (string)$repetition['moment']) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + } + + return; + case 'monthly': + $dayOfMonth = (int)$repetition['moment']; + if ($dayOfMonth < 1 || $dayOfMonth > 31) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + } + + return; + case 'ndom': + $parameters = explode(',', $repetition['moment']); + if (\count($parameters) !== 2) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + + return; + } + $nthDay = (int)($parameters[0] ?? 0.0); + $dayOfWeek = (int)($parameters[1] ?? 0.0); + if ($nthDay < 1 || $nthDay > 5) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + + return; + } + if ($dayOfWeek < 1 || $dayOfWeek > 7) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + + return; + } + + return; + case 'weekly': + $dayOfWeek = (int)$repetition['moment']; + if ($dayOfWeek < 1 || $dayOfWeek > 7) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + + return; + } + break; + case 'yearly': + try { + Carbon::createFromFormat('Y-m-d', $repetition['moment']); + } catch (InvalidArgumentException $e) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + + return; + } + } + } + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/RuleGroupRequest.php b/app/Api/V1/Requests/RuleGroupRequest.php new file mode 100644 index 0000000000..a8b88601e4 --- /dev/null +++ b/app/Api/V1/Requests/RuleGroupRequest.php @@ -0,0 +1,79 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\RuleGroup; + + +/** + * + * Class RuleGroupRequest + */ +class RuleGroupRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'title' => $this->string('title'), + 'description' => $this->string('description'), + 'active' => $this->boolean('active'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'title' => 'required|between:1,100|uniqueObjectForUser:rule_groups,title', + 'description' => 'between:1,5000|nullable', + 'active' => 'required|boolean', + ]; + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + /** @var RuleGroup $ruleGroup */ + $ruleGroup = $this->route()->parameter('ruleGroup'); + $rules['title'] = 'required|between:1,100|uniqueObjectForUser:rule_groups,title,' . $ruleGroup->id; + break; + } + + return $rules; + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/RuleRequest.php b/app/Api/V1/Requests/RuleRequest.php new file mode 100644 index 0000000000..84f6afb52b --- /dev/null +++ b/app/Api/V1/Requests/RuleRequest.php @@ -0,0 +1,156 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use Illuminate\Validation\Validator; + + +/** + * Class RuleRequest + */ +class RuleRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + $data = [ + 'title' => $this->string('title'), + 'description' => $this->string('description'), + 'rule_group_id' => $this->integer('rule_group_id'), + 'rule_group_title' => $this->string('rule_group_title'), + 'trigger' => $this->string('trigger'), + 'strict' => $this->boolean('strict'), + 'stop-processing' => $this->boolean('stop_processing'), + 'active' => $this->boolean('active'), + 'rule-triggers' => [], + 'rule-actions' => [], + ]; + + foreach ($this->get('rule-triggers') as $trigger) { + $data['rule-triggers'][] = [ + 'name' => $trigger['name'], + 'value' => $trigger['value'], + 'stop-processing' => (int)($trigger['stop-processing'] ?? 0) === 1, + ]; + } + foreach ($this->get('rule-actions') as $action) { + $data['rule-actions'][] = [ + 'name' => $action['name'], + 'value' => $action['value'], + 'stop-processing' => (int)($action['stop-processing'] ?? 0) === 1, + ]; + } + + return $data; + } + + /** + * @return array + */ + public function rules(): array + { + $validTriggers = array_keys(config('firefly.rule-triggers')); + $validActions = array_keys(config('firefly.rule-actions')); + + // some actions require text: + $contextActions = implode(',', config('firefly.rule-actions-text')); + + $rules = [ + 'title' => 'required|between:1,100|uniqueObjectForUser:rules,title', + 'description' => 'between:1,5000|nullable', + 'rule_group_id' => 'required|belongsToUser:rule_groups|required_without:rule_group_title', + 'rule_group_title' => 'nullable|between:1,255|required_without:rule_group_id|belongsToUser:rule_groups,title', + 'trigger' => 'required|in:store-journal,update-journal', + 'rule-triggers.*.name' => 'required|in:' . implode(',', $validTriggers), + 'rule-triggers.*.stop-processing' => 'boolean', + 'rule-triggers.*.value' => 'required|min:1|ruleTriggerValue', // + 'rule-actions.*.name' => 'required|in:' . implode(',', $validActions), + 'rule-actions.*.value' => 'required_if:rule-action.*.type,' . $contextActions . '|ruleActionValue', + 'rule-actions.*.stop-processing' => 'boolean', + 'strict' => 'required|boolean', + 'stop_processing' => 'required|boolean', + 'active' => 'required|boolean', + ]; + + return $rules; + } + + /** + * Configure the validator instance. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + $this->atLeastOneTrigger($validator); + $this->atLeastOneAction($validator); + } + ); + } + + /** + * Adds an error to the validator when there are no repetitions in the array of data. + * + * @param Validator $validator + */ + protected function atLeastOneAction(Validator $validator): void + { + $data = $validator->getData(); + $repetitions = $data['rule-actions'] ?? []; + // need at least one transaction + if (\count($repetitions) === 0) { + $validator->errors()->add('title', trans('validation.at_least_one_action')); + } + } + + /** + * Adds an error to the validator when there are no repetitions in the array of data. + * + * @param Validator $validator + */ + protected function atLeastOneTrigger(Validator $validator): void + { + $data = $validator->getData(); + $repetitions = $data['rule-triggers'] ?? []; + // need at least one transaction + if (\count($repetitions) === 0) { + $validator->errors()->add('title', trans('validation.at_least_one_trigger')); + } + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/TransactionRequest.php b/app/Api/V1/Requests/TransactionRequest.php index ab03ec561a..ce0c27e1be 100644 --- a/app/Api/V1/Requests/TransactionRequest.php +++ b/app/Api/V1/Requests/TransactionRequest.php @@ -188,6 +188,8 @@ class TransactionRequest extends Request /** * Throws an error when this asset account is invalid. * + * @noinspection MoreThanThreeArgumentsInspection + * * @param Validator $validator * @param int|null $accountId * @param null|string $accountName @@ -256,7 +258,7 @@ class TransactionRequest extends Request * * @param Validator $validator */ - protected function checkValidDescriptions(Validator $validator) + protected function checkValidDescriptions(Validator $validator): void { $data = $validator->getData(); $transactions = $data['transactions'] ?? []; @@ -317,6 +319,8 @@ class TransactionRequest extends Request } /** + * TODO can be made a rule? + * * If the transactions contain foreign amounts, there must also be foreign currency information. * * @param Validator $validator @@ -342,6 +346,8 @@ class TransactionRequest extends Request * Throws an error when the given opposing account (of type $type) is invalid. * Empty data is allowed, system will default to cash. * + * @noinspection MoreThanThreeArgumentsInspection + * * @param Validator $validator * @param string $type * @param int|null $accountId @@ -454,7 +460,7 @@ class TransactionRequest extends Request * * @throws FireflyException */ - protected function validateSplitAccounts(Validator $validator) + protected function validateSplitAccounts(Validator $validator): void { $data = $validator->getData(); $count = isset($data['transactions']) ? \count($data['transactions']) : 0; diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index c4f31d21d2..a533aedf2f 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -184,7 +184,7 @@ class UpgradeDatabase extends Command ] ); } - if($bill->amount_max === $bill->amount_min) { + if ($bill->amount_max === $bill->amount_min) { RuleTrigger::create( [ 'rule_id' => $rule->id, diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index 08ec12f3df..0b3ecc5f4d 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -90,6 +90,7 @@ class VerifyDatabase extends Command $this->createAccessTokens(); $this->fixDoubleAmounts(); $this->fixBadMeta(); + $this->removeBills(); } /** @@ -164,7 +165,8 @@ class VerifyDatabase extends Command if (isset($results[$key]) && $results[$key] !== $category) { $this->error( sprintf( - 'Transaction #%d referred to the wrong category. Was category #%d but is fixed to be category #%d.', $obj->transaction_journal_id, $category, $results[$key] + 'Transaction #%d referred to the wrong category. Was category #%d but is fixed to be category #%d.', $obj->transaction_journal_id, + $category, $results[$key] ) ); DB::table('category_transaction')->where('id', $obj->ct_id)->update(['category_id' => $results[$key]]); @@ -184,14 +186,15 @@ class VerifyDatabase extends Command ->get(['transactions.id', 'transaction_journal_id', 'identifier', 'budget_transaction.budget_id', 'budget_transaction.id as ct_id']); $results = []; foreach ($set as $obj) { - $key = $obj->transaction_journal_id . '-' . $obj->identifier; + $key = $obj->transaction_journal_id . '-' . $obj->identifier; $budget = (int)$obj->budget_id; // value exists and is not budget: if (isset($results[$key]) && $results[$key] !== $budget) { $this->error( sprintf( - 'Transaction #%d referred to the wrong budget. Was budget #%d but is fixed to be budget #%d.', $obj->transaction_journal_id, $budget, $results[$key] + 'Transaction #%d referred to the wrong budget. Was budget #%d but is fixed to be budget #%d.', $obj->transaction_journal_id, $budget, + $results[$key] ) ); DB::table('budget_transaction')->where('id', $obj->ct_id)->update(['budget_id' => $results[$key]]); @@ -253,6 +256,23 @@ class VerifyDatabase extends Command } } + /** + * + */ + private function removeBills(): void + { + /** @var TransactionType $withdrawal */ + $withdrawal = TransactionType::where('type', TransactionType::WITHDRAWAL)->first(); + $journals = TransactionJournal::whereNotNull('bill_id') + ->where('transaction_type_id', '!=', $withdrawal->id)->get(); + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $this->line(sprintf('Transaction journal #%d should not be linked to bill #%d.', $journal->id, $journal->bill_id)); + $journal->bill_id = null; + $journal->save(); + } + } + /** * Eeport (and fix) piggy banks. Make sure there are only transfers linked to piggy bank events. */ diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 3f24caad90..bda9202d93 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -24,11 +24,13 @@ declare(strict_types=1); namespace FireflyIII\Console; +use Carbon\Carbon; +use FireflyIII\Jobs\CreateRecurringTransactions; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; /** - * File to make sure commnds work. + * File to make sure commands work. */ class Kernel extends ConsoleKernel { @@ -44,7 +46,7 @@ class Kernel extends ConsoleKernel /** * Register the commands for the application. */ - protected function commands() + protected function commands(): void { $this->load(__DIR__ . '/Commands'); @@ -55,10 +57,9 @@ class Kernel extends ConsoleKernel * Define the application's command schedule. * * @param \Illuminate\Console\Scheduling\Schedule $schedule - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - protected function schedule(Schedule $schedule) + protected function schedule(Schedule $schedule): void { + $schedule->job(new CreateRecurringTransactions(new Carbon))->daily(); } } diff --git a/app/Events/RequestedReportOnJournals.php b/app/Events/RequestedReportOnJournals.php new file mode 100644 index 0000000000..d4bdf0dbb7 --- /dev/null +++ b/app/Events/RequestedReportOnJournals.php @@ -0,0 +1,46 @@ +userId = $userId; + $this->journals = $journals; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('channel-name'); + } +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index d66d42a274..6a72b8ab29 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -132,20 +132,15 @@ class Handler extends ExceptionHandler $doMailError = env('SEND_ERROR_MESSAGE', true); if ( // if the user wants us to mail: - $doMailError === true && - (( + $doMailError === true + && ( // and if is one of these error instances - $exception instanceof FireflyException - || $exception instanceof ErrorException - || $exception instanceof OAuthServerException + $exception instanceof FireflyException + || $exception instanceof ErrorException + || $exception instanceof OAuthServerException - ) - || ( - // or this one, but it's a JSON exception. - $exception instanceof AuthenticationException - && Request::expectsJson() === true - )) - ) { + ) + ) { // then, send email $userData = [ 'id' => 0, diff --git a/app/Export/ExpandedProcessor.php b/app/Export/ExpandedProcessor.php index d8ad004f9f..3c964efeba 100644 --- a/app/Export/ExpandedProcessor.php +++ b/app/Export/ExpandedProcessor.php @@ -36,12 +36,12 @@ use FireflyIII\Models\AccountMeta; use FireflyIII\Models\ExportJob; use FireflyIII\Models\Note; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Support\Collection; use Log; use Storage; use ZipArchive; -use FireflyIII\Models\TransactionJournal; /** * Class ExpandedProcessor. diff --git a/app/Factory/AttachmentFactory.php b/app/Factory/AttachmentFactory.php new file mode 100644 index 0000000000..b728625a4c --- /dev/null +++ b/app/Factory/AttachmentFactory.php @@ -0,0 +1,79 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Factory; + +use FireflyIII\Models\Attachment; +use FireflyIII\Models\Note; +use FireflyIII\User; + +/** + * Class AttachmentFactory + */ +class AttachmentFactory +{ + /** @var User */ + private $user; + + /** + * @param array $data + * + * @return Attachment|null + */ + public function create(array $data): ?Attachment + { + // create attachment: + $attachment = Attachment::create( + [ + 'user_id' => $this->user->id, + 'attachable_id' => $data['model_id'], + 'attachable_type' => $data['model'], + 'md5' => '', + 'filename' => $data['filename'], + 'title' => '' === $data['title'] ? null : $data['title'], + 'description' => null, + 'mime' => '', + 'size' => 0, + 'uploaded' => 0, + ] + ); + $notes = (string)($data['notes'] ?? ''); + if ('' !== $notes) { + $note = new Note; + $note->noteable()->associate($attachment); + $note->text = $notes; + $note->save(); + } + + return $attachment; + } + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + +} \ No newline at end of file diff --git a/app/Factory/BillFactory.php b/app/Factory/BillFactory.php index 94327af9ee..101d225452 100644 --- a/app/Factory/BillFactory.php +++ b/app/Factory/BillFactory.php @@ -58,8 +58,8 @@ class BillFactory 'date' => $data['date'], 'repeat_freq' => $data['repeat_freq'], 'skip' => $data['skip'], - 'automatch' => true, - 'active' => $data['active'], + 'automatch' => $data['automatch'] ?? true, + 'active' => $data['active'] ?? true, ] ); diff --git a/app/Factory/CategoryFactory.php b/app/Factory/CategoryFactory.php index ff80a61a7b..245d8e9eb6 100644 --- a/app/Factory/CategoryFactory.php +++ b/app/Factory/CategoryFactory.php @@ -72,7 +72,7 @@ class CategoryFactory Log::debug(sprintf('Going to find category with ID %d and name "%s"', $categoryId, $categoryName)); - if (\strlen($categoryName) === 0 && $categoryId === 0) { + if ('' === $categoryName && $categoryId === 0) { return null; } // first by ID: diff --git a/app/Factory/RecurrenceFactory.php b/app/Factory/RecurrenceFactory.php new file mode 100644 index 0000000000..f144022ed7 --- /dev/null +++ b/app/Factory/RecurrenceFactory.php @@ -0,0 +1,91 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Factory; + + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Recurrence; +use FireflyIII\Services\Internal\Support\RecurringTransactionTrait; +use FireflyIII\Services\Internal\Support\TransactionServiceTrait; +use FireflyIII\Services\Internal\Support\TransactionTypeTrait; +use FireflyIII\User; +use Log; + +/** + * Class RecurrenceFactory + */ +class RecurrenceFactory +{ + use TransactionTypeTrait, TransactionServiceTrait, RecurringTransactionTrait; + + /** @var User */ + private $user; + + /** + * @param array $data + * + * @return Recurrence + */ + public function create(array $data): ?Recurrence + { + try { + $type = $this->findTransactionType(ucfirst($data['recurrence']['type'])); + } catch (FireflyException $e) { + Log::error($e->getMessage()); + + return null; + } + $repetitions = (int)$data['recurrence']['repetitions']; + $recurrence = new Recurrence( + [ + 'user_id' => $this->user->id, + 'transaction_type_id' => $type->id, + 'title' => $data['recurrence']['title'], + 'description' => $data['recurrence']['description'], + 'first_date' => $data['recurrence']['first_date']->format('Y-m-d'), + 'repeat_until' => $repetitions > 0 ? null : $data['recurrence']['repeat_until'], + 'latest_date' => null, + 'repetitions' => $data['recurrence']['repetitions'], + 'apply_rules' => $data['recurrence']['apply_rules'], + 'active' => $data['recurrence']['active'], + ] + ); + $recurrence->save(); + + $this->updateMetaData($recurrence, $data); + $this->createRepetitions($recurrence, $data['repetitions'] ?? []); + $this->createTransactions($recurrence, $data['transactions'] ?? []); + + return $recurrence; + } + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + +} \ No newline at end of file diff --git a/app/Factory/TransactionCurrencyFactory.php b/app/Factory/TransactionCurrencyFactory.php index 3cee423c42..e57e1f81d1 100644 --- a/app/Factory/TransactionCurrencyFactory.php +++ b/app/Factory/TransactionCurrencyFactory.php @@ -71,6 +71,7 @@ class TransactionCurrencyFactory if ('' === $currencyCode && $currencyId === 0) { Log::warning('Cannot find anything on empty currency code and empty currency ID!'); + return null; } diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index c9c787d092..bb7bdc2550 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -26,6 +26,7 @@ namespace FireflyIII\Factory; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; @@ -56,6 +57,7 @@ class TransactionFactory $currencyId = $data['currency_id'] ?? null; $currencyId = isset($data['currency']) ? $data['currency']->id : $currencyId; if ('' === $data['amount']) { + Log::error('Empty string in data.', $data); throw new FireflyException('Amount is an empty string, which Firefly III cannot handle. Apologies.'); // @codeCoverageIgnore } if (null === $currencyId) { @@ -96,14 +98,27 @@ class TransactionFactory $description = $journal->description === $data['description'] ? null : $data['description']; // type of source account depends on journal type: - $sourceType = $this->accountType($journal, 'source'); + $sourceType = $this->accountType($journal, 'source'); Log::debug(sprintf('Expect source account to be of type %s', $sourceType)); $sourceAccount = $this->findAccount($sourceType, $data['source_id'], $data['source_name']); // same for destination account: - $destinationType = $this->accountType($journal, 'destination'); + $destinationType = $this->accountType($journal, 'destination'); Log::debug(sprintf('Expect source destination to be of type %s', $destinationType)); $destinationAccount = $this->findAccount($destinationType, $data['destination_id'], $data['destination_name']); + + Log::debug(sprintf('Source type is "%s", destination type is "%s"', $sourceAccount->accountType->type, $destinationAccount->accountType->type)); + // throw big fat error when source type === dest type + if ($sourceAccount->accountType->type === $destinationAccount->accountType->type + && ($journal->transactionType->type !== TransactionType::TRANSFER + && $journal->transactionType->type !== TransactionType::RECONCILIATION) + ) { + throw new FireflyException(sprintf('Source and destination account cannot be both of the type "%s"', $destinationAccount->accountType->type)); + } + if ($sourceAccount->accountType->type !== AccountType::ASSET && $destinationAccount->accountType->type !== AccountType::ASSET) { + throw new FireflyException('At least one of the accounts must be an asset account.'); + } + // first make a "negative" (source) transaction based on the data in the array. $source = $this->create( [ diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index fbee73ea96..e81c50c087 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -26,8 +26,8 @@ namespace FireflyIII\Factory; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; use FireflyIII\Services\Internal\Support\JournalServiceTrait; +use FireflyIII\Services\Internal\Support\TransactionTypeTrait; use FireflyIII\User; use Log; @@ -36,7 +36,7 @@ use Log; */ class TransactionJournalFactory { - use JournalServiceTrait; + use JournalServiceTrait, TransactionTypeTrait; /** @var User */ private $user; @@ -53,7 +53,7 @@ class TransactionJournalFactory $type = $this->findTransactionType($data['type']); $defaultCurrency = app('amount')->getDefaultCurrencyByUser($this->user); Log::debug(sprintf('Going to store a %s', $type->type)); - $journal = TransactionJournal::create( + $journal = TransactionJournal::create( [ 'user_id' => $data['user'], 'transaction_type_id' => $type->id, @@ -67,6 +67,10 @@ class TransactionJournalFactory ] ); + if (isset($data['transactions'][0]['amount']) && '' === $data['transactions'][0]['amount']) { + Log::error('Empty amount in data', $data); + } + // store basic transactions: /** @var TransactionFactory $factory */ $factory = app(TransactionFactory::class); @@ -95,13 +99,17 @@ class TransactionJournalFactory // store date meta fields (if present): $fields = ['sepa-cc', 'sepa-ct-op', 'sepa-ct-id', 'sepa-db', 'sepa-country', 'sepa-ep', 'sepa-ci', 'interest_date', 'book_date', 'process_date', - 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'bunq_payment_id', 'importHash','importHashV2', 'external_id']; + 'due_date', 'recurrence_id', 'payment_date', 'invoice_date', 'internal_reference', 'bunq_payment_id', 'importHash', 'importHashV2', + 'external_id']; foreach ($fields as $field) { $this->storeMeta($journal, $data, $field); } Log::debug('End of TransactionJournalFactory::create()'); + // invalidate cache. + app('preferences')->mark(); + return $journal; } @@ -133,25 +141,4 @@ class TransactionJournalFactory } } - /** - * Get the transaction type. Since this is mandatory, will throw an exception when nothing comes up. Will always - * use TransactionType repository. - * - * @param string $type - * - * @return TransactionType - * @throws FireflyException - */ - protected function findTransactionType(string $type): TransactionType - { - $factory = app(TransactionTypeFactory::class); - $transactionType = $factory->find($type); - if (null === $transactionType) { - Log::error(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore - throw new FireflyException(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore - } - - return $transactionType; - } - } diff --git a/app/Generator/Chart/Basic/ChartJsGenerator.php b/app/Generator/Chart/Basic/ChartJsGenerator.php index da6252548f..ef66a2b7cf 100644 --- a/app/Generator/Chart/Basic/ChartJsGenerator.php +++ b/app/Generator/Chart/Basic/ChartJsGenerator.php @@ -90,7 +90,9 @@ class ChartJsGenerator implements GeneratorInterface if (isset($set['currency_symbol'])) { $currentSet['currency_symbol'] = $set['currency_symbol']; } - + if(isset($set['backgroundColor'])) { + $currentSet['backgroundColor'] = $set['backgroundColor']; + } $chartData['datasets'][] = $currentSet; } diff --git a/app/Handlers/Events/APIEventHandler.php b/app/Handlers/Events/APIEventHandler.php index 64aa286420..e73b151e86 100644 --- a/app/Handlers/Events/APIEventHandler.php +++ b/app/Handlers/Events/APIEventHandler.php @@ -28,7 +28,6 @@ use Exception; use FireflyIII\Mail\AccessTokenCreatedMail; use FireflyIII\Repositories\User\UserRepositoryInterface; use Laravel\Passport\Events\AccessTokenCreated; -use Laravel\Passport\Token; use Log; use Mail; use Request; diff --git a/app/Handlers/Events/AutomationHandler.php b/app/Handlers/Events/AutomationHandler.php new file mode 100644 index 0000000000..3585e0ccf2 --- /dev/null +++ b/app/Handlers/Events/AutomationHandler.php @@ -0,0 +1,73 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Handlers\Events; + +use Exception; +use FireflyIII\Events\RequestedReportOnJournals; +use FireflyIII\Mail\ReportNewJournalsMail; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use Log; +use Mail; + +/** + * Class AutomationHandler + */ +class AutomationHandler +{ + + /** + * @param RequestedReportOnJournals $event + * + * @return bool + */ + public function reportJournals(RequestedReportOnJournals $event): bool + { + Log::debug('In reportJournals.'); + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + $user = $repository->findNull($event->userId); + if (null === $user) { + Log::debug('User is NULL'); + + return true; + } + if ($event->journals->count() === 0) { + Log::debug('No journals.'); + + return true; + } + + try { + Log::debug('Trying to mail...'); + Mail::to($user->email)->send(new ReportNewJournalsMail($user->email, '127.0.0.1', $event->journals)); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + Log::error($e->getMessage()); + } + Log::debug('Done!'); + + // @codeCoverageIgnoreEnd + return true; + } +} \ No newline at end of file diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index e953e039d1..64ef4a83fc 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -108,6 +108,27 @@ class UserEventHandler return true; } + /** + * @param Login $event + * + * @return bool + */ + public function demoUserBackToEnglish(Login $event): bool + { + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + + /** @var User $user */ + $user = $event->user; + if ($repository->hasRole($user, 'demo')) { + // set user back to English. + app('preferences')->setForUser($user, 'language', 'en_US'); + app('preferences')->mark(); + } + + return true; + } + /** * @param UserChangedEmail $event * diff --git a/app/Handlers/Events/VersionCheckEventHandler.php b/app/Handlers/Events/VersionCheckEventHandler.php index 2cecd97947..2b7b9a1f0f 100644 --- a/app/Handlers/Events/VersionCheckEventHandler.php +++ b/app/Handlers/Events/VersionCheckEventHandler.php @@ -41,7 +41,7 @@ class VersionCheckEventHandler /** * @param RequestedVersionCheckStatus $event */ - public function checkForUpdates(RequestedVersionCheckStatus $event) + public function checkForUpdates(RequestedVersionCheckStatus $event): void { // in Sandstorm, cannot check for updates: $sandstorm = 1 === (int)getenv('SANDSTORM'); diff --git a/app/Helpers/Attachments/AttachmentHelper.php b/app/Helpers/Attachments/AttachmentHelper.php index 72a00bab0c..b6d950042f 100644 --- a/app/Helpers/Attachments/AttachmentHelper.php +++ b/app/Helpers/Attachments/AttachmentHelper.php @@ -122,6 +122,46 @@ class AttachmentHelper implements AttachmentHelperInterface return $this->messages; } + /** + * Uploads a file as a string. + * + * @param Attachment $attachment + * @param string $content + * + * @return bool + */ + public function saveAttachmentFromApi(Attachment $attachment, string $content): bool + { + $resource = tmpfile(); + if (false === $resource) { + Log::error('Cannot create temp-file for file upload.'); + + return false; + } + $path = stream_get_meta_data($resource)['uri']; + fwrite($resource, $content); + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mime = finfo_file($finfo, $path); + $allowedMime = config('firefly.allowedMimes'); + if (!\in_array($mime, $allowedMime, true)) { + Log::error(sprintf('Mime type %s is not allowed for API file upload.', $mime)); + + return false; + } + // is allowed? Save the file! + $encrypted = Crypt::encrypt($content); + $this->uploadDisk->put($attachment->fileName(), $encrypted); + + // update attachment. + $attachment->md5 = md5_file($path); + $attachment->mime = $mime; + $attachment->size = \strlen($content); + $attachment->uploaded = 1; + $attachment->save(); + + return true; + } + /** * @param Model $model * @param array|null $files @@ -232,7 +272,7 @@ class AttachmentHelper implements AttachmentHelperInterface Log::debug(sprintf('Name is %s, and mime is %s', $name, $mime)); Log::debug('Valid mimes are', $this->allowedMimes); - if (!\in_array($mime, $this->allowedMimes)) { + if (!\in_array($mime, $this->allowedMimes, true)) { $msg = (string)trans('validation.file_invalid_mime', ['name' => $name, 'mime' => $mime]); $this->errors->add('attachments', $msg); Log::error($msg); diff --git a/app/Helpers/Attachments/AttachmentHelperInterface.php b/app/Helpers/Attachments/AttachmentHelperInterface.php index 276d312a27..8f34041faa 100644 --- a/app/Helpers/Attachments/AttachmentHelperInterface.php +++ b/app/Helpers/Attachments/AttachmentHelperInterface.php @@ -37,14 +37,14 @@ interface AttachmentHelperInterface * * @return string */ - public function getAttachmentLocation(Attachment $attachment): string; + public function getAttachmentContent(Attachment $attachment): string; /** * @param Attachment $attachment * * @return string */ - public function getAttachmentContent(Attachment $attachment): string; + public function getAttachmentLocation(Attachment $attachment): string; /** * @return Collection @@ -61,6 +61,16 @@ interface AttachmentHelperInterface */ public function getMessages(): MessageBag; + /** + * Uploads a file as a string. + * + * @param Attachment $attachment + * @param string $content + * + * @return bool + */ + public function saveAttachmentFromApi(Attachment $attachment, string $content): bool; + /** * @param Model $model * @param null|array $files diff --git a/app/Helpers/Collection/BalanceLine.php b/app/Helpers/Collection/BalanceLine.php index a998b4784e..9d62a4aa21 100644 --- a/app/Helpers/Collection/BalanceLine.php +++ b/app/Helpers/Collection/BalanceLine.php @@ -40,10 +40,6 @@ class BalanceLine * */ public const ROLE_TAGROLE = 2; - /** - * - */ - public const ROLE_DIFFROLE = 3; /** @var Collection */ protected $balanceEntries; @@ -167,9 +163,6 @@ class BalanceLine if (self::ROLE_TAGROLE === $this->getRole()) { return (string)trans('firefly.coveredWithTags'); } - if (self::ROLE_DIFFROLE === $this->getRole()) { - return (string)trans('firefly.leftUnbalanced'); - } return ''; } diff --git a/app/Helpers/Collector/JournalCollector.php b/app/Helpers/Collector/JournalCollector.php index b2eb90dc1a..33d24fcaf7 100644 --- a/app/Helpers/Collector/JournalCollector.php +++ b/app/Helpers/Collector/JournalCollector.php @@ -321,6 +321,14 @@ class JournalCollector implements JournalCollectorInterface return $journals; } + /** + * @return EloquentBuilder + */ + public function getQuery(): EloquentBuilder + { + return $this->query; + } + /** * @return JournalCollectorInterface */ diff --git a/app/Helpers/Collector/JournalCollectorInterface.php b/app/Helpers/Collector/JournalCollectorInterface.php index b710326175..b9c29ff4bf 100644 --- a/app/Helpers/Collector/JournalCollectorInterface.php +++ b/app/Helpers/Collector/JournalCollectorInterface.php @@ -27,6 +27,7 @@ use FireflyIII\Models\Budget; use FireflyIII\Models\Category; use FireflyIII\Models\Tag; use FireflyIII\User; +use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -35,6 +36,7 @@ use Illuminate\Support\Collection; */ interface JournalCollectorInterface { + /** * @param string $filter * @@ -78,6 +80,11 @@ interface JournalCollectorInterface */ public function getPaginatedJournals(): LengthAwarePaginator; + /** + * @return EloquentBuilder + */ + public function getQuery(): EloquentBuilder; + /** * @return JournalCollectorInterface */ diff --git a/app/Helpers/Help/Help.php b/app/Helpers/Help/Help.php index 7c9358e4bb..a9fcd1869a 100644 --- a/app/Helpers/Help/Help.php +++ b/app/Helpers/Help/Help.php @@ -24,9 +24,10 @@ namespace FireflyIII\Helpers\Help; use Cache; use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; use League\CommonMark\CommonMarkConverter; use Log; -use Requests; use Route; /** @@ -64,20 +65,21 @@ class Help implements HelpInterface { $uri = sprintf('https://raw.githubusercontent.com/firefly-iii/help/master/%s/%s.md', $language, $route); Log::debug(sprintf('Trying to get %s...', $uri)); - $opt = ['useragent' => $this->userAgent]; + $opt = ['headers' => ['User-Agent' => $this->userAgent]]; $content = ''; try { - $result = Requests::get($uri, [], $opt); - } catch (Exception $e) { + $client = new Client; + $res = $client->request('GET', $uri, $opt); + } catch (GuzzleException|Exception $e) { Log::error($e); return ''; } - Log::debug(sprintf('Status code is %d', $result->status_code)); + Log::debug(sprintf('Status code is %d', $res->getStatusCode())); - if (200 === $result->status_code) { - $content = trim($result->body); + if (200 === $res->getStatusCode()) { + $content = trim($res->getBody()->getContents()); } if (\strlen($content) > 0) { diff --git a/app/Helpers/Report/PopupReport.php b/app/Helpers/Report/PopupReport.php index 73e786e24b..24c1bd2d9e 100644 --- a/app/Helpers/Report/PopupReport.php +++ b/app/Helpers/Report/PopupReport.php @@ -36,33 +36,6 @@ use Illuminate\Support\Collection; */ class PopupReport implements PopupReportInterface { - /** - * @param $account - * @param $attributes - * - * @return Collection - */ - public function balanceDifference($account, $attributes): Collection - { - // row that displays difference - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector - ->setAccounts(new Collection([$account])) - ->setTypes([TransactionType::WITHDRAWAL]) - ->setRange($attributes['startDate'], $attributes['endDate']) - ->withoutBudget(); - $journals = $collector->getJournals(); - - return $journals->filter( - function (Transaction $transaction) { - $tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count(); - - return 0 === $tags; - } - ); - } - /** * @param Budget $budget * @param Account $account diff --git a/app/Helpers/Report/PopupReportInterface.php b/app/Helpers/Report/PopupReportInterface.php index 69efef80e6..6345afdcc3 100644 --- a/app/Helpers/Report/PopupReportInterface.php +++ b/app/Helpers/Report/PopupReportInterface.php @@ -32,14 +32,6 @@ use Illuminate\Support\Collection; */ interface PopupReportInterface { - /** - * @param $account - * @param $attributes - * - * @return Collection - */ - public function balanceDifference($account, $attributes): Collection; - /** * @param Budget $budget * @param Account $account diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index db68a5c841..225e52b89a 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -34,7 +34,6 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; @@ -52,8 +51,6 @@ class AccountController extends Controller { /** @var CurrencyRepositoryInterface */ private $currencyRepos; - /** @var JournalRepositoryInterface */ - private $journalRepos; /** @var AccountRepositoryInterface */ private $repository; @@ -72,7 +69,6 @@ class AccountController extends Controller $this->repository = app(AccountRepositoryInterface::class); $this->currencyRepos = app(CurrencyRepositoryInterface::class); - $this->journalRepos = app(JournalRepositoryInterface::class); return $next($request); } @@ -188,7 +184,9 @@ class AccountController extends Controller $currency = $default; } - $preFilled = [ + // code to handle active-checkboxes + $hasOldInput = null !== $request->old('_token'); + $preFilled = [ 'accountNumber' => $repository->getMetaValue($account, 'accountNumber'), 'accountRole' => $repository->getMetaValue($account, 'accountRole'), 'ccType' => $repository->getMetaValue($account, 'ccType'), @@ -199,7 +197,7 @@ class AccountController extends Controller 'virtualBalance' => $account->virtual_balance, 'currency_id' => $currency->id, 'notes' => '', - 'active' => $account->active, + 'active' => $hasOldInput ? (bool)$request->old('active') : $account->active, ]; /** @var Note $note */ $note = $this->repository->getNote($account); @@ -207,7 +205,6 @@ class AccountController extends Controller $preFilled['notes'] = $note->text; } - $request->session()->flash('preFilled', $preFilled); return view('accounts.edit', compact('account', 'currency', 'subTitle', 'subTitleIcon', 'what', 'roles', 'preFilled')); diff --git a/app/Http/Controllers/Admin/HomeController.php b/app/Http/Controllers/Admin/HomeController.php index 92d0760119..b53ac0b274 100644 --- a/app/Http/Controllers/Admin/HomeController.php +++ b/app/Http/Controllers/Admin/HomeController.php @@ -51,8 +51,9 @@ class HomeController extends Controller { $title = (string)trans('firefly.administration'); $mainTitleIcon = 'fa-hand-spock-o'; + $sandstorm = 1 === (int)getenv('SANDSTORM'); - return view('admin.index', compact('title', 'mainTitleIcon')); + return view('admin.index', compact('title', 'mainTitleIcon', 'sandstorm')); } /** diff --git a/app/Http/Controllers/Admin/LinkController.php b/app/Http/Controllers/Admin/LinkController.php index be3be8fdb4..5884596fa0 100644 --- a/app/Http/Controllers/Admin/LinkController.php +++ b/app/Http/Controllers/Admin/LinkController.php @@ -112,7 +112,7 @@ class LinkController extends Controller public function destroy(Request $request, LinkTypeRepositoryInterface $repository, LinkType $linkType) { $name = $linkType->name; - $moveTo = $repository->find((int)$request->get('move_link_type_before_delete')); + $moveTo = $repository->findNull((int)$request->get('move_link_type_before_delete')); $repository->destroy($linkType, $moveTo); $request->session()->flash('success', (string)trans('firefly.deleted_link_type', ['name' => $name])); diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index bbb5a96234..b0ae134570 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -161,10 +161,13 @@ class BillController extends Controller $bill->amount_max = round($bill->amount_max, $currency->decimal_places); $defaultCurrency = app('amount')->getDefaultCurrency(); + // code to handle active-checkboxes + $hasOldInput = null !== $request->old('_token'); + $preFilled = [ 'notes' => $this->billRepository->getNoteText($bill), 'transaction_currency_id' => $bill->transaction_currency_id, - 'active' => $bill->active, + 'active' => $hasOldInput ? (bool)$request->old('active') : $bill->active, ]; $request->session()->flash('preFilled', $preFilled); diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index ed199483d5..df6f0d8954 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -96,7 +96,7 @@ class BudgetController extends Controller // if today is between start and end, use the diff in days between end and today (days left) // otherwise, use diff between start and end. $today = new Carbon; - Log::debug(sprintf('Start is %s, end is %s, today is %s', $start->format('Y-m-d'), $end->format('Y-m-d'),$today->format('Y-m-d'))); + Log::debug(sprintf('Start is %s, end is %s, today is %s', $start->format('Y-m-d'), $end->format('Y-m-d'), $today->format('Y-m-d'))); if ($today->gte($start) && $today->lte($end)) { $days = $end->diffInDays($today); $daysInMonth = $start->diffInDays($today); @@ -216,11 +216,18 @@ class BudgetController extends Controller { $subTitle = trans('firefly.edit_budget', ['name' => $budget->name]); + // code to handle active-checkboxes + $hasOldInput = null !== $request->old('_token'); + $preFilled = [ + 'active' => $hasOldInput ? (bool)$request->old('active') : $budget->active, + ]; + // put previous url in session if not redirect from store (not "return_to_edit"). if (true !== session('budgets.edit.fromUpdate')) { $this->rememberPreviousUri('budgets.edit.uri'); } $request->session()->forget('budgets.edit.fromUpdate'); + $request->session()->flash('preFilled', $preFilled); return view('budgets.edit', compact('budget', 'subTitle')); } diff --git a/app/Http/Controllers/Chart/ReportController.php b/app/Http/Controllers/Chart/ReportController.php index 30c5657d59..dfc8f77e1d 100644 --- a/app/Http/Controllers/Chart/ReportController.php +++ b/app/Http/Controllers/Chart/ReportController.php @@ -27,6 +27,7 @@ use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Repositories\Account\AccountTaskerInterface; use FireflyIII\Support\CacheProperties; +use Illuminate\Http\JsonResponse; use Illuminate\Support\Collection; use Log; use Steam; @@ -57,9 +58,9 @@ class ReportController extends Controller * @param Carbon $start * @param Carbon $end * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ - public function netWorth(Collection $accounts, Carbon $start, Carbon $end) + public function netWorth(Collection $accounts, Carbon $start, Carbon $end): JsonResponse { // chart properties for cache: $cache = new CacheProperties; @@ -111,14 +112,16 @@ class ReportController extends Controller $source = $this->getChartData($accounts, $start, $end); $chartData = [ [ - 'label' => trans('firefly.income'), - 'type' => 'bar', - 'entries' => [], + 'label' => trans('firefly.income'), + 'type' => 'bar', + 'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green + 'entries' => [], ], [ - 'label' => trans('firefly.expenses'), - 'type' => 'bar', - 'entries' => [], + 'label' => trans('firefly.expenses'), + 'type' => 'bar', + 'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red + 'entries' => [], ], ]; @@ -188,17 +191,19 @@ class ReportController extends Controller $chartData = [ [ - 'label' => (string)trans('firefly.income'), - 'type' => 'bar', - 'entries' => [ + 'label' => (string)trans('firefly.income'), + 'type' => 'bar', + 'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green + 'entries' => [ (string)trans('firefly.sum_of_period') => $numbers['sum_earned'], (string)trans('firefly.average_in_period') => $numbers['avg_earned'], ], ], [ - 'label' => trans('firefly.expenses'), - 'type' => 'bar', - 'entries' => [ + 'label' => trans('firefly.expenses'), + 'type' => 'bar', + 'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red + 'entries' => [ (string)trans('firefly.sum_of_period') => $numbers['sum_spent'], (string)trans('firefly.average_in_period') => $numbers['avg_spent'], ], diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index 11f52acf5c..1caf4925df 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -23,13 +23,18 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; +use Artisan; use Carbon\Carbon; use DB; use Exception; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Middleware\IsDemoUser; use Illuminate\Http\Request; +use Illuminate\Routing\Route; use Log; use Monolog\Handler\RotatingFileHandler; +use Preferences; +use Route as RouteFacade; /** * Class DebugController @@ -45,6 +50,52 @@ class DebugController extends Controller $this->middleware(IsDemoUser::class); } + /** + * @throws FireflyException + */ + public function displayError() + { + Log::debug('This is a test message at the DEBUG level.'); + Log::info('This is a test message at the INFO level.'); + Log::notice('This is a test message at the NOTICE level.'); + Log::warning('This is a test message at the WARNING level.'); + Log::error('This is a test message at the ERROR level.'); + Log::critical('This is a test message at the CRITICAL level.'); + Log::alert('This is a test message at the ALERT level.'); + Log::emergency('This is a test message at the EMERGENCY level.'); + throw new FireflyException('A very simple test error.'); + } + + /** + * @param Request $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function flush(Request $request) + { + Preferences::mark(); + $request->session()->forget(['start', 'end', '_previous', 'viewRange', 'range', 'is_custom_range']); + Log::debug('Call cache:clear...'); + Artisan::call('cache:clear'); + Log::debug('Call config:clear...'); + Artisan::call('config:clear'); + Log::debug('Call route:clear...'); + Artisan::call('route:clear'); + Log::debug('Call twig:clean...'); + try { + Artisan::call('twig:clean'); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + // don't care + Log::debug('Called twig:clean.'); + } + // @codeCoverageIgnoreEnd + Log::debug('Call view:clear...'); + Artisan::call('view:clear'); + Log::debug('Done! Redirecting...'); + + return redirect(route('index')); + } /** * @param Request $request @@ -111,13 +162,70 @@ class DebugController extends Controller return view( 'debug', compact( - 'phpVersion', 'extensions', 'localeAttempts', 'appEnv', 'appDebug', 'appLog', 'appLogLevel', 'now', 'packages', 'drivers', 'currentDriver', - 'userAgent', 'displayErrors', 'errorReporting', 'phpOs', 'interface', 'logContent', 'cacheDriver', 'isDocker', 'isSandstorm', 'trustedProxies', - 'toSandbox' - ) + 'phpVersion', 'extensions', 'localeAttempts', 'appEnv', 'appDebug', 'appLog', 'appLogLevel', 'now', 'packages', 'drivers', + 'currentDriver', + 'userAgent', 'displayErrors', 'errorReporting', 'phpOs', 'interface', 'logContent', 'cacheDriver', 'isDocker', 'isSandstorm', + 'trustedProxies', + 'toSandbox' + ) ); } + /** + * @return string + */ + public function routes(): string + { + $set = RouteFacade::getRoutes(); + $ignore = ['chart.', 'javascript.', 'json.', 'report-data.', 'popup.', 'debugbar.', 'attachments.download', 'attachments.preview', + 'bills.rescan', 'budgets.income', 'currencies.def', 'error', 'flush', 'help.show', 'import.file', + 'login', 'logout', 'password.reset', 'profile.confirm-email-change', 'profile.undo-email-change', + 'register', 'report.options', 'routes', 'rule-groups.down', 'rule-groups.up', 'rules.up', 'rules.down', + 'rules.select', 'search.search', 'test-flash', 'transactions.link.delete', 'transactions.link.switch', + 'two-factor.lost', 'reports.options', 'debug', 'import.create-job', 'import.download', 'import.start', 'import.status.json', + 'preferences.delete-code', 'rules.test-triggers', 'piggy-banks.remove-money', 'piggy-banks.add-money', + 'accounts.reconcile.transactions', 'accounts.reconcile.overview', 'export.download', + 'transactions.clone', 'two-factor.index', 'api.v1', 'installer.', 'attachments.view', 'import.create', + 'import.job.download', 'import.job.start', 'import.job.status.json', 'import.job.store', 'recurring.events', + 'recurring.suggest', + ]; + $return = ' '; + /** @var Route $route */ + foreach ($set as $route) { + $name = $route->getName(); + if (null !== $name && \strlen($name) > 0 && \in_array('GET', $route->methods(), true)) { + + $found = false; + foreach ($ignore as $string) { + if (!(false === stripos($name, $string))) { + $found = true; + break; + } + } + if ($found === false) { + $return .= 'touch ' . $route->getName() . '.md;'; + } + } + } + + return $return; + } + + /** + * @param Request $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function testFlash(Request $request) + { + $request->session()->flash('success', 'This is a success message.'); + $request->session()->flash('info', 'This is an info message.'); + $request->session()->flash('warning', 'This is a warning.'); + $request->session()->flash('error', 'This is an error!'); + + return redirect(route('home')); + } + /** * Some common combinations. * @@ -149,7 +257,7 @@ class DebugController extends Controller private function collectPackages(): array { $packages = []; - $file = realpath(__DIR__ . '/../../../vendor/composer/installed.json'); + $file = \dirname(__DIR__, 3) . '/vendor/composer/installed.json'; if (!($file === false) && file_exists($file)) { // file exists! $content = file_get_contents($file); diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 993d3273e5..6dc45a198d 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -22,11 +22,8 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; -use Artisan; use Carbon\Carbon; -use Exception; use FireflyIII\Events\RequestedVersionCheckStatus; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Middleware\Installer; use FireflyIII\Http\Middleware\IsDemoUser; @@ -35,11 +32,9 @@ use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use Illuminate\Http\Request; -use Illuminate\Routing\Route; use Illuminate\Support\Collection; use Log; use Preferences; -use Route as RouteFacade; use View; /** @@ -99,53 +94,6 @@ class HomeController extends Controller } - /** - * @throws FireflyException - */ - public function displayError() - { - Log::debug('This is a test message at the DEBUG level.'); - Log::info('This is a test message at the INFO level.'); - Log::notice('This is a test message at the NOTICE level.'); - Log::warning('This is a test message at the WARNING level.'); - Log::error('This is a test message at the ERROR level.'); - Log::critical('This is a test message at the CRITICAL level.'); - Log::alert('This is a test message at the ALERT level.'); - Log::emergency('This is a test message at the EMERGENCY level.'); - throw new FireflyException('A very simple test error.'); - } - - /** - * @param Request $request - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function flush(Request $request) - { - Preferences::mark(); - $request->session()->forget(['start', 'end', '_previous', 'viewRange', 'range', 'is_custom_range']); - Log::debug('Call cache:clear...'); - Artisan::call('cache:clear'); - Log::debug('Call config:clear...'); - Artisan::call('config:clear'); - Log::debug('Call route:clear...'); - Artisan::call('route:clear'); - Log::debug('Call twig:clean...'); - try { - Artisan::call('twig:clean'); - // @codeCoverageIgnoreStart - } catch (Exception $e) { - // don't care - Log::debug('Called twig:clean.'); - } - // @codeCoverageIgnoreEnd - Log::debug('Call view:clear...'); - Artisan::call('view:clear'); - Log::debug('Done! Redirecting...'); - - return redirect(route('index')); - } - /** * @param AccountRepositoryInterface $repository * @@ -193,56 +141,4 @@ class HomeController extends Controller ); } - /** - * @return string - */ - public function routes() - { - $set = RouteFacade::getRoutes(); - $ignore = ['chart.', 'javascript.', 'json.', 'report-data.', 'popup.', 'debugbar.', 'attachments.download', 'attachments.preview', - 'bills.rescan', 'budgets.income', 'currencies.def', 'error', 'flush', 'help.show', 'import.file', - 'login', 'logout', 'password.reset', 'profile.confirm-email-change', 'profile.undo-email-change', - 'register', 'report.options', 'routes', 'rule-groups.down', 'rule-groups.up', 'rules.up', 'rules.down', - 'rules.select', 'search.search', 'test-flash', 'transactions.link.delete', 'transactions.link.switch', - 'two-factor.lost', 'reports.options', 'debug', 'import.create-job', 'import.download', 'import.start', 'import.status.json', - 'preferences.delete-code', 'rules.test-triggers', 'piggy-banks.remove-money', 'piggy-banks.add-money', - 'accounts.reconcile.transactions', 'accounts.reconcile.overview', 'export.download', - 'transactions.clone', 'two-factor.index', - ]; - $return = ' '; - /** @var Route $route */ - foreach ($set as $route) { - $name = $route->getName(); - if (null !== $name && \in_array('GET', $route->methods()) && \strlen($name) > 0) { - - $found = false; - foreach ($ignore as $string) { - if (!(false === stripos($name, $string))) { - $found = true; - break; - } - } - if ($found === false) { - $return .= 'touch ' . $route->getName() . '.md;'; - } - } - } - - return $return; - } - - /** - * @param Request $request - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function testFlash(Request $request) - { - $request->session()->flash('success', 'This is a success message.'); - $request->session()->flash('info', 'This is an info message.'); - $request->session()->flash('warning', 'This is a warning.'); - $request->session()->flash('error', 'This is an error!'); - - return redirect(route('home')); - } } diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index a6fe9d3167..72c422caad 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -161,10 +161,8 @@ class IndexController extends Controller $config['delimiter'] = $config['delimiter'] ?? ','; $config['delimiter'] = "\t" === $config['delimiter'] ? 'tab' : $config['delimiter']; - // this prevents private information from escaping - $config['column-mapping-config'] = []; - $result = json_encode($config, JSON_PRETTY_PRINT); - $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); + $result = json_encode($config, JSON_PRETTY_PRINT); + $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); /** @var LaravelResponse $response */ $response = response($result, 200); $response->header('Content-disposition', 'attachment; filename=' . $name) diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php index d512e82196..8a9ce1d851 100644 --- a/app/Http/Controllers/Import/JobStatusController.php +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -127,7 +127,7 @@ class JobStatusController extends Controller public function start(ImportJob $importJob): JsonResponse { // catch impossible status: - $allowed = ['ready_to_run', 'need_job_config', 'error']; // todo remove error + $allowed = ['ready_to_run', 'need_job_config']; if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { Log::error('Job is not ready.'); diff --git a/app/Http/Controllers/Json/BoxController.php b/app/Http/Controllers/Json/BoxController.php index 27c857d97b..e61c522daf 100644 --- a/app/Http/Controllers/Json/BoxController.php +++ b/app/Http/Controllers/Json/BoxController.php @@ -153,6 +153,12 @@ class BoxController extends Controller $incomes[$currencyId] = Amount::formatAnything($currency, $incomes[$currencyId] ?? '0', false); $expenses[$currencyId] = Amount::formatAnything($currency, $expenses[$currencyId] ?? '0', false); } + if (\count($sums) === 0) { + $currency = app('amount')->getDefaultCurrency(); + $sums[$currency->id] = Amount::formatAnything($currency, '0', false); + $incomes[$currency->id] = Amount::formatAnything($currency, '0', false); + $expenses[$currency->id] = Amount::formatAnything($currency, '0', false); + } $response = [ 'incomes' => $incomes, @@ -161,6 +167,7 @@ class BoxController extends Controller 'size' => \count($sums), ]; + $cache->store($response); return response()->json($response); diff --git a/app/Http/Controllers/Json/ExchangeController.php b/app/Http/Controllers/Json/ExchangeController.php index e4846f43f7..34b8eeddd6 100644 --- a/app/Http/Controllers/Json/ExchangeController.php +++ b/app/Http/Controllers/Json/ExchangeController.php @@ -50,7 +50,7 @@ class ExchangeController extends Controller $rate = $repository->getExchangeRate($fromCurrency, $toCurrency, $date); - if (null === $rate->id) { + if (null === $rate) { Log::debug(sprintf('No cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d'))); // create service: diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php index ed57022b60..0f30b1c9d7 100644 --- a/app/Http/Controllers/Popup/ReportController.php +++ b/app/Http/Controllers/Popup/ReportController.php @@ -127,6 +127,7 @@ class ReportController extends Controller $budget = $this->budgetRepository->findNull((int)$attributes['budgetId']); $account = $this->accountRepository->findNull((int)$attributes['accountId']); + switch (true) { case BalanceLine::ROLE_DEFAULTROLE === $role && null !== $budget->id: // normal row with a budget: @@ -137,10 +138,6 @@ class ReportController extends Controller $journals = $this->popupHelper->balanceForNoBudget($account, $attributes); $budget->name = (string)trans('firefly.no_budget'); break; - case BalanceLine::ROLE_DIFFROLE === $role: - $journals = $this->popupHelper->balanceDifference($account, $attributes); - $budget->name = (string)trans('firefly.leftUnbalanced'); - break; case BalanceLine::ROLE_TAGROLE === $role: // row with tag info. throw new FireflyException('Firefly cannot handle this type of info-button (BalanceLine::TagRole)'); diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 581f88b35d..fc02ea3c55 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -107,9 +107,10 @@ class ProfileController extends Controller $domain = $this->getDomain(); $secret = Google2FA::generateSecretKey(); session()->flash('two-factor-secret', $secret); + $image = Google2FA::getQRCodeInline($domain, auth()->user()->email, $secret, 200); - return view('profile.code', compact('image')); + return view('profile.code', compact('image', 'secret')); } /** diff --git a/app/Http/Controllers/Recurring/CreateController.php b/app/Http/Controllers/Recurring/CreateController.php new file mode 100644 index 0000000000..817f6f73dd --- /dev/null +++ b/app/Http/Controllers/Recurring/CreateController.php @@ -0,0 +1,142 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Recurring; + + +use Carbon\Carbon; +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Http\Requests\RecurrenceFormRequest; +use FireflyIII\Models\RecurrenceRepetition; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use Illuminate\Http\Request; + +/** + * + * Class CreateController + */ +class CreateController extends Controller +{ + /** @var BudgetRepositoryInterface */ + private $budgets; + /** @var RecurringRepositoryInterface */ + private $recurring; + + /** + * + */ + public function __construct() + { + parent::__construct(); + + // translations: + $this->middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-paint-brush'); + app('view')->share('title', trans('firefly.recurrences')); + app('view')->share('subTitle', trans('firefly.create_new_recurrence')); + + $this->recurring = app(RecurringRepositoryInterface::class); + $this->budgets = app(BudgetRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * @param Request $request + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function create(Request $request) + { + $budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets()); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $tomorrow = new Carbon; + $oldRepetitionType = $request->old('repetition_type'); + $tomorrow->addDay(); + + // put previous url in session if not redirect from store (not "create another"). + if (true !== session('recurring.create.fromStore')) { + $this->rememberPreviousUri('recurring.create.uri'); + } + $request->session()->forget('recurring.create.fromStore'); + + // when will it end? + $repetitionEnds = [ + 'forever' => trans('firefly.repeat_forever'), + 'until_date' => trans('firefly.repeat_until_date'), + 'times' => trans('firefly.repeat_times'), + ]; + // what to do in the weekend? + $weekendResponses = [ + RecurrenceRepetition::WEEKEND_DO_NOTHING => trans('firefly.do_nothing'), + RecurrenceRepetition::WEEKEND_SKIP_CREATION => trans('firefly.skip_transaction'), + RecurrenceRepetition::WEEKEND_TO_FRIDAY => trans('firefly.jump_to_friday'), + RecurrenceRepetition::WEEKEND_TO_MONDAY => trans('firefly.jump_to_monday'), + ]; + + // flash some data: + $hasOldInput = null !== $request->old('_token'); + $preFilled = [ + 'first_date' => $tomorrow->format('Y-m-d'), + 'transaction_type' => $hasOldInput ? $request->old('transaction_type') : 'withdrawal', + 'active' => $hasOldInput ? (bool)$request->old('active') : true, + 'apply_rules' => $hasOldInput ? (bool)$request->old('apply_rules') : true, + ]; + $request->session()->flash('preFilled', $preFilled); + + return view( + 'recurring.create', compact('tomorrow', 'oldRepetitionType', 'weekendResponses', 'preFilled', 'repetitionEnds', 'defaultCurrency', 'budgets') + ); + } + + /** + * @param RecurrenceFormRequest $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function store(RecurrenceFormRequest $request) + { + $data = $request->getAll(); + $recurrence = $this->recurring->store($data); + + $request->session()->flash('success', (string)trans('firefly.stored_new_recurrence', ['title' => $recurrence->title])); + app('preferences')->mark(); + + if (1 === (int)$request->get('create_another')) { + // set value so create routine will not overwrite URL: + $request->session()->put('recurring.create.fromStore', true); + + return redirect(route('recurring.create'))->withInput(); + } + + // redirect to previous URL. + return redirect($this->getPreviousUri('recurring.create.uri')); + + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Recurring/DeleteController.php b/app/Http/Controllers/Recurring/DeleteController.php new file mode 100644 index 0000000000..a36a0ce018 --- /dev/null +++ b/app/Http/Controllers/Recurring/DeleteController.php @@ -0,0 +1,93 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Recurring; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\Recurrence; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use Illuminate\Http\Request; + +/** + * Class DeleteController + */ +class DeleteController extends Controller +{ + /** @var RecurringRepositoryInterface */ + private $recurring; + + /** + * + */ + public function __construct() + { + parent::__construct(); + + // translations: + $this->middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-paint-brush'); + app('view')->share('title', trans('firefly.recurrences')); + + $this->recurring = app(RecurringRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * @param Recurrence $recurrence + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function delete(Recurrence $recurrence) + { + $subTitle = trans('firefly.delete_recurring', ['title' => $recurrence->title]); + // put previous url in session + $this->rememberPreviousUri('recurrences.delete.uri'); + + // todo actual number. + $journalsCreated = $this->recurring->getTransactions($recurrence)->count(); + + return view('recurring.delete', compact('recurrence', 'subTitle', 'journalsCreated')); + } + + /** + * @param RecurringRepositoryInterface $repository + * @param Request $request + * @param Recurrence $recurrence + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function destroy(RecurringRepositoryInterface $repository, Request $request, Recurrence $recurrence) + { + $repository->destroy($recurrence); + $request->session()->flash('success', (string)trans('firefly.' . 'recurrence_deleted', ['title' => $recurrence->title])); + app('preferences')->mark(); + + return redirect($this->getPreviousUri('recurrences.delete.uri')); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Recurring/EditController.php b/app/Http/Controllers/Recurring/EditController.php new file mode 100644 index 0000000000..e78b8a820f --- /dev/null +++ b/app/Http/Controllers/Recurring/EditController.php @@ -0,0 +1,169 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Recurring; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Http\Requests\RecurrenceFormRequest; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceRepetition; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Transformers\RecurrenceTransformer; +use Illuminate\Http\Request; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * + * Class EditController + */ +class EditController extends Controller +{ + /** @var BudgetRepositoryInterface */ + private $budgets; + /** @var RecurringRepositoryInterface */ + private $recurring; + + /** + * + */ + public function __construct() + { + parent::__construct(); + + // translations: + $this->middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-paint-brush'); + app('view')->share('title', trans('firefly.recurrences')); + app('view')->share('subTitle', trans('firefly.recurrences')); + + $this->recurring = app(RecurringRepositoryInterface::class); + $this->budgets = app(BudgetRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * @param Request $request + * @param Recurrence $recurrence + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function edit(Request $request, Recurrence $recurrence) + { + + + // use transformer: + $transformer = new RecurrenceTransformer(new ParameterBag); + $array = $transformer->transform($recurrence); + $budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets()); + + // get recurrence type: + // todo move to repository + // todo handle old repetition type as well. + + + /** @var RecurrenceRepetition $repetition */ + $repetition = $recurrence->recurrenceRepetitions()->first(); + $currentRepetitionType = $repetition->repetition_type; + if ('' !== $repetition->repetition_moment) { + $currentRepetitionType .= ',' . $repetition->repetition_moment; + } + + // put previous url in session if not redirect from store (not "return_to_edit"). + if (true !== session('recurrences.edit.fromUpdate')) { + $this->rememberPreviousUri('recurrences.edit.uri'); + } + $request->session()->forget('recurrences.edit.fromUpdate'); + + // assume repeats forever: + $repetitionEnd = 'forever'; + // types of repetitions: + $repetitionEnds = [ + 'forever' => trans('firefly.repeat_forever'), + 'until_date' => trans('firefly.repeat_until_date'), + 'times' => trans('firefly.repeat_times'), + ]; + if (null !== $recurrence->repeat_until) { + $repetitionEnd = 'until_date'; + } + if ($recurrence->repetitions > 0) { + $repetitionEnd = 'times'; + } + + // what to do in the weekend? + $weekendResponses = [ + RecurrenceRepetition::WEEKEND_DO_NOTHING => trans('firefly.do_nothing'), + RecurrenceRepetition::WEEKEND_SKIP_CREATION => trans('firefly.skip_transaction'), + RecurrenceRepetition::WEEKEND_TO_FRIDAY => trans('firefly.jump_to_friday'), + RecurrenceRepetition::WEEKEND_TO_MONDAY => trans('firefly.jump_to_monday'), + ]; + + // code to handle active-checkboxes + $hasOldInput = null !== $request->old('_token'); + // $hasOldInput = false; + $preFilled = [ + 'transaction_type' => strtolower($recurrence->transactionType->type), + 'active' => $hasOldInput ? (bool)$request->old('active') : $recurrence->active, + 'apply_rules' => $hasOldInput ? (bool)$request->old('apply_rules') : $recurrence->apply_rules, + ]; + + return view( + 'recurring.edit', + compact('recurrence', 'array', 'weekendResponses', 'budgets', 'preFilled', 'currentRepetitionType', 'repetitionEnd', 'repetitionEnds') + ); + } + + /** + * @param RecurrenceFormRequest $request + * @param Recurrence $recurrence + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function update(RecurrenceFormRequest $request, Recurrence $recurrence) + { + $data = $request->getAll(); + $this->recurring->update($recurrence, $data); + + $request->session()->flash('success', (string)trans('firefly.updated_recurrence', ['title' => $recurrence->title])); + app('preferences')->mark(); + + if (1 === (int)$request->get('return_to_edit')) { + // set value so edit routine will not overwrite URL: + $request->session()->put('recurrences.edit.fromUpdate', true); + + return redirect(route('recurring.edit', [$recurrence->id]))->withInput(['return_to_edit' => 1]); + } + + // redirect to previous URL. + return redirect($this->getPreviousUri('recurrences.edit.uri')); + } + + +} \ No newline at end of file diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php new file mode 100644 index 0000000000..d7f02b5071 --- /dev/null +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -0,0 +1,240 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Recurring; + + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceRepetition; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Transformers\RecurrenceTransformer; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * + * Class IndexController + */ +class IndexController extends Controller +{ + /** @var RecurringRepositoryInterface */ + private $recurring; + + /** + * + */ + public function __construct() + { + parent::__construct(); + + // translations: + $this->middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-paint-brush'); + app('view')->share('title', trans('firefly.recurrences')); + + $this->recurring = app(RecurringRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * @param Request $request + * + * @throws FireflyException + * @return JsonResponse + */ + function events(Request $request): JsonResponse + { + $return = []; + $start = Carbon::createFromFormat('Y-m-d', $request->get('start')); + $end = Carbon::createFromFormat('Y-m-d', $request->get('end')); + $firstDate = Carbon::createFromFormat('Y-m-d', $request->get('first_date')); + $endDate = '' !== (string)$request->get('end_date') ? Carbon::createFromFormat('Y-m-d', $request->get('end_date')) : null; + $endsAt = (string)$request->get('ends'); + $repetitionType = explode(',', $request->get('type'))[0]; + $repetitions = (int)$request->get('reps'); + $repetitionMoment = ''; + $start->startOfDay(); + + // if $firstDate is beyond $end, simply return an empty array. + if ($firstDate->gt($end)) { + return response()->json([]); + } + // if $firstDate is beyond start, use that one: + $actualStart = clone $firstDate; + + switch ($repetitionType) { + default: + throw new FireflyException(sprintf('Cannot handle repetition type "%s"', $repetitionType)); + case 'daily': + break; + case 'weekly': + case 'monthly': + $repetitionMoment = explode(',', $request->get('type'))[1] ?? '1'; + break; + case 'ndom': + $repetitionMoment = str_ireplace('ndom,', '', $request->get('type')); + break; + case 'yearly': + $repetitionMoment = explode(',', $request->get('type'))[1] ?? '2018-01-01'; + break; + } + $repetition = new RecurrenceRepetition; + $repetition->repetition_type = $repetitionType; + $repetition->repetition_moment = $repetitionMoment; + $repetition->repetition_skip = (int)$request->get('skip'); + $repetition->weekend = (int)$request->get('weekend'); + + $actualEnd = clone $end; + switch ($endsAt) { + default: + throw new FireflyException(sprintf('Cannot generate events for type that ends at "%s".', $endsAt)); + case 'forever': + // simply generate up until $end. No change from default behavior. + $occurrences = $this->recurring->getOccurrencesInRange($repetition, $actualStart, $actualEnd); + break; + case 'until_date': + $actualEnd = $endDate ?? clone $end; + $occurrences = $this->recurring->getOccurrencesInRange($repetition, $actualStart, $actualEnd); + break; + case 'times': + $occurrences = $this->recurring->getXOccurrences($repetition, $actualStart, $repetitions); + break; + } + + + /** @var Carbon $current */ + foreach ($occurrences as $current) { + if ($current->gte($start)) { + $event = [ + 'id' => $repetitionType . $firstDate->format('Ymd'), + 'title' => 'X', + 'allDay' => true, + 'start' => $current->format('Y-m-d'), + 'end' => $current->format('Y-m-d'), + 'editable' => false, + 'rendering' => 'background', + ]; + $return[] = $event; + } + } + + return response()->json($return); + } + + + /** + * @param Request $request + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function index(Request $request) + { + $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $collection = $this->recurring->get(); + + // TODO: split collection into pages + + $transformer = new RecurrenceTransformer(new ParameterBag); + $recurring = []; + /** @var Recurrence $recurrence */ + foreach ($collection as $recurrence) { + $array = $transformer->transform($recurrence); + $array['first_date'] = new Carbon($array['first_date']); + $array['repeat_until'] = null === $array['repeat_until'] ? null : new Carbon($array['repeat_until']); + $array['latest_date'] = null === $array['latest_date'] ? null : new Carbon($array['latest_date']); + $recurring[] = $array; + } + + return view('recurring.index', compact('recurring', 'page', 'pageSize')); + } + + /** + * @param Request $request + * @param Recurrence $recurrence + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws FireflyException + */ + public function show(Request $request, Recurrence $recurrence) + { + $transformer = new RecurrenceTransformer(new ParameterBag); + $array = $transformer->transform($recurrence); + $page = (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $transactions = $this->recurring->getTransactions($recurrence, $page, $pageSize); + + // transform dates back to Carbon objects: + foreach ($array['recurrence_repetitions'] as $index => $repetition) { + foreach ($repetition['occurrences'] as $item => $occurrence) { + $array['recurrence_repetitions'][$index]['occurrences'][$item] = new Carbon($occurrence); + } + } + + $subTitle = trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]); + + return view('recurring.show', compact('recurrence', 'subTitle', 'array','transactions')); + } + + /** + * @param Request $request + * + * @return JsonResponse + */ + public function suggest(Request $request): JsonResponse + { + $today = new Carbon; + $date = Carbon::createFromFormat('Y-m-d', $request->get('date')); + $preSelected = (string)$request->get('pre_select'); + $result = []; + if ($date > $today || (string)$request->get('past') === 'true') { + $weekly = sprintf('weekly,%s', $date->dayOfWeekIso); + $monthly = sprintf('monthly,%s', $date->day); + $dayOfWeek = trans(sprintf('config.dow_%s', $date->dayOfWeekIso)); + $ndom = sprintf('ndom,%s,%s', $date->weekOfMonth, $date->dayOfWeekIso); + $yearly = sprintf('yearly,%s', $date->format('Y-m-d')); + $yearlyDate = $date->formatLocalized(trans('config.month_and_day_no_year')); + $result = [ + 'daily' => ['label' => trans('firefly.recurring_daily'), 'selected' => 0 === strpos($preSelected, 'daily')], + $weekly => ['label' => trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek]), 'selected' => 0 === strpos($preSelected, 'weekly')], + $monthly => ['label' => trans('firefly.recurring_monthly', ['dayOfMonth' => $date->day]), 'selected' => 0 === strpos($preSelected, 'monthly')], + $ndom => ['label' => trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $date->weekOfMonth]), + 'selected' => 0 === strpos($preSelected, 'ndom')], + $yearly => ['label' => trans('firefly.recurring_yearly', ['date' => $yearlyDate]), 'selected' => 0 === strpos($preSelected, 'yearly')], + ]; + } + + + return response()->json($result); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/RuleController.php b/app/Http/Controllers/RuleController.php index 98a781017c..4ec76ebee9 100644 --- a/app/Http/Controllers/RuleController.php +++ b/app/Http/Controllers/RuleController.php @@ -23,13 +23,11 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; -use ExpandedForm; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Requests\RuleFormRequest; use FireflyIII\Http\Requests\SelectTransactionsRequest; use FireflyIII\Http\Requests\TestRuleFormRequest; use FireflyIII\Jobs\ExecuteRuleOnExistingTransactions; -use FireflyIII\Models\AccountType; use FireflyIII\Models\Bill; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleAction; @@ -231,6 +229,14 @@ class RuleController extends Controller $actionCount = \count($oldActions); } + $hasOldInput = null !== $request->old('_token'); + $preFilled = [ + 'active' => $hasOldInput ? (bool)$request->old('active') : $rule->active, + 'stop_processing' => $hasOldInput ? (bool)$request->old('stop_processing') : $rule->stop_processing, + 'strict' => $hasOldInput ? (bool)$request->old('strict') : $rule->strict, + + ]; + // get rule trigger for update / store-journal: $primaryTrigger = $this->ruleRepos->getPrimaryTrigger($rule); $subTitle = trans('firefly.edit_rule', ['title' => $rule->title]); @@ -241,6 +247,8 @@ class RuleController extends Controller } session()->forget('rules.edit.fromUpdate'); + $request->session()->flash('preFilled', $preFilled); + return view('rules.rule.edit', compact('rule', 'subTitle', 'primaryTrigger', 'oldTriggers', 'oldActions', 'triggerCount', 'actionCount')); } @@ -331,11 +339,11 @@ class RuleController extends Controller public function selectTransactions(Rule $rule) { // does the user have shared accounts? - $first = session('first')->format('Y-m-d'); - $today = Carbon::create()->format('Y-m-d'); - $subTitle = (string)trans('firefly.apply_rule_selection', ['title' => $rule->title]); + $first = session('first')->format('Y-m-d'); + $today = Carbon::create()->format('Y-m-d'); + $subTitle = (string)trans('firefly.apply_rule_selection', ['title' => $rule->title]); - return view('rules.rule.select-transactions', compact( 'first', 'today', 'rule', 'subTitle')); + return view('rules.rule.select-transactions', compact('first', 'today', 'rule', 'subTitle')); } /** @@ -429,6 +437,7 @@ class RuleController extends Controller Log::error(sprintf('Could not render view in testTriggers(): %s', $exception->getMessage())); Log::error($exception->getTraceAsString()); } + // @codeCoverageIgnoreEnd return response()->json(['html' => $view, 'warning' => $warning]); @@ -490,6 +499,7 @@ class RuleController extends Controller Log::error(sprintf('Could not render view in testTriggersByRule(): %s', $exception->getMessage())); Log::error($exception->getTraceAsString()); } + // @codeCoverageIgnoreEnd return response()->json(['html' => $view, 'warning' => $warning]); @@ -539,23 +549,39 @@ class RuleController extends Controller { if (0 === $this->ruleRepos->count()) { $data = [ - 'rule_group_id' => $this->ruleRepos->getFirstRuleGroup()->id, - 'stop_processing' => 0, - 'title' => trans('firefly.default_rule_name'), - 'description' => trans('firefly.default_rule_description'), - 'trigger' => 'store-journal', - 'strict' => true, - 'rule-trigger-values' => [ - trans('firefly.default_rule_trigger_description'), - trans('firefly.default_rule_trigger_from_account'), - ], - 'rule-action-values' => [ - trans('firefly.default_rule_action_prepend'), - trans('firefly.default_rule_action_set_category'), - ], + 'rule_group_id' => $this->ruleRepos->getFirstRuleGroup()->id, + 'stop-processing' => 0, + 'title' => trans('firefly.default_rule_name'), + 'description' => trans('firefly.default_rule_description'), + 'trigger' => 'store-journal', + 'strict' => true, + 'rule-triggers' => [ + [ + 'name' => 'description_is', + 'value' => trans('firefly.default_rule_trigger_description'), + 'stop-processing' => false, - 'rule-triggers' => ['description_is', 'from_account_is'], - 'rule-actions' => ['prepend_description', 'set_category'], + ], + [ + 'name' => 'from_account_is', + 'value' => trans('firefly.default_rule_trigger_from_account'), + 'stop-processing' => false, + + ], + + ], + 'rule-actions' => [ + [ + 'name' => 'prepend_description', + 'value' => trans('firefly.default_rule_action_prepend'), + 'stop-processing' => false, + ], + [ + 'name' => 'set_category', + 'value' => trans('firefly.default_rule_action_set_category'), + 'stop-processing' => false, + ], + ], ]; $this->ruleRepos->store($data); @@ -600,6 +626,7 @@ class RuleController extends Controller Log::error(sprintf('Throwable was thrown in getActionsForBill(): %s', $e->getMessage())); Log::error($e->getTraceAsString()); } + // @codeCoverageIgnoreEnd return $actions; @@ -809,6 +836,7 @@ class RuleController extends Controller Log::debug(sprintf('Throwable was thrown in getTriggersForBill(): %s', $e->getMessage())); Log::debug($e->getTraceAsString()); } + // @codeCoverageIgnoreEnd return $triggers; diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php index 360f73eb82..27d3005ee5 100644 --- a/app/Http/Controllers/RuleGroupController.php +++ b/app/Http/Controllers/RuleGroupController.php @@ -121,16 +121,18 @@ class RuleGroupController extends Controller } /** + * @param Request $request * @param RuleGroup $ruleGroup * - * @return View + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function edit(RuleGroup $ruleGroup) + public function edit(Request $request, RuleGroup $ruleGroup) { $subTitle = trans('firefly.edit_rule_group', ['title' => $ruleGroup->title]); - $preFilled = [ - 'active' => $ruleGroup->active, + $hasOldInput = null !== $request->old('_token'); + $preFilled = [ + 'active' => $hasOldInput ? (bool)$request->old('active') : $ruleGroup->active, ]; diff --git a/app/Http/Controllers/Transaction/BulkController.php b/app/Http/Controllers/Transaction/BulkController.php index b1dcce8f4e..aeac8035ea 100644 --- a/app/Http/Controllers/Transaction/BulkController.php +++ b/app/Http/Controllers/Transaction/BulkController.php @@ -87,7 +87,7 @@ class BulkController extends Controller /** - * @param BulkEditJournalRequest $request + * @param BulkEditJournalRequest $request * * @return mixed */ diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index 1b09ccc760..6a4b644244 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -30,6 +30,7 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Http\Request; +use Log; use View; /** @@ -70,6 +71,8 @@ class ConvertController extends Controller { // @codeCoverageIgnoreStart if ($this->isOpeningBalance($journal)) { + Log::debug('This is an opening balance.'); + return $this->redirectToAccount($journal); } // @codeCoverageIgnoreEnd @@ -80,6 +83,7 @@ class ConvertController extends Controller // cannot convert to its own type. if ($sourceType->type === $destinationType->type) { + Log::debug('This is already a transaction of the expected type..'); session()->flash('info', trans('firefly.convert_is_already_type_' . $destinationType->type)); return redirect(route('transactions.show', [$journal->id])); @@ -87,6 +91,7 @@ class ConvertController extends Controller // cannot convert split. if ($journal->transactions()->count() > 2) { + Log::info('This journal has more than two transactions.'); session()->flash('error', trans('firefly.cannot_convert_split_journal')); return redirect(route('transactions.show', [$journal->id])); @@ -98,8 +103,9 @@ class ConvertController extends Controller return view( 'transactions.convert', compact( - 'sourceType', 'destinationType', 'journal', 'positiveAmount', 'sourceAccount', 'destinationAccount', 'sourceType', 'subTitle', 'subTitleIcon' - ) + 'sourceType', 'destinationType', 'journal', 'positiveAmount', 'sourceAccount', 'destinationAccount', 'sourceType', + 'subTitle', 'subTitleIcon' + ) ); } @@ -117,6 +123,7 @@ class ConvertController extends Controller { // @codeCoverageIgnoreStart if ($this->isOpeningBalance($journal)) { + Log::debug('Journal is opening balance, return to account.'); return $this->redirectToAccount($journal); } // @codeCoverageIgnoreEnd @@ -124,12 +131,14 @@ class ConvertController extends Controller $data = $request->all(); if ($journal->transactionType->type === $destinationType->type) { + Log::info('Journal is already of the desired type.'); session()->flash('error', trans('firefly.convert_is_already_type_' . $destinationType->type)); return redirect(route('transactions.show', [$journal->id])); } if ($journal->transactions()->count() > 2) { + Log::info('Journal has more than two transactions.'); session()->flash('error', trans('firefly.cannot_convert_split_journal')); return redirect(route('transactions.show', [$journal->id])); diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php index 8b78a9ccb9..cd08cf2435 100644 --- a/app/Http/Controllers/Transaction/MassController.php +++ b/app/Http/Controllers/Transaction/MassController.php @@ -24,7 +24,6 @@ namespace FireflyIII\Http\Controllers\Transaction; use Carbon\Carbon; use FireflyIII\Helpers\Collector\JournalCollectorInterface; -use FireflyIII\Helpers\Filter\NegativeAmountFilter; use FireflyIII\Helpers\Filter\TransactionViewFilter; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\MassDeleteJournalRequest; @@ -37,10 +36,9 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Transformers\TransactionTransformer; use Illuminate\Support\Collection; +use Illuminate\View\View as IlluminateView; use Preferences; use Symfony\Component\HttpFoundation\ParameterBag; -use View; -use Illuminate\View\View as IlluminateView; /** * Class MassController. @@ -155,10 +153,11 @@ class MassController extends Controller // transform to array $transactions = $collection->map( function (Transaction $transaction) use ($transformer) { - $transaction= $transformer->transform($transaction); + $transaction = $transformer->transform($transaction); // make sure amount is positive: - $transaction['amount'] = app('steam')->positive((string)$transaction['amount']); + $transaction['amount'] = app('steam')->positive((string)$transaction['amount']); $transaction['foreign_amount'] = app('steam')->positive((string)$transaction['foreign_amount']); + return $transaction; } ); @@ -182,11 +181,11 @@ class MassController extends Controller if (null !== $journal) { // get optional fields: $what = strtolower($this->repository->getTransactionType($journal)); - $sourceAccountId = $request->get('source_account_id')[$journal->id] ?? null; + $sourceAccountId = $request->get('source_id')[$journal->id] ?? null; $currencyId = $request->get('transaction_currency_id')[$journal->id] ?? 1; - $sourceAccountName = $request->get('source_account_name')[$journal->id] ?? null; - $destAccountId = $request->get('destination_account_id')[$journal->id] ?? null; - $destAccountName = $request->get('destination_account_name')[$journal->id] ?? null; + $sourceAccountName = $request->get('source_name')[$journal->id] ?? null; + $destAccountId = $request->get('destination_id')[$journal->id] ?? null; + $destAccountName = $request->get('destination_name')[$journal->id] ?? null; $budgetId = (int)($request->get('budget_id')[$journal->id] ?? 0.0); $category = $request->get('category')[$journal->id]; $tags = $journal->tags->pluck('tag')->toArray(); diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 0f65bf03ff..17da1e6fb5 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -34,9 +34,8 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Log; use Preferences; @@ -49,14 +48,8 @@ class SingleController extends Controller { /** @var AttachmentHelperInterface */ private $attachments; - /** @var BudgetRepositoryInterface */ private $budgets; - /** @var CurrencyRepositoryInterface */ - private $currency; - /** @var PiggyBankRepositoryInterface */ - private $piggyBanks; - /** @var JournalRepositoryInterface */ private $repository; @@ -76,9 +69,7 @@ class SingleController extends Controller $this->middleware( function ($request, $next) { $this->budgets = app(BudgetRepositoryInterface::class); - $this->piggyBanks = app(PiggyBankRepositoryInterface::class); $this->attachments = app(AttachmentHelperInterface::class); - $this->currency = app(CurrencyRepositoryInterface::class); $this->repository = app(JournalRepositoryInterface::class); app('view')->share('title', trans('firefly.transactions')); @@ -100,8 +91,7 @@ class SingleController extends Controller $destination = $this->repository->getJournalDestinationAccounts($journal)->first(); $budgetId = $this->repository->getJournalBudgetId($journal); $categoryName = $this->repository->getJournalCategoryName($journal); - - $tags = implode(',', $this->repository->getTags($journal)); + $tags = implode(',', $this->repository->getTags($journal)); /** @var Transaction $transaction */ $transaction = $journal->transactions()->first(); $amount = app('steam')->positive($transaction->amount); @@ -109,10 +99,10 @@ class SingleController extends Controller $preFilled = [ 'description' => $journal->description, - 'source_account_id' => $source->id, - 'source_account_name' => $source->name, - 'destination_account_id' => $destination->id, - 'destination_account_name' => $destination->name, + 'source_id' => $source->id, + 'source_name' => $source->name, + 'destination_id' => $destination->id, + 'destination_name' => $destination->name, 'amount' => $amount, 'source_amount' => $amount, 'destination_amount' => $foreignAmount, @@ -155,19 +145,21 @@ class SingleController extends Controller $what = strtolower($what); $what = $request->old('what') ?? $what; $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); - $piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount(); - $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks); $preFilled = session()->has('preFilled') ? session('preFilled') : []; $subTitle = trans('form.add_new_' . $what); $subTitleIcon = 'fa-plus'; $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; $source = (int)$request->get('source'); + // grab old currency ID from old data: + $currencyID = (int)$request->old('amount_currency_id_amount'); + $preFilled['amount_currency_id_amount'] = $currencyID; + if (($what === 'withdrawal' || $what === 'transfer') && $source > 0) { - $preFilled['source_account_id'] = $source; + $preFilled['source_id'] = $source; } if ($what === 'deposit' && $source > 0) { - $preFilled['destination_account_id'] = $source; + $preFilled['destination_id'] = $source; } session()->put('preFilled', $preFilled); @@ -178,11 +170,9 @@ class SingleController extends Controller } session()->forget('transactions.create.fromStore'); - asort($piggies); - return view( 'transactions.single.create', - compact('subTitleIcon', 'budgets', 'what', 'piggies', 'subTitle', 'optionalFields', 'preFilled') + compact('subTitleIcon', 'budgets', 'what', 'subTitle', 'optionalFields', 'preFilled') ); } @@ -218,7 +208,7 @@ class SingleController extends Controller * * @internal param JournalRepositoryInterface $repository */ - public function destroy(TransactionJournal $transactionJournal) + public function destroy(TransactionJournal $transactionJournal): RedirectResponse { // @codeCoverageIgnoreStart if ($this->isOpeningBalance($transactionJournal)) { @@ -273,35 +263,35 @@ class SingleController extends Controller $pTransaction = $repository->getFirstPosTransaction($journal); $foreignCurrency = $pTransaction->foreignCurrency ?? $pTransaction->transactionCurrency; $preFilled = [ - 'date' => $repository->getJournalDate($journal, null), // $journal->dateAsString() - 'interest_date' => $repository->getJournalDate($journal, 'interest_date'), - 'book_date' => $repository->getJournalDate($journal, 'book_date'), - 'process_date' => $repository->getJournalDate($journal, 'process_date'), - 'category' => $repository->getJournalCategoryName($journal), - 'budget_id' => $repository->getJournalBudgetId($journal), - 'tags' => implode(',', $repository->getTags($journal)), - 'source_account_id' => $sourceAccounts->first()->id, - 'source_account_name' => $sourceAccounts->first()->edit_name, - 'destination_account_id' => $destinationAccounts->first()->id, - 'destination_account_name' => $destinationAccounts->first()->edit_name, + 'date' => $repository->getJournalDate($journal, null), // $journal->dateAsString() + 'interest_date' => $repository->getJournalDate($journal, 'interest_date'), + 'book_date' => $repository->getJournalDate($journal, 'book_date'), + 'process_date' => $repository->getJournalDate($journal, 'process_date'), + 'category' => $repository->getJournalCategoryName($journal), + 'budget_id' => $repository->getJournalBudgetId($journal), + 'tags' => implode(',', $repository->getTags($journal)), + 'source_id' => $sourceAccounts->first()->id, + 'source_name' => $sourceAccounts->first()->edit_name, + 'destination_id' => $destinationAccounts->first()->id, + 'destination_name' => $destinationAccounts->first()->edit_name, // new custom fields: - 'due_date' => $repository->getJournalDate($journal, 'due_date'), - 'payment_date' => $repository->getJournalDate($journal, 'payment_date'), - 'invoice_date' => $repository->getJournalDate($journal, 'invoice_date'), - 'interal_reference' => $repository->getMetaField($journal, 'internal_reference'), - 'notes' => $repository->getNoteText($journal), + 'due_date' => $repository->getJournalDate($journal, 'due_date'), + 'payment_date' => $repository->getJournalDate($journal, 'payment_date'), + 'invoice_date' => $repository->getJournalDate($journal, 'invoice_date'), + 'interal_reference' => $repository->getMetaField($journal, 'internal_reference'), + 'notes' => $repository->getNoteText($journal), // amount fields - 'amount' => $pTransaction->amount, - 'source_amount' => $pTransaction->amount, - 'native_amount' => $pTransaction->amount, - 'destination_amount' => $pTransaction->foreign_amount, - 'currency' => $pTransaction->transactionCurrency, - 'source_currency' => $pTransaction->transactionCurrency, - 'native_currency' => $pTransaction->transactionCurrency, - 'foreign_currency' => $foreignCurrency, - 'destination_currency' => $foreignCurrency, + 'amount' => $pTransaction->amount, + 'source_amount' => $pTransaction->amount, + 'native_amount' => $pTransaction->amount, + 'destination_amount' => $pTransaction->foreign_amount, + 'currency' => $pTransaction->transactionCurrency, + 'source_currency' => $pTransaction->transactionCurrency, + 'native_currency' => $pTransaction->transactionCurrency, + 'foreign_currency' => $foreignCurrency, + 'destination_currency' => $foreignCurrency, ]; // amounts for withdrawals and deposits: @@ -329,9 +319,10 @@ class SingleController extends Controller * @param JournalFormRequest $request * @param JournalRepositoryInterface $repository * - * @return \Illuminate\Http\RedirectResponse + * @return RedirectResponse + * @throws \FireflyIII\Exceptions\FireflyException */ - public function store(JournalFormRequest $request, JournalRepositoryInterface $repository) + public function store(JournalFormRequest $request, JournalRepositoryInterface $repository): RedirectResponse { $doSplit = 1 === (int)$request->get('split_journal'); $createAnother = 1 === (int)$request->get('create_another'); diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index aa25f47fdc..a0b0014576 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -157,7 +157,7 @@ class SplitController extends Controller $type = strtolower($this->repository->getTransactionType($journal)); session()->flash('success', (string)trans('firefly.updated_' . $type, ['description' => $journal->description])); - Preferences::mark(); + app('preferences')->mark(); // @codeCoverageIgnoreStart if (1 === (int)$request->get('return_to_edit')) { @@ -184,30 +184,30 @@ class SplitController extends Controller $sourceAccounts = $this->repository->getJournalSourceAccounts($journal); $destinationAccounts = $this->repository->getJournalDestinationAccounts($journal); $array = [ - 'journal_description' => $request->old('journal_description', $journal->description), - 'journal_amount' => '0', - 'journal_foreign_amount' => '0', - 'sourceAccounts' => $sourceAccounts, - 'journal_source_account_id' => $request->old('journal_source_account_id', $sourceAccounts->first()->id), - 'journal_source_account_name' => $request->old('journal_source_account_name', $sourceAccounts->first()->name), - 'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id), - 'destinationAccounts' => $destinationAccounts, - 'what' => strtolower($this->repository->getTransactionType($journal)), - 'date' => $request->old('date', $this->repository->getJournalDate($journal, null)), - 'tags' => implode(',', $journal->tags->pluck('tag')->toArray()), + 'journal_description' => $request->old('journal_description', $journal->description), + 'journal_amount' => '0', + 'journal_foreign_amount' => '0', + 'sourceAccounts' => $sourceAccounts, + 'journal_source_id' => $request->old('journal_source_id', $sourceAccounts->first()->id), + 'journal_source_name' => $request->old('journal_source_name', $sourceAccounts->first()->name), + 'journal_destination_id' => $request->old('journal_destination_id', $destinationAccounts->first()->id), + 'destinationAccounts' => $destinationAccounts, + 'what' => strtolower($this->repository->getTransactionType($journal)), + 'date' => $request->old('date', $this->repository->getJournalDate($journal, null)), + 'tags' => implode(',', $journal->tags->pluck('tag')->toArray()), // all custom fields: - 'interest_date' => $request->old('interest_date', $this->repository->getMetaField($journal, 'interest_date')), - 'book_date' => $request->old('book_date', $this->repository->getMetaField($journal, 'book_date')), - 'process_date' => $request->old('process_date', $this->repository->getMetaField($journal, 'process_date')), - 'due_date' => $request->old('due_date', $this->repository->getMetaField($journal, 'due_date')), - 'payment_date' => $request->old('payment_date', $this->repository->getMetaField($journal, 'payment_date')), - 'invoice_date' => $request->old('invoice_date', $this->repository->getMetaField($journal, 'invoice_date')), - 'internal_reference' => $request->old('internal_reference', $this->repository->getMetaField($journal, 'internal_reference')), - 'notes' => $request->old('notes', $this->repository->getNoteText($journal)), + 'interest_date' => $request->old('interest_date', $this->repository->getMetaField($journal, 'interest_date')), + 'book_date' => $request->old('book_date', $this->repository->getMetaField($journal, 'book_date')), + 'process_date' => $request->old('process_date', $this->repository->getMetaField($journal, 'process_date')), + 'due_date' => $request->old('due_date', $this->repository->getMetaField($journal, 'due_date')), + 'payment_date' => $request->old('payment_date', $this->repository->getMetaField($journal, 'payment_date')), + 'invoice_date' => $request->old('invoice_date', $this->repository->getMetaField($journal, 'invoice_date')), + 'internal_reference' => $request->old('internal_reference', $this->repository->getMetaField($journal, 'internal_reference')), + 'notes' => $request->old('notes', $this->repository->getNoteText($journal)), // transactions. - 'transactions' => $this->getTransactionDataFromJournal($journal), + 'transactions' => $this->getTransactionDataFromJournal($journal), ]; // update transactions array with old request data. $array['transactions'] = $this->updateWithPrevious($array['transactions'], $request->old()); diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 95cd7d7464..7ad5d34f99 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -34,6 +34,7 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; use FireflyIII\Transformers\TransactionTransformer; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Collection; use Log; @@ -96,6 +97,9 @@ class TransactionController extends Controller if ($end < $start) { [$start, $end] = [$end, $start]; } + + $path = route('transactions.index', [$what, $start->format('Y-m-d'), $end->format('Y-m-d')]); + $startStr = $start->formatLocalized($this->monthAndDayFormat); $endStr = $end->formatLocalized($this->monthAndDayFormat); $subTitle = trans('firefly.title_' . $what . '_between', ['start' => $startStr, 'end' => $endStr]); @@ -150,9 +154,9 @@ class TransactionController extends Controller /** * @param Request $request * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ - public function reconcile(Request $request) + public function reconcile(Request $request): JsonResponse { $transactionIds = $request->get('transactions'); foreach ($transactionIds as $transactionId) { diff --git a/app/Http/Requests/BillFormRequest.php b/app/Http/Requests/BillFormRequest.php index 7b0b54083f..dc2d35cf94 100644 --- a/app/Http/Requests/BillFormRequest.php +++ b/app/Http/Requests/BillFormRequest.php @@ -49,15 +49,15 @@ class BillFormRequest extends Request 'date' => $this->date('date'), 'repeat_freq' => $this->string('repeat_freq'), 'skip' => $this->integer('skip'), - 'active' => $this->boolean('active'), 'notes' => $this->string('notes'), + 'active' => $this->boolean('active'), ]; } /** * @return array */ - public function rules() + public function rules(): array { $nameRule = 'required|between:1,255|uniqueObjectForUser:bills,name'; if ($this->integer('id') > 0) { @@ -73,8 +73,7 @@ class BillFormRequest extends Request 'date' => 'required|date', 'repeat_freq' => 'required|in:weekly,monthly,quarterly,half-year,yearly', 'skip' => 'required|between:0,31', - 'automatch' => 'in:1', - 'active' => 'in:1', + 'active' => 'boolean', ]; return $rules; diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index eaa39797eb..24e450fbb1 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -24,6 +24,8 @@ namespace FireflyIII\Http\Requests; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionType; +use Illuminate\Validation\Validator; +use Log; /** * Class JournalFormRequest. @@ -81,10 +83,10 @@ class JournalFormRequest extends Request 'budget_name' => null, 'category_id' => null, 'category_name' => $this->string('category'), - 'source_id' => $this->integer('source_account_id'), - 'source_name' => $this->string('source_account_name'), - 'destination_id' => $this->integer('destination_account_id'), - 'destination_name' => $this->string('destination_account_name'), + 'source_id' => $this->integer('source_id'), + 'source_name' => $this->string('source_name'), + 'destination_id' => $this->integer('destination_id'), + 'destination_name' => $this->string('destination_name'), 'foreign_currency_id' => null, 'foreign_currency_code' => null, 'foreign_amount' => null, @@ -161,11 +163,11 @@ class JournalFormRequest extends Request 'amount' => 'numeric|required|more:0', 'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable', 'category' => 'between:1,255|nullable', - 'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable', - 'source_account_name' => 'between:1,255|nullable', - 'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable', - 'destination_account_name' => 'between:1,255|nullable', - 'piggy_bank_id' => 'between:1,255|nullable', + 'source_id' => 'numeric|belongsToUser:accounts,id|nullable', + 'source_name' => 'between:1,255|nullable', + 'destination_id' => 'numeric|belongsToUser:accounts,id|nullable', + 'destination_name' => 'between:1,255|nullable', + 'piggy_bank_id' => 'numeric|nullable', // foreign currency amounts 'native_amount' => 'numeric|more:0|nullable', @@ -179,6 +181,22 @@ class JournalFormRequest extends Request return $rules; } + /** + * Configure the validator instance. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + $this->validNativeAmount($validator); + } + ); + } + /** * Inspired by https://www.youtube.com/watch?v=WwnI0RS6J5A. * @@ -193,17 +211,17 @@ class JournalFormRequest extends Request { switch ($what) { case strtolower(TransactionType::WITHDRAWAL): - $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; - $rules['destination_account_name'] = 'between:1,255|nullable'; + $rules['source_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; + $rules['destination_name'] = 'between:1,255|nullable'; break; case strtolower(TransactionType::DEPOSIT): - $rules['source_account_name'] = 'between:1,255|nullable'; - $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; + $rules['source_name'] = 'between:1,255|nullable'; + $rules['destination_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; break; case strtolower(TransactionType::TRANSFER): // this may not work: - $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_account_id'; - $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_account_id'; + $rules['source_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_id'; + $rules['destination_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_id'; break; default: @@ -212,4 +230,73 @@ class JournalFormRequest extends Request return $rules; } + + /** + * @param Validator $validator + */ + private function validNativeAmount(Validator $validator): void + { + $data = $validator->getData(); + $type = $data['what'] ?? 'invalid'; + Log::debug(sprintf('Type is %s', $type)); + if ($type === 'withdrawal') { + + $selectedCurrency = (int)($data['amount_currency_id_amount'] ?? 0); + $accountCurrency = (int)($data['source_account_currency'] ?? 0); + Log::debug(sprintf('Selected currency is %d, account currency is %d', $selectedCurrency, $accountCurrency)); + $nativeAmount = (string)($data['native_amount'] ?? ''); + if ($selectedCurrency !== $accountCurrency && '' === $nativeAmount + && $selectedCurrency !== 0 + && $accountCurrency !== 0 + ) { + Log::debug('ADD validation error on native_amount'); + $validator->errors()->add('native_amount', trans('validation.numeric_native')); + + return; + } + } + + // same thing for deposits: + if ($type === 'deposit') { + $selectedCurrency = (int)($data['amount_currency_id_amount'] ?? 0); + $accountCurrency = (int)($data['destination_account_currency'] ?? 0); + $nativeAmount = (string)($data['native_amount'] ?? ''); + if ($selectedCurrency !== $accountCurrency && '' === $nativeAmount + && $selectedCurrency !== 0 + && $accountCurrency !== 0 + ) { + $validator->errors()->add('native_amount', trans('validation.numeric_native')); + + return; + } + } + + // and for transfers + if ($type === 'transfer') { + + $sourceCurrency = (int)($data['source_account_currency'] ?? 0); + $destinationCurrency = (int)($data['destination_account_currency'] ?? 0); + $sourceAmount = (string)($data['source_amount'] ?? ''); + $destinationAmount = (string)($data['destination_amount'] ?? ''); + + Log::debug(sprintf('Source currency is %d, destination currency is %d', $sourceCurrency, $destinationCurrency)); + + if ($sourceCurrency !== $destinationCurrency && '' === $sourceAmount + && $sourceCurrency !== 0 + && $destinationCurrency !== 0 + ) { + $validator->errors()->add('source_amount', trans('validation.numeric_source')); + } + + if ($sourceCurrency !== $destinationCurrency && '' === $destinationAmount + && $sourceCurrency !== 0 + && $destinationCurrency !== 0 + ) { + $validator->errors()->add('destination_amount', trans('validation.numeric_destination')); + $validator->errors()->add('destination_amount', trans('validation.numeric', ['attribute' => 'destination_amount'])); + } + + return; + } + } } diff --git a/app/Http/Requests/MassEditJournalRequest.php b/app/Http/Requests/MassEditJournalRequest.php index e82e243c80..cd82765c6e 100644 --- a/app/Http/Requests/MassEditJournalRequest.php +++ b/app/Http/Requests/MassEditJournalRequest.php @@ -45,11 +45,11 @@ class MassEditJournalRequest extends Request // fixed return [ - 'description.*' => 'required|min:1,max:255', - 'source_account_id.*' => 'numeric|belongsToUser:accounts,id', - 'destination_account_id.*' => 'numeric|belongsToUser:accounts,id', - 'revenue_account' => 'max:255', - 'expense_account' => 'max:255', + 'description.*' => 'required|min:1,max:255', + 'source_id.*' => 'numeric|belongsToUser:accounts,id', + 'destination_id.*' => 'numeric|belongsToUser:accounts,id', + 'revenue_account' => 'max:255', + 'expense_account' => 'max:255', ]; } } diff --git a/app/Http/Requests/PiggyBankFormRequest.php b/app/Http/Requests/PiggyBankFormRequest.php index fae23428f7..bd6488edd5 100644 --- a/app/Http/Requests/PiggyBankFormRequest.php +++ b/app/Http/Requests/PiggyBankFormRequest.php @@ -62,12 +62,12 @@ class PiggyBankFormRequest extends Request } $rules = [ - 'name' => $nameRule, - 'account_id' => 'required|belongsToUser:accounts', - 'targetamount' => 'required|numeric|more:0', - 'startdate' => 'date', - 'targetdate' => 'date|nullable', - 'order' => 'integer|min:1', + 'name' => $nameRule, + 'account_id' => 'required|belongsToUser:accounts', + 'targetamount' => 'required|numeric|more:0', + 'startdate' => 'date', + 'targetdate' => 'date|nullable', + 'order' => 'integer|min:1', ]; return $rules; diff --git a/app/Http/Requests/RecurrenceFormRequest.php b/app/Http/Requests/RecurrenceFormRequest.php new file mode 100644 index 0000000000..7bebfacbce --- /dev/null +++ b/app/Http/Requests/RecurrenceFormRequest.php @@ -0,0 +1,256 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Requests; + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\TransactionType; +use FireflyIII\Rules\ValidRecurrenceRepetitionType; +use FireflyIII\Rules\ValidRecurrenceRepetitionValue; + +/** + * Class RecurrenceFormRequest + */ +class RecurrenceFormRequest extends Request +{ + + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow logged in users + return auth()->check(); + } + + /** + * @return array + * @throws FireflyException + */ + public function getAll(): array + { + $repetitionData = $this->parseRepetitionData(); + $return = [ + 'recurrence' => [ + 'type' => $this->string('transaction_type'), + 'title' => $this->string('title'), + 'description' => $this->string('recurring_description'), + 'first_date' => $this->date('first_date'), + 'repeat_until' => $this->date('repeat_until'), + 'repetitions' => $this->integer('repetitions'), + 'apply_rules' => $this->boolean('apply_rules'), + 'active' => $this->boolean('active'), + 'repetition_end' => $this->string('repetition_end'), + ], + 'transactions' => [ + [ + 'currency_id' => $this->integer('transaction_currency_id'), + 'currency_code' => null, + 'type' => $this->string('transaction_type'), + 'description' => $this->string('transaction_description'), + 'amount' => $this->string('amount'), + 'foreign_amount' => null, + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'budget_id' => $this->integer('budget_id'), + 'budget_name' => null, + 'category_id' => null, + 'category_name' => $this->string('category'), + + ], + ], + 'meta' => [ + // tags and piggy bank ID. + 'tags' => '' !== $this->string('tags') ? explode(',', $this->string('tags')) : [], + 'piggy_bank_id' => $this->integer('piggy_bank_id'), + 'piggy_bank_name' => null, + ], + 'repetitions' => [ + [ + 'type' => $repetitionData['type'], + 'moment' => $repetitionData['moment'], + 'skip' => $this->integer('skip'), + 'weekend' => $this->integer('weekend'), + ], + ], + + ]; + + // fill in foreign currency data + if (null !== $this->float('foreign_amount')) { + $return['transactions'][0]['foreign_amount'] = $this->string('foreign_amount'); + $return['transactions'][0]['foreign_currency_id'] = $this->integer('foreign_currency_id'); + } + // default values: + $return['transactions'][0]['source_id'] = null; + $return['transactions'][0]['source_name'] = null; + $return['transactions'][0]['destination_id'] = null; + $return['transactions'][0]['destination_name'] = null; + // fill in source and destination account data + switch ($this->string('transaction_type')) { + default: + throw new FireflyException(sprintf('Cannot handle transaction type "%s"', $this->string('transaction_type'))); + case 'withdrawal': + $return['transactions'][0]['source_id'] = $this->integer('source_id'); + $return['transactions'][0]['destination_name'] = $this->string('destination_name'); + break; + case 'deposit': + $return['transactions'][0]['source_name'] = $this->string('source_name'); + $return['transactions'][0]['destination_id'] = $this->integer('destination_id'); + break; + case 'transfer': + $return['transactions'][0]['source_id'] = $this->integer('source_id'); + $return['transactions'][0]['destination_id'] = $this->integer('destination_id'); + break; + } + + return $return; + } + + /** + * @return array + * @throws FireflyException + */ + public function rules(): array + { + $today = new Carbon; + $tomorrow = clone $today; + $tomorrow->addDay(); + $rules = [ + // mandatory info for recurrence. + 'title' => 'required|between:1,255|uniqueObjectForUser:recurrences,title', + 'first_date' => 'required|date|after:' . $today->format('Y-m-d'), + 'repetition_type' => ['required', new ValidRecurrenceRepetitionValue, new ValidRecurrenceRepetitionType, 'between:1,20'], + 'skip' => 'required|numeric|between:0,31', + + // optional for recurrence: + 'recurring_description' => 'between:0,65000', + 'active' => 'numeric|between:0,1', + 'apply_rules' => 'numeric|between:0,1', + + // mandatory for transaction: + 'transaction_description' => 'required|between:1,255', + 'transaction_type' => 'required|in:withdrawal,deposit,transfer', + 'transaction_currency_id' => 'required|exists:transaction_currencies,id', + 'amount' => 'numeric|required|more:0', + // mandatory account info: + 'source_id' => 'numeric|belongsToUser:accounts,id|nullable', + 'source_name' => 'between:1,255|nullable', + 'destination_id' => 'numeric|belongsToUser:accounts,id|nullable', + 'destination_name' => 'between:1,255|nullable', + + // foreign amount data: + 'foreign_amount' => 'nullable|more:0', + + // optional fields: + 'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable', + 'category' => 'between:1,255|nullable', + 'tags' => 'between:1,255|nullable', + ]; + if ($this->integer('foreign_currency_id') > 0) { + $rules['foreign_currency_id'] = 'exists:transaction_currencies,id'; + } + + // if ends after X repetitions, set another rule + if ($this->string('repetition_end') === 'times') { + $rules['repetitions'] = 'required|numeric|between:0,254'; + } + // if foreign amount, currency must be different. + if ($this->float('foreign_amount') !== 0.0) { + $rules['foreign_currency_id'] = 'exists:transaction_currencies,id|different:transaction_currency_id'; + } + + // if ends at date X, set another rule. + if ($this->string('repetition_end') === 'until_date') { + $rules['repeat_until'] = 'required|date|after:' . $tomorrow->format('Y-m-d'); + } + + // switchc on type to expand rules for source and destination accounts: + switch ($this->string('transaction_type')) { + case strtolower(TransactionType::WITHDRAWAL): + $rules['source_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; + $rules['destination_name'] = 'between:1,255|nullable'; + break; + case strtolower(TransactionType::DEPOSIT): + $rules['source_name'] = 'between:1,255|nullable'; + $rules['destination_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; + break; + case strtolower(TransactionType::TRANSFER): + // this may not work: + $rules['source_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_id'; + $rules['destination_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_id'; + + break; + default: + throw new FireflyException(sprintf('Cannot handle transaction type of type "%s"', $this->string('transaction_type'))); // @codeCoverageIgnore + } + + // update some rules in case the user is editing a post: + /** @var Recurrence $recurrence */ + $recurrence = $this->route()->parameter('recurrence'); + if ($recurrence instanceof Recurrence) { + $rules['id'] = 'required|numeric|exists:recurrences,id'; + $rules['title'] = 'required|between:1,255|uniqueObjectForUser:recurrences,title,' . $recurrence->id; + $rules['first_date'] = 'required|date'; + } + + + return $rules; + } + + /** + * @return array + */ + private function parseRepetitionData(): array + { + $value = $this->string('repetition_type'); + $return = [ + 'type' => '', + 'moment' => '', + ]; + + if ($value === 'daily') { + $return['type'] = $value; + } + //monthly,17 + //ndom,3,7 + if (\in_array(substr($value, 0, 6), ['yearly', 'weekly'])) { + $return['type'] = substr($value, 0, 6); + $return['moment'] = substr($value, 7); + } + if (0 === strpos($value, 'monthly')) { + $return['type'] = substr($value, 0, 7); + $return['moment'] = substr($value, 8); + } + if (0 === strpos($value, 'ndom')) { + $return['type'] = substr($value, 0, 4); + $return['moment'] = substr($value, 5); + } + + return $return; + + + } +} \ No newline at end of file diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 1146b31017..c293b6237e 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -47,6 +47,16 @@ class Request extends FormRequest return 1 === (int)$this->input($field); } + /** + * @param string $field + * + * @return float + */ + public function float(string $field): float + { + return (float)$this->get($field); + } + /** * @param string $field * diff --git a/app/Http/Requests/RuleFormRequest.php b/app/Http/Requests/RuleFormRequest.php index 148c24aa43..9ef8d49a60 100644 --- a/app/Http/Requests/RuleFormRequest.php +++ b/app/Http/Requests/RuleFormRequest.php @@ -32,7 +32,7 @@ class RuleFormRequest extends Request /** * @return bool */ - public function authorize() + public function authorize(): bool { // Only allow logged in users return auth()->check(); @@ -43,27 +43,52 @@ class RuleFormRequest extends Request */ public function getRuleData(): array { - return [ - 'title' => $this->string('title'), - 'rule_group_id' => $this->integer('rule_group_id'), - 'active' => $this->boolean('active'), - 'trigger' => $this->string('trigger'), - 'description' => $this->string('description'), - 'rule-triggers' => $this->get('rule-trigger'), - 'rule-trigger-values' => $this->get('rule-trigger-value'), - 'rule-trigger-stop' => $this->get('rule-trigger-stop'), - 'rule-actions' => $this->get('rule-action'), - 'rule-action-values' => $this->get('rule-action-value'), - 'rule-action-stop' => $this->get('rule-action-stop'), - 'stop_processing' => $this->boolean('stop_processing'), - 'strict' => $this->boolean('strict'), + $data = [ + 'title' => $this->string('title'), + 'rule_group_id' => $this->integer('rule_group_id'), + 'active' => $this->boolean('active'), + 'trigger' => $this->string('trigger'), + 'description' => $this->string('description'), + 'stop-processing' => $this->boolean('stop_processing'), + 'strict' => $this->boolean('strict'), + 'rule-triggers' => [], + 'rule-actions' => [], ]; + $triggers = $this->get('rule-trigger'); + $triggerValues = $this->get('rule-trigger-value'); + $triggerStop = $this->get('rule-trigger-stop'); + + $actions = $this->get('rule-action'); + $actionValues = $this->get('rule-action-value'); + $actionStop = $this->get('rule-action-stop'); + + if (\is_array($triggers)) { + foreach ($triggers as $index => $value) { + $data['rule-triggers'][] = [ + 'name' => $value, + 'value' => $triggerValues[$index] ?? '', + 'stop-processing' => (int)($triggerStop[$index] ?? 0) === 1, + ]; + } + } + + if (\is_array($actions)) { + foreach ($actions as $index => $value) { + $data['rule-actions'][] = [ + 'name' => $value, + 'value' => $actionValues[$index] ?? '', + 'stop-processing' => (int)($actionStop[$index] ?? 0) === 1, + ]; + } + } + + return $data; } /** * @return array */ - public function rules() + public function rules(): array { /** @var RuleRepositoryInterface $repository */ $repository = app(RuleRepositoryInterface::class); diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php index ce99023a1f..5e1a4d062f 100644 --- a/app/Http/Requests/SplitJournalFormRequest.php +++ b/app/Http/Requests/SplitJournalFormRequest.php @@ -64,16 +64,16 @@ class SplitJournalFormRequest extends Request foreach ($this->get('transactions') as $index => $transaction) { switch ($data['type']) { case 'withdrawal': - $sourceId = $this->integer('journal_source_account_id'); + $sourceId = $this->integer('journal_source_id'); $destinationName = $transaction['destination_name'] ?? ''; break; case 'deposit': $sourceName = $transaction['source_name'] ?? ''; - $destinationId = $this->integer('journal_destination_account_id'); + $destinationId = $this->integer('journal_destination_id'); break; case 'transfer': - $sourceId = $this->integer('journal_source_account_id'); - $destinationId = $this->integer('journal_destination_account_id'); + $sourceId = $this->integer('journal_source_id'); + $destinationId = $this->integer('journal_destination_id'); break; } $foreignAmount = $transaction['foreign_amount'] ?? null; @@ -91,7 +91,7 @@ class SplitJournalFormRequest extends Request 'currency_id' => $this->integer('journal_currency_id'), 'currency_code' => null, 'description' => $transaction['transaction_description'] ?? '', - 'amount' => $transaction['amount'], + 'amount' => $transaction['amount'] ?? '', 'budget_id' => (int)($transaction['budget_id'] ?? 0.0), 'budget_name' => null, 'category_id' => null, @@ -109,23 +109,23 @@ class SplitJournalFormRequest extends Request public function rules(): array { return [ - 'what' => 'required|in:withdrawal,deposit,transfer', - 'journal_description' => 'required|between:1,255', - 'id' => 'numeric|belongsToUser:transaction_journals,id', - 'journal_source_account_id' => 'numeric|belongsToUser:accounts,id', - 'journal_source_account_name.*' => 'between:1,255', - 'journal_currency_id' => 'required|exists:transaction_currencies,id', - 'date' => 'required|date', - 'interest_date' => 'date|nullable', - 'book_date' => 'date|nullable', - 'process_date' => 'date|nullable', - 'transactions.*.transaction_description' => 'required|between:1,255', - 'transactions.*.destination_account_id' => 'numeric|belongsToUser:accounts,id', - 'transactions.*.destination_name' => 'between:1,255|nullable', - 'transactions.*.amount' => 'required|numeric', - 'transactions.*.budget_id' => 'belongsToUser:budgets,id', - 'transactions.*.category_name' => 'between:1,255|nullable', - 'transactions.*.piggy_bank_id' => 'between:1,255|nullable', + 'what' => 'required|in:withdrawal,deposit,transfer', + 'journal_description' => 'required|between:1,255', + 'id' => 'numeric|belongsToUser:transaction_journals,id', + 'journal_source_id' => 'numeric|belongsToUser:accounts,id', + 'journal_source_name.*' => 'between:1,255', + 'journal_currency_id' => 'required|exists:transaction_currencies,id', + 'date' => 'required|date', + 'interest_date' => 'date|nullable', + 'book_date' => 'date|nullable', + 'process_date' => 'date|nullable', + 'transactions.*.transaction_description' => 'required|between:1,255', + 'transactions.*.destination_id' => 'numeric|belongsToUser:accounts,id', + 'transactions.*.destination_name' => 'between:1,255|nullable', + 'transactions.*.amount' => 'required|numeric', + 'transactions.*.budget_id' => 'belongsToUser:budgets,id', + 'transactions.*.category_name' => 'between:1,255|nullable', + 'transactions.*.piggy_bank_id' => 'numeric|nullable', ]; } @@ -155,8 +155,8 @@ class SplitJournalFormRequest extends Request /** @var array $array */ foreach ($transactions as $array) { if ($array['destination_id'] !== null && $array['source_id'] !== null && $array['destination_id'] === $array['source_id']) { - $validator->errors()->add('journal_source_account_id', trans('validation.source_equals_destination')); - $validator->errors()->add('journal_destination_account_id', trans('validation.source_equals_destination')); + $validator->errors()->add('journal_source_id', trans('validation.source_equals_destination')); + $validator->errors()->add('journal_destination_id', trans('validation.source_equals_destination')); } } diff --git a/app/Import/Configuration/BunqConfigurator.php b/app/Import/Configuration/BunqConfigurator.php deleted file mode 100644 index a115f8e933..0000000000 --- a/app/Import/Configuration/BunqConfigurator.php +++ /dev/null @@ -1,214 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Import\Configuration; - -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Models\ImportJob; -use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Support\Import\Configuration\Bunq\HaveAccounts; -use Log; - -/** - * @deprecated - * @codeCoverageIgnore - * Class BunqConfigurator. - */ -class BunqConfigurator implements ConfiguratorInterface -{ - /** @var ImportJob */ - private $job; - - /** @var ImportJobRepositoryInterface */ - private $repository; - - /** @var string */ - private $warning = ''; - - /** - * ConfiguratorInterface constructor. - */ - public function __construct() - { - } - - /** - * Store any data from the $data array into the job. - * - * @param array $data - * - * @return bool - * - * @throws FireflyException - */ - public function configureJob(array $data): bool - { - if (null === $this->job) { - throw new FireflyException('Cannot call configureJob() without a job.'); - } - $stage = $this->getConfig()['stage'] ?? 'initial'; - Log::debug(sprintf('in getNextData(), for stage "%s".', $stage)); - - switch ($stage) { - case 'have-accounts': - /** @var HaveAccounts $class */ - $class = app(HaveAccounts::class); - $class->setJob($this->job); - $class->storeConfiguration($data); - - // update job for next step and set to "configured". - $config = $this->getConfig(); - $config['stage'] = 'have-account-mapping'; - $this->repository->setConfiguration($this->job, $config); - - return true; - default: - throw new FireflyException(sprintf('Cannot store configuration when job is in state "%s"', $stage)); - break; - } - } - - /** - * Return the data required for the next step in the job configuration. - * - * @return array - * - * @throws FireflyException - */ - public function getNextData(): array - { - if (null === $this->job) { - throw new FireflyException('Cannot call configureJob() without a job.'); - } - $config = $this->getConfig(); - $stage = $config['stage'] ?? 'initial'; - - Log::debug(sprintf('in getNextData(), for stage "%s".', $stage)); - - switch ($stage) { - case 'have-accounts': - /** @var HaveAccounts $class */ - $class = app(HaveAccounts::class); - $class->setJob($this->job); - - return $class->getData(); - default: - return []; - } - } - - /** - * @return string - * - * @throws FireflyException - */ - public function getNextView(): string - { - if (null === $this->job) { - throw new FireflyException('Cannot call configureJob() without a job.'); - } - $stage = $this->getConfig()['stage'] ?? 'initial'; - - Log::debug(sprintf('getNextView: in getNextView(), for stage "%s".', $stage)); - switch ($stage) { - case 'have-accounts': - return 'import.bunq.accounts'; - default: - return ''; - } - } - - /** - * Return possible warning to user. - * - * @return string - */ - public function getWarningMessage(): string - { - return $this->warning; - } - - /** - * @return bool - * - * @throws FireflyException - */ - public function isJobConfigured(): bool - { - if (null === $this->job) { - throw new FireflyException('Cannot call configureJob() without a job.'); - } - $stage = $this->getConfig()['stage'] ?? 'initial'; - - Log::debug(sprintf('in isJobConfigured(), for stage "%s".', $stage)); - switch ($stage) { - case 'have-accounts': - Log::debug('isJobConfigured returns false'); - - return false; - default: - Log::debug('isJobConfigured returns true'); - - return true; - } - } - - /** - * @param ImportJob $job - */ - public function setJob(ImportJob $job): void - { - // make repository - $this->repository = app(ImportJobRepositoryInterface::class); - $this->repository->setUser($job->user); - - // set default config: - $defaultConfig = [ - 'is-redirected' => false, - 'stage' => 'initial', - 'auto-start' => true, - 'apply-rules' => true, - ]; - $currentConfig = $this->repository->getConfiguration($job); - $finalConfig = array_merge($defaultConfig, $currentConfig); - - // set default extended status: - $extendedStatus = $this->repository->getExtendedStatus($job); - $extendedStatus['steps'] = 8; - - // save to job: - $job = $this->repository->setConfiguration($job, $finalConfig); - $job = $this->repository->setExtendedStatus($job, $extendedStatus); - $this->job = $job; - - } - - /** - * Shorthand method. - * - * @return array - */ - private function getConfig(): array - { - return $this->repository->getConfiguration($this->job); - } -} diff --git a/app/Import/Configuration/ConfiguratorInterface.php b/app/Import/Configuration/ConfiguratorInterface.php deleted file mode 100644 index a1e961b0ec..0000000000 --- a/app/Import/Configuration/ConfiguratorInterface.php +++ /dev/null @@ -1,80 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Import\Configuration; - -use FireflyIII\Models\ImportJob; - -/** - * @deprecated - * @codeCoverageIgnore - * Interface ConfiguratorInterface. - */ -interface ConfiguratorInterface -{ - /** - * ConfiguratorInterface constructor. - */ - public function __construct(); - - /** - * Store any data from the $data array into the job. - * - * @param array $data - * - * @return bool - */ - public function configureJob(array $data): bool; - - /** - * Return the data required for the next step in the job configuration. - * - * @return array - */ - public function getNextData(): array; - - /** - * Returns the view of the next step in the job configuration. - * - * @return string - */ - public function getNextView(): string; - - /** - * Return possible warning to user. - * - * @return string - */ - public function getWarningMessage(): string; - - /** - * Returns true when the initial configuration for this job is complete. - * - * @return bool - */ - public function isJobConfigured(): bool; - - /** - * @param ImportJob $job - */ - public function setJob(ImportJob $job); -} diff --git a/app/Import/Converter/Amount.php b/app/Import/Converter/Amount.php index 21b3769c0c..070842a050 100644 --- a/app/Import/Converter/Amount.php +++ b/app/Import/Converter/Amount.php @@ -98,8 +98,31 @@ class Amount implements ConverterInterface $value = str_replace($search, '', $value); Log::debug(sprintf('No decimal character found. Converted amount from "%s" to "%s".', $original, $value)); } + if ($value{0} === '.') { + $value = '0' . $value; + } - return (string)number_format(round(floatval($value), 12), 12, '.', ''); + if (is_numeric($value)) { + Log::debug(sprintf('Final NUMERIC value is: "%s"', $value)); + + return $value; + } + Log::debug(sprintf('Final value is: "%s"', $value)); + $formatted = sprintf('%01.12f', $value); + Log::debug(sprintf('Is formatted to : "%s"', $formatted)); + + return $formatted; + } + + private function bcround($number, $scale = 0) + { + $fix = "5"; + for ($i = 0; $i < $scale; $i++) { + $fix = "0$fix"; + } + $number = bcadd($number, "0.$fix", $scale + 1); + + return bcdiv($number, "1.0", $scale); } /** @@ -109,6 +132,11 @@ class Amount implements ConverterInterface */ private function stripAmount(string $value): string { + if (0 === strpos($value, '--')) { + $value = substr($value, 2); + } + + $str = preg_replace('/[^\-\(\)\.\,0-9 ]/', '', $value); $len = \strlen($str); if ('(' === $str[0] && ')' === $str[$len - 1]) { diff --git a/app/Import/Converter/RabobankDebitCredit.php b/app/Import/Converter/RabobankDebitCredit.php index d426e52b5b..764a7f907e 100644 --- a/app/Import/Converter/RabobankDebitCredit.php +++ b/app/Import/Converter/RabobankDebitCredit.php @@ -44,7 +44,7 @@ class RabobankDebitCredit implements ConverterInterface return -1; } // old format: - if('A' === $value) { + if ('A' === $value) { Log::debug('Return -1'); return -1; diff --git a/app/Import/JobConfiguration/FakeJobConfiguration.php b/app/Import/JobConfiguration/FakeJobConfiguration.php index 81a96adfd7..3812a78d59 100644 --- a/app/Import/JobConfiguration/FakeJobConfiguration.php +++ b/app/Import/JobConfiguration/FakeJobConfiguration.php @@ -106,6 +106,7 @@ class FakeJobConfiguration implements JobConfigurationInterface /** * Return the data required for the next step in the job configuration. + * * @codeCoverageIgnore * @return array */ @@ -144,6 +145,7 @@ class FakeJobConfiguration implements JobConfigurationInterface if (strtolower($album) !== 'station to station' && $this->importJob->stage !== 'new') { return 'import.fake.enter-album'; } + return 'impossible-view'; // @codeCoverageIgnore } @@ -152,7 +154,7 @@ class FakeJobConfiguration implements JobConfigurationInterface */ public function setImportJob(ImportJob $importJob): void { - $this->importJob = $importJob; + $this->importJob = $importJob; $this->repository = app(ImportJobRepositoryInterface::class); $this->repository->setUser($importJob->user); } diff --git a/app/Import/JobConfiguration/FileJobConfiguration.php b/app/Import/JobConfiguration/FileJobConfiguration.php index cfc1d18966..5820b38951 100644 --- a/app/Import/JobConfiguration/FileJobConfiguration.php +++ b/app/Import/JobConfiguration/FileJobConfiguration.php @@ -28,10 +28,10 @@ namespace FireflyIII\Import\JobConfiguration; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Support\Import\JobConfiguration\File\FileConfigurationInterface; use FireflyIII\Support\Import\JobConfiguration\File\ConfigureMappingHandler; use FireflyIII\Support\Import\JobConfiguration\File\ConfigureRolesHandler; use FireflyIII\Support\Import\JobConfiguration\File\ConfigureUploadHandler; +use FireflyIII\Support\Import\JobConfiguration\File\FileConfigurationInterface; use FireflyIII\Support\Import\JobConfiguration\File\NewFileJobHandler; use Illuminate\Support\MessageBag; @@ -120,7 +120,7 @@ class FileJobConfiguration implements JobConfigurationInterface */ public function setImportJob(ImportJob $importJob): void { - $this->importJob = $importJob; + $this->importJob = $importJob; $this->repository = app(ImportJobRepositoryInterface::class); $this->repository->setUser($importJob->user); } diff --git a/app/Import/Prerequisites/BunqPrerequisites.php b/app/Import/Prerequisites/BunqPrerequisites.php index ea39453f3a..21334ced35 100644 --- a/app/Import/Prerequisites/BunqPrerequisites.php +++ b/app/Import/Prerequisites/BunqPrerequisites.php @@ -116,10 +116,11 @@ class BunqPrerequisites implements PrerequisitesInterface $environment = $this->getBunqEnvironment(); $deviceDescription = 'Firefly III v' . config('firefly.version'); $permittedIps = [$externalIP]; + Log::debug(sprintf('Environment for bunq is %s', $environment->getChoiceString())); try { /** @var ApiContext $object */ - $object = app(ApiContext::class); + $object = app(ApiContext::class); $apiContext = $object->create($environment, $apiKey, $deviceDescription, $permittedIps); } catch (FireflyException $e) { $messages = new MessageBag(); diff --git a/app/Import/Prerequisites/FakePrerequisites.php b/app/Import/Prerequisites/FakePrerequisites.php index ac8c07affb..86642e8066 100644 --- a/app/Import/Prerequisites/FakePrerequisites.php +++ b/app/Import/Prerequisites/FakePrerequisites.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace FireflyIII\Import\Prerequisites; use FireflyIII\User; -use Illuminate\Http\Request; use Illuminate\Support\MessageBag; /** diff --git a/app/Import/Prerequisites/PrerequisitesInterface.php b/app/Import/Prerequisites/PrerequisitesInterface.php index 588821eeca..f3f82ae5be 100644 --- a/app/Import/Prerequisites/PrerequisitesInterface.php +++ b/app/Import/Prerequisites/PrerequisitesInterface.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace FireflyIII\Import\Prerequisites; use FireflyIII\User; -use Illuminate\Http\Request; use Illuminate\Support\MessageBag; /** diff --git a/app/Import/Routine/SpectreRoutine.php b/app/Import/Routine/SpectreRoutine.php index 1c51d8d697..83aea2664a 100644 --- a/app/Import/Routine/SpectreRoutine.php +++ b/app/Import/Routine/SpectreRoutine.php @@ -25,8 +25,8 @@ namespace FireflyIII\Import\Routine; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Support\Import\Routine\Spectre\StageImportDataHandler; use FireflyIII\Support\Import\Routine\Spectre\StageAuthenticatedHandler; +use FireflyIII\Support\Import\Routine\Spectre\StageImportDataHandler; use FireflyIII\Support\Import\Routine\Spectre\StageNewHandler; use Log; diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 01315fbe9f..15d5ae58fa 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -108,6 +108,8 @@ class ImportArrayStorage $this->setStatus('rules_applied'); } + app('preferences')->mark(); + return $collection; } @@ -215,7 +217,7 @@ class ImportArrayStorage $collector = app(JournalCollectorInterface::class); $collector->setUser($this->importJob->user); $collector->setAllAssetAccounts() - ->ignoreCache() + ->ignoreCache() ->setTypes([TransactionType::TRANSFER]) ->withOpposingAccount(); $collector->removeFilter(InternalTransferFilter::class); @@ -301,7 +303,7 @@ class ImportArrayStorage 'existing' => $existingId, 'description' => $transaction['description'] ?? '', 'amount' => $transaction['transactions'][0]['amount'] ?? 0, - 'date' => isset($transaction['date']) ? $transaction['date'] : '', + 'date' => $transaction['date'] ?? '', ] ); @@ -411,7 +413,14 @@ class ImportArrayStorage $store['date'] = Carbon::createFromFormat('Y-m-d', $store['date']); $store['description'] = $store['description'] === '' ? '(empty description)' : $store['description']; // store the journal. - $journal = $this->journalRepos->store($store); + try { + $journal = $this->journalRepos->store($store); + } catch (FireflyException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + $this->repository->addErrorMessage($this->importJob, sprintf('Row #%d could not be imported. %s', $index, $e->getMessage())); + continue; + } Log::debug(sprintf('Stored as journal #%d', $journal->id)); $collection->push($journal); } diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php new file mode 100644 index 0000000000..d4a09795af --- /dev/null +++ b/app/Jobs/CreateRecurringTransactions.php @@ -0,0 +1,422 @@ +startOfDay(); + $this->date = $date; + $this->repository = app(RecurringRepositoryInterface::class); + $this->journalRepository = app(JournalRepositoryInterface::class); + + } + + /** + * Execute the job. + * + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function handle(): void + { + Log::debug(sprintf('Now at start of CreateRecurringTransactions() job for %s.', $this->date->format('D d M Y'))); + $recurrences = $this->repository->getAll(); + Log::debug(sprintf('Count of collection is %d', $recurrences->count())); + + $result = []; + + /** @var Collection $filtered */ + $filtered = $recurrences->filter( + function (Recurrence $recurrence) { + return $this->validRecurrence($recurrence); + + } + ); + Log::debug(sprintf('Left after filtering is %d', $filtered->count())); + /** @var Recurrence $recurrence */ + foreach ($filtered as $recurrence) { + if (!isset($result[$recurrence->user_id])) { + $result[$recurrence->user_id] = new Collection; + } + + $this->repository->setUser($recurrence->user); + $this->journalRepository->setUser($recurrence->user); + Log::debug(sprintf('Now at recurrence #%d', $recurrence->id)); + $created = $this->handleRepetitions($recurrence); + Log::debug(sprintf('Done with recurrence #%d', $recurrence->id)); + $result[$recurrence->user_id] = $result[$recurrence->user_id]->merge($created); + + // apply rules: + $this->applyRules($recurrence->user, $created); + } + + Log::debug('Now running report thing.'); + // will now send email to users. + foreach ($result as $userId => $journals) { + event(new RequestedReportOnJournals($userId, $journals)); + } + + Log::debug('Done with handle()'); + } + + /** + * Return recurring transaction is active. + * + * @param Recurrence $recurrence + * + * @return bool + */ + private function active(Recurrence $recurrence): bool + { + return $recurrence->active; + } + + /** + * @param User $user + * @param Collection $journals + */ + private function applyRules(User $user, Collection $journals): void + { + $userId = $user->id; + if (!isset($this->rules[$userId])) { + $this->rules[$userId] = $this->getRules($user); + } + // run the rules: + if ($this->rules[$userId]->count() > 0) { + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $this->rules[$userId]->each( + function (Rule $rule) use ($journal) { + Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id)); + $processor = Processor::make($rule); + $processor->handleTransactionJournal($journal); + if ($rule->stop_processing) { + return; + } + } + ); + } + } + } + + /** + * @param array $occurrences + * + * @return array + */ + private function debugArray(array $occurrences): array + { + $return = []; + foreach ($occurrences as $entry) { + $return[] = $entry->format('Y-m-d'); + } + + return $return; + } + + /** + * @param User $user + * + * @return Collection + */ + private function getRules(User $user): Collection + { + /** @var RuleRepositoryInterface $repository */ + $repository = app(RuleRepositoryInterface::class); + $repository->setUser($user); + $set = $repository->getForImport(); + + Log::debug(sprintf('Found %d user rules.', $set->count())); + + return $set; + } + + /** + * @param Recurrence $recurrence + * + * @return Carbon + */ + private function getStartDate(Recurrence $recurrence): Carbon + { + $startDate = clone $recurrence->first_date; + if (null !== $recurrence->latest_date && $recurrence->latest_date->gte($startDate)) { + $startDate = clone $recurrence->latest_date; + } + + return $startDate; + } + + /** + * @param Recurrence $recurrence + * + * @return array + */ + private function getTransactionData(Recurrence $recurrence): array + { + $transactions = $recurrence->recurrenceTransactions()->get(); + $return = []; + /** @var RecurrenceTransaction $transaction */ + foreach ($transactions as $index => $transaction) { + $single = [ + 'currency_id' => $transaction->transaction_currency_id, + 'currency_code' => null, + 'description' => null, + 'amount' => $transaction->amount, + 'budget_id' => $this->repository->getBudget($transaction), + 'budget_name' => null, + 'category_id' => null, + 'category_name' => $this->repository->getCategory($transaction), + 'source_id' => $transaction->source_id, + 'source_name' => null, + 'destination_id' => $transaction->destination_id, + 'destination_name' => null, + 'foreign_currency_id' => $transaction->foreign_currency_id, + 'foreign_currency_code' => null, + 'foreign_amount' => $transaction->foreign_amount, + 'reconciled' => false, + 'identifier' => $index, + ]; + $return[] = $single; + } + + return $return; + } + + /** + * @param Recurrence $recurrence + * @param array $occurrences + * + * @return Collection + * @throws \FireflyIII\Exceptions\FireflyException + */ + private function handleOccurrences(Recurrence $recurrence, array $occurrences): Collection + { + $collection = new Collection; + /** @var Carbon $date */ + foreach ($occurrences as $date) { + Log::debug(sprintf('Now at date %s.', $date->format('Y-m-d'))); + if ($date->ne($this->date)) { + Log::debug(sprintf('%s is not not today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d'))); + + continue; + } + Log::debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d'))); + + // count created journals on THIS day. + $created = $this->repository->getJournals($recurrence, $date, $date); + if ($created->count() > 0) { + Log::info(sprintf('Already created %d journal(s) for date %s', $created->count(), $date->format('Y-m-d'))); + continue; + } + + // create transaction array and send to factory. + $array = [ + 'type' => $recurrence->transactionType->type, + 'date' => $date, + 'tags' => $this->repository->getTags($recurrence), + 'user' => $recurrence->user_id, + 'notes' => trans('firefly.created_from_recurrence', ['id' => $recurrence->id, 'title' => $recurrence->title]), + + // journal data: + 'description' => $recurrence->recurrenceTransactions()->first()->description, + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'bill_id' => null, + 'bill_name' => null, + 'recurrence_id' => (int)$recurrence->id, + + // transaction data: + 'transactions' => $this->getTransactionData($recurrence), + ]; + $journal = $this->journalRepository->store($array); + Log::info(sprintf('Created new journal #%d', $journal->id)); + + $collection->push($journal); + // update recurring thing: + $recurrence->latest_date = $date; + $recurrence->save(); + } + + return $collection; + } + + /** + * Separate method that will loop all repetitions and do something with it. Will return + * all created transaction journals. + * + * @param Recurrence $recurrence + * + * @return Collection + * + * @throws \FireflyIII\Exceptions\FireflyException + */ + private function handleRepetitions(Recurrence $recurrence): Collection + { + $collection = new Collection; + /** @var RecurrenceRepetition $repetition */ + foreach ($recurrence->recurrenceRepetitions as $repetition) { + Log::debug( + sprintf( + 'Now repeating %s with value "%s", skips every %d time(s)', $repetition->repetition_type, $repetition->repetition_moment, + $repetition->repetition_skip + ) + ); + + // start looping from $startDate to today perhaps we have a hit? + // add two days to $this->date so we always include the weekend. + $includeWeekend = clone $this->date; + $includeWeekend->addDays(2); + $occurrences = $this->repository->getOccurrencesInRange($repetition, $recurrence->first_date, $includeWeekend); + Log::debug( + sprintf( + 'Calculated %d occurrences between %s and %s', + \count($occurrences), + $recurrence->first_date->format('Y-m-d'), + $includeWeekend->format('Y-m-d') + ), $this->debugArray($occurrences) + ); + unset($includeWeekend); + + $result = $this->handleOccurrences($recurrence, $occurrences); + $collection = $collection->merge($result); + } + + return $collection; + } + + /** + * @param Recurrence $recurrence + * + * @return bool + */ + private function hasFiredToday(Recurrence $recurrence): bool + { + return null !== $recurrence->latest_date && $recurrence->latest_date->eq($this->date); + } + + /** + * @param $recurrence + * + * @return bool + */ + private function hasNotStartedYet(Recurrence $recurrence): bool + { + $startDate = $this->getStartDate($recurrence); + + return $startDate->gt($this->date); + } + + /** + * Return true if the $repeat_until date is in the past. + * + * @param Recurrence $recurrence + * + * @return bool + */ + private function repeatUntilHasPassed(Recurrence $recurrence): bool + { + // date has passed + return null !== $recurrence->repeat_until && $recurrence->repeat_until->lt($this->date); + } + + /** + * @param Recurrence $recurrence + * + * @return bool + */ + private function validRecurrence(Recurrence $recurrence): bool + { + // is not active. + if (!$this->active($recurrence)) { + Log::info(sprintf('Recurrence #%d is not active. Skipped.', $recurrence->id)); + + return false; + } + + // has repeated X times. + $journals = $this->repository->getJournals($recurrence, null, null); + if ($recurrence->repetitions !== 0 && $journals->count() >= $recurrence->repetitions) { + Log::info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $recurrence->repetitions)); + + return false; + } + + // is no longer running + if ($this->repeatUntilHasPassed($recurrence)) { + Log::info( + sprintf( + 'Recurrence #%d was set to run until %s, and today\'s date is %s. Skipped.', + $recurrence->id, + $recurrence->repeat_until->format('Y-m-d'), + $this->date->format('Y-m-d') + ) + ); + + return false; + } + + // first_date is in the future + if ($this->hasNotStartedYet($recurrence)) { + Log::info( + sprintf( + 'Recurrence #%d is set to run on %s, and today\'s date is %s. Skipped.', + $recurrence->id, + $recurrence->first_date->format('Y-m-d'), + $this->date->format('Y-m-d') + ) + ); + + return false; + } + + // already fired today (with success): + if ($this->hasFiredToday($recurrence)) { + Log::info(sprintf('Recurrence #%d has already fired today. Skipped.', $recurrence->id)); + + return false; + } + + return true; + + } +} diff --git a/app/Mail/ReportNewJournalsMail.php b/app/Mail/ReportNewJournalsMail.php new file mode 100644 index 0000000000..a483625c1a --- /dev/null +++ b/app/Mail/ReportNewJournalsMail.php @@ -0,0 +1,77 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Mail; + +use Illuminate\Bus\Queueable; +use Illuminate\Mail\Mailable; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Collection; + +/** + * Class ReportNewJournalsMail. + * + * Sends a list of newly created journals to the user. + */ +class ReportNewJournalsMail extends Mailable +{ + use Queueable, SerializesModels; + + /** @var string Email address of the user */ + public $email; + /** @var string IP address of user (if known) */ + public $ipAddress; + + /** @var Collection A collection of journals */ + public $journals; + + /** + * ConfirmEmailChangeMail constructor. + * + * @param string $email + * @param string $ipAddress + * @param Collection $journals + */ + public function __construct(string $email, string $ipAddress, Collection $journals) + { + $this->email = $email; + $this->ipAddress = $ipAddress; + $this->journals = $journals; + } + + /** + * Build the message. + * + * @return $this + */ + public function build(): self + { + $subject = $this->journals->count() === 1 + ? 'Firefly III has created a new transaction' + : sprintf( + 'Firefly III has created new %d transactions', $this->journals->count() + ); + + return $this->view('emails.report-new-journals-html')->text('emails.report-new-journals-text') + ->subject($subject); + } +} diff --git a/app/Models/Account.php b/app/Models/Account.php index 1e79b0aa2c..adcfb61dc7 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -38,10 +38,13 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Account. * - * @property int $id - * @property string $name - * @property string $iban + * @property int $id + * @property string $name + * @property string $iban * @property AccountType $accountType + * @property bool $active + * @property string $virtual_balance + * @property User $user */ class Account extends Model { diff --git a/app/Models/AccountMeta.php b/app/Models/AccountMeta.php index aa5bb522fd..7635b179dc 100644 --- a/app/Models/AccountMeta.php +++ b/app/Models/AccountMeta.php @@ -24,10 +24,10 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use FireflyIII\Models\Account; /** * Class AccountMeta. + * @property string $data */ class AccountMeta extends Model { diff --git a/app/Models/AccountType.php b/app/Models/AccountType.php index 9729d5a34f..de1d5794cd 100644 --- a/app/Models/AccountType.php +++ b/app/Models/AccountType.php @@ -24,10 +24,10 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; -use FireflyIII\Models\Account; /** * Class AccountType. + * * @property string $type * */ diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php index 2c1a1e61b6..c0d71a8fa7 100644 --- a/app/Models/Attachment.php +++ b/app/Models/Attachment.php @@ -22,16 +22,31 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Crypt; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\User; /** * Class Attachment. + * + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property string $attachable_type + * @property string $md5 + * @property string $filename + * @property string $title + * @property string $description + * @property string $notes + * @property string $mime + * @property int $size + * @property User $user + * @property bool $uploaded */ class Attachment extends Model { @@ -100,9 +115,9 @@ class Attachment extends Model * @return null|string * @throws \Illuminate\Contracts\Encryption\DecryptException */ - public function getDescriptionAttribute($value) + public function getDescriptionAttribute($value): ?string { - if (null === $value || 0 === \strlen($value)) { + if (null === $value || '' === $value) { return null; } @@ -116,9 +131,9 @@ class Attachment extends Model * @return null|string * @throws \Illuminate\Contracts\Encryption\DecryptException */ - public function getFilenameAttribute($value) + public function getFilenameAttribute($value): ?string { - if (null === $value || 0 === \strlen($value)) { + if (null === $value || '' === $value) { return null; } @@ -132,9 +147,9 @@ class Attachment extends Model * @return null|string * @throws \Illuminate\Contracts\Encryption\DecryptException */ - public function getMimeAttribute($value) + public function getMimeAttribute($value): ?string { - if (null === $value || 0 === \strlen($value)) { + if (null === $value || '' === $value) { return null; } @@ -148,9 +163,9 @@ class Attachment extends Model * @return null|string * @throws \Illuminate\Contracts\Encryption\DecryptException */ - public function getTitleAttribute($value) + public function getTitleAttribute($value): ?string { - if (null === $value || 0 === \strlen($value)) { + if (null === $value || '' === $value) { return null; } @@ -169,13 +184,14 @@ class Attachment extends Model /** * @codeCoverageIgnore * - * @param string $value - * - * @throws \Illuminate\Contracts\Encryption\EncryptException + * @param string|null $value */ - public function setDescriptionAttribute(string $value) + public function setDescriptionAttribute(string $value = null): void { - $this->attributes['description'] = Crypt::encrypt($value); + if (null !== $value) { + $this->attributes['description'] = Crypt::encrypt($value); + } + } /** @@ -185,7 +201,7 @@ class Attachment extends Model * * @throws \Illuminate\Contracts\Encryption\EncryptException */ - public function setFilenameAttribute(string $value) + public function setFilenameAttribute(string $value): void { $this->attributes['filename'] = Crypt::encrypt($value); } @@ -197,7 +213,7 @@ class Attachment extends Model * * @throws \Illuminate\Contracts\Encryption\EncryptException */ - public function setMimeAttribute(string $value) + public function setMimeAttribute(string $value): void { $this->attributes['mime'] = Crypt::encrypt($value); } @@ -209,9 +225,11 @@ class Attachment extends Model * * @throws \Illuminate\Contracts\Encryption\EncryptException */ - public function setTitleAttribute(string $value) + public function setTitleAttribute(string $value = null): void { - $this->attributes['title'] = Crypt::encrypt($value); + if (null !== $value) { + $this->attributes['title'] = Crypt::encrypt($value); + } } /** diff --git a/app/Models/AvailableBudget.php b/app/Models/AvailableBudget.php index 1e74d00af1..6cfbc53862 100644 --- a/app/Models/AvailableBudget.php +++ b/app/Models/AvailableBudget.php @@ -22,14 +22,25 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; -use FireflyIII\User; -use FireflyIII\Models\TransactionCurrency; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class AvailableBudget. + * + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property User $user + * @property TransactionCurrency $transactionCurrency + * @property int $transaction_currency_id + * @property Carbon $start_date + * @property Carbon $end_date + * @property string $amount */ class AvailableBudget extends Model { @@ -50,11 +61,29 @@ class AvailableBudget extends Model /** @var array */ protected $fillable = ['user_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date']; + /** + * @param string $value + * + * @return AvailableBudget + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public static function routeBinder(string $value): AvailableBudget + { + if (auth()->check()) { + $availableBudgetId = (int)$value; + $availableBudget = auth()->user()->availableBudgets()->find($availableBudgetId); + if (null !== $availableBudget) { + return $availableBudget; + } + } + throw new NotFoundHttpException; + } + /** * @codeCoverageIgnore * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function transactionCurrency() + public function transactionCurrency(): BelongsTo { return $this->belongsTo(TransactionCurrency::class); } diff --git a/app/Models/Bill.php b/app/Models/Bill.php index bf4c01ff12..6693972b20 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -22,21 +22,34 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Crypt; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Collection; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Bill. * - * @property bool $active - * @property int $transaction_currency_id - * @property string $amount_min - * @property string $amount_max + * @property bool $active + * @property int $transaction_currency_id + * @property string $amount_min + * @property string $amount_max + * @property int $id + * @property string $name + * @property Collection $notes + * @property TransactionCurrency $transactionCurrency + * @property Carbon $created_at + * @property Carbon $updated_at + * @property Carbon $date + * @property string $repeat_freq + * @property int $skip + * @property bool $automatch + * @property User $user */ class Bill extends Model { diff --git a/app/Models/Budget.php b/app/Models/Budget.php index 6fc3e3d6f4..d3c0ff820d 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -23,17 +23,18 @@ declare(strict_types=1); namespace FireflyIII\Models; use Crypt; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\User; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\BudgetLimit; /** * Class Budget. + * + * @property int $id + * @property string $name + * @property bool $active */ class Budget extends Model { diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index 615c2d90ee..7559c258b2 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -22,12 +22,21 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\Budget; /** * Class BudgetLimit. + * + * @property Budget $budget + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property Carbon $start_date + * @property Carbon $end_date + * @property string $amount */ class BudgetLimit extends Model { @@ -42,8 +51,8 @@ class BudgetLimit extends Model 'updated_at' => 'datetime', 'start_date' => 'date', 'end_date' => 'date', - 'repeats' => 'boolean', ]; + protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount']; /** * @param string $value @@ -68,20 +77,10 @@ class BudgetLimit extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function budget() + public function budget(): BelongsTo { return $this->belongsTo(Budget::class); } - - /** - * @codeCoverageIgnore - * - * @param $value - */ - public function setAmountAttribute($value) - { - $this->attributes['amount'] = (string)round($value, 12); - } } diff --git a/app/Models/Category.php b/app/Models/Category.php index 2e2f0aaf26..5f06f91fbe 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -23,16 +23,17 @@ declare(strict_types=1); namespace FireflyIII\Models; use Crypt; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\User; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; /** * Class Category. + * + * @property string $name + * @property int $id */ class Category extends Model { diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 6a2b97c9bf..8f29c16dc0 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -27,6 +27,9 @@ use Illuminate\Database\Eloquent\SoftDeletes; /** * Class Configuration. + * + * @property string $data + * @property string $name */ class Configuration extends Model { diff --git a/app/Models/CurrencyExchangeRate.php b/app/Models/CurrencyExchangeRate.php index 7701cb9fff..e77bb2484c 100644 --- a/app/Models/CurrencyExchangeRate.php +++ b/app/Models/CurrencyExchangeRate.php @@ -22,12 +22,24 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * Class CurrencyExchange. + * + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property TransactionCurrency $fromCurrency + * @property TransactionCurrency $toCurrency + * @property float $rate + * @property Carbon $date + * @property int $from_currency_id + * @property int $to_currency_id + * */ class CurrencyExchangeRate extends Model { diff --git a/app/Models/LinkType.php b/app/Models/LinkType.php index 04dd71f3aa..b9caaf05b7 100644 --- a/app/Models/LinkType.php +++ b/app/Models/LinkType.php @@ -22,15 +22,26 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** - * @property int $journalCount + * @property int $journalCount + * @property string $inward + * @property string $outward + * @property string $name + * @property bool $editable + * @property Carbon $created_at + * @property Carbon $updated_at + * @property int $id * Class LinkType + * */ class LinkType extends Model { + use SoftDeletes; /** * The attributes that should be casted to native types. * diff --git a/app/Models/Note.php b/app/Models/Note.php index a6588c8564..81ebc4a9ff 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -22,13 +22,22 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; /** * Class Note. + * + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property string $text + * @property string $title */ class Note extends Model { + use SoftDeletes; /** * The attributes that should be casted to native types. * diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index 4e13b540c5..e6cc263d3e 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -29,17 +29,21 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Steam; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\PiggyBankRepetition; -use FireflyIII\Models\PiggyBankEvent; -use FireflyIII\Models\Note; -use FireflyIII\Models\Account; /** * Class PiggyBank. * - * @property Carbon $targetdate - * @property Carbon $startdate - * @property string $targetamount + * @property Carbon $targetdate + * @property Carbon $startdate + * @property string $targetamount + * @property int $id + * @property string $name + * @property Account $account + * @property Carbon $updated_at + * @property Carbon $created_at + * @property int $order + * @property bool $active + * @property int $account_id * */ class PiggyBank extends Model diff --git a/app/Models/PiggyBankEvent.php b/app/Models/PiggyBankEvent.php index 9463fcc39c..0f9f364766 100644 --- a/app/Models/PiggyBankEvent.php +++ b/app/Models/PiggyBankEvent.php @@ -23,8 +23,6 @@ declare(strict_types=1); namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\PiggyBank; /** * Class PiggyBankEvent. diff --git a/app/Models/PiggyBankRepetition.php b/app/Models/PiggyBankRepetition.php index 3efac08652..470d9b7a11 100644 --- a/app/Models/PiggyBankRepetition.php +++ b/app/Models/PiggyBankRepetition.php @@ -25,11 +25,13 @@ namespace FireflyIII\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Model; -use FireflyIII\Models\PiggyBank; /** * Class PiggyBankRepetition. + * * @property string $currentamount + * @property Carbon $startdate + * @property Carbon $targetdate */ class PiggyBankRepetition extends Model { diff --git a/app/Models/Preference.php b/app/Models/Preference.php index 3a873f7086..19a337bb5c 100644 --- a/app/Models/Preference.php +++ b/app/Models/Preference.php @@ -22,19 +22,24 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Crypt; use Exception; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\User; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Database\Eloquent\Model; use Log; -use FireflyIII\User; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Preference. * - * @property mixed $data + * @property mixed $data * @property string $name + * @property Carbon $updated_at + * @property Carbon $created_at + * @property int $id */ class Preference extends Model { @@ -52,6 +57,25 @@ class Preference extends Model /** @var array */ protected $fillable = ['user_id', 'data', 'name']; + /** + * @param string $value + * + * @return Account + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public static function routeBinder(string $value): Preference + { + if (auth()->check()) { + $preferenceId = (int)$value; + $preference = auth()->user()->preferences()->find($preferenceId); + if (null !== $preference) { + return $preference; + } + } + throw new NotFoundHttpException; + } + + /** * @param $value * diff --git a/app/Models/Recurrence.php b/app/Models/Recurrence.php new file mode 100644 index 0000000000..bc60c3bad2 --- /dev/null +++ b/app/Models/Recurrence.php @@ -0,0 +1,171 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Models; + + +use FireflyIII\User; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\SoftDeletes; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Class Recurrence + * + * @property int $id + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * @property int $user_id + * @property int $transaction_type_id + * @property int $transaction_currency_id + * @property string $title + * @property string $description + * @property \Carbon\Carbon $first_date + * @property \Carbon\Carbon $repeat_until + * @property \Carbon\Carbon $latest_date + * @property string $repetition_type + * @property string $repetition_moment + * @property int $repetition_skip + * @property int $repetitions + * @property bool $active + * @property bool $apply_rules + * @property \FireflyIII\User $user + * @property \Illuminate\Support\Collection $recurrenceRepetitions + * @property \Illuminate\Support\Collection $recurrenceMeta + * @property \Illuminate\Support\Collection $recurrenceTransactions + * @property \FireflyIII\Models\TransactionType $transactionType + * + */ +class Recurrence extends Model +{ + use SoftDeletes; + /** + * The attributes that should be casted to native types. + * + * @var array + */ + protected $casts + = [ + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'title' => 'string', + 'id' => 'int', + 'description' => 'string', + 'first_date' => 'date', + 'repeat_until' => 'date', + 'latest_date' => 'date', + 'repetitions' => 'int', + 'active' => 'bool', + 'apply_rules' => 'bool', + ]; + /** @var array */ + protected $fillable + = ['user_id', 'transaction_type_id', 'title', 'description', 'first_date', 'repeat_until', 'latest_date', 'repetitions', 'apply_rules', 'active']; + /** @var string */ + protected $table = 'recurrences'; + + /** + * @param string $value + * + * @return Recurrence + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public static function routeBinder(string $value): Recurrence + { + if (auth()->check()) { + $recurrenceId = (int)$value; + $recurrence = auth()->user()->recurrences()->find($recurrenceId); + if (null !== $recurrence) { + return $recurrence; + } + } + throw new NotFoundHttpException; + } + + /** + * @codeCoverageIgnore + * Get all of the notes. + */ + public function notes() + { + return $this->morphMany(Note::class, 'noteable'); + } + + /** + * @return HasMany + * @codeCoverageIgnore + */ + public function recurrenceMeta(): HasMany + { + return $this->hasMany(RecurrenceMeta::class); + } + + /** + * @return HasMany + * @codeCoverageIgnore + */ + public function recurrenceRepetitions(): HasMany + { + return $this->hasMany(RecurrenceRepetition::class); + } + + /** + * @return HasMany + * @codeCoverageIgnore + */ + public function recurrenceTransactions(): HasMany + { + return $this->hasMany(RecurrenceTransaction::class); + } + + /** + * @codeCoverageIgnore + * @return BelongsTo + */ + public function transactionCurrency(): BelongsTo + { + return $this->belongsTo(TransactionCurrency::class); + } + + /** + * @codeCoverageIgnore + * @return BelongsTo + */ + public function transactionType(): BelongsTo + { + return $this->belongsTo(TransactionType::class); + } + + /** + * @codeCoverageIgnore + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + +} \ No newline at end of file diff --git a/app/Models/RecurrenceMeta.php b/app/Models/RecurrenceMeta.php new file mode 100644 index 0000000000..97dc354c87 --- /dev/null +++ b/app/Models/RecurrenceMeta.php @@ -0,0 +1,63 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Models; + + +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\SoftDeletes; + +/** + * Class RecurrenceMeta + * + * @property string $name + * @property string $value + */ +class RecurrenceMeta extends Model +{ + use SoftDeletes; + /** @var array */ + protected $casts + = [ + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'name' => 'string', + 'value' => 'string', + ]; + /** @var array */ + protected $fillable = ['recurrence_id', 'name', 'value']; + /** @var string */ + protected $table = 'recurrences_meta'; + + /** + * @return BelongsTo + * @codeCoverageIgnore + */ + public function recurrence(): BelongsTo + { + return $this->belongsTo(Recurrence::class); + } + +} \ No newline at end of file diff --git a/app/Models/RecurrenceRepetition.php b/app/Models/RecurrenceRepetition.php new file mode 100644 index 0000000000..015a777a0e --- /dev/null +++ b/app/Models/RecurrenceRepetition.php @@ -0,0 +1,77 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Models; + + +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\SoftDeletes; + +/** + * Class RecurrenceRepetition + * + * @property string $repetition_type + * @property string $repetition_moment + * @property int $repetition_skip + * @property int $weekend + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $deleted_at + * @property \Carbon\Carbon $updated_at + * @property int $id + */ +class RecurrenceRepetition extends Model +{ + /** @var int */ + public const WEEKEND_DO_NOTHING = 1; + /** @var int */ + public const WEEKEND_SKIP_CREATION = 2; + /** @var int */ + public const WEEKEND_TO_FRIDAY = 3; + /** @var int */ + public const WEEKEND_TO_MONDAY = 4; + use SoftDeletes; + /** @var array */ + protected $casts + = [ + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'repetition_type' => 'string', + 'repetition_moment' => 'string', + 'repetition_skip' => 'int', + 'weekend' => 'int', + ]; + protected $fillable = ['recurrence_id', 'weekend', 'repetition_type', 'repetition_moment', 'repetition_skip']; + /** @var string */ + protected $table = 'recurrences_repetitions'; + + /** + * @return BelongsTo + * @codeCoverageIgnore + */ + public function recurrence(): BelongsTo + { + return $this->belongsTo(Recurrence::class); + } +} \ No newline at end of file diff --git a/app/Models/RecurrenceTransaction.php b/app/Models/RecurrenceTransaction.php new file mode 100644 index 0000000000..bea0b38ff6 --- /dev/null +++ b/app/Models/RecurrenceTransaction.php @@ -0,0 +1,123 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Models; + + +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\SoftDeletes; + +/** + * + * Class RecurrenceTransaction + * + * @property int $transaction_currency_id, + * @property int $foreign_currency_id + * @property int $source_id + * @property int $destination_id + * @property string $amount + * @property string $foreign_amount + * @property string $description + * @property \FireflyIII\Models\TransactionCurrency $transactionCurrency + * @property \FireflyIII\Models\TransactionCurrency $foreignCurrency + * @property \FireflyIII\Models\Account $sourceAccount + * @property \FireflyIII\Models\Account $destinationAccount + * @property \Illuminate\Support\Collection $recurrenceTransactionMeta + * @property int $id + */ +class RecurrenceTransaction extends Model +{ + use SoftDeletes; + /** @var array */ + protected $casts + = [ + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'amount' => 'string', + 'foreign_amount' => 'string', + 'description' => 'string', + ]; + /** @var array */ + protected $fillable + = ['recurrence_id', 'transaction_currency_id', 'foreign_currency_id', 'source_id', 'destination_id', 'amount', 'foreign_amount', + 'description']; + /** @var string */ + protected $table = 'recurrences_transactions'; + + /** + * @codeCoverageIgnore + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function destinationAccount(): BelongsTo + { + return $this->belongsTo(Account::class,'destination_id'); + } + + /** + * @codeCoverageIgnore + * @return BelongsTo + */ + public function foreignCurrency(): BelongsTo + { + return $this->belongsTo(TransactionCurrency::class); + } + + /** + * @return BelongsTo + * @codeCoverageIgnore + */ + public function recurrence(): BelongsTo + { + return $this->belongsTo(Recurrence::class); + } + + /** + * @return HasMany + * @codeCoverageIgnore + */ + public function recurrenceTransactionMeta(): HasMany + { + return $this->hasMany(RecurrenceTransactionMeta::class, 'rt_id'); + } + + /** + * @codeCoverageIgnore + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function sourceAccount(): BelongsTo + { + return $this->belongsTo(Account::class,'source_id'); + } + + /** + * @codeCoverageIgnore + * @return BelongsTo + */ + public function transactionCurrency(): BelongsTo + { + return $this->belongsTo(TransactionCurrency::class); + } +} \ No newline at end of file diff --git a/app/Models/RecurrenceTransactionMeta.php b/app/Models/RecurrenceTransactionMeta.php new file mode 100644 index 0000000000..aa246529cf --- /dev/null +++ b/app/Models/RecurrenceTransactionMeta.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Models; + + +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\SoftDeletes; + +/** + * Class RecurrenceTransactionMeta + * + * @property string $name + * @property string $value + */ +class RecurrenceTransactionMeta extends Model +{ + use SoftDeletes; + /** @var array */ + protected $casts + = [ + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'name' => 'string', + 'value' => 'string', + ]; + protected $fillable = ['rt_id', 'name', 'value']; + /** @var string */ + protected $table = 'rt_meta'; + + /** + * @return BelongsTo + * @codeCoverageIgnore + */ + public function recurrenceTransaction(): BelongsTo + { + return $this->belongsTo(RecurrenceTransaction::class, 'rt_id'); + } + +} \ No newline at end of file diff --git a/app/Models/Role.php b/app/Models/Role.php index 02fcfb5a5d..ee735d4605 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -22,9 +22,9 @@ declare(strict_types=1); namespace FireflyIII\Models; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; -use FireflyIII\User; /** * Class Role. diff --git a/app/Models/Rule.php b/app/Models/Rule.php index e6e65ba73e..36982b40c3 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -22,19 +22,33 @@ declare(strict_types=1); namespace FireflyIII\Models; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\SoftDeletes; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Carbon\Carbon; use FireflyIII\User; -use FireflyIII\Models\RuleTrigger; -use FireflyIII\Models\RuleGroup; -use FireflyIII\Models\RuleAction; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Collection; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Rule. - * @property bool $stop_processing - * @property int $id - * @property \Illuminate\Support\Collection $ruleTriggers + * + * @property bool $stop_processing + * @property int $id + * @property Collection $ruleTriggers + * @property Collection $ruleActions + * @property bool $active + * @property bool $strict + * @property User $user + * @property Carbon $created_at + * @property Carbon $updated_at + * @property string $title + * @property string $text + * @property int $order + * @property RuleGroup $ruleGroup + * @property int $rule_group_id + * @property string $description */ class Rule extends Model { @@ -53,10 +67,11 @@ class Rule extends Model 'active' => 'boolean', 'order' => 'int', 'stop_processing' => 'boolean', + 'id' => 'int', 'strict' => 'boolean', ]; /** @var array */ - protected $fillable = ['rule_group_id', 'order', 'active', 'title', 'description', 'user_id','strict']; + protected $fillable = ['rule_group_id', 'order', 'active', 'title', 'description', 'user_id', 'strict']; /** * @param string $value @@ -78,27 +93,27 @@ class Rule extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function ruleActions() + public function ruleActions(): HasMany { return $this->hasMany(RuleAction::class); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function ruleGroup() + public function ruleGroup(): BelongsTo { return $this->belongsTo(RuleGroup::class); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function ruleTriggers() + public function ruleTriggers(): HasMany { return $this->hasMany(RuleTrigger::class); } @@ -106,16 +121,16 @@ class Rule extends Model /** * @param $value */ - public function setDescriptionAttribute($value) + public function setDescriptionAttribute($value): void { $this->attributes['description'] = e($value); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function user() + public function user(): BelongsTo { return $this->belongsTo(User::class); } diff --git a/app/Models/RuleAction.php b/app/Models/RuleAction.php index d498ee11f6..6bad677108 100644 --- a/app/Models/RuleAction.php +++ b/app/Models/RuleAction.php @@ -22,11 +22,21 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; -use FireflyIII\Models\Rule; /** * Class RuleAction. + * + * @property string $action_value + * @property string $action_type + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property int $order + * @property bool $active + * @property bool $stop_processing + * @property Rule $rule */ class RuleAction extends Model { diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index cfe4ae2c35..142ec71bda 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -22,15 +22,25 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Collection; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\User; -use FireflyIII\Models\Rule; /** * Class RuleGroup. - * @property bool $active + * + * @property bool $active + * @property User $user + * @property Carbon $created_at + * @property Carbon $updated_at + * @property string $title + * @property string $text + * @property int $id + * @property int $order + * @property Collection $rules */ class RuleGroup extends Model { diff --git a/app/Models/RuleTrigger.php b/app/Models/RuleTrigger.php index 58265d0d1c..b030f9d836 100644 --- a/app/Models/RuleTrigger.php +++ b/app/Models/RuleTrigger.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; /** @@ -29,6 +30,12 @@ use Illuminate\Database\Eloquent\Model; * * @property string $trigger_value * @property string $trigger_type + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property int $order + * @property bool $active + * @property bool $stop_processing */ class RuleTrigger extends Model { diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 9a085b0325..a60b07449b 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -23,19 +23,18 @@ declare(strict_types=1); namespace FireflyIII\Models; use Crypt; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\User; -use FireflyIII\Models\TransactionJournal; /** * Class Tag. * - * @property Collection $transactionJournals - * @property string $tag - * @property int $id + * @property Collection $transactionJournals + * @property string $tag + * @property int $id * @property \Carbon\Carbon $date */ class Tag extends Model diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index b591f1d956..5d4721becb 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -25,6 +25,7 @@ namespace FireflyIII\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -71,7 +72,12 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string $description * @property bool $is_split * @property int $attachmentCount - * @property int $transaction_currency_id + * @property int $transaction_currency_id + * @property int $foreign_currency_id + * @property string $amount + * @property string $foreign_amount + * @property TransactionJournal $transactionJournal + * @property Account $account */ class Transaction extends Model { @@ -252,18 +258,18 @@ class Transaction extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function transactionCurrency() + public function transactionCurrency(): BelongsTo { return $this->belongsTo(TransactionCurrency::class); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function transactionJournal() + public function transactionJournal(): BelongsTo { return $this->belongsTo(TransactionJournal::class); } diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index 439962a257..26e7d9fa0c 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -25,13 +25,14 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\TransactionJournal; /** * Class TransactionCurrency. * * @property string $code - * @property int $decimal_places + * @property string $symbol + * @property int $decimal_places + * @property int $id * */ class TransactionCurrency extends Model diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 40595f644d..e6be30af6a 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -32,26 +32,20 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Collection; use Log; use Preferences; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionType; -use FireflyIII\Models\TransactionJournalMeta; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\Models\Tag; -use FireflyIII\Models\PiggyBankEvent; -use FireflyIII\Models\Note; -use FireflyIII\Models\Category; -use FireflyIII\Models\Budget; -use FireflyIII\Models\Bill; -use FireflyIII\Models\Attachment; /** * Class TransactionJournal. * - * @property User $user - * @property int $bill_id + * @property User $user + * @property int $bill_id + * @property Collection $categories + * @property bool $completed + * @property string $description + * @property string $transaction_type_id */ class TransactionJournal extends Model { diff --git a/app/Models/TransactionJournalLink.php b/app/Models/TransactionJournalLink.php index af47153390..aa5146b3e7 100644 --- a/app/Models/TransactionJournalLink.php +++ b/app/Models/TransactionJournalLink.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Crypt; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -29,6 +30,17 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class TransactionJournalLink. + * + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property string $comment + * @property TransactionJournal $source + * @property TransactionJournal $destination + * @property LinkType $linkType + * @property int $link_type_id + * @property int $source_id + * @property int $destination_id */ class TransactionJournalLink extends Model { diff --git a/app/Models/TransactionJournalMeta.php b/app/Models/TransactionJournalMeta.php index bef13e7fa8..e6acea708f 100644 --- a/app/Models/TransactionJournalMeta.php +++ b/app/Models/TransactionJournalMeta.php @@ -25,12 +25,12 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; -use FireflyIII\Models\TransactionJournal; /** * Class TransactionJournalMeta. + * * @property string $name - * @property int $transaction_journal_id + * @property int $transaction_journal_id */ class TransactionJournalMeta extends Model { diff --git a/app/Models/TransactionType.php b/app/Models/TransactionType.php index 67a1b906e1..4c9bc1f04a 100644 --- a/app/Models/TransactionType.php +++ b/app/Models/TransactionType.php @@ -25,11 +25,12 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\TransactionJournal; /** * Class TransactionType. + * * @property string $type + * @property int $id */ class TransactionType extends Model { diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 352e357d22..2ffb23d88b 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -26,6 +26,7 @@ use Exception; use FireflyIII\Events\AdminRequestedTestMessage; use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RequestedNewPassword; +use FireflyIII\Events\RequestedReportOnJournals; use FireflyIII\Events\RequestedVersionCheckStatus; use FireflyIII\Events\StoredTransactionJournal; use FireflyIII\Events\UpdatedTransactionJournal; @@ -64,11 +65,15 @@ class EventServiceProvider extends ServiceProvider // is a User related event. Login::class => [ 'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin', + 'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish', ], RequestedVersionCheckStatus::class => [ 'FireflyIII\Handlers\Events\VersionCheckEventHandler@checkForUpdates', ], + RequestedReportOnJournals::class => [ + 'FireflyIII\Handlers\Events\AutomationHandler@reportJournals', + ], // is a User related event. RequestedNewPassword::class => [ @@ -112,7 +117,7 @@ class EventServiceProvider extends ServiceProvider */ protected function registerCreateEvents(): void { - // move this routine to a filter + // todo move this routine to a filter // in case of repeated piggy banks and/or other problems. PiggyBank::created( function (PiggyBank $piggyBank) { diff --git a/app/Providers/RecurringServiceProvider.php b/app/Providers/RecurringServiceProvider.php new file mode 100644 index 0000000000..d41f54e743 --- /dev/null +++ b/app/Providers/RecurringServiceProvider.php @@ -0,0 +1,63 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Providers; + +use FireflyIII\Repositories\Recurring\RecurringRepository; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use Illuminate\Foundation\Application; +use Illuminate\Support\ServiceProvider; + +/** + * @codeCoverageIgnore + * Class RecurringServiceProvider. + */ +class RecurringServiceProvider extends ServiceProvider +{ + /** + * Bootstrap the application services. + */ + public function boot(): void + { + } + + /** + * Register the application services. + */ + public function register(): void + { + $this->app->bind( + RecurringRepositoryInterface::class, + function (Application $app) { + /** @var RecurringRepositoryInterface $repository */ + $repository = app(RecurringRepository::class); + + if ($app->auth->check()) { + $repository->setUser(auth()->user()); + } + + return $repository; + } + ); + } + +} diff --git a/app/Repositories/Account/FindAccountsTrait.php b/app/Repositories/Account/FindAccountsTrait.php index 79451ff6e4..8cfadbe8dc 100644 --- a/app/Repositories/Account/FindAccountsTrait.php +++ b/app/Repositories/Account/FindAccountsTrait.php @@ -58,6 +58,7 @@ trait FindAccountsTrait /** * @param string $number * @param array $types + * * @return Account|null */ public function findByAccountNumber(string $number, array $types): ?Account @@ -157,9 +158,11 @@ trait FindAccountsTrait Log::debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id)); return $account; + } else { + Log::debug(sprintf('"%s" does not equal "%s"', $account->name, $name)); } } - Log::debug(sprintf('There is no account with name "%s" or types', $name), $types); + Log::debug(sprintf('There is no account with name "%s" of types', $name), $types); return null; } diff --git a/app/Repositories/Attachment/AttachmentRepository.php b/app/Repositories/Attachment/AttachmentRepository.php index 36d38ade9d..d5eac20346 100644 --- a/app/Repositories/Attachment/AttachmentRepository.php +++ b/app/Repositories/Attachment/AttachmentRepository.php @@ -25,6 +25,8 @@ namespace FireflyIII\Repositories\Attachment; use Carbon\Carbon; use Crypt; use Exception; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\AttachmentFactory; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Models\Attachment; use FireflyIII\Models\Note; @@ -179,11 +181,30 @@ class AttachmentRepository implements AttachmentRepositoryInterface /** * @param User $user */ - public function setUser(User $user) + public function setUser(User $user): void { $this->user = $user; } + /** + * @param array $data + * + * @return Attachment + * @throws FireflyException + */ + public function store(array $data): Attachment + { + /** @var AttachmentFactory $factory */ + $factory = app(AttachmentFactory::class); + $factory->setUser($this->user); + $result = $factory->create($data); + if (null === $result) { + throw new FireflyException('Could not store attachment.'); + } + + return $result; + } + /** * @param Attachment $attachment * @param array $data @@ -193,8 +214,13 @@ class AttachmentRepository implements AttachmentRepositoryInterface public function update(Attachment $attachment, array $data): Attachment { $attachment->title = $data['title']; + + // update filename, if present and different: + if (isset($data['filename']) && '' !== $data['filename'] && $data['filename'] !== $attachment->filename) { + $attachment->filename = $data['filename']; + } $attachment->save(); - $this->updateNote($attachment, $data['notes']); + $this->updateNote($attachment, $data['notes'] ?? ''); return $attachment; } @@ -207,7 +233,7 @@ class AttachmentRepository implements AttachmentRepositoryInterface */ public function updateNote(Attachment $attachment, string $note): bool { - if (0 === \strlen($note)) { + if ('' === $note) { $dbNote = $attachment->notes()->first(); if (null !== $dbNote) { $dbNote->delete(); diff --git a/app/Repositories/Attachment/AttachmentRepositoryInterface.php b/app/Repositories/Attachment/AttachmentRepositoryInterface.php index 0a2b63b537..30caf2c253 100644 --- a/app/Repositories/Attachment/AttachmentRepositoryInterface.php +++ b/app/Repositories/Attachment/AttachmentRepositoryInterface.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Attachment; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Attachment; use FireflyIII\User; use Illuminate\Support\Collection; @@ -95,6 +96,14 @@ interface AttachmentRepositoryInterface */ public function setUser(User $user); + /** + * @param array $data + * + * @return Attachment + * @throws FireflyException + */ + public function store(array $data): Attachment; + /** * @param Attachment $attachment * @param array $attachmentData diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index c0ee968441..443a117405 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Budget; use Carbon\Carbon; +use Exception; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\AccountType; use FireflyIII\Models\AvailableBudget; @@ -152,19 +154,48 @@ class BudgetRepository implements BudgetRepositoryInterface return $return; } + /** + * Deletes a budget limit. + * + * @param BudgetLimit $budgetLimit + */ + public function deleteBudgetLimit(BudgetLimit $budgetLimit): void + { + try { + $budgetLimit->delete(); + } catch (Exception $e) { + Log::error(sprintf('Could not delete budget limit: %s', $e->getMessage())); + } + } + /** * @param Budget $budget * * @return bool - * @throws \Exception */ public function destroy(Budget $budget): bool { - $budget->delete(); + try { + $budget->delete(); + } catch (Exception $e) { + Log::error(sprintf('Could not delete budget: %s', $e->getMessage())); + } return true; } + /** + * @param AvailableBudget $availableBudget + */ + public function destroyAvailableBudget(AvailableBudget $availableBudget): void + { + try { + $availableBudget->delete(); + } catch (Exception $e) { + Log::error(sprintf('Could not delete available budget: %s', $e->getMessage())); + } + } + /** * Filters entries from the result set generated by getBudgetPeriodReport. * @@ -298,8 +329,35 @@ class BudgetRepository implements BudgetRepositoryInterface * * @return Collection */ - public function getAllBudgetLimits(Carbon $start, Carbon $end): Collection + public function getAllBudgetLimits(Carbon $start = null, Carbon $end = null): Collection { + // both are NULL: + if (null === $start && null === $end) { + $set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') + ->with(['budget']) + ->where('budgets.user_id', $this->user->id) + ->get(['budget_limits.*']); + + return $set; + } + // one of the two is NULL. + if (null === $start xor null === $end) { + $query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') + ->with(['budget']) + ->where('budgets.user_id', $this->user->id); + if (null !== $end) { + // end date must be before $end. + $query->where('end_date', '<=', $end->format('Y-m-d 00:00:00')); + } + if (null !== $start) { + // start date must be after $start. + $query->where('start_date', '>=', $start->format('Y-m-d 00:00:00')); + } + $set = $query->get(['budget_limits.*']); + + return $set; + } + // neither are NULL: $set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') ->with(['budget']) ->where('budgets.user_id', $this->user->id) @@ -355,6 +413,16 @@ class BudgetRepository implements BudgetRepositoryInterface return $amount; } + /** + * Returns all available budget objects. + * + * @return Collection + */ + public function getAvailableBudgets(): Collection + { + return $this->user->availableBudgets()->get(); + } + /** * @param Budget $budget * @param Carbon $start @@ -362,8 +430,28 @@ class BudgetRepository implements BudgetRepositoryInterface * * @return Collection */ - public function getBudgetLimits(Budget $budget, Carbon $start, Carbon $end): Collection + public function getBudgetLimits(Budget $budget, Carbon $start = null, Carbon $end = null): Collection { + if (null === $end && null === $start) { + return $budget->budgetlimits()->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']); + } + if (null === $end xor null === $start) { + $query = $budget->budgetlimits()->orderBy('budget_limits.start_date', 'DESC'); + // one of the two is null + if (null !== $end) { + // end date must be before $end. + $query->where('end_date', '<=', $end->format('Y-m-d 00:00:00')); + } + if (null !== $start) { + // start date must be after $start. + $query->where('start_date', '>=', $start->format('Y-m-d 00:00:00')); + } + $set = $query->get(['budget_limits.*']); + + return $set; + } + + // when both dates are set: $set = $budget->budgetlimits() ->where( function (Builder $q5) use ($start, $end) { @@ -527,9 +615,9 @@ class BudgetRepository implements BudgetRepositoryInterface * @param Carbon $end * @param string $amount * - * @return bool + * @return AvailableBudget */ - public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): bool + public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): AvailableBudget { $availableBudget = $this->user->availableBudgets() ->where('transaction_currency_id', $currency->id) @@ -545,7 +633,7 @@ class BudgetRepository implements BudgetRepositoryInterface $availableBudget->amount = $amount; $availableBudget->save(); - return true; + return $availableBudget; } /** @@ -636,6 +724,42 @@ class BudgetRepository implements BudgetRepositoryInterface return $newBudget; } + /** + * @param array $data + * + * @throws FireflyException + * @return BudgetLimit + */ + public function storeBudgetLimit(array $data): BudgetLimit + { + $this->cleanupBudgets(); + /** @var Budget $budget */ + $budget = $data['budget']; + + // find limit with same date range. + // if it exists, throw error. + $limits = $budget->budgetlimits() + ->where('budget_limits.start_date', $data['start_date']->format('Y-m-d 00:00:00')) + ->where('budget_limits.end_date', $data['end_date']->format('Y-m-d 00:00:00')) + ->get(['budget_limits.*'])->count(); + Log::debug(sprintf('Found %d budget limits.', $limits)); + if ($limits > 0) { + throw new FireflyException('A budget limit for this budget, and this date range already exists. You must update the existing one.'); + } + + Log::debug('No existing budget limit, create a new one'); + // or create one and return it. + $limit = new BudgetLimit; + $limit->budget()->associate($budget); + $limit->start_date = $data['start_date']->format('Y-m-d 00:00:00'); + $limit->end_date = $data['end_date']->format('Y-m-d 00:00:00'); + $limit->amount = $data['amount']; + $limit->save(); + Log::debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $data['amount'])); + + return $limit; + } + /** * @param Budget $budget * @param array $data @@ -652,6 +776,58 @@ class BudgetRepository implements BudgetRepositoryInterface return $budget; } + /** + * @param AvailableBudget $availableBudget + * @param array $data + * + * @return AvailableBudget + * @throws FireflyException + */ + public function updateAvailableBudget(AvailableBudget $availableBudget, array $data): AvailableBudget + { + $existing = $this->user->availableBudgets() + ->where('transaction_currency_id', $data['transaction_currency_id']) + ->where('start_date', $data['start_date']->format('Y-m-d 00:00:00')) + ->where('end_date', $data['end_date']->format('Y-m-d 00:00:00')) + ->where('id', '!=', $availableBudget->id) + ->first(); + + if (null !== $existing) { + throw new FireflyException(sprintf('An entry already exists for these parameters: available budget object with ID #%d', $existing->id)); + } + $availableBudget->transaction_currency_id = $data['transaction_currency_id']; + $availableBudget->start_date = $data['start_date']; + $availableBudget->end_date = $data['end_date']; + $availableBudget->amount = $data['amount']; + $availableBudget->save(); + + return $availableBudget; + + } + + /** + * @param BudgetLimit $budgetLimit + * @param array $data + * + * @return BudgetLimit + * @throws Exception + */ + public function updateBudgetLimit(BudgetLimit $budgetLimit, array $data): BudgetLimit + { + $this->cleanupBudgets(); + /** @var Budget $budget */ + $budget = $data['budget']; + + $budgetLimit->budget()->associate($budget); + $budgetLimit->start_date = $data['start_date']->format('Y-m-d 00:00:00'); + $budgetLimit->end_date = $data['end_date']->format('Y-m-d 00:00:00'); + $budgetLimit->amount = $data['amount']; + $budgetLimit->save(); + Log::debug(sprintf('Updated budget limit with ID #%d and amount %s', $budgetLimit->id, $data['amount'])); + + return $budgetLimit; + } + /** * @param Budget $budget * @param Carbon $start diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index a929e92484..d8e69f2fef 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Budget; use Carbon\Carbon; +use FireflyIII\Models\AvailableBudget; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\TransactionCurrency; @@ -35,15 +36,6 @@ use Illuminate\Support\Collection; interface BudgetRepositoryInterface { - /** - * Get all budgets with these ID's. - * - * @param array $budgetIds - * - * @return Collection - */ - public function getByIds(array $budgetIds): Collection; - /** * A method that returns the amount of money budgeted per day for this budget, * on average. @@ -71,6 +63,13 @@ interface BudgetRepositoryInterface */ public function collectBudgetInformation(Collection $budgets, Carbon $start, Carbon $end): array; + /** + * Deletes a budget limit. + * + * @param BudgetLimit $budgetLimit + */ + public function deleteBudgetLimit(BudgetLimit $budgetLimit): void; + /** * @param Budget $budget * @@ -78,6 +77,11 @@ interface BudgetRepositoryInterface */ public function destroy(Budget $budget): bool; + /** + * @param AvailableBudget $availableBudget + */ + public function destroyAvailableBudget(AvailableBudget $availableBudget): void; + /** * Filters entries from the result set generated by getBudgetPeriodReport. * @@ -139,7 +143,7 @@ interface BudgetRepositoryInterface * * @return Collection */ - public function getAllBudgetLimits(Carbon $start, Carbon $end): Collection; + public function getAllBudgetLimits(Carbon $start = null, Carbon $end = null): Collection; /** * @param TransactionCurrency $currency @@ -150,6 +154,13 @@ interface BudgetRepositoryInterface */ public function getAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end): string; + /** + * Returns all available budget objects. + * + * @return Collection + */ + public function getAvailableBudgets(): Collection; + /** * @param Budget $budget * @param Carbon $start @@ -157,7 +168,7 @@ interface BudgetRepositoryInterface * * @return Collection */ - public function getBudgetLimits(Budget $budget, Carbon $start, Carbon $end): Collection; + public function getBudgetLimits(Budget $budget, Carbon $start = null, Carbon $end = null): Collection; /** * @param Collection $budgets @@ -174,6 +185,15 @@ interface BudgetRepositoryInterface */ public function getBudgets(): Collection; + /** + * Get all budgets with these ID's. + * + * @param array $budgetIds + * + * @return Collection + */ + public function getByIds(array $budgetIds): Collection; + /** * @return Collection */ @@ -194,9 +214,9 @@ interface BudgetRepositoryInterface * @param Carbon $end * @param string $amount * - * @return bool + * @return AvailableBudget */ - public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): bool; + public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): AvailableBudget; /** * @param User $user @@ -213,6 +233,7 @@ interface BudgetRepositoryInterface */ public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): string; + /** * @param Collection $accounts * @param Carbon $start @@ -229,6 +250,13 @@ interface BudgetRepositoryInterface */ public function store(array $data): Budget; + /** + * @param array $data + * + * @return BudgetLimit + */ + public function storeBudgetLimit(array $data): BudgetLimit; + /** * @param Budget $budget * @param array $data @@ -237,6 +265,22 @@ interface BudgetRepositoryInterface */ public function update(Budget $budget, array $data): Budget; + /** + * @param AvailableBudget $availableBudget + * @param array $data + * + * @return AvailableBudget + */ + public function updateAvailableBudget(AvailableBudget $availableBudget, array $data): AvailableBudget; + + /** + * @param BudgetLimit $budgetLimit + * @param array $data + * + * @return BudgetLimit + */ + public function updateBudgetLimit(BudgetLimit $budgetLimit, array $data): BudgetLimit; + /** * @param Budget $budget * @param Carbon $start diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index 4258432124..f754c1bf0a 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -265,13 +265,15 @@ class CurrencyRepository implements CurrencyRepositoryInterface } /** + * Get currency exchange rate. + * * @param TransactionCurrency $fromCurrency * @param TransactionCurrency $toCurrency * @param Carbon $date * - * @return CurrencyExchangeRate + * @return CurrencyExchangeRate|null */ - public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate + public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): ?CurrencyExchangeRate { if ($fromCurrency->id === $toCurrency->id) { $rate = new CurrencyExchangeRate; @@ -280,7 +282,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface return $rate; } - + /** @var CurrencyExchangeRate $rate */ $rate = $this->user->currencyExchangeRates() ->where('from_currency_id', $fromCurrency->id) ->where('to_currency_id', $toCurrency->id) @@ -291,7 +293,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface return $rate; } - return new CurrencyExchangeRate; + return null; } /** diff --git a/app/Repositories/Currency/CurrencyRepositoryInterface.php b/app/Repositories/Currency/CurrencyRepositoryInterface.php index 6f1be847d7..72f12a0188 100644 --- a/app/Repositories/Currency/CurrencyRepositoryInterface.php +++ b/app/Repositories/Currency/CurrencyRepositoryInterface.php @@ -154,13 +154,15 @@ interface CurrencyRepositoryInterface public function getCurrencyByPreference(Preference $preference): TransactionCurrency; /** + * Get currency exchange rate. + * * @param TransactionCurrency $fromCurrency * @param TransactionCurrency $toCurrency * @param Carbon $date * - * @return CurrencyExchangeRate + * @return CurrencyExchangeRate|null */ - public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate; + public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): ?CurrencyExchangeRate; /** * @param User $user diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index 4d42698aec..7bf69d8a18 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -360,6 +360,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface $newConfig = array_merge($currentConfig, $configuration); $job->configuration = $newConfig; $job->save(); + //Log::debug(sprintf('Set config of job "%s" to: ', $job->key), $newConfig); return $job; diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php index baf1d51ce1..fd5ccd1d5e 100644 --- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php +++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php @@ -36,52 +36,13 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; interface ImportJobRepositoryInterface { /** - * Return all attachments for job. - * * @param ImportJob $job - * - * @return Collection - */ - public function getAttachments(ImportJob $job): Collection; - - /** - * Handle upload for job. - * - * @param ImportJob $job - * @param string $name - * @param UploadedFile $file - * - * @return MessageBag - * @throws FireflyException - */ - public function storeFileUpload(ImportJob $job, string $name, UploadedFile $file): MessageBag; - - /** - * Store file. - * - * @param ImportJob $job - * @param string $name - * @param string $fileName - * - * @return MessageBag - */ - public function storeCLIUpload(ImportJob $job, string $name, string $fileName): MessageBag; - - /** - * @param ImportJob $job - * @param array $transactions + * @param int $index + * @param string $error * * @return ImportJob */ - public function setTransactions(ImportJob $job, array $transactions): ImportJob; - - /** - * @param ImportJob $job - * @param Tag $tag - * - * @return ImportJob - */ - public function setTag(ImportJob $job, Tag $tag): ImportJob; + public function addError(ImportJob $job, int $index, string $error): ImportJob; /** * Add message to job. @@ -93,15 +54,6 @@ interface ImportJobRepositoryInterface */ public function addErrorMessage(ImportJob $job, string $error): ImportJob; - /** - * @param ImportJob $job - * @param int $index - * @param string $error - * - * @return ImportJob - */ - public function addError(ImportJob $job, int $index, string $error): ImportJob; - /** * @param ImportJob $job * @param int $steps @@ -141,6 +93,15 @@ interface ImportJobRepositoryInterface */ public function findByKey(string $key): ImportJob; + /** + * Return all attachments for job. + * + * @param ImportJob $job + * + * @return Collection + */ + public function getAttachments(ImportJob $job): Collection; + /** * Return configuration of job. * @@ -198,14 +159,6 @@ interface ImportJobRepositoryInterface */ public function setExtendedStatus(ImportJob $job, array $array): ImportJob; - /** - * @param ImportJob $job - * @param string $status - * - * @return ImportJob - */ - public function setStatus(ImportJob $job, string $status): ImportJob; - /** * @param ImportJob $job * @param string $stage @@ -214,6 +167,14 @@ interface ImportJobRepositoryInterface */ public function setStage(ImportJob $job, string $stage): ImportJob; + /** + * @param ImportJob $job + * @param string $status + * + * @return ImportJob + */ + public function setStatus(ImportJob $job, string $status): ImportJob; + /** * @param ImportJob $job * @param int $steps @@ -222,6 +183,14 @@ interface ImportJobRepositoryInterface */ public function setStepsDone(ImportJob $job, int $steps): ImportJob; + /** + * @param ImportJob $job + * @param Tag $tag + * + * @return ImportJob + */ + public function setTag(ImportJob $job, Tag $tag): ImportJob; + /** * @param ImportJob $job * @param int $count @@ -230,11 +199,42 @@ interface ImportJobRepositoryInterface */ public function setTotalSteps(ImportJob $job, int $count): ImportJob; + /** + * @param ImportJob $job + * @param array $transactions + * + * @return ImportJob + */ + public function setTransactions(ImportJob $job, array $transactions): ImportJob; + /** * @param User $user */ public function setUser(User $user); + /** + * Store file. + * + * @param ImportJob $job + * @param string $name + * @param string $fileName + * + * @return MessageBag + */ + public function storeCLIUpload(ImportJob $job, string $name, string $fileName): MessageBag; + + /** + * Handle upload for job. + * + * @param ImportJob $job + * @param string $name + * @param UploadedFile $file + * + * @return MessageBag + * @throws FireflyException + */ + public function storeFileUpload(ImportJob $job, string $name, UploadedFile $file): MessageBag; + /** * @param ImportJob $job * @param string $status diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index e5dc693a60..f3397e1d6b 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -23,8 +23,8 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Journal; use Carbon\Carbon; -use DB; use Exception; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\TransactionJournalFactory; use FireflyIII\Factory\TransactionJournalMetaFactory; use FireflyIII\Models\Account; @@ -101,6 +101,11 @@ class JournalRepository implements JournalRepositoryInterface $transaction->budgets()->detach(); } } + // if journal is not a withdrawal, remove the bill ID. + if (TransactionType::WITHDRAWAL !== $type->type) { + $journal->bill_id = null; + $journal->save(); + } Preferences::mark(); @@ -739,8 +744,7 @@ class JournalRepository implements JournalRepositoryInterface * * @return TransactionJournal * - * @throws \FireflyIII\Exceptions\FireflyException - * @throws \FireflyIII\Exceptions\FireflyException + * @throws FireflyException */ public function store(array $data): TransactionJournal { diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 33ea68806d..e0884a3858 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Journal; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\Note; use FireflyIII\Models\Transaction; @@ -38,15 +39,6 @@ use Illuminate\Support\MessageBag; */ interface JournalRepositoryInterface { - /** - * Find a journal by its hash. - * - * @param string $hash - * - * @return TransactionJournalMeta|null - */ - public function findByHash(string $hash): ?TransactionJournalMeta; - /** * @param TransactionJournal $journal * @param TransactionType $type @@ -77,12 +69,22 @@ interface JournalRepositoryInterface * Find a specific journal. * * @param int $journalId + * * @deprecated * * @return TransactionJournal */ public function find(int $journalId): TransactionJournal; + /** + * Find a journal by its hash. + * + * @param string $hash + * + * @return TransactionJournalMeta|null + */ + public function findByHash(string $hash): ?TransactionJournalMeta; + /** * Find a specific journal. * @@ -325,6 +327,7 @@ interface JournalRepositoryInterface /** * @param array $data * + * @throws FireflyException * @return TransactionJournal */ public function store(array $data): TransactionJournal; diff --git a/app/Repositories/LinkType/LinkTypeRepository.php b/app/Repositories/LinkType/LinkTypeRepository.php index a4eb99cff6..a7c79b891a 100644 --- a/app/Repositories/LinkType/LinkTypeRepository.php +++ b/app/Repositories/LinkType/LinkTypeRepository.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\LinkType; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\LinkType; use FireflyIII\Models\Note; @@ -56,9 +57,9 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface * @return bool * @throws \Exception */ - public function destroy(LinkType $linkType, LinkType $moveTo): bool + public function destroy(LinkType $linkType, LinkType $moveTo = null): bool { - if (null !== $moveTo->id) { + if (null !== $moveTo) { TransactionJournalLink::where('link_type_id', $linkType->id)->update(['link_type_id' => $moveTo->id]); } $linkType->delete(); @@ -94,6 +95,20 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $linkType; } + /** + * @param string|null $name + * + * @return LinkType|null + */ + public function findByName(string $name = null): ?LinkType + { + if (null === $name) { + return null; + } + + return LinkType::where('name', $name)->first(); + } + /** * Check if link exists between journals. * @@ -110,6 +125,34 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $count + $opposingCount > 0; } + /** + * @param int $id + * + * @return LinkType|null + */ + public function findNull(int $id): ?LinkType + { + return LinkType::find($id); + } + + /** + * See if such a link already exists (and get it). + * + * @param LinkType $linkType + * @param TransactionJournal $inward + * @param TransactionJournal $outward + * + * @return TransactionJournalLink|null + */ + public function findSpecificLink(LinkType $linkType, TransactionJournal $inward, TransactionJournal $outward): ?TransactionJournalLink + { + return TransactionJournalLink + ::where('link_type_id', $linkType->id) + ->where('source_id', $inward->id) + ->where('destination_id', $outward->id)->first(); + + } + /** * @return Collection */ @@ -118,6 +161,30 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return LinkType::orderBy('name', 'ASC')->get(); } + /** + * Returns all the journal links (of a specific type). + * + * @param $linkType + * + * @return Collection + */ + public function getJournalLinks(LinkType $linkType = null): Collection + { + $query = TransactionJournalLink + ::leftJoin('transaction_journals as source_journals', 'journal_links.source_id', '=', 'source_journals.id') + ->leftJoin('transaction_journals as dest_journals', 'journal_links.destination_id', '=', 'dest_journals.id') + ->where('source_journals.user_id', $this->user->id) + ->where('dest_journals.user_id', $this->user->id) + ->whereNull('source_journals.deleted_at') + ->whereNull('dest_journals.deleted_at'); + + if (null !== $linkType) { + $query->where('journal_links.link_type_id', $linkType->id); + } + + return $query->get(['journal_links.*']); + } + /** * Return list of existing connections. * @@ -169,35 +236,42 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface * Store link between two journals. * * @param array $information - * @param TransactionJournal $left - * @param TransactionJournal $right + * @param TransactionJournal $inward + * @param TransactionJournal $outward * * @return mixed * @throws FireflyException */ - public function storeLink(array $information, TransactionJournal $left, TransactionJournal $right): TransactionJournalLink + public function storeLink(array $information, TransactionJournal $inward, TransactionJournal $outward): TransactionJournalLink { $linkType = $this->find((int)($information['link_type_id'] ?? 0)); if (null === $linkType->id) { throw new FireflyException(sprintf('Link type #%d cannot be resolved to an actual link type', $information['link_type_id'] ?? 0)); } + + // might exist already: + $existing = $this->findSpecificLink($linkType, $inward, $outward); + if (null !== $existing) { + return $existing; + } + $link = new TransactionJournalLink; $link->linkType()->associate($linkType); if ('inward' === $information['direction']) { - Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->inward, $left->id, $right->id)); - $link->source()->associate($left); - $link->destination()->associate($right); + Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->inward, $inward->id, $outward->id)); + $link->source()->associate($inward); + $link->destination()->associate($outward); } if ('outward' === $information['direction']) { - Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->outward, $right->id, $left->id)); - $link->source()->associate($right); - $link->destination()->associate($left); + Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->outward, $outward->id, $inward->id)); + $link->source()->associate($outward); + $link->destination()->associate($inward); } $link->save(); // make note in noteable: - if (\strlen($information['notes']) > 0) { + if (\strlen((string)$information['notes']) > 0) { $dbNote = $link->notes()->first(); if (null === $dbNote) { $dbNote = new Note(); @@ -240,4 +314,42 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $linkType; } + + /** + * Update an existing transaction journal link. + * + * @param TransactionJournalLink $journalLink + * @param array $data + * + * @return TransactionJournalLink + */ + public function updateLink(TransactionJournalLink $journalLink, array $data): TransactionJournalLink + { + $journalLink->source_id = $data['inward']->id; + $journalLink->destination_id = $data['outward']->id; + $journalLink->link_type_id = $data['link_type_id']; + $journalLink->save(); + /** @var Note $note */ + $note = $journalLink->notes()->first(); + // delete note: + if (null !== $note && '' === $data['notes']) { + try { + $note->delete(); + } catch (Exception $e) { + Log::debug(sprintf('Could not delete note for journal link: %s', $e->getMessage())); + } + } + // create note: + if (null === $note && '' !== $data['notes']) { + $note = new Note; + $note->noteable()->associate($journalLink); + } + // update note + if ('' !== $data['notes']) { + $note->text = $data['notes']; + $note->save(); + } + + return $journalLink; + } } diff --git a/app/Repositories/LinkType/LinkTypeRepositoryInterface.php b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php index 19ae1c8846..617cb45b59 100644 --- a/app/Repositories/LinkType/LinkTypeRepositoryInterface.php +++ b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php @@ -45,7 +45,7 @@ interface LinkTypeRepositoryInterface * * @return bool */ - public function destroy(LinkType $linkType, LinkType $moveTo): bool; + public function destroy(LinkType $linkType, LinkType $moveTo = null): bool; /** * @param TransactionJournalLink $link @@ -57,10 +57,20 @@ interface LinkTypeRepositoryInterface /** * @param int $id * + * @deprecated * @return LinkType */ public function find(int $id): LinkType; + /** + * Find link type by name. + * + * @param string|null $name + * + * @return LinkType|null + */ + public function findByName(string $name = null): ?LinkType; + /** * Check if link exists between journals. * @@ -71,11 +81,36 @@ interface LinkTypeRepositoryInterface */ public function findLink(TransactionJournal $one, TransactionJournal $two): bool; + /** + * @param int $id + * + * @return LinkType|null + */ + public function findNull(int $id): ?LinkType; + + /** + * See if such a link already exists (and get it). + * + * @param LinkType $linkType + * @param TransactionJournal $inward + * @param TransactionJournal $outward + * + * @return TransactionJournalLink|null + */ + public function findSpecificLink(LinkType $linkType, TransactionJournal $inward, TransactionJournal $outward): ?TransactionJournalLink; + /** * @return Collection */ public function get(): Collection; + /** + * @param LinkType|null $linkType + * + * @return Collection + */ + public function getJournalLinks(LinkType $linkType = null): Collection; + /** * Return list of existing connections. * @@ -96,12 +131,12 @@ interface LinkTypeRepositoryInterface * Store link between two journals. * * @param array $information - * @param TransactionJournal $left - * @param TransactionJournal $right + * @param TransactionJournal $inward + * @param TransactionJournal $outward * * @return mixed */ - public function storeLink(array $information, TransactionJournal $left, TransactionJournal $right): TransactionJournalLink; + public function storeLink(array $information, TransactionJournal $inward, TransactionJournal $outward): TransactionJournalLink; /** * @param TransactionJournalLink $link @@ -117,4 +152,14 @@ interface LinkTypeRepositoryInterface * @return LinkType */ public function update(LinkType $linkType, array $data): LinkType; + + /** + * Update an existing transaction journal link. + * + * @param TransactionJournalLink $journalLink + * @param array $data + * + * @return TransactionJournalLink + */ + public function updateLink(TransactionJournalLink $journalLink, array $data): TransactionJournalLink; } diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 677b48c23e..cacf70be61 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -49,7 +49,10 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface */ public function addAmount(PiggyBank $piggyBank, string $amount): bool { - $repetition = $piggyBank->currentRelevantRep(); + $repetition = $this->getRepetition($piggyBank); + if (null === $repetition) { + return false; + } $currentAmount = $repetition->currentamount ?? '0'; $repetition->currentamount = bcadd($currentAmount, $amount); $repetition->save(); @@ -99,7 +102,11 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface */ public function canRemoveAmount(PiggyBank $piggyBank, string $amount): bool { - $savedSoFar = $piggyBank->currentRelevantRep()->currentamount; + $repetition = $this->getRepetition($piggyBank); + if (null === $repetition) { + return false; + } + $savedSoFar = $repetition->currentamount; return bccomp($amount, $savedSoFar) <= 0; } @@ -171,6 +178,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param int $piggyBankid * + * @deprecated * @return PiggyBank */ public function find(int $piggyBankid): PiggyBank @@ -203,6 +211,21 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return null; } + /** + * @param int $piggyBankId + * + * @return PiggyBank|null + */ + public function findNull(int $piggyBankId): ?PiggyBank + { + $piggyBank = $this->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); + if (null !== $piggyBank) { + return $piggyBank; + } + + return null; + } + /** * Get current amount saved in piggy bank. * @@ -253,7 +276,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); // if piggy account matches source account, the amount is positive - if (\in_array($piggyBank->account_id, $sources)) { + if (\in_array($piggyBank->account_id, $sources, true)) { $amount = bcmul($amount, '-1'); Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id)); } @@ -334,8 +357,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface public function getSuggestedMonthlyAmount(PiggyBank $piggyBank): string { $savePerMonth = '0'; - $repetition = $this->getRepetition($piggyBank); - if(null === $repetition) { + $repetition = $this->getRepetition($piggyBank); + if (null === $repetition) { return $savePerMonth; } if (null !== $piggyBank->targetdate && $repetition->currentamount < $piggyBank->targetamount) { @@ -423,8 +446,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * set id of piggy bank. * - * @param int $piggyBankId - * @param int $order + * @param PiggyBank $piggyBank + * @param int $order * * @return bool */ @@ -439,7 +462,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param User $user */ - public function setUser(User $user) + public function setUser(User $user): void { $this->user = $user; } @@ -455,7 +478,14 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** @var PiggyBank $piggyBank */ $piggyBank = PiggyBank::create($data); - $this->updateNote($piggyBank, $data['note']); + $this->updateNote($piggyBank, $data['note']); // todo rename to 'notes' + + // repetition is auto created. + $repetition = $this->getRepetition($piggyBank); + if (null !== $repetition && isset($data['current_amount'])) { + $repetition->currentamount = $data['current_amount']; + $repetition->save(); + } return $piggyBank; } @@ -470,9 +500,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface { $piggyBank->name = $data['name']; $piggyBank->account_id = (int)$data['account_id']; - $piggyBank->targetamount = round($data['targetamount'], 2); - $piggyBank->targetdate = $data['targetdate']; - $piggyBank->startdate = $data['startdate']; + $piggyBank->targetamount = $data['targetamount']; + $piggyBank->targetdate = $data['targetdate'] ?? $piggyBank->targetdate; + $piggyBank->startdate = $data['startdate'] ?? $piggyBank->startdate; $piggyBank->save(); @@ -480,7 +510,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface // if the piggy bank is now smaller than the current relevant rep, // remove money from the rep. - $repetition = $piggyBank->currentRelevantRep(); + $repetition = $this->getRepetition($piggyBank); if ($repetition->currentamount > $piggyBank->targetamount) { $diff = bcsub($piggyBank->targetamount, $repetition->currentamount); $this->createEvent($piggyBank, $diff); diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index 9b22a8d3b8..4b4d7d062e 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -103,6 +103,7 @@ interface PiggyBankRepositoryInterface /** * @param int $piggyBankid * + * @deprecated * @return PiggyBank */ public function find(int $piggyBankid): PiggyBank; @@ -116,6 +117,13 @@ interface PiggyBankRepositoryInterface */ public function findByName(string $name): ?PiggyBank; + /** + * @param int $piggyBankId + * + * @return PiggyBank|null + */ + public function findNull(int $piggyBankId): ?PiggyBank; + /** * Get current amount saved in piggy bank. * diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php new file mode 100644 index 0000000000..43be804321 --- /dev/null +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -0,0 +1,662 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Repositories\Recurring; + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\RecurrenceFactory; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Models\Note; +use FireflyIII\Models\Preference; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceMeta; +use FireflyIII\Models\RecurrenceRepetition; +use FireflyIII\Models\RecurrenceTransaction; +use FireflyIII\Models\RecurrenceTransactionMeta; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionJournalMeta; +use FireflyIII\Services\Internal\Destroy\RecurrenceDestroyService; +use FireflyIII\Services\Internal\Update\RecurrenceUpdateService; +use FireflyIII\User; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; +use Log; + +/** + * + * Class RecurringRepository + */ +class RecurringRepository implements RecurringRepositoryInterface +{ + /** @var User */ + private $user; + + /** + * Destroy a recurring transaction. + * + * @param Recurrence $recurrence + */ + public function destroy(Recurrence $recurrence): void + { + /** @var RecurrenceDestroyService $service */ + $service = app(RecurrenceDestroyService::class); + $service->destroy($recurrence); + } + + /** + * Returns all of the user's recurring transactions. + * + * @return Collection + */ + public function get(): Collection + { + return $this->user->recurrences() + ->with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions']) + ->orderBy('active', 'DESC') + ->orderBy('transaction_type_id', 'ASC') + ->orderBy('title', 'ASC') + ->get(); + } + + /** + * Get ALL recurring transactions. + * + * @return Collection + */ + public function getAll(): Collection + { + // grab ALL recurring transactions: + return Recurrence + ::with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions']) + ->orderBy('active', 'DESC') + ->orderBy('title', 'ASC') + ->get(); + } + + /** + * Get the budget ID from a recurring transaction transaction. + * + * @param RecurrenceTransaction $recurrenceTransaction + * + * @return null|int + */ + public function getBudget(RecurrenceTransaction $recurrenceTransaction): ?int + { + $return = 0; + /** @var RecurrenceTransactionMeta $meta */ + foreach ($recurrenceTransaction->recurrenceTransactionMeta as $meta) { + if ($meta->name === 'budget_id') { + $return = (int)$meta->value; + } + } + + return $return === 0 ? null : $return; + } + + /** + * Get the category from a recurring transaction transaction. + * + * @param RecurrenceTransaction $recurrenceTransaction + * + * @return null|string + */ + public function getCategory(RecurrenceTransaction $recurrenceTransaction): ?string + { + $return = ''; + /** @var RecurrenceTransactionMeta $meta */ + foreach ($recurrenceTransaction->recurrenceTransactionMeta as $meta) { + if ($meta->name === 'category_name') { + $return = (string)$meta->value; + } + } + + return $return === '' ? null : $return; + } + + /** + * Returns the journals created for this recurrence, possibly limited by time. + * + * @param Recurrence $recurrence + * @param Carbon|null $start + * @param Carbon|null $end + * + * @return Collection + */ + public function getJournals(Recurrence $recurrence, Carbon $start = null, Carbon $end = null): Collection + { + $query = TransactionJournal + ::leftJoin('journal_meta', 'journal_meta.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transaction_journals.user_id', $recurrence->user_id) + ->whereNull('transaction_journals.deleted_at') + ->where('journal_meta.name', 'recurrence_id') + ->where('journal_meta.data', '"' . $recurrence->id . '"'); + if (null !== $start) { + $query->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')); + } + if (null !== $end) { + $query->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')); + } + $result = $query->get(['transaction_journals.*']); + + return $result; + } + + /** + * Get the notes. + * + * @param Recurrence $recurrence + * + * @return string + */ + public function getNoteText(Recurrence $recurrence): string + { + /** @var Note $note */ + $note = $recurrence->notes()->first(); + if (null !== $note) { + return (string)$note->text; + } + + return ''; + } + + /** + * Generate events in the date range. + * + * @param RecurrenceRepetition $repetition + * @param Carbon $start + * @param Carbon $end + * + * @throws FireflyException + * + * @return array + */ + public function getOccurrencesInRange(RecurrenceRepetition $repetition, Carbon $start, Carbon $end): array + { + $return = []; + $mutator = clone $start; + $mutator->startOfDay(); + $skipMod = $repetition->repetition_skip + 1; + $attempts = 0; + Log::debug(sprintf('Calculating occurrences for rep type "%s"', $repetition->repetition_type)); + Log::debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d'))); + switch ($repetition->repetition_type) { + default: + throw new FireflyException( + sprintf('Cannot calculate occurrences for recurring transaction repetition type "%s"', $repetition->repetition_type) + ); + case 'daily': + Log::debug('Rep is daily. Start of loop.'); + while ($mutator <= $end) { + Log::debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d'))); + if ($attempts % $skipMod === 0) { + Log::debug(sprintf('Attempts modulo skipmod is zero, include %s', $mutator->format('Y-m-d'))); + $return[] = clone $mutator; + } + $mutator->addDay(); + $attempts++; + } + break; + case 'weekly': + Log::debug('Rep is weekly.'); + // monday = 1 + // sunday = 7 + $dayOfWeek = (int)$repetition->repetition_moment; + Log::debug(sprintf('DoW in repetition is %d, in mutator is %d', $dayOfWeek, $mutator->dayOfWeekIso)); + if ($mutator->dayOfWeekIso > $dayOfWeek) { + // day has already passed this week, add one week: + $mutator->addWeek(); + Log::debug(sprintf('Jump to next week, so mutator is now: %s', $mutator->format('Y-m-d'))); + } + // today is wednesday (3), expected is friday (5): add two days. + // today is friday (5), expected is monday (1), subtract four days. + Log::debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d'))); + $dayDifference = $dayOfWeek - $mutator->dayOfWeekIso; + $mutator->addDays($dayDifference); + Log::debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d'))); + while ($mutator <= $end) { + if ($attempts % $skipMod === 0 && $start->lte($mutator) && $end->gte($mutator)) { + Log::debug('Date is in range of start+end, add to set.'); + $return[] = clone $mutator; + } + $attempts++; + $mutator->addWeek(); + Log::debug(sprintf('Mutator is now (end of loop): %s', $mutator->format('Y-m-d'))); + } + break; + case 'monthly': + $dayOfMonth = (int)$repetition->repetition_moment; + Log::debug(sprintf('Day of month in repetition is %d', $dayOfMonth)); + Log::debug(sprintf('Start is %s.', $start->format('Y-m-d'))); + Log::debug(sprintf('End is %s.', $end->format('Y-m-d'))); + if ($mutator->day > $dayOfMonth) { + Log::debug('Add a month.'); + // day has passed already, add a month. + $mutator->addMonth(); + } + Log::debug(sprintf('Start is now %s.', $mutator->format('Y-m-d'))); + Log::debug('Start loop.'); + while ($mutator < $end) { + Log::debug(sprintf('Mutator is now %s.', $mutator->format('Y-m-d'))); + $domCorrected = min($dayOfMonth, $mutator->daysInMonth); + Log::debug(sprintf('DoM corrected is %d', $domCorrected)); + $mutator->day = $domCorrected; + Log::debug(sprintf('Mutator is now %s.', $mutator->format('Y-m-d'))); + Log::debug(sprintf('$attempts %% $skipMod === 0 is %s', var_export($attempts % $skipMod === 0, true))); + Log::debug(sprintf('$start->lte($mutator) is %s', var_export($start->lte($mutator), true))); + Log::debug(sprintf('$end->gte($mutator) is %s', var_export($end->gte($mutator), true))); + if ($attempts % $skipMod === 0 && $start->lte($mutator) && $end->gte($mutator)) { + Log::debug(sprintf('ADD %s to return!', $mutator->format('Y-m-d'))); + $return[] = clone $mutator; + } + $attempts++; + $mutator->endOfMonth()->startOfDay()->addDay(); + } + break; + case 'ndom': + $mutator->startOfMonth(); + // this feels a bit like a cop out but why reinvent the wheel? + $counters = [1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth',]; + $daysOfWeek = [1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday',]; + $parts = explode(',', $repetition->repetition_moment); + while ($mutator <= $end) { + $string = sprintf('%s %s of %s %s', $counters[$parts[0]], $daysOfWeek[$parts[1]], $mutator->format('F'), $mutator->format('Y')); + $newCarbon = new Carbon($string); + $return[] = clone $newCarbon; + $mutator->endOfMonth()->addDay(); + } + break; + case 'yearly': + $date = new Carbon($repetition->repetition_moment); + $date->year = $mutator->year; + if ($mutator > $date) { + $date->addYear(); + + } + + // is $date between $start and $end? + $obj = clone $date; + $count = 0; + while ($obj <= $end && $obj >= $mutator && $count < 10) { + + $return[] = clone $obj; + $obj->addYears(1); + $count++; + } + break; + } + // filter out all the weekend days: + $return = $this->filterWeekends($repetition, $return); + + return $return; + } + + /** + * Get the tags from the recurring transaction. + * + * @param Recurrence $recurrence + * + * @return array + */ + public function getTags(Recurrence $recurrence): array + { + $tags = []; + /** @var RecurrenceMeta $meta */ + foreach ($recurrence->recurrenceMeta as $meta) { + if ($meta->name === 'tags' && '' !== $meta->value) { + $tags = explode(',', $meta->value); + } + } + + return $tags; + } + + /** + * @param Recurrence $recurrence + * @param int $page + * @param int $pageSize + * + * @return LengthAwarePaginator + */ + public function getTransactionPaginator(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator + { + $journalMeta = TransactionJournalMeta + ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') + ->whereNull('transaction_journals.deleted_at') + ->where('transaction_journals.user_id', $this->user->id) + ->where('name', 'recurrence_id') + ->where('data', json_encode((string)$recurrence->id)) + ->get()->pluck('transaction_journal_id')->toArray(); + $search = []; + foreach ($journalMeta as $journalId) { + $search[] = ['id' => (int)$journalId]; + } + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setUser($recurrence->user); + $collector->withOpposingAccount()->setAllAssetAccounts()->withCategoryInformation()->withBudgetInformation()->setLimit($pageSize)->setPage($page); + // filter on specific journals. + $collector->removeFilter(InternalTransferFilter::class); + $collector->setJournals(new Collection($search)); + + return $collector->getPaginatedJournals(); + } + + /** + * @param Recurrence $recurrence + * + * @return Collection + */ + public function getTransactions(Recurrence $recurrence): Collection + { + $journalMeta = TransactionJournalMeta + ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') + ->whereNull('transaction_journals.deleted_at') + ->where('transaction_journals.user_id', $this->user->id) + ->where('name', 'recurrence_id') + ->where('data', json_encode((string)$recurrence->id)) + ->get()->pluck('transaction_journal_id')->toArray(); + $search = []; + foreach ($journalMeta as $journalId) { + $search[] = ['id' => (int)$journalId]; + } + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setUser($recurrence->user); + $collector->withOpposingAccount()->setAllAssetAccounts()->withCategoryInformation()->withBudgetInformation(); + // filter on specific journals. + $collector->removeFilter(InternalTransferFilter::class); + $collector->setJournals(new Collection($search)); + + return $collector->getJournals(); + } + + /** + * Calculate the next X iterations starting on the date given in $date. + * + * @param RecurrenceRepetition $repetition + * @param Carbon $date + * @param int $count + * + * @return array + * @throws FireflyException + */ + public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count): array + { + $return = []; + $mutator = clone $date; + $skipMod = $repetition->repetition_skip + 1; + $total = 0; + $attempts = 0; + switch ($repetition->repetition_type) { + default: + throw new FireflyException( + sprintf('Cannot calculate occurrences for recurring transaction repetition type "%s"', $repetition->repetition_type) + ); + case 'daily': + while ($total < $count) { + $mutator->addDay(); + if ($attempts % $skipMod === 0) { + $return[] = clone $mutator; + $total++; + } + $attempts++; + } + break; + case 'weekly': + // monday = 1 + // sunday = 7 + $mutator->addDay(); // always assume today has passed. + $dayOfWeek = (int)$repetition->repetition_moment; + if ($mutator->dayOfWeekIso > $dayOfWeek) { + // day has already passed this week, add one week: + $mutator->addWeek(); + } + // today is wednesday (3), expected is friday (5): add two days. + // today is friday (5), expected is monday (1), subtract four days. + $dayDifference = $dayOfWeek - $mutator->dayOfWeekIso; + $mutator->addDays($dayDifference); + + while ($total < $count) { + if ($attempts % $skipMod === 0) { + $return[] = clone $mutator; + $total++; + } + $attempts++; + $mutator->addWeek(); + } + break; + case 'monthly': + $mutator->addDay(); // always assume today has passed. + $dayOfMonth = (int)$repetition->repetition_moment; + if ($mutator->day > $dayOfMonth) { + // day has passed already, add a month. + $mutator->addMonth(); + } + + while ($total < $count) { + $domCorrected = min($dayOfMonth, $mutator->daysInMonth); + $mutator->day = $domCorrected; + if ($attempts % $skipMod === 0) { + $return[] = clone $mutator; + $total++; + } + $attempts++; + $mutator->endOfMonth()->addDay(); + } + break; + case 'ndom': + $mutator->addDay(); // always assume today has passed. + $mutator->startOfMonth(); + // this feels a bit like a cop out but why reinvent the wheel? + $counters = [1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth',]; + $daysOfWeek = [1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday',]; + $parts = explode(',', $repetition->repetition_moment); + + while ($total < $count) { + $string = sprintf('%s %s of %s %s', $counters[$parts[0]], $daysOfWeek[$parts[1]], $mutator->format('F'), $mutator->format('Y')); + $newCarbon = new Carbon($string); + if ($attempts % $skipMod === 0) { + $return[] = clone $newCarbon; + $total++; + } + $attempts++; + $mutator->endOfMonth()->addDay(); + } + break; + case 'yearly': + $date = new Carbon($repetition->repetition_moment); + $date->year = $mutator->year; + if ($mutator > $date) { + $date->addYear(); + } + $obj = clone $date; + while ($total < $count) { + if ($attempts % $skipMod === 0) { + $return[] = clone $obj; + $total++; + } + $obj->addYears(1); + $attempts++; + } + break; + } + // filter out all the weekend days: + $return = $this->filterWeekends($repetition, $return); + + return $return; + } + + /** + * Parse the repetition in a string that is user readable. + * + * @param RecurrenceRepetition $repetition + * + * @return string + * @throws FireflyException + */ + public function repetitionDescription(RecurrenceRepetition $repetition): string + { + /** @var Preference $pref */ + $pref = app('preferences')->getForUser($this->user, 'language', config('firefly.default_language', 'en_US')); + $language = $pref->data; + switch ($repetition->repetition_type) { + default: + throw new FireflyException(sprintf('Cannot translate recurring transaction repetition type "%s"', $repetition->repetition_type)); + break; + case 'daily': + return trans('firefly.recurring_daily', [], $language); + break; + case 'weekly': + $dayOfWeek = trans(sprintf('config.dow_%s', $repetition->repetition_moment), [], $language); + + return trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language); + break; + case 'monthly': + // format a date: + return trans('firefly.recurring_monthly', ['dayOfMonth' => $repetition->repetition_moment], $language); + break; + case 'ndom': + $parts = explode(',', $repetition->repetition_moment); + // first part is number of week, second is weekday. + $dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $language); + + return trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language); + break; + case 'yearly': + // + $today = new Carbon; + $today->endOfYear(); + $repDate = Carbon::createFromFormat('Y-m-d', $repetition->repetition_moment); + $diffInYears = $today->diffInYears($repDate); + $repDate->addYears($diffInYears); // technically not necessary. + $string = $repDate->formatLocalized(trans('config.month_and_day_no_year')); + + return trans('firefly.recurring_yearly', ['date' => $string], $language); + break; + + } + + } + + /** + * Set user for in repository. + * + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + + /** + * @param array $data + * + * @return Recurrence + */ + public function store(array $data): Recurrence + { + $factory = new RecurrenceFactory; + $factory->setUser($this->user); + + return $factory->create($data); + } + + /** + * Update a recurring transaction. + * + * @param Recurrence $recurrence + * @param array $data + * + * @return Recurrence + * @throws FireflyException + */ + public function update(Recurrence $recurrence, array $data): Recurrence + { + /** @var RecurrenceUpdateService $service */ + $service = app(RecurrenceUpdateService::class); + + return $service->update($recurrence, $data); + } + + /** + * Filters out all weekend entries, if necessary. + * + * @param RecurrenceRepetition $repetition + * @param array $dates + * + * @return array + */ + protected function filterWeekends(RecurrenceRepetition $repetition, array $dates): array + { + if ((int)$repetition->weekend === RecurrenceRepetition::WEEKEND_DO_NOTHING) { + Log::debug('Repetition will not be filtered on weekend days.'); + + return $dates; + } + $return = []; + /** @var Carbon $date */ + foreach ($dates as $date) { + $isWeekend = $date->isWeekend(); + if (!$isWeekend) { + $return[] = clone $date; + Log::debug(sprintf('Date is %s, not a weekend date.', $date->format('D d M Y'))); + continue; + } + + // is weekend and must set back to Friday? + if ($repetition->weekend === RecurrenceRepetition::WEEKEND_TO_FRIDAY) { + $clone = clone $date; + $clone->addDays(5 - $date->dayOfWeekIso); + Log::debug( + sprintf('Date is %s, and this is in the weekend, so corrected to %s (Friday).', $date->format('D d M Y'), $clone->format('D d M Y')) + ); + $return[] = clone $clone; + continue; + } + + // postpone to Monday? + if ($repetition->weekend === RecurrenceRepetition::WEEKEND_TO_MONDAY) { + $clone = clone $date; + $clone->addDays(8 - $date->dayOfWeekIso); + Log::debug( + sprintf('Date is %s, and this is in the weekend, so corrected to %s (Monday).', $date->format('D d M Y'), $clone->format('D d M Y')) + ); + $return[] = $clone; + continue; + } + Log::debug(sprintf('Date is %s, removed from final result', $date->format('D d M Y'))); + } + + // filter unique dates + Log::debug(sprintf('Count before filtering: %d', \count($dates))); + $collection = new Collection($return); + $filtered = $collection->unique(); + $return = $filtered->toArray(); + + Log::debug(sprintf('Count after filtering: %d', \count($return))); + + return $return; + } +} \ No newline at end of file diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php new file mode 100644 index 0000000000..d738d768d6 --- /dev/null +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -0,0 +1,190 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Repositories\Recurring; + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceRepetition; +use FireflyIII\Models\RecurrenceTransaction; +use FireflyIII\User; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; + + +/** + * Interface RecurringRepositoryInterface + * + * @package FireflyIII\Repositories\Recurring + */ +interface RecurringRepositoryInterface +{ + /** + * Destroy a recurring transaction. + * + * @param Recurrence $recurrence + */ + public function destroy(Recurrence $recurrence): void; + + /** + * Returns all of the user's recurring transactions. + * + * @return Collection + */ + public function get(): Collection; + + /** + * Get ALL recurring transactions. + * + * @return Collection + */ + public function getAll(): Collection; + + /** + * Get the budget ID from a recurring transaction transaction. + * + * @param RecurrenceTransaction $recurrenceTransaction + * + * @return null|int + */ + public function getBudget(RecurrenceTransaction $recurrenceTransaction): ?int; + + /** + * Get the category from a recurring transaction transaction. + * + * @param RecurrenceTransaction $recurrenceTransaction + * + * @return null|string + */ + public function getCategory(RecurrenceTransaction $recurrenceTransaction): ?string; + + /** + * Returns the journals created for this recurrence, possibly limited by time. + * TODO make consistent with getTransactions + * + * @param Recurrence $recurrence + * @param Carbon|null $start + * @param Carbon|null $end + * + * @return Collection + */ + public function getJournals(Recurrence $recurrence, Carbon $start = null, Carbon $end = null): Collection; + + /** + * Get the notes. + * + * @param Recurrence $recurrence + * + * @return string + */ + public function getNoteText(Recurrence $recurrence): string; + + /** + * Generate events in the date range. + * + * @param RecurrenceRepetition $repetition + * @param Carbon $start + * @param Carbon $end + * + * @throws FireflyException + * + * @return array + */ + public function getOccurrencesInRange(RecurrenceRepetition $repetition, Carbon $start, Carbon $end): array; + + /** + * Get the tags from the recurring transaction. + * + * @param Recurrence $recurrence + * + * @return array + */ + public function getTags(Recurrence $recurrence): array; + + /** + * @param Recurrence $recurrence + * @param int $page + * @param int $pageSize + * + * @return LengthAwarePaginator + */ + public function getTransactionPaginator(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator; + + /** + * @param Recurrence $recurrence + * + * @return Collection + */ + public function getTransactions(Recurrence $recurrence): Collection; + + /** + * Calculate the next X iterations starting on the date given in $date. + * Returns an array of Carbon objects. + * + * @param RecurrenceRepetition $repetition + * @param Carbon $date + * @param int $count + * + * @throws FireflyException + * @return array + */ + public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count): array; + + /** + * Parse the repetition in a string that is user readable. + * + * @param RecurrenceRepetition $repetition + * + * @return string + */ + public function repetitionDescription(RecurrenceRepetition $repetition): string; + + /** + * Set user for in repository. + * + * @param User $user + */ + public function setUser(User $user): void; + + /** + * Store a new recurring transaction. + *\ + * + * @param array $data + * + * @return Recurrence + */ + public function store(array $data): Recurrence; + + /** + * Update a recurring transaction. + * + * @param Recurrence $recurrence + * @param array $data + * + * @return Recurrence + */ + public function update(Recurrence $recurrence, array $data): Recurrence; + +} \ No newline at end of file diff --git a/app/Repositories/Rule/RuleRepository.php b/app/Repositories/Rule/RuleRepository.php index 06d29aefa0..cb33208d77 100644 --- a/app/Repositories/Rule/RuleRepository.php +++ b/app/Repositories/Rule/RuleRepository.php @@ -80,6 +80,16 @@ class RuleRepository implements RuleRepositoryInterface return $rule; } + /** + * Get all the users rules. + * + * @return Collection + */ + public function getAll(): Collection + { + return $this->user->rules()->with(['ruleGroup'])->get(); + } + /** * FIxXME can return null. * @@ -280,7 +290,7 @@ class RuleRepository implements RuleRepositoryInterface $rule->order = ($order + 1); $rule->active = 1; $rule->strict = $data['strict'] ?? false; - $rule->stop_processing = 1 === (int)$data['stop_processing']; + $rule->stop_processing = 1 === (int)$data['stop-processing']; $rule->title = $data['title']; $rule->description = \strlen($data['description']) > 0 ? $data['description'] : null; @@ -346,7 +356,7 @@ class RuleRepository implements RuleRepositoryInterface // update rule: $rule->rule_group_id = $data['rule_group_id']; $rule->active = $data['active']; - $rule->stop_processing = $data['stop_processing']; + $rule->stop_processing = $data['stop-processing']; $rule->title = $data['title']; $rule->strict = $data['strict'] ?? false; $rule->description = $data['description']; @@ -377,11 +387,11 @@ class RuleRepository implements RuleRepositoryInterface { $order = 1; foreach ($data['rule-actions'] as $index => $action) { - $value = $data['rule-action-values'][$index] ?? ''; - $stopProcessing = isset($data['rule-action-stop'][$index]) ? true : false; + $value = $action['value'] ?? ''; + $stopProcessing = $action['stop-processing'] ?? false; $actionValues = [ - 'action' => $action, + 'action' => $action['name'], 'value' => $value, 'stopProcessing' => $stopProcessing, 'order' => $order, @@ -413,11 +423,11 @@ class RuleRepository implements RuleRepositoryInterface $this->storeTrigger($rule, $triggerValues); foreach ($data['rule-triggers'] as $index => $trigger) { - $value = $data['rule-trigger-values'][$index] ?? ''; - $stopProcessing = isset($data['rule-trigger-stop'][$index]) ? true : false; + $value = $trigger['value'] ?? ''; + $stopProcessing = $trigger['stop-processing'] ?? false; $triggerValues = [ - 'action' => $trigger, + 'action' => $trigger['name'], 'value' => $value, 'stopProcessing' => $stopProcessing, 'order' => $order, diff --git a/app/Repositories/Rule/RuleRepositoryInterface.php b/app/Repositories/Rule/RuleRepositoryInterface.php index 5fcdd4c89d..cefbf46a95 100644 --- a/app/Repositories/Rule/RuleRepositoryInterface.php +++ b/app/Repositories/Rule/RuleRepositoryInterface.php @@ -54,6 +54,13 @@ interface RuleRepositoryInterface */ public function find(int $ruleId): Rule; + /** + * Get all the users rules. + * + * @return Collection + */ + public function getAll(): Collection; + /** * @return RuleGroup */ diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index 90c545cb58..2cefe02b68 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -136,6 +136,16 @@ class TagRepository implements TagRepositoryInterface return new Tag; } + /** + * @param int $tagId + * + * @return Tag|null + */ + public function findNull(int $tagId): ?Tag + { + return $this->user->tags()->find($tagId); + } + /** * @param Tag $tag * @@ -192,6 +202,16 @@ class TagRepository implements TagRepositoryInterface return new Carbon; } + /** + * Will return the newest tag (if known) or NULL. + * + * @return Tag|null + */ + public function newestTag(): ?Tag + { + return $this->user->tags()->whereNotNull('date')->orderBy('date', 'DESC')->first(); + } + /** * @return Tag */ @@ -453,23 +473,4 @@ class TagRepository implements TagRepositoryInterface return (int)($range[0] + $extra); } - - /** - * @param int $tagId - * - * @return Tag|null - */ - public function findNull(int $tagId): ?Tag - { - return $this->user->tags()->find($tagId); - } - - /** - * Will return the newest tag (if known) or NULL. - * - * @return Tag|null - */ - public function newestTag(): ?Tag - { - return $this->user->tags()->whereNotNull('date')->orderBy('date', 'DESC')->first(); -}} +} diff --git a/app/Repositories/Tag/TagRepositoryInterface.php b/app/Repositories/Tag/TagRepositoryInterface.php index e4e8b44ba5..3b6b7cd19a 100644 --- a/app/Repositories/Tag/TagRepositoryInterface.php +++ b/app/Repositories/Tag/TagRepositoryInterface.php @@ -69,12 +69,6 @@ interface TagRepositoryInterface /** * @param int $tagId * - * @return Tag|null - */ - public function findNull(int $tagId): ?Tag; - - /** - * @param int $tagId * @deprecated * @return Tag */ @@ -87,6 +81,13 @@ interface TagRepositoryInterface */ public function findByTag(string $tag): Tag; + /** + * @param int $tagId + * + * @return Tag|null + */ + public function findNull(int $tagId): ?Tag; + /** * @param Tag $tag * @@ -117,15 +118,18 @@ interface TagRepositoryInterface /** * Will return the newest tag (if known) or NULL. + * + * @return Tag|null + */ + public function newestTag(): ?Tag; + + /** + * Will return the newest tag (if known) or NULL. + * * @return Tag|null */ public function oldestTag(): ?Tag; - /** - * Will return the newest tag (if known) or NULL. - * @return Tag|null - */ - public function newestTag(): ?Tag; /** * @param User $user */ diff --git a/app/Rules/IsAssetAccountId.php b/app/Rules/IsAssetAccountId.php new file mode 100644 index 0000000000..7ffa717037 --- /dev/null +++ b/app/Rules/IsAssetAccountId.php @@ -0,0 +1,46 @@ +find($accountId); + if (null === $account) { + return false; + } + if ($account->accountType->type !== AccountType::ASSET && $account->accountType->type !== AccountType::DEFAULT) { + return false; + } + + return true; + } +} diff --git a/app/Rules/IsValidAttachmentModel.php b/app/Rules/IsValidAttachmentModel.php new file mode 100644 index 0000000000..954e5a2151 --- /dev/null +++ b/app/Rules/IsValidAttachmentModel.php @@ -0,0 +1,88 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Rules; + + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use Illuminate\Contracts\Validation\Rule; + +/** + * Class IsValidAttachmentModel + */ +class IsValidAttachmentModel implements Rule +{ + /** @var string */ + private $model; + + /** + * IsValidAttachmentModel constructor. + * + * @param string $model + */ + public function __construct(string $model) + { + $this->model = $model; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message(): string + { + return trans('validation.model_id_invalid'); + } + + /** + * Determine if the validation rule passes. + * + * @param string $attribute + * @param mixed $value + * + * @return bool + * @throws FireflyException + */ + public function passes($attribute, $value): bool + { + if (!auth()->check()) { + return false; + } + $user = auth()->user(); + switch ($this->model) { + default: + throw new FireflyException(sprintf('Model "%s" cannot be validated.', $this->model)); + case TransactionJournal::class: + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + $repository->setUser($user); + $result = $repository->findNull((int)$value); + + return null !== $result; + break; + } + } +} \ No newline at end of file diff --git a/app/Rules/ValidRecurrenceRepetitionType.php b/app/Rules/ValidRecurrenceRepetitionType.php new file mode 100644 index 0000000000..c8aa3eedbd --- /dev/null +++ b/app/Rules/ValidRecurrenceRepetitionType.php @@ -0,0 +1,72 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Rules; + +use Illuminate\Contracts\Validation\Rule; + +/** + * Class ValidRecurrenceRepetitionType + */ +class ValidRecurrenceRepetitionType implements Rule +{ + + /** + * Get the validation error message. + * + * @return string + */ + public function message(): string + { + return trans('validation.valid_recurrence_rep_type'); + } + + /** + * Determine if the validation rule passes. + * + * @param string $attribute + * @param mixed $value + * + * @return bool + */ + public function passes($attribute, $value): bool + { + $value = (string)$value; + if ($value === 'daily') { + return true; + } + //monthly,17 + //ndom,3,7 + if (\in_array(substr($value, 0, 6), ['yearly', 'weekly'])) { + return true; + } + if (0 === strpos($value, 'monthly')) { + return true; + } + if (0 === strpos($value, 'ndom')) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/app/Rules/ValidRecurrenceRepetitionValue.php b/app/Rules/ValidRecurrenceRepetitionValue.php new file mode 100644 index 0000000000..3e2afe32a5 --- /dev/null +++ b/app/Rules/ValidRecurrenceRepetitionValue.php @@ -0,0 +1,107 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Rules; + + +use Carbon\Carbon; +use Illuminate\Contracts\Validation\Rule; +use InvalidArgumentException; + +/** + * Class ValidRecurrenceRepetitionValue + */ +class ValidRecurrenceRepetitionValue implements Rule +{ + + /** + * Get the validation error message. + * + * @return string + */ + public function message(): string + { + return trans('validation.valid_recurrence_rep_type'); + } + + /** + * Determine if the validation rule passes. + * + * @param string $attribute + * @param mixed $value + * + * @return bool + */ + public function passes($attribute, $value): bool + { + $value = (string)$value; + + if ($value === 'daily') { + return true; + } + + if (0 === strpos($value, 'monthly')) { + $dayOfMonth = (int)substr($value, 8); + + return $dayOfMonth > 0 && $dayOfMonth < 32; + } + + //ndom,3,7 + // nth x-day of the month. + if (0 === strpos($value, 'ndom')) { + $parameters = explode(',', substr($value, 5)); + if (\count($parameters) !== 2) { + return false; + } + $nthDay = (int)($parameters[0] ?? 0.0); + $dayOfWeek = (int)($parameters[1] ?? 0.0); + if ($nthDay < 1 || $nthDay > 5) { + return false; + } + + return $dayOfWeek > 0 && $dayOfWeek < 8; + } + + //weekly,7 + if (0 === strpos($value, 'weekly')) { + $dayOfWeek = (int)substr($value, 7); + + return $dayOfWeek > 0 && $dayOfWeek < 8; + } + + //yearly,2018-01-01 + if (0 === strpos($value, 'yearly')) { + // rest of the string must be valid date: + $dateString = substr($value, 7); + try { + $date = Carbon::createFromFormat('Y-m-d', $dateString); + } catch (InvalidArgumentException $e) { + return false; + } + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/app/Services/Currency/FixerIO.php b/app/Services/Currency/FixerIO.php deleted file mode 100644 index 561cac7315..0000000000 --- a/app/Services/Currency/FixerIO.php +++ /dev/null @@ -1,96 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Services\Currency; - -use Carbon\Carbon; -use Exception; -use FireflyIII\Models\CurrencyExchangeRate; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\User; -use Log; -use Requests; - -/** - * Class FixerIO. - */ -class FixerIO implements ExchangeRateInterface -{ - /** @var User */ - protected $user; - - /** - * @param TransactionCurrency $fromCurrency - * @param TransactionCurrency $toCurrency - * @param Carbon $date - * - * @return CurrencyExchangeRate - */ - public function getRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate - { - $uri = sprintf('https://api.fixer.io/%s?base=%s&symbols=%s', $date->format('Y-m-d'), $fromCurrency->code, $toCurrency->code); - $statusCode = -1; - try { - $result = Requests::get($uri); - $statusCode = $result->status_code; - $body = $result->body; - } catch (Exception $e) { - // don't care about error - $body = sprintf('Requests_Exception: %s', $e->getMessage()); - } - // Requests_Exception - $rate = 1.0; - $content = null; - if (200 !== $statusCode) { - Log::error(sprintf('Something went wrong. Received error code %d and body "%s" from FixerIO.', $statusCode, $body)); - } - // get rate from body: - if (200 === $statusCode) { - $content = json_decode($body, true); - } - if (null !== $content) { - $code = $toCurrency->code; - $rate = $content['rates'][$code] ?? '1'; - } - - // create new currency exchange rate object: - $exchangeRate = new CurrencyExchangeRate; - $exchangeRate->user()->associate($this->user); - $exchangeRate->fromCurrency()->associate($fromCurrency); - $exchangeRate->toCurrency()->associate($toCurrency); - $exchangeRate->date = $date; - $exchangeRate->rate = $rate; - $exchangeRate->save(); - - return $exchangeRate; - } - - /** - * @param User $user - * - * @return mixed|void - */ - public function setUser(User $user) - { - $this->user = $user; - } -} diff --git a/app/Services/Currency/FixerIOv2.php b/app/Services/Currency/FixerIOv2.php index 67d4a77c82..b224e65de4 100644 --- a/app/Services/Currency/FixerIOv2.php +++ b/app/Services/Currency/FixerIOv2.php @@ -27,8 +27,9 @@ use Exception; use FireflyIII\Models\CurrencyExchangeRate; use FireflyIII\Models\TransactionCurrency; use FireflyIII\User; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; use Log; -use Requests; /** * Class FixerIOv2. @@ -48,19 +49,21 @@ class FixerIOv2 implements ExchangeRateInterface public function getRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate { // create new exchange rate with default values. - // create new currency exchange rate object: + $rate = 0; $exchangeRate = new CurrencyExchangeRate; $exchangeRate->user()->associate($this->user); $exchangeRate->fromCurrency()->associate($fromCurrency); $exchangeRate->toCurrency()->associate($toCurrency); $exchangeRate->date = $date; - $exchangeRate->rate = 0; + $exchangeRate->rate = $rate; // get API key $apiKey = env('FIXER_API_KEY', ''); // if no API key, return unsaved exchange rate. - if (\strlen($apiKey) === 0) { + if ('' === $apiKey) { + Log::warning('No fixer.IO API key, will not do conversion.'); + return $exchangeRate; } @@ -72,31 +75,36 @@ class FixerIOv2 implements ExchangeRateInterface $statusCode = -1; Log::debug(sprintf('Going to request exchange rate using URI %s', str_replace($apiKey, 'xxxx', $uri))); try { - $result = Requests::get($uri); - $statusCode = $result->status_code; - $body = $result->body; + $client = new Client; + $res = $client->request('GET', $uri); + $statusCode = $res->getStatusCode(); + $body = $res->getBody()->getContents(); Log::debug(sprintf('Result status code is %d', $statusCode)); - } catch (Exception $e) { + Log::debug(sprintf('Result body is: %s', $body)); + } catch (GuzzleException|Exception $e) { // don't care about error - $body = sprintf('Requests_Exception: %s', $e->getMessage()); + $body = sprintf('Guzzle exception: %s', $e->getMessage()); } - // Requests_Exception $content = null; if (200 !== $statusCode) { Log::error(sprintf('Something went wrong. Received error code %d and body "%s" from FixerIO.', $statusCode, $body)); } + $success = false; // get rate from body: if (200 === $statusCode) { $content = json_decode($body, true); + $success = $content['success'] ?? false; } - if (null !== $content) { + if (null !== $content && true === $success) { $code = $toCurrency->code; $rate = (float)($content['rates'][$code] ?? 0); + Log::debug('Got the following rates from Fixer: ', $content['rates'] ?? []); } - Log::debug('Got the following rates from Fixer: ', $content['rates'] ?? []); + $exchangeRate->rate = $rate; if ($rate !== 0.0) { + Log::debug('Rate is not zero, save it!'); $exchangeRate->save(); } diff --git a/app/Services/Github/Request/GithubRequest.php b/app/Services/Github/Request/GithubRequest.php index 3e7a5ca5d6..fc3552a18c 100644 --- a/app/Services/Github/Request/GithubRequest.php +++ b/app/Services/Github/Request/GithubRequest.php @@ -29,6 +29,6 @@ namespace FireflyIII\Services\Github\Request; */ interface GithubRequest { - public function call(); + public function call(): void; } diff --git a/app/Services/Github/Request/UpdateRequest.php b/app/Services/Github/Request/UpdateRequest.php index 9fb0f212dd..fd2d173049 100644 --- a/app/Services/Github/Request/UpdateRequest.php +++ b/app/Services/Github/Request/UpdateRequest.php @@ -26,7 +26,9 @@ namespace FireflyIII\Services\Github\Request; use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Services\Github\Object\Release; -use Requests; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; +use Log; use SimpleXMLElement; /** @@ -41,30 +43,34 @@ class UpdateRequest implements GithubRequest * * @throws FireflyException */ - public function call() + public function call(): void { $uri = 'https://github.com/firefly-iii/firefly-iii/releases.atom'; + Log::debug(sprintf('Going to call %s', $uri)); try { - $response = Requests::get($uri); - } catch (Exception $e) { + $client = new Client(); + $res = $client->request('GET', $uri); + } catch (GuzzleException|Exception $e) { throw new FireflyException(sprintf('Response error from Github: %s', $e->getMessage())); } - if ($response->status_code !== 200) { - throw new FireflyException(sprintf('Returned code %d, error: %s', $response->status_code, $response->body)); + if ($res->getStatusCode() !== 200) { + throw new FireflyException(sprintf('Returned code %d, error: %s', $res->getStatusCode(), $res->getBody()->getContents())); } - $releaseXml = new SimpleXMLElement($response->body, LIBXML_NOCDATA); + $releaseXml = new SimpleXMLElement($res->getBody()->getContents(), LIBXML_NOCDATA); //fetch the products for each category if (isset($releaseXml->entry)) { + Log::debug(sprintf('Count of entries is: %d', \count($releaseXml->entry))); foreach ($releaseXml->entry as $entry) { - $array = [ + $array = [ 'id' => (string)$entry->id, 'updated' => (string)$entry->updated, 'title' => (string)$entry->title, 'content' => (string)$entry->content, ]; + Log::debug(sprintf('Found version %s', $entry->title)); $this->releases[] = new Release($array); } } diff --git a/app/Services/IP/IpifyOrg.php b/app/Services/IP/IpifyOrg.php index 2e72e23c9c..0c0f8a3404 100644 --- a/app/Services/IP/IpifyOrg.php +++ b/app/Services/IP/IpifyOrg.php @@ -24,8 +24,9 @@ declare(strict_types=1); namespace FireflyIII\Services\IP; use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; use Log; -use Requests; /** * Class IpifyOrg @@ -42,19 +43,20 @@ class IpifyOrg implements IPRetrievalInterface { $result = null; try { - $response = Requests::get('https://api.ipify.org'); - } catch (Exception $e) { + $client = new Client; + $res = $client->request('GET', 'https://api.ipify.org'); + } catch (GuzzleException|Exception $e) { Log::warning(sprintf('The ipify.org service could not retrieve external IP: %s', $e->getMessage())); Log::warning($e->getTraceAsString()); return null; } - if (200 !== $response->status_code) { - Log::warning(sprintf('Could not retrieve external IP: %d %s', $response->status_code, $response->body)); + if (200 !== $res->getStatusCode()) { + Log::warning(sprintf('Could not retrieve external IP: %d %s', $res->getStatusCode(), $res->getBody()->getContents())); return null; } - return (string)$response->body; + return (string)$res->getBody()->getContents(); } } diff --git a/app/Services/Internal/Destroy/RecurrenceDestroyService.php b/app/Services/Internal/Destroy/RecurrenceDestroyService.php new file mode 100644 index 0000000000..6f59892f4f --- /dev/null +++ b/app/Services/Internal/Destroy/RecurrenceDestroyService.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Internal\Destroy; + +use Exception; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceTransaction; +use Log; + +/** + * @codeCoverageIgnore + * Class RecurrenceDestroyService + */ +class RecurrenceDestroyService +{ + /** + * @param Recurrence $recurrence + */ + public function destroy(Recurrence $recurrence): void + { + try { + // delete all meta data + $recurrence->recurrenceMeta()->delete(); + + // delete all transactions. + /** @var RecurrenceTransaction $transaction */ + foreach($recurrence->recurrenceTransactions as $transaction) { + $transaction->recurrenceTransactionMeta()->delete(); + $transaction->delete(); + } + // delete all repetitions + $recurrence->recurrenceRepetitions()->delete(); + + // delete recurrence + $recurrence->delete(); + } catch (Exception $e) { // @codeCoverageIgnore + Log::error(sprintf('Could not delete recurrence: %s', $e->getMessage())); // @codeCoverageIgnore + } + } + +} diff --git a/app/Services/Internal/Support/RecurringTransactionTrait.php b/app/Services/Internal/Support/RecurringTransactionTrait.php new file mode 100644 index 0000000000..b36d7d4c0d --- /dev/null +++ b/app/Services/Internal/Support/RecurringTransactionTrait.php @@ -0,0 +1,214 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Internal\Support; + +use Exception; +use FireflyIII\Factory\BudgetFactory; +use FireflyIII\Factory\CategoryFactory; +use FireflyIII\Factory\PiggyBankFactory; +use FireflyIII\Factory\TransactionCurrencyFactory; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceMeta; +use FireflyIII\Models\RecurrenceRepetition; +use FireflyIII\Models\RecurrenceTransaction; +use FireflyIII\Models\RecurrenceTransactionMeta; +use FireflyIII\Models\TransactionType; +use Log; + + +/** + * Trait RecurringTransactionTrait + * + * @package FireflyIII\Services\Internal\Support + */ +trait RecurringTransactionTrait +{ + /** + * @param Recurrence $recurrence + * @param array $repetitions + */ + public function createRepetitions(Recurrence $recurrence, array $repetitions): void + { + /** @var array $array */ + foreach ($repetitions as $array) { + RecurrenceRepetition::create( + [ + 'recurrence_id' => $recurrence->id, + 'repetition_type' => $array['type'], + 'repetition_moment' => $array['moment'], + 'repetition_skip' => $array['skip'], + 'weekend' => $array['weekend'] ?? 1, + ] + ); + + } + } + + /** + * @param Recurrence $recurrence + * @param array $transactions + */ + public function createTransactions(Recurrence $recurrence, array $transactions): void + { + foreach ($transactions as $array) { + $source = null; + $destination = null; + switch ($recurrence->transactionType->type) { + case TransactionType::WITHDRAWAL: + $source = $this->findAccount(AccountType::ASSET, $array['source_id'], $array['source_name']); + $destination = $this->findAccount(AccountType::EXPENSE, $array['destination_id'], $array['destination_name']); + break; + case TransactionType::DEPOSIT: + $source = $this->findAccount(AccountType::REVENUE, $array['source_id'], $array['source_name']); + $destination = $this->findAccount(AccountType::ASSET, $array['destination_id'], $array['destination_name']); + break; + case TransactionType::TRANSFER: + $source = $this->findAccount(AccountType::ASSET, $array['source_id'], $array['source_name']); + $destination = $this->findAccount(AccountType::ASSET, $array['destination_id'], $array['destination_name']); + break; + } + + /** @var TransactionCurrencyFactory $factory */ + $factory = app(TransactionCurrencyFactory::class); + $currency = $factory->find($array['currency_id'], $array['currency_code']); + $foreignCurrency = $factory->find($array['foreign_currency_id'], $array['foreign_currency_code']); + $defaultCurrency = app('amount')->getDefaultCurrencyByUser($recurrence->user); + if (null === $currency) { + $currency = $defaultCurrency; + } + $transaction = new RecurrenceTransaction( + [ + 'recurrence_id' => $recurrence->id, + 'transaction_currency_id' => $currency->id, + 'foreign_currency_id' => null === $foreignCurrency ? null : $foreignCurrency->id, + 'source_id' => $source->id, + 'destination_id' => $destination->id, + 'amount' => $array['amount'], + 'foreign_amount' => '' === (string)$array['foreign_amount'] ? null : (string)$array['foreign_amount'], + 'description' => $array['description'], + ] + ); + $transaction->save(); + + /** @var BudgetFactory $budgetFactory */ + $budgetFactory = app(BudgetFactory::class); + $budgetFactory->setUser($recurrence->user); + $budget = $budgetFactory->find($array['budget_id'], $array['budget_name']); + + /** @var CategoryFactory $categoryFactory */ + $categoryFactory = app(CategoryFactory::class); + $categoryFactory->setUser($recurrence->user); + $category = $categoryFactory->findOrCreate($array['category_id'], $array['category_name']); + + // create recurrence transaction meta: + if (null !== $budget) { + RecurrenceTransactionMeta::create( + [ + 'rt_id' => $transaction->id, + 'name' => 'budget_id', + 'value' => $budget->id, + ] + ); + } + if (null !== $category) { + RecurrenceTransactionMeta::create( + [ + 'rt_id' => $transaction->id, + 'name' => 'category_name', + 'value' => $category->name, + ] + ); + } + } + } + + /** + * @param Recurrence $recurrence + */ + public function deleteRepetitions(Recurrence $recurrence): void + { + $recurrence->recurrenceRepetitions()->delete(); + } + + /** + * @param Recurrence $recurrence + */ + public function deleteTransactions(Recurrence $recurrence): void + { + /** @var RecurrenceTransaction $transaction */ + foreach ($recurrence->recurrenceTransactions as $transaction) { + $transaction->recurrenceTransactionMeta()->delete(); + try { + $transaction->delete(); + } catch (Exception $e) { + Log::debug($e->getMessage()); + } + } + } + + /** + * @param Recurrence $recurrence + * @param array $data + */ + public function updateMetaData(Recurrence $recurrence, array $data): void + { + // only two special meta fields right now. Let's just hard code them. + $piggyId = (int)($data['meta']['piggy_bank_id'] ?? 0.0); + $piggyName = $data['meta']['piggy_bank_name'] ?? ''; + /** @var PiggyBankFactory $factory */ + $factory = app(PiggyBankFactory::class); + $factory->setUser($recurrence->user); + $piggyBank = $factory->find($piggyId, $piggyName); + if (null !== $piggyBank) { + /** @var RecurrenceMeta $entry */ + $entry = $recurrence->recurrenceMeta()->where('name', 'piggy_bank_id')->first(); + if (null === $entry) { + $entry = RecurrenceMeta::create(['recurrence_id' => $recurrence->id, 'name' => 'piggy_bank_id', 'value' => $piggyBank->id]); + } + $entry->value = $piggyBank->id; + $entry->save(); + } + if (null === $piggyBank) { + // delete if present + $recurrence->recurrenceMeta()->where('name', 'piggy_bank_id')->delete(); + } + + + $tags = $data['meta']['tags'] ?? []; + if (\count($tags) > 0) { + /** @var RecurrenceMeta $entry */ + $entry = $recurrence->recurrenceMeta()->where('name', 'tags')->first(); + if (null === $entry) { + $entry = RecurrenceMeta::create(['recurrence_id' => $recurrence->id, 'name' => 'tags', 'value' => implode(',', $tags)]); + } + $entry->value = implode(',', $tags); + $entry->save(); + } + if (\count($tags) === 0) { + // delete if present + $recurrence->recurrenceMeta()->where('name', 'tags')->delete(); + } + } +} \ No newline at end of file diff --git a/app/Services/Internal/Support/TransactionServiceTrait.php b/app/Services/Internal/Support/TransactionServiceTrait.php index 06fe1fe99c..d0cb0a3f8f 100644 --- a/app/Services/Internal/Support/TransactionServiceTrait.php +++ b/app/Services/Internal/Support/TransactionServiceTrait.php @@ -190,6 +190,7 @@ trait TransactionServiceTrait */ protected function findCategory(?int $categoryId, ?string $categoryName): ?Category { + Log::debug(sprintf('Going to find or create category #%d, with name "%s"', $categoryId, $categoryName)); /** @var CategoryFactory $factory */ $factory = app(CategoryFactory::class); $factory->setUser($this->user); diff --git a/app/Services/Internal/Support/TransactionTypeTrait.php b/app/Services/Internal/Support/TransactionTypeTrait.php new file mode 100644 index 0000000000..204110c66b --- /dev/null +++ b/app/Services/Internal/Support/TransactionTypeTrait.php @@ -0,0 +1,57 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Internal\Support; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\TransactionTypeFactory; +use FireflyIII\Models\TransactionType; +use Log; + +/** + * Trait TransactionTypeTrait + * + * @package FireflyIII\Services\Internal\Support + */ +trait TransactionTypeTrait +{ + /** + * Get the transaction type. Since this is mandatory, will throw an exception when nothing comes up. Will always + * use TransactionType repository. + * + * @param string $type + * + * @return TransactionType + * @throws FireflyException + */ + protected function findTransactionType(string $type): TransactionType + { + $factory = app(TransactionTypeFactory::class); + $transactionType = $factory->find($type); + if (null === $transactionType) { + Log::error(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore + throw new FireflyException(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore + } + + return $transactionType; + } +} \ No newline at end of file diff --git a/app/Services/Internal/Update/BillUpdateService.php b/app/Services/Internal/Update/BillUpdateService.php index 97e89b1753..72c811bee2 100644 --- a/app/Services/Internal/Update/BillUpdateService.php +++ b/app/Services/Internal/Update/BillUpdateService.php @@ -51,7 +51,7 @@ class BillUpdateService $bill->repeat_freq = $data['repeat_freq']; $bill->skip = $data['skip']; $bill->automatch = true; - $bill->active = $data['active']; + $bill->active = $data['active']??true; $bill->save(); // update note: diff --git a/app/Services/Internal/Update/RecurrenceUpdateService.php b/app/Services/Internal/Update/RecurrenceUpdateService.php new file mode 100644 index 0000000000..241a36cf50 --- /dev/null +++ b/app/Services/Internal/Update/RecurrenceUpdateService.php @@ -0,0 +1,91 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Internal\Update; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceMeta; +use FireflyIII\Services\Internal\Support\RecurringTransactionTrait; +use FireflyIII\Services\Internal\Support\TransactionServiceTrait; +use FireflyIII\Services\Internal\Support\TransactionTypeTrait; +use FireflyIII\User; + +/** + * Class RecurrenceUpdateService + */ +class RecurrenceUpdateService +{ + use TransactionTypeTrait, TransactionServiceTrait, RecurringTransactionTrait; + + /** @var User */ + private $user; + + /** + * Updates a recurrence. + * + * @param Recurrence $recurrence + * @param array $data + * + * @return Recurrence + * @throws FireflyException + */ + public function update(Recurrence $recurrence, array $data): Recurrence + { + // is expected by TransactionServiceTrait + $this->user = $recurrence->user; + $transactionType = $this->findTransactionType(ucfirst($data['recurrence']['type'])); + // update basic fields first: + $recurrence->transaction_type_id = $transactionType->id; + $recurrence->title = $data['recurrence']['title'] ?? $recurrence->title; + $recurrence->description = $data['recurrence']['description'] ?? $recurrence->description; + $recurrence->first_date = $data['recurrence']['first_date'] ?? $recurrence->first_date; + $recurrence->repeat_until = $data['recurrence']['repeat_until'] ?? $recurrence->repeat_until; + $recurrence->repetitions = $data['recurrence']['repetitions'] ?? $recurrence->repetitions; + $recurrence->apply_rules = $data['recurrence']['apply_rules'] ?? $recurrence->apply_rules; + $recurrence->active = $data['recurrence']['active'] ?? $recurrence->active; + + if(isset($data['recurrence']['repetition_end'])) { + if (\in_array($data['recurrence']['repetition_end'], ['forever ', 'until_date'])) { + $recurrence->repetitions = 0; + } + if (\in_array($data['recurrence']['repetition_end'], ['forever ', 'times'])) { + $recurrence->repeat_until = null; + } + } + $recurrence->save(); + + // update all meta data: + $this->updateMetaData($recurrence, $data); + + // update all repetitions + $this->deleteRepetitions($recurrence); + $this->createRepetitions($recurrence, $data['repetitions'] ?? []); + + // update all transactions (and associated meta-data); + $this->deleteTransactions($recurrence); + $this->createTransactions($recurrence, $data['transactions'] ?? []); + + return $recurrence; + } +} \ No newline at end of file diff --git a/app/Services/Password/PwndVerifier.php b/app/Services/Password/PwndVerifier.php deleted file mode 100644 index d5bc349253..0000000000 --- a/app/Services/Password/PwndVerifier.php +++ /dev/null @@ -1,56 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Services\Password; - -use Exception; -use Log; -use Requests; - -/** - * Class PwndVerifier. - */ -class PwndVerifier implements Verifier -{ - /** - * Verify the given password against (some) service. - * - * @param string $password - * - * @return bool - */ - public function validPassword(string $password): bool - { - $hash = sha1($password); - $uri = sprintf('https://haveibeenpwned.com/api/v2/pwnedpassword/%s', $hash); - $opt = ['useragent' => 'Firefly III v' . config('firefly.version'), 'timeout' => 2]; - - try { - $result = Requests::get($uri, ['originalPasswordIsAHash' => 'true'], $opt); - } catch (Exception $e) { - return true; - } - Log::debug(sprintf('Status code returned is %d', $result->status_code)); - - return 404 === $result->status_code; - } -} diff --git a/app/Services/Password/PwndVerifierV2.php b/app/Services/Password/PwndVerifierV2.php index bf3cd330cf..014fbbcbe6 100644 --- a/app/Services/Password/PwndVerifierV2.php +++ b/app/Services/Password/PwndVerifierV2.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace FireflyIII\Services\Password; use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; use Log; -use Requests; /** * Class PwndVerifierV2. @@ -44,27 +45,30 @@ class PwndVerifierV2 implements Verifier $prefix = substr($hash, 0, 5); $rest = substr($hash, 5); $uri = sprintf('https://api.pwnedpasswords.com/range/%s', $prefix); - $opt = ['useragent' => 'Firefly III v' . config('firefly.version'), 'timeout' => 2]; + $opt = [ + 'headers' => ['User-Agent' => 'Firefly III v' . config('firefly.version')], + 'timeout' => 2]; Log::debug(sprintf('hash prefix is %s', $prefix)); Log::debug(sprintf('rest is %s', $rest)); try { - $result = Requests::get($uri, $opt); - } catch (Exception $e) { + $client = new Client(); + $res = $client->request('GET', $uri, $opt); + } catch (GuzzleException|Exception $e) { return true; } - Log::debug(sprintf('Status code returned is %d', $result->status_code)); - if (404 === $result->status_code) { + Log::debug(sprintf('Status code returned is %d', $res->getStatusCode())); + if (404 === $res->getStatusCode()) { return true; } - $strpos = stripos($result->body, $rest); + $strpos = stripos($res->getBody()->getContents(), $rest); if ($strpos === false) { Log::debug(sprintf('%s was not found in result body. Return true.', $rest)); return true; } - Log::debug('Could not find %s, return FALSE.'); + Log::debug(sprintf('Could not find %s, return FALSE.', $rest)); return false; } diff --git a/app/Services/Spectre/Object/Attempt.php b/app/Services/Spectre/Object/Attempt.php index 6aecd25246..495fa9566e 100644 --- a/app/Services/Spectre/Object/Attempt.php +++ b/app/Services/Spectre/Object/Attempt.php @@ -87,9 +87,9 @@ class Attempt extends SpectreObject private $successAt; /** @var Carbon */ private $toDate; - /** @var Carbon */ + /** @var Carbon */ private $updatedAt; // undocumented -/** @var string */ + /** @var string */ private $userAgent; /** diff --git a/app/Services/Spectre/Request/NewCustomerRequest.php b/app/Services/Spectre/Request/NewCustomerRequest.php index eea0b17c05..099a40cfd9 100644 --- a/app/Services/Spectre/Request/NewCustomerRequest.php +++ b/app/Services/Spectre/Request/NewCustomerRequest.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Services\Spectre\Request; use FireflyIII\Services\Spectre\Object\Customer; - +use Log; /** * Class NewCustomerRequest */ @@ -43,6 +43,7 @@ class NewCustomerRequest extends SpectreRequest ], ]; $uri = '/api/v4/customers/'; + Log::debug(sprintf('Going to call %s with info:', $uri), $data); $response = $this->sendSignedSpectrePost($uri, $data); // create customer: $this->customer = new Customer($response['data']); diff --git a/app/Services/Spectre/Request/SpectreRequest.php b/app/Services/Spectre/Request/SpectreRequest.php index 6b735de91b..1fafadf23c 100644 --- a/app/Services/Spectre/Request/SpectreRequest.php +++ b/app/Services/Spectre/Request/SpectreRequest.php @@ -25,9 +25,9 @@ namespace FireflyIII\Services\Spectre\Request; use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\User; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; use Log; -use Requests; -use Requests_Response; /** * Class SpectreRequest @@ -195,23 +195,24 @@ abstract class SpectreRequest } $headers = $this->getDefaultHeaders(); - $body = json_encode($data); + $sendBody = json_encode($data); // OK $fullUri = $this->server . $uri; - $signature = $this->generateSignature('get', $fullUri, $body); + $signature = $this->generateSignature('get', $fullUri, $sendBody); $headers['Signature'] = $signature; Log::debug('Final headers for spectre signed get request:', $headers); try { - $response = Requests::get($fullUri, $headers); - } catch (Exception $e) { - throw new FireflyException(sprintf('Request Exception: %s', $e->getMessage())); + $client = new Client; + $res = $client->request('GET', $fullUri, ['headers' => $headers]); + } catch (GuzzleException|Exception $e) { + throw new FireflyException(sprintf('Guzzle Exception: %s', $e->getMessage())); } - $this->detectError($response); - $statusCode = (int)$response->status_code; + $statusCode = $res->getStatusCode(); + $returnBody = $res->getBody()->getContents(); + $this->detectError($returnBody, $statusCode); - $body = $response->body; - $array = json_decode($body, true); - $responseHeaders = $response->headers->getAll(); + $array = json_decode($returnBody, true); + $responseHeaders = $res->getHeaders(); $array['ResponseHeaders'] = $responseHeaders; $array['ResponseStatusCode'] = $statusCode; @@ -220,6 +221,7 @@ abstract class SpectreRequest throw new FireflyException(sprintf('Error of class %s: %s', $array['error_class'], $message)); } + return $array; } @@ -245,28 +247,32 @@ abstract class SpectreRequest Log::debug('Final headers for spectre signed POST request:', $headers); try { - $response = Requests::post($fullUri, $headers, $body); - } catch (Exception $e) { + $client = new Client; + $res = $client->request('POST', $fullUri, ['headers' => $headers, 'body' => $body]); + } catch (GuzzleException|Exception $e) { throw new FireflyException(sprintf('Request Exception: %s', $e->getMessage())); } - $this->detectError($response); - $body = $response->body; + $body = $res->getBody()->getContents(); + $statusCode = $res->getStatusCode(); + $this->detectError($body, $statusCode); + $array = json_decode($body, true); - $responseHeaders = $response->headers->getAll(); + $responseHeaders = $res->getHeaders(); $array['ResponseHeaders'] = $responseHeaders; - $array['ResponseStatusCode'] = $response->status_code; + $array['ResponseStatusCode'] = $statusCode; return $array; } /** - * @param Requests_Response $response + * @param string $body + * + * @param int $statusCode * * @throws FireflyException */ - private function detectError(Requests_Response $response): void + private function detectError(string $body, int $statusCode): void { - $body = $response->body; $array = json_decode($body, true); if (isset($array['error_class'])) { $message = $array['error_message'] ?? '(no message)'; @@ -279,9 +285,8 @@ abstract class SpectreRequest throw new FireflyException(sprintf('Error of class %s: %s', $errorClass, $message)); } - $statusCode = (int)$response->status_code; if (200 !== $statusCode) { - throw new FireflyException(sprintf('Status code %d: %s', $statusCode, $response->body)); + throw new FireflyException(sprintf('Status code %d: %s', $statusCode, $body)); } } } diff --git a/app/Support/Binder/SimpleJournalList.php b/app/Support/Binder/SimpleJournalList.php index c356331b48..070aa6dd06 100644 --- a/app/Support/Binder/SimpleJournalList.php +++ b/app/Support/Binder/SimpleJournalList.php @@ -28,7 +28,6 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Routing\Route; use Illuminate\Support\Collection; -use Session; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -58,7 +57,7 @@ class SimpleJournalList implements BinderInterface // prep some vars $messages = []; - $final = new Collection; + $final = new Collection; /** @var JournalRepositoryInterface $repository */ $repository = app(JournalRepositoryInterface::class); diff --git a/app/Support/CacheProperties.php b/app/Support/CacheProperties.php index 0e0be45819..d6d34aa8ed 100644 --- a/app/Support/CacheProperties.php +++ b/app/Support/CacheProperties.php @@ -24,7 +24,6 @@ namespace FireflyIII\Support; use Cache; use Illuminate\Support\Collection; -use Preferences as Prefs; /** * Class CacheProperties. @@ -44,7 +43,7 @@ class CacheProperties $this->properties = new Collection; if (auth()->check()) { $this->addProperty(auth()->user()->id); - $this->addProperty(Prefs::lastActivity()); + $this->addProperty(app('preferences')->lastActivity()); } } diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 17467594c0..24669d64a0 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -27,14 +27,17 @@ use Carbon\Carbon; use Eloquent; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; +use FireflyIII\Models\PiggyBank; use FireflyIII\Models\RuleGroup; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use Form; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; +use Log; use RuntimeException; use Session; @@ -43,6 +46,45 @@ use Session; */ class ExpandedForm { + /** + * @param string $name + * @param null $options + * + * @return string + * @throws \Throwable + */ + public function activeAssetAccountList(string $name, $value = null, array $options = []): string + { + // make repositories + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + /** @var CurrencyRepositoryInterface $currencyRepos */ + $currencyRepos = app(CurrencyRepositoryInterface::class); + + $assetAccounts = $repository->getActiveAccountsByType([AccountType::ASSET, AccountType::DEFAULT]); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $grouped = []; + // group accounts: + /** @var Account $account */ + foreach ($assetAccounts as $account) { + $balance = app('steam')->balance($account, new Carbon); + $currencyId = (int)$account->getMeta('currency_id'); + $currency = $currencyRepos->findNull($currencyId); + $role = $account->getMeta('accountRole'); + if ('' === $role) { + $role = 'no_account_type'; // @codeCoverageIgnore + } + if (null === $currency) { + $currency = $defaultCurrency; + } + + $key = (string)trans('firefly.opt_group_' . $role); + $grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')'; + } + + return $this->select($name, $grouped, $value, $options); + } + /** * @param string $name * @param null $value @@ -99,7 +141,6 @@ class ExpandedForm /** * @param string $name - * @param $selected * @param null $options * * @return string @@ -183,6 +224,7 @@ class ExpandedForm * * @return string * @throws \FireflyIII\Exceptions\FireflyException + * @throws \Throwable */ public function balance(string $name, $value = null, array $options = []): string { @@ -244,6 +286,33 @@ class ExpandedForm return $res; } + /** + * @param string $name + * @param null $value + * @param array $options + * + * @return string + * @throws \Throwable + */ + public function currencyListEmpty(string $name, $value = null, array $options = []): string + { + /** @var CurrencyRepositoryInterface $currencyRepos */ + $currencyRepos = app(CurrencyRepositoryInterface::class); + + // get all currencies: + $list = $currencyRepos->get(); + $array = [ + 0 => trans('firefly.no_currency'), + ]; + /** @var TransactionCurrency $currency */ + foreach ($list as $currency) { + $array[$currency->id] = $currency->name . ' (' . $currency->symbol . ')'; + } + $res = $this->select($name, $array, $value, $options); + + return $res; + } + /** * @param string $name * @param null $value @@ -485,16 +554,7 @@ class ExpandedForm */ public function optionsList(string $type, string $name): string { - $previousValue = null; - - try { - $previousValue = request()->old('post_submit_action'); - } catch (RuntimeException $e) { - // don't care - } - - $previousValue = $previousValue ?? 'store'; - $html = view('form.options', compact('type', 'name', 'previousValue'))->render(); + $html = view('form.options', compact('type', 'name'))->render(); return $html; } @@ -506,8 +566,9 @@ class ExpandedForm * @return string * @throws \Throwable */ - public function password(string $name, array $options = []): string + public function password(string $name, array $options = null): string { + $options = $options ?? []; $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); $classes = $this->getHolderClasses($name); @@ -516,6 +577,32 @@ class ExpandedForm return $html; } + /** + * @param string $name + * @param null $value + * @param array $options + * + * @return string + * @throws \Throwable + */ + public function piggyBankList(string $name, $value = null, array $options = null): string + { + $options = $options ?? []; + // make repositories + /** @var PiggyBankRepositoryInterface $repository */ + $repository = app(PiggyBankRepositoryInterface::class); + $piggyBanks = $repository->getPiggyBanksWithAmount(); + $array = [ + 0 => trans('firefly.none_in_select_list'), + ]; + /** @var PiggyBank $piggy */ + foreach ($piggyBanks as $piggy) { + $array[$piggy->id] = $piggy->name; + } + + return $this->select($name, $array, $value, $options); + } + /** * @param string $name * @param null $value @@ -773,10 +860,13 @@ class ExpandedForm $key = 'amount_currency_id_' . $name; $sentCurrencyId = isset($preFilled[$key]) ? (int)$preFilled[$key] : $defaultCurrency->id; + Log::debug(sprintf('Sent currency ID is %d', $sentCurrencyId)); + // find this currency in set of currencies: foreach ($currencies as $currency) { if ($currency->id === $sentCurrencyId) { $defaultCurrency = $currency; + Log::debug(sprintf('default currency is now %s', $defaultCurrency->code)); break; } } diff --git a/app/Support/FireflyConfig.php b/app/Support/FireflyConfig.php index 19b8feca8a..0890e601ad 100644 --- a/app/Support/FireflyConfig.php +++ b/app/Support/FireflyConfig.php @@ -55,7 +55,7 @@ class FireflyConfig * * @return \FireflyIII\Models\Configuration|null */ - public function get($name, $default = null) + public function get($name, $default = null): ?Configuration { $fullName = 'ff-config-' . $name; if (Cache::has($fullName)) { diff --git a/app/Support/Import/Information/GetSpectreCustomerTrait.php b/app/Support/Import/Information/GetSpectreCustomerTrait.php index 62660e7a48..bb3010d3f3 100644 --- a/app/Support/Import/Information/GetSpectreCustomerTrait.php +++ b/app/Support/Import/Information/GetSpectreCustomerTrait.php @@ -51,6 +51,8 @@ trait GetSpectreCustomerTrait /** @var NewCustomerRequest $request */ $request = app(NewCustomerRequest::class); $request->setUser($importJob->user); + $request->call(); + // todo what if customer is still null? $customer = $request->getCustomer(); diff --git a/app/Support/Import/JobConfiguration/Bunq/BunqJobConfigurationInterface.php b/app/Support/Import/JobConfiguration/Bunq/BunqJobConfigurationInterface.php index 3a52f6af99..dc85d3b541 100644 --- a/app/Support/Import/JobConfiguration/Bunq/BunqJobConfigurationInterface.php +++ b/app/Support/Import/JobConfiguration/Bunq/BunqJobConfigurationInterface.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\JobConfiguration\Bunq; + use FireflyIII\Models\ImportJob; use Illuminate\Support\MessageBag; diff --git a/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php b/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php index 247a7a2916..ff8764cb52 100644 --- a/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php +++ b/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php @@ -76,10 +76,11 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface */ public function configureJob(array $data): MessageBag { - $config = $this->repository->getConfiguration($this->importJob); - $accounts = $config['accounts'] ?? []; - $mapping = $data['account_mapping'] ?? []; - $final = []; + $config = $this->repository->getConfiguration($this->importJob); + $accounts = $config['accounts'] ?? []; + $mapping = $data['account_mapping'] ?? []; + $applyRules = (int)$data['apply_rules'] === 1; + $final = []; if (\count($accounts) === 0) { throw new FireflyException('No bunq accounts found. Import cannot continue.'); // @codeCoverageIgnore } @@ -98,7 +99,8 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface $accountId = $this->validLocalAccount($localId); $final[$bunqId] = $accountId; } - $config['mapping'] = $final; + $config['mapping'] = $final; + $config['apply-rules'] = $applyRules; $this->repository->setConfiguration($this->importJob, $config); return new MessageBag; diff --git a/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php b/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php index 2f3f208c93..e83d4247b5 100644 --- a/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php +++ b/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php @@ -45,11 +45,6 @@ class NewBunqJobHandler implements BunqJobConfigurationInterface */ public function configurationComplete(): bool { - // simply set the job configuration "apply-rules" to true. - $config = $this->repository->getConfiguration($this->importJob); - $config['apply-rules'] = true; - $this->repository->setConfiguration($this->importJob, $config); - return true; } diff --git a/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php b/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php index 144d472f86..76962d0132 100644 --- a/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php +++ b/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php @@ -35,61 +35,12 @@ use Log; */ class ConfigureUploadHandler implements FileConfigurationInterface { - /** @var ImportJob */ - private $importJob; - - /** @var ImportJobRepositoryInterface */ - private $repository; - /** @var AccountRepositoryInterface */ private $accountRepos; - - /** - * Get the data necessary to show the configuration screen. - * - * @return array - */ - public function getNextData(): array - { - $delimiters = [ - ',' => trans('form.csv_comma'), - ';' => trans('form.csv_semicolon'), - 'tab' => trans('form.csv_tab'), - ]; - $config = $this->importJob->configuration; - $config['date-format'] = $config['date-format'] ?? 'Ymd'; - $specifics = []; - $this->repository->setConfiguration($this->importJob, $config); - - // collect specifics. - foreach (config('csv.import_specifics') as $name => $className) { - $specifics[$name] = [ - 'name' => trans($className::getName()), - 'description' => trans($className::getDescription()), - ]; - } - - $data = [ - 'accounts' => [], - 'delimiters' => $delimiters, - 'specifics' => $specifics, - ]; - - return $data; - } - - /** - * @param ImportJob $importJob - */ - public function setImportJob(ImportJob $importJob): void - { - $this->importJob = $importJob; - $this->repository = app(ImportJobRepositoryInterface::class); - $this->repository->setUser($importJob->user); - $this->accountRepos = app(AccountRepositoryInterface::class); - $this->accountRepos->setUser($importJob->user); - - } + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; /** * Store data associated with current stage. @@ -137,6 +88,40 @@ class ConfigureUploadHandler implements FileConfigurationInterface return new MessageBag; } + /** + * Get the data necessary to show the configuration screen. + * + * @return array + */ + public function getNextData(): array + { + $delimiters = [ + ',' => trans('form.csv_comma'), + ';' => trans('form.csv_semicolon'), + 'tab' => trans('form.csv_tab'), + ]; + $config = $this->importJob->configuration; + $config['date-format'] = $config['date-format'] ?? 'Ymd'; + $specifics = []; + $this->repository->setConfiguration($this->importJob, $config); + + // collect specifics. + foreach (config('csv.import_specifics') as $name => $className) { + $specifics[$name] = [ + 'name' => trans($className::getName()), + 'description' => trans($className::getDescription()), + ]; + } + + $data = [ + 'accounts' => [], + 'delimiters' => $delimiters, + 'specifics' => $specifics, + ]; + + return $data; + } + /** * @param array $data * @@ -159,4 +144,17 @@ class ConfigureUploadHandler implements FileConfigurationInterface return $return; } + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + $this->accountRepos = app(AccountRepositoryInterface::class); + $this->accountRepos->setUser($importJob->user); + + } } diff --git a/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php b/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php index 27ba527bc2..40c9ac8888 100644 --- a/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php +++ b/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php @@ -80,6 +80,7 @@ class NewFileJobHandler implements FileConfigurationInterface /** * * Get the data necessary to show the configuration screen. + * * @codeCoverageIgnore * @return array */ diff --git a/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php b/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php index ee85ebfb8d..9797acd428 100644 --- a/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php +++ b/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php @@ -83,9 +83,10 @@ class ChooseAccountsHandler implements SpectreJobConfigurationInterface public function configureJob(array $data): MessageBag { Log::debug('Now in ChooseAccountsHandler::configureJob()', $data); - $config = $this->importJob->configuration; - $mapping = $data['account_mapping'] ?? []; - $final = []; + $config = $this->importJob->configuration; + $mapping = $data['account_mapping'] ?? []; + $final = []; + $applyRules = (int)$data['apply_rules'] === 1; foreach ($mapping as $spectreId => $localId) { // validate each $spectreId = $this->validSpectreAccount((int)$spectreId); @@ -96,7 +97,7 @@ class ChooseAccountsHandler implements SpectreJobConfigurationInterface Log::debug('Final mapping is:', $final); $messages = new MessageBag; $config['account_mapping'] = $final; - + $config['apply-rules'] = $applyRules; $this->repository->setConfiguration($this->importJob, $config); if ($final === [0 => 0] || \count($final) === 0) { $messages->add('count', trans('import.spectre_no_mapping')); diff --git a/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php b/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php index 1687a0689d..d4f1a60e73 100644 --- a/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php +++ b/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php @@ -26,12 +26,7 @@ namespace FireflyIII\Support\Import\JobConfiguration\Spectre; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Services\Spectre\Object\Customer; use FireflyIII\Services\Spectre\Object\Login; -use FireflyIII\Services\Spectre\Object\Token; -use FireflyIII\Services\Spectre\Request\CreateTokenRequest; -use FireflyIII\Services\Spectre\Request\ListCustomersRequest; -use FireflyIII\Services\Spectre\Request\NewCustomerRequest; use FireflyIII\Support\Import\Information\GetSpectreCustomerTrait; use FireflyIII\Support\Import\Information\GetSpectreTokenTrait; use Illuminate\Support\MessageBag; diff --git a/app/Support/Import/Placeholder/ColumnValue.php b/app/Support/Import/Placeholder/ColumnValue.php index edefbcc168..b55cd00a67 100644 --- a/app/Support/Import/Placeholder/ColumnValue.php +++ b/app/Support/Import/Placeholder/ColumnValue.php @@ -25,6 +25,7 @@ namespace FireflyIII\Support\Import\Placeholder; /** * Class ColumnValue + * * @codeCoverageIgnore */ class ColumnValue diff --git a/app/Support/Import/Placeholder/ImportTransaction.php b/app/Support/Import/Placeholder/ImportTransaction.php index a9ce98af63..bdda187930 100644 --- a/app/Support/Import/Placeholder/ImportTransaction.php +++ b/app/Support/Import/Placeholder/ImportTransaction.php @@ -179,7 +179,9 @@ class ImportTransaction $this->budgetName = $columnValue->getValue(); break; case 'category-id': - $this->categoryId = $this->getMappedValue($columnValue); + $value = $this->getMappedValue($columnValue); + Log::debug(sprintf('Set category ID to %d in ImportTransaction object', $value)); + $this->categoryId = $value; break; case 'category-name': $this->categoryName = $columnValue->getValue(); diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php index 0d338f0fb4..e5b295af2a 100644 --- a/app/Support/Import/Routine/File/CSVProcessor.php +++ b/app/Support/Import/Routine/File/CSVProcessor.php @@ -62,7 +62,7 @@ class CSVProcessor implements FileProcessorInterface // validate mapped values: /** @var MappedValuesValidator $validator */ - $validator = app(MappedValuesValidator::class); + $validator = app(MappedValuesValidator::class); $validator->setImportJob($this->importJob); $mappedValues = $validator->validate($mappingConverger->getMappedValues()); diff --git a/app/Support/Import/Routine/File/CurrencyMapper.php b/app/Support/Import/Routine/File/CurrencyMapper.php index 2dd366d4d6..b2a45500f3 100644 --- a/app/Support/Import/Routine/File/CurrencyMapper.php +++ b/app/Support/Import/Routine/File/CurrencyMapper.php @@ -71,10 +71,13 @@ class CurrencyMapper return $result; } } + if (!isset($data['code']) || $data['code'] === null) { + return null; + } + // if still nothing, and fields not null, try to create it - $code = $data['code'] ?? null; $creation = [ - 'code' => $code, + 'code' => $data['code'], 'name' => $data['name'] ?? $code, 'symbol' => $data['symbol'] ?? $code, 'decimal_places' => 2, diff --git a/app/Support/Import/Routine/File/FileProcessorInterface.php b/app/Support/Import/Routine/File/FileProcessorInterface.php index c361dd9a44..13f6dfffd8 100644 --- a/app/Support/Import/Routine/File/FileProcessorInterface.php +++ b/app/Support/Import/Routine/File/FileProcessorInterface.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\Routine\File; + use FireflyIII\Models\ImportJob; diff --git a/app/Support/Import/Routine/File/ImportableConverter.php b/app/Support/Import/Routine/File/ImportableConverter.php index 5084f24743..bb851e24e4 100644 --- a/app/Support/Import/Routine/File/ImportableConverter.php +++ b/app/Support/Import/Routine/File/ImportableConverter.php @@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\AccountType; use FireflyIII\Models\ImportJob; use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Support\Import\Placeholder\ImportTransaction; use InvalidArgumentException; @@ -39,6 +40,8 @@ use Log; */ class ImportableConverter { + /** @var AccountRepositoryInterface */ + private $accountRepository; /** @var AssetAccountMapper */ private $assetMapper; /** @var array */ @@ -102,6 +105,10 @@ class ImportableConverter $this->assetMapper->setUser($importJob->user); $this->assetMapper->setDefaultAccount($this->config['import-account'] ?? 0); + // asset account repository is used for currency information + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->accountRepository->setUser($importJob->user); + // opposing account mapper: $this->opposingMapper = app(OpposingAccountMapper::class); $this->opposingMapper->setUser($importJob->user); @@ -122,6 +129,28 @@ class ImportableConverter $this->mappedValues = $mappedValues; } + /** + * @param string|null $date + * + * @return string|null + */ + private function convertDateValue(string $date = null): ?string + { + if (null === $date) { + return null; + } + try { + $object = Carbon::createFromFormat($this->config['date-format'] ?? 'Ymd', $date); + } catch (InvalidDateException|InvalidArgumentException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + + return null; + } + + return $object->format('Y-m-d'); + } + /** * @param ImportTransaction $importable * @@ -154,14 +183,17 @@ class ImportableConverter $currency = $this->currencyMapper->map($currencyId, $importable->getCurrencyData()); $foreignCurrency = $this->currencyMapper->map($foreignCurrencyId, $importable->getForeignCurrencyData()); - if (null === $currency) { - Log::debug(sprintf('Could not map currency, use default (%s)', $this->defaultCurrency->code)); - $currency = $this->defaultCurrency; - } Log::debug(sprintf('"%s" (#%d) is source and "%s" (#%d) is destination.', $source->name, $source->id, $destination->name, $destination->id)); - if (bccomp($amount, '0') === 1) { - // amount is positive? Then switch: + + if ($source->accountType->type === AccountType::ASSET && $destination->accountType->type === AccountType::ASSET) { + Log::debug('Source and destination are asset accounts. This is a transfer.'); + $transactionType = 'transfer'; + } + + // amount is positive and its not a transfer? Then switch: + if ($transactionType !== 'transfer' && bccomp($amount, '0') === 1) { + [$destination, $source] = [$source, $destination]; Log::debug( sprintf( @@ -170,11 +202,41 @@ class ImportableConverter ) ); } - - if ($source->accountType->type === AccountType::ASSET && $destination->accountType->type === AccountType::ASSET) { - Log::debug('Source and destination are asset accounts. This is a transfer.'); - $transactionType = 'transfer'; + // amount is negative and type is transfer? then switch. + if ($transactionType === 'transfer' && bccomp($amount, '0') === -1) { + // amount is positive? Then switch: + [$destination, $source] = [$source, $destination]; + Log::debug( + sprintf( + '%s is negative, so "%s" (#%d) is now source and "%s" (#%d) is now destination.', + $amount, $source->name, $source->id, $destination->name, $destination->id + ) + ); } + + // get currency preference from source asset account (preferred) + // or destination asset account + if (null === $currency) { + if ($destination->accountType->type === AccountType::ASSET) { + // destination is asset, might have currency preference: + $destinationCurrencyId = (int)$this->accountRepository->getMetaValue($destination, 'currency_id'); + $currency = $destinationCurrencyId === 0 ? $this->defaultCurrency : $this->currencyMapper->map($destinationCurrencyId, []); + Log::debug(sprintf('Destination is an asset account, and has currency preference %s', $currency->code)); + } + + if ($source->accountType->type === AccountType::ASSET) { + // source is asset, might have currency preference: + $sourceCurrencyId = (int)$this->accountRepository->getMetaValue($source, 'currency_id'); + $currency = $sourceCurrencyId === 0 ? $this->defaultCurrency : $this->currencyMapper->map($sourceCurrencyId, []); + Log::debug(sprintf('Source is an asset account, and has currency preference %s', $currency->code)); + } + } + if (null === $currency) { + Log::debug(sprintf('Could not map currency, use default (%s)', $this->defaultCurrency->code)); + $currency = $this->defaultCurrency; + } + + if ($source->accountType->type === AccountType::REVENUE) { Log::debug('Source is a revenue account. This is a deposit.'); $transactionType = 'deposit'; @@ -199,19 +261,13 @@ class ImportableConverter throw new FireflyException($message); } - // throw error when both are he same - - try { - $date = Carbon::createFromFormat($this->config['date-format'] ?? 'Ymd', $importable->date); - } catch (InvalidDateException|InvalidArgumentException $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - $date = new Carbon; + $dateStr = $this->convertDateValue($importable->date); + if (null === $dateStr) { + $date = new Carbon; + $dateStr = $date->format('Y-m-d'); + unset($date); } - - $dateStr = $date->format('Y-m-d'); - return [ 'type' => $transactionType, 'date' => $dateStr, @@ -228,12 +284,12 @@ class ImportableConverter 'sepa-country' => $importable->meta['sepa-countru'] ?? null, 'sepa-ep' => $importable->meta['sepa-ep'] ?? null, 'sepa-ci' => $importable->meta['sepa-ci'] ?? null, - 'interest_date' => $importable->meta['date-interest'] ?? null, - 'book_date' => $importable->meta['date-book'] ?? null, - 'process_date' => $importable->meta['date-process'] ?? null, - 'due_date' => $importable->meta['date-due'] ?? null, - 'payment_date' => $importable->meta['date-payment'] ?? null, - 'invoice_date' => $importable->meta['date-invoice'] ?? null, + 'interest_date' => $this->convertDateValue($importable->meta['date-interest'] ?? null), + 'book_date' => $this->convertDateValue($importable->meta['date-book'] ?? null), + 'process_date' => $this->convertDateValue($importable->meta['date-process'] ?? null), + 'due_date' => $this->convertDateValue($importable->meta['date-due'] ?? null), + 'payment_date' => $this->convertDateValue($importable->meta['date-payment'] ?? null), + 'invoice_date' => $this->convertDateValue($importable->meta['date-invoice'] ?? null), 'external_id' => $importable->externalId, // journal data: @@ -241,7 +297,7 @@ class ImportableConverter 'piggy_bank_id' => null, 'piggy_bank_name' => null, 'bill_id' => $billId, - 'bill_name' => null === $billId ? $importable->billName : null, + 'bill_name' => $importable->billName, // transaction data: 'transactions' => [ @@ -279,11 +335,16 @@ class ImportableConverter */ private function verifyObjectId(string $key, int $objectId): ?int { + if (isset($this->mappedValues[$key]) && \in_array($objectId, $this->mappedValues[$key], true)) { + Log::debug(sprintf('verifyObjectId(%s, %d) is valid!', $key, $objectId)); + return $objectId; } - return null; + Log::debug(sprintf('verifyObjectId(%s, %d) is NOT in the list, but it could still be valid.', $key, $objectId)); + + return $objectId; } diff --git a/app/Support/Import/Routine/File/LineReader.php b/app/Support/Import/Routine/File/LineReader.php index 36744c5d38..927847313b 100644 --- a/app/Support/Import/Routine/File/LineReader.php +++ b/app/Support/Import/Routine/File/LineReader.php @@ -164,6 +164,7 @@ class LineReader } catch (\League\Csv\Exception $e) { throw new FireflyException(sprintf('Cannot set delimiter: %s', $e->getMessage())); } + // @codeCoverageIgnoreEnd return $reader; diff --git a/app/Support/Import/Routine/File/MappedValuesValidator.php b/app/Support/Import/Routine/File/MappedValuesValidator.php index 8526b2b1f7..e9229a275a 100644 --- a/app/Support/Import/Routine/File/MappedValuesValidator.php +++ b/app/Support/Import/Routine/File/MappedValuesValidator.php @@ -87,6 +87,7 @@ class MappedValuesValidator $return = []; Log::debug('Now in validateMappedValues()'); foreach ($mappings as $role => $values) { + Log::debug(sprintf('Now at role "%s"', $role)); $values = array_unique($values); if (\count($values) > 0) { switch ($role) { @@ -115,9 +116,11 @@ class MappedValuesValidator $return[$role] = $valid; break; case 'category-id': + Log::debug('Going to validate these category ids: ', $values); $set = $this->catRepos->getByIds($values); $valid = $set->pluck('id')->toArray(); $return[$role] = $valid; + Log::debug('Valid category IDs are: ', $valid); break; } } diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index 38825077c6..8774c29d6e 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -125,14 +125,14 @@ class Preferences * * @return \FireflyIII\Models\Preference|null */ - public function getForUser(User $user, $name, $default = null) + public function getForUser(User $user, $name, $default = null): ?Preference { $fullName = sprintf('preference%s%s', $user->id, $name); if (Cache::has($fullName)) { return Cache::get($fullName); } - $preference = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data']); + $preference = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']); if (null !== $preference && null === $preference->data) { try { $preference->delete(); @@ -163,14 +163,17 @@ class Preferences { $lastActivity = microtime(); $preference = $this->get('lastActivity', microtime()); + if (null !== $preference && null !== $preference->data) { $lastActivity = $preference->data; } if (\is_array($lastActivity)) { $lastActivity = implode(',', $lastActivity); } + $hash = md5($lastActivity); + Log::debug(sprintf('Value of last activity is %s, hash is %s', $lastActivity, $hash)); - return md5($lastActivity); + return $hash; } /** @@ -208,7 +211,7 @@ class Preferences /** * @param \FireflyIII\User $user * @param $name - * @param mixed $value + * @param mixed $value * * @return Preference */ @@ -216,7 +219,7 @@ class Preferences { $fullName = sprintf('preference%s%s', $user->id, $name); Cache::forget($fullName); - $pref = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data']); + $pref = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']); if (null !== $pref) { $pref->data = $value; diff --git a/app/Support/Steam.php b/app/Support/Steam.php index e3ab8d1d72..6c6003e6d2 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -55,7 +55,7 @@ class Steam // /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); - $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); + $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); // use system default currency: if (0 === $currencyId) { @@ -77,9 +77,9 @@ class Steam ->where('transactions.transaction_currency_id', '!=', $currencyId) ->sum('transactions.foreign_amount'); - $balance = bcadd($nativeBalance, $foreignBalance); - $virtual = null === $account->virtual_balance ? '0' : (string)$account->virtual_balance; - $balance = bcadd($balance, $virtual); + $balance = bcadd($nativeBalance, $foreignBalance); + $virtual = null === $account->virtual_balance ? '0' : (string)$account->virtual_balance; + $balance = bcadd($balance, $virtual); $cache->store($balance); return $balance; diff --git a/app/Support/Twig/Extension/Transaction.php b/app/Support/Twig/Extension/Transaction.php index 759880ff88..5294840ac7 100644 --- a/app/Support/Twig/Extension/Transaction.php +++ b/app/Support/Twig/Extension/Transaction.php @@ -26,11 +26,11 @@ use FireflyIII\Models\AccountType; use FireflyIII\Models\Attachment; use FireflyIII\Models\Transaction as TransactionModel; use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use Lang; use Log; use Twig_Extension; -use FireflyIII\Models\TransactionJournal; /** * Class Transaction. diff --git a/app/Support/Twig/Extension/TransactionJournal.php b/app/Support/Twig/Extension/TransactionJournal.php index c9b56a3d8b..66f05708d3 100644 --- a/app/Support/Twig/Extension/TransactionJournal.php +++ b/app/Support/Twig/Extension/TransactionJournal.php @@ -96,10 +96,51 @@ class TransactionJournal extends Twig_Extension * @return string */ public function totalAmount(JournalModel $journal): string + { + $type = $journal->transactionType->type; + $totals = $this->getTotalAmount($journal); + $array = []; + foreach ($totals as $total) { + if (TransactionType::WITHDRAWAL === $type) { + $total['amount'] = bcmul($total['amount'], '-1'); + } + $array[] = app('amount')->formatAnything($total['currency'], $total['amount']); + } + + return implode(' / ', $array); + } + + /** + * @param JournalModel $journal + * + * @return string + */ + public function totalAmountPlain(JournalModel $journal): string + { + $type = $journal->transactionType->type; + $totals = $this->getTotalAmount($journal); + $array = []; + + foreach ($totals as $total) { + if (TransactionType::WITHDRAWAL === $type) { + $total['amount'] = bcmul($total['amount'], '-1'); + } + $array[] = app('amount')->formatAnything($total['currency'], $total['amount'], false); + } + + return implode(' / ', $array); + } + + /** + * @param JournalModel $journal + * + * @return string + */ + private function getTotalAmount(JournalModel $journal): array { $transactions = $journal->transactions()->where('amount', '>', 0)->get(); $totals = []; - $type = $journal->transactionType->type; + /** @var TransactionModel $transaction */ foreach ($transactions as $transaction) { $currencyId = $transaction->transaction_currency_id; @@ -128,14 +169,7 @@ class TransactionJournal extends Twig_Extension ); } } - $array = []; - foreach ($totals as $total) { - if (TransactionType::WITHDRAWAL === $type) { - $total['amount'] = bcmul($total['amount'], '-1'); - } - $array[] = app('amount')->formatAnything($total['currency'], $total['amount']); - } - return implode(' / ', $array); + return $totals; } } diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index a2945e833d..e744976414 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -24,12 +24,12 @@ namespace FireflyIII\Support\Twig; use Carbon\Carbon; use FireflyIII\Models\Account; +use FireflyIII\Support\Twig\Extension\Account as AccountExtension; use League\CommonMark\CommonMarkConverter; use Route; use Twig_Extension; use Twig_SimpleFilter; use Twig_SimpleFunction; -use FireflyIII\Support\Twig\Extension\Account as AccountExtension; /** * Class TwigSupport. diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php index c68a7bedfe..c6502a8f72 100644 --- a/app/Support/Twig/Journal.php +++ b/app/Support/Twig/Journal.php @@ -79,6 +79,7 @@ class Journal extends Twig_Extension { $filters = [ new Twig_SimpleFilter('journalTotalAmount', [TransactionJournalExtension::class, 'totalAmount'], ['is_safe' => ['html']]), + new Twig_SimpleFilter('journalTotalAmountPlain', [TransactionJournalExtension::class, 'totalAmountPlain'], ['is_safe' => ['html']]), ]; return $filters; @@ -198,6 +199,7 @@ class Journal extends Twig_Extension ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->where('categories.user_id', $journal->user_id) ->where('transaction_journals.id', $journal->id) + ->whereNull('transactions.deleted_at') ->get(['categories.*']); /** @var Category $category */ foreach ($set as $category) { diff --git a/app/TransactionRules/Actions/LinkToBill.php b/app/TransactionRules/Actions/LinkToBill.php index 6ffb84cb8e..d0bd43240c 100644 --- a/app/TransactionRules/Actions/LinkToBill.php +++ b/app/TransactionRules/Actions/LinkToBill.php @@ -58,8 +58,8 @@ class LinkToBill implements ActionInterface /** @var BillRepositoryInterface $repository */ $repository = app(BillRepositoryInterface::class); $repository->setUser($this->action->rule->user); - $billName = (string)$this->action->action_value; - $bill = $repository->findByName($billName); + $billName = (string)$this->action->action_value; + $bill = $repository->findByName($billName); if (null !== $bill && $journal->transactionType->type === TransactionType::WITHDRAWAL) { $journal->bill()->associate($bill); diff --git a/app/TransactionRules/Actions/SetDestinationAccount.php b/app/TransactionRules/Actions/SetDestinationAccount.php index 1ad5fb1a46..956ae71670 100644 --- a/app/TransactionRules/Actions/SetDestinationAccount.php +++ b/app/TransactionRules/Actions/SetDestinationAccount.php @@ -101,8 +101,8 @@ class SetDestinationAccount implements ActionInterface // update destination transaction with new destination account: // get destination transaction: - $transaction = $journal->transactions()->where('amount', '>', 0)->first(); - if(null === $transaction) { + $transaction = $journal->transactions()->where('amount', '>', 0)->first(); + if (null === $transaction) { return true; } $transaction->account_id = $this->newDestinationAccount->id; diff --git a/app/TransactionRules/Triggers/HasAnyBudget.php b/app/TransactionRules/Triggers/HasAnyBudget.php index 46bd011a64..224c4b9212 100644 --- a/app/TransactionRules/Triggers/HasAnyBudget.php +++ b/app/TransactionRules/Triggers/HasAnyBudget.php @@ -81,7 +81,7 @@ final class HasAnyBudget extends AbstractTrigger implements TriggerInterface } } - Log::debug(sprintf('RuleTrigger HasAnyBudget for journal #%d: count is %d, return false.', $journal->id, $count)); + Log::debug(sprintf('RuleTrigger HasAnyBudget for journal #%d: final is false.', $journal->id)); return false; } diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index 099398fd8c..64ec626911 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -132,7 +132,7 @@ class AccountTransformer extends TransformerAbstract */ public function includeUser(Account $account): Item { - return $this->item($account->user, new UserTransformer($this->parameters), 'user'); + return $this->item($account->user, new UserTransformer($this->parameters), 'users'); } /** @@ -153,7 +153,7 @@ class AccountTransformer extends TransformerAbstract } $currencyId = (int)$this->repository->getMetaValue($account, 'currency_id'); $currencyCode = null; - $currencySymbol = 'x'; + $currencySymbol = null; $decimalPlaces = 2; if ($currencyId > 0) { $currency = TransactionCurrency::find($currencyId); diff --git a/app/Transformers/AttachmentTransformer.php b/app/Transformers/AttachmentTransformer.php index 0db1993352..d65d4db907 100644 --- a/app/Transformers/AttachmentTransformer.php +++ b/app/Transformers/AttachmentTransformer.php @@ -25,6 +25,7 @@ namespace FireflyIII\Transformers; use FireflyIII\Models\Attachment; +use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\TransformerAbstract; use Symfony\Component\HttpFoundation\ParameterBag; @@ -39,13 +40,13 @@ class AttachmentTransformer extends TransformerAbstract * * @var array */ - protected $availableIncludes = ['user']; + protected $availableIncludes = ['user', 'notes']; /** * List of resources to automatically include * * @var array */ - protected $defaultIncludes = ['user']; + protected $defaultIncludes = ['user', 'notes']; /** @var ParameterBag */ protected $parameters; @@ -62,6 +63,20 @@ class AttachmentTransformer extends TransformerAbstract $this->parameters = $parameters; } + /** + * Attach the notes. + * + * @codeCoverageIgnore + * + * @param Attachment $attachment + * + * @return FractalCollection + */ + public function includeNotes(Attachment $attachment): FractalCollection + { + return $this->collection($attachment->notes, new NoteTransformer($this->parameters), 'notes'); + } + /** * Attach the user. * @@ -73,7 +88,7 @@ class AttachmentTransformer extends TransformerAbstract */ public function includeUser(Attachment $attachment): Item { - return $this->item($attachment->user, new UserTransformer($this->parameters), 'user'); + return $this->item($attachment->user, new UserTransformer($this->parameters), 'users'); } /** @@ -92,11 +107,11 @@ class AttachmentTransformer extends TransformerAbstract 'attachable_type' => $attachment->attachable_type, 'md5' => $attachment->md5, 'filename' => $attachment->filename, + 'download_uri' => route('api.v1.attachments.download', [$attachment->id]), + 'upload_uri' => route('api.v1.attachments.upload', [$attachment->id]), 'title' => $attachment->title, - 'description' => $attachment->description, - 'notes' => $attachment->notes, 'mime' => $attachment->mime, - 'size' => $attachment->size, + 'size' => (int)$attachment->size, 'links' => [ [ 'rel' => 'self', diff --git a/app/Transformers/AvailableBudgetTransformer.php b/app/Transformers/AvailableBudgetTransformer.php new file mode 100644 index 0000000000..e50d0a162f --- /dev/null +++ b/app/Transformers/AvailableBudgetTransformer.php @@ -0,0 +1,120 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\AvailableBudget; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class AvailableBudgetTransformer + */ +class AvailableBudgetTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['transaction_currency', 'user']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['transaction_currency']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Attach the currency. + * + * @codeCoverageIgnore + * + * @param AvailableBudget $availableBudget + * + * @return Item + */ + public function includeTransactionCurrency(AvailableBudget $availableBudget): Item + { + return $this->item($availableBudget->transactionCurrency, new CurrencyTransformer($this->parameters), 'transaction_currencies'); + } + + /** + * Attach the user. + * + * @codeCoverageIgnore + * + * @param AvailableBudget $availableBudget + * + * @return Item + */ + public function includeUser(AvailableBudget $availableBudget): Item + { + return $this->item($availableBudget->user, new UserTransformer($this->parameters), 'users'); + } + + /** + * Transform the note. + * + * @param AvailableBudget $availableBudget + * + * @return array + */ + public function transform(AvailableBudget $availableBudget): array + { + $data = [ + 'id' => (int)$availableBudget->id, + 'updated_at' => $availableBudget->updated_at->toAtomString(), + 'created_at' => $availableBudget->created_at->toAtomString(), + 'start_date' => $availableBudget->start_date->format('Y-m-d'), + 'end_date' => $availableBudget->end_date->format('Y-m-d'), + 'amount' => round($availableBudget->amount, $availableBudget->transactionCurrency->decimal_places), + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/available_budgets/' . $availableBudget->id, + ], + ], + ]; + + return $data; + } + +} \ No newline at end of file diff --git a/app/Transformers/BillTransformer.php b/app/Transformers/BillTransformer.php index 78a17fe43d..86c7516833 100644 --- a/app/Transformers/BillTransformer.php +++ b/app/Transformers/BillTransformer.php @@ -26,7 +26,6 @@ namespace FireflyIII\Transformers; use Carbon\Carbon; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Bill; -use FireflyIII\Models\Note; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use Illuminate\Support\Collection; use League\Fractal\Resource\Collection as FractalCollection; @@ -45,13 +44,13 @@ class BillTransformer extends TransformerAbstract * * @var array */ - protected $availableIncludes = ['attachments', 'transactions', 'user']; + protected $availableIncludes = ['attachments', 'transactions', 'user', 'notes', 'rules']; /** * List of resources to automatically include * * @var array */ - protected $defaultIncludes = []; + protected $defaultIncludes = ['notes', 'rules']; /** @var ParameterBag */ protected $parameters; @@ -83,6 +82,40 @@ class BillTransformer extends TransformerAbstract return $this->collection($attachments, new AttachmentTransformer($this->parameters), 'attachments'); } + /** + * Attach the notes. + * + * @codeCoverageIgnore + * + * @param Bill $bill + * + * @return FractalCollection + */ + public function includeNotes(Bill $bill): FractalCollection + { + return $this->collection($bill->notes, new NoteTransformer($this->parameters), 'notes'); + } + + /** + * Attach the rules. + * + * @codeCoverageIgnore + * + * @param Bill $bill + * + * @return FractalCollection + */ + public function includeRules(Bill $bill): FractalCollection + { + /** @var BillRepositoryInterface $repository */ + $repository = app(BillRepositoryInterface::class); + $repository->setUser($bill->user); + // add info about rules: + $rules = $repository->getRulesForBill($bill); + + return $this->collection($rules, new RuleTransformer($this->parameters), 'rules'); + } + /** * Include any transactions. * @@ -141,19 +174,17 @@ class BillTransformer extends TransformerAbstract 'name' => $bill->name, 'currency_id' => $bill->transaction_currency_id, 'currency_code' => $bill->transactionCurrency->code, - 'match' => explode(',', $bill->match), 'amount_min' => round($bill->amount_min, 2), 'amount_max' => round($bill->amount_max, 2), 'date' => $bill->date->format('Y-m-d'), 'repeat_freq' => $bill->repeat_freq, 'skip' => (int)$bill->skip, - 'automatch' => (int)$bill->automatch === 1, - 'active' => (int)$bill->active === 1, + 'automatch' => $bill->automatch, + 'active' => $bill->active, 'attachments_count' => $bill->attachments()->count(), 'pay_dates' => $payDates, 'paid_dates' => $paidData['paid_dates'], 'next_expected_match' => $paidData['next_expected_match'], - 'notes' => null, 'links' => [ [ 'rel' => 'self', @@ -161,11 +192,6 @@ class BillTransformer extends TransformerAbstract ], ], ]; - /** @var Note $note */ - $note = $bill->notes()->first(); - if (null !== $note) { - $data['notes'] = $note->text; - } return $data; diff --git a/app/Transformers/BudgetLimitTransformer.php b/app/Transformers/BudgetLimitTransformer.php new file mode 100644 index 0000000000..90e858bcdc --- /dev/null +++ b/app/Transformers/BudgetLimitTransformer.php @@ -0,0 +1,104 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + +use FireflyIII\Models\BudgetLimit; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class BudgetLimitTransformer + */ +class BudgetLimitTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['budget']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['budget']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Attach the budget. + * + * @codeCoverageIgnore + * + * @param BudgetLimit $budgetLimit + * + * @return Item + */ + public function includeBudget(BudgetLimit $budgetLimit): Item + { + return $this->item($budgetLimit->budget, new BudgetTransformer($this->parameters), 'budgets'); + } + + /** + * Transform the note. + * + * @param BudgetLimit $budgetLimit + * + * @return array + */ + public function transform(BudgetLimit $budgetLimit): array + { + $data = [ + 'id' => (int)$budgetLimit->id, + 'updated_at' => $budgetLimit->updated_at->toAtomString(), + 'created_at' => $budgetLimit->created_at->toAtomString(), + 'start_date' => $budgetLimit->start_date->format('Y-m-d'), + 'end_date' => $budgetLimit->end_date->format('Y-m-d'), + 'amount' => $budgetLimit->amount, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/budget_limits/' . $budgetLimit->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Transformers/BudgetTransformer.php b/app/Transformers/BudgetTransformer.php index 698c3009b8..4b9562292a 100644 --- a/app/Transformers/BudgetTransformer.php +++ b/app/Transformers/BudgetTransformer.php @@ -48,7 +48,7 @@ class BudgetTransformer extends TransformerAbstract * * @var array */ - protected $defaultIncludes = []; + protected $defaultIncludes = ['user']; /** @var ParameterBag */ protected $parameters; diff --git a/app/Transformers/CategoryTransformer.php b/app/Transformers/CategoryTransformer.php index 3d7de337c9..421946e6b2 100644 --- a/app/Transformers/CategoryTransformer.php +++ b/app/Transformers/CategoryTransformer.php @@ -48,7 +48,7 @@ class CategoryTransformer extends TransformerAbstract * * @var array */ - protected $defaultIncludes = []; + protected $defaultIncludes = ['user']; /** @var ParameterBag */ protected $parameters; diff --git a/app/Transformers/CurrencyExchangeRateTransformer.php b/app/Transformers/CurrencyExchangeRateTransformer.php new file mode 100644 index 0000000000..228ef4dbbb --- /dev/null +++ b/app/Transformers/CurrencyExchangeRateTransformer.php @@ -0,0 +1,99 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\CurrencyExchangeRate; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +class CurrencyExchangeRateTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['from_currency', 'to_currency']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['from_currency', 'to_currency']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * PiggyBankEventTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * @param CurrencyExchangeRate $rate + * + * @return Item + */ + public function includeFromCurrency(CurrencyExchangeRate $rate): Item + { + return $this->item($rate->fromCurrency, new CurrencyTransformer($this->parameters), 'transaction_currencies'); + } + + /** + * @param CurrencyExchangeRate $rate + * + * @return \League\Fractal\Resource\Item + */ + public function includeToCurrency(CurrencyExchangeRate $rate): Item + { + return $this->item($rate->toCurrency, new CurrencyTransformer($this->parameters), 'transaction_currencies'); + } + + public function transform(CurrencyExchangeRate $rate): array + { + $data = [ + 'id' => (int)$rate->id, + 'updated_at' => $rate->updated_at->toAtomString(), + 'created_at' => $rate->created_at->toAtomString(), + 'rate' => (float)$rate->rate, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/currency_exchange_rates/' . $rate->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Transformers/JournalLinkTransformer.php b/app/Transformers/JournalLinkTransformer.php new file mode 100644 index 0000000000..944c9f9b20 --- /dev/null +++ b/app/Transformers/JournalLinkTransformer.php @@ -0,0 +1,146 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Models\Note; +use FireflyIII\Models\TransactionJournalLink; +use Illuminate\Support\Collection; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * + * Class JournalLinkTransformer + */ +class JournalLinkTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['inward', 'outward', 'link_type']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['inward', 'outward', 'link_type']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * @param TransactionJournalLink $link + * + * @return Item + */ + public function includeInward(TransactionJournalLink $link): Item + { + // need to use the collector to get the transaction :( + // journals always use collector and limited using URL parameters. + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setUser($link->source->user); + $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + $collector->setJournals(new Collection([$link->source])); + $transactions = $collector->getJournals(); + + return $this->item($transactions->first(), new TransactionTransformer($this->parameters), 'transactions'); + } + + /** + * @param TransactionJournalLink $link + * + * @return Item + */ + public function includeLinkType(TransactionJournalLink $link): Item + { + return $this->item($link->linkType, new LinkTypeTransformer($this->parameters), 'link_types'); + } + + /** + * @param TransactionJournalLink $link + * + * @return Item + */ + public function includeOutward(TransactionJournalLink $link): Item + { + // need to use the collector to get the transaction :( + // journals always use collector and limited using URL parameters. + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setUser($link->source->user); + $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + $collector->setJournals(new Collection([$link->destination])); + $transactions = $collector->getJournals(); + + return $this->item($transactions->first(), new TransactionTransformer($this->parameters), 'transactions'); + } + + /** + * @param TransactionJournalLink $link + * + * @return array + */ + public function transform(TransactionJournalLink $link): array + { + $notes = ''; + /** @var Note $note */ + $note = $link->notes()->first(); + if (null !== $note) { + $notes = $note->text; + } + + $data = [ + 'id' => (int)$link->id, + 'updated_at' => $link->updated_at->toAtomString(), + 'created_at' => $link->created_at->toAtomString(), + 'notes' => $notes, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/journal_links/' . $link->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Transformers/LinkTypeTransformer.php b/app/Transformers/LinkTypeTransformer.php new file mode 100644 index 0000000000..625d390c09 --- /dev/null +++ b/app/Transformers/LinkTypeTransformer.php @@ -0,0 +1,89 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\LinkType; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +class LinkTypeTransformer extends TransformerAbstract +{ + + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = []; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Transform the currency. + * + * @param LinkType $linkType + * + * @return array + */ + public function transform(LinkType $linkType): array + { + $data = [ + 'id' => (int)$linkType->id, + 'updated_at' => $linkType->updated_at->toAtomString(), + 'created_at' => $linkType->created_at->toAtomString(), + 'name' => $linkType->name, + 'inward' => $linkType->inward, + 'outward' => $linkType->outward, + 'editable' => (int)$linkType->editable, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/link_types/' . $linkType->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Transformers/NoteTransformer.php b/app/Transformers/NoteTransformer.php new file mode 100644 index 0000000000..9cf804c45a --- /dev/null +++ b/app/Transformers/NoteTransformer.php @@ -0,0 +1,92 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\Note; +use League\CommonMark\CommonMarkConverter; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class NoteTransformer + */ +class NoteTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = []; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Transform the note. + * + * @param Note $note + * + * @return array + */ + public function transform(Note $note): array + { + $converter = new CommonMarkConverter; + $data = [ + 'id' => (int)$note->id, + 'updated_at' => $note->updated_at->toAtomString(), + 'created_at' => $note->created_at->toAtomString(), + 'title' => $note->title, + 'text' => $note->text, + 'markdown' => $converter->convertToHtml($note->text), + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/notes/' . $note->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Transformers/PiggyBankTransformer.php b/app/Transformers/PiggyBankTransformer.php index 5afe13a6ca..0816fb0f63 100644 --- a/app/Transformers/PiggyBankTransformer.php +++ b/app/Transformers/PiggyBankTransformer.php @@ -166,9 +166,9 @@ class PiggyBankTransformer extends TransformerAbstract 'percentage' => $percentage, 'current_amount' => $currentAmount, 'left_to_save' => round($leftToSave, $decimalPlaces), - 'save_per_month' => $piggyRepos->getSuggestedMonthlyAmount($piggyBank), - 'startdate' => $startDate, - 'targetdate' => $targetDate, + 'save_per_month' => round($piggyRepos->getSuggestedMonthlyAmount($piggyBank), $decimalPlaces), + 'start_date' => $startDate, + 'target_date' => $targetDate, 'order' => (int)$piggyBank->order, 'active' => (int)$piggyBank->active === 1, 'notes' => null, diff --git a/app/Transformers/PreferenceTransformer.php b/app/Transformers/PreferenceTransformer.php new file mode 100644 index 0000000000..76a9d253cb --- /dev/null +++ b/app/Transformers/PreferenceTransformer.php @@ -0,0 +1,72 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\Preference; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +class PreferenceTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include. + * + * @var array + */ + protected $availableIncludes = ['user']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + /** @var ParameterBag */ + protected $parameters; + + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Transform the preference + * + * @param Preference $preference + * + * @return array + */ + public function transform(Preference $preference): array + { + return [ + 'id' => (int)$preference->id, + 'updated_at' => $preference->updated_at->toAtomString(), + 'created_at' => $preference->created_at->toAtomString(), + 'name' => $preference->name, + 'data' => $preference->data, + ]; + + } + +} \ No newline at end of file diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php new file mode 100644 index 0000000000..f35df38ddd --- /dev/null +++ b/app/Transformers/RecurrenceTransformer.php @@ -0,0 +1,258 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\CategoryFactory; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceMeta; +use FireflyIII\Models\RecurrenceRepetition; +use FireflyIII\Models\RecurrenceTransaction; +use FireflyIII\Models\RecurrenceTransactionMeta; +use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * + * Class RecurringTransactionTransformer + */ +class RecurrenceTransformer extends TransformerAbstract +{ + /** @noinspection ClassOverridesFieldOfSuperClassInspection */ + /** + * List of resources possible to include. + * + * @var array + */ + protected $availableIncludes = ['user', 'transactions']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + /** @var ParameterBag */ + protected $parameters; + + /** @var RecurringRepositoryInterface */ + protected $repository; + + + public function __construct(ParameterBag $parameters) + { + $this->repository = app(RecurringRepositoryInterface::class); + $this->parameters = $parameters; + } + + /** + * Include user data in end result. + * + * @codeCoverageIgnore + * + * @param Recurrence $recurrence + * + * + * @return Item + */ + public function includeUser(Recurrence $recurrence): Item + { + return $this->item($recurrence->user, new UserTransformer($this->parameters), 'users'); + } + + /** + * Transform the piggy bank. + * + * @param Recurrence $recurrence + * + * @return array + * @throws FireflyException + */ + public function transform(Recurrence $recurrence): array + { + $this->repository->setUser($recurrence->user); + $return = [ + 'id' => (int)$recurrence->id, + 'updated_at' => $recurrence->updated_at->toAtomString(), + 'created_at' => $recurrence->created_at->toAtomString(), + 'transaction_type_id' => $recurrence->transaction_type_id, + 'transaction_type' => $recurrence->transactionType->type, + 'title' => $recurrence->title, + 'description' => $recurrence->description, + 'first_date' => $recurrence->first_date->format('Y-m-d'), + 'latest_date' => null === $recurrence->latest_date ? null : $recurrence->latest_date->format('Y-m-d'), + 'repeat_until' => null === $recurrence->repeat_until ? null : $recurrence->repeat_until->format('Y-m-d'), + 'apply_rules' => $recurrence->apply_rules, + 'active' => $recurrence->active, + 'repetitions' => $recurrence->repetitions, + 'notes' => $this->repository->getNoteText($recurrence), + 'recurrence_repetitions' => [], + 'transactions' => [], + 'meta' => [], + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/recurring/' . $recurrence->id, + ], + ], + ]; + $fromDate = $recurrence->latest_date ?? $recurrence->first_date; + // date in the past? use today: + $today = new Carbon; + $fromDate = $fromDate->lte($today) ? $today : $fromDate; + + /** @var RecurrenceRepetition $repetition */ + foreach ($recurrence->recurrenceRepetitions as $repetition) { + $repetitionArray = [ + 'id' => $repetition->id, + 'updated_at' => $repetition->updated_at->toAtomString(), + 'created_at' => $repetition->created_at->toAtomString(), + 'repetition_type' => $repetition->repetition_type, + 'repetition_moment' => $repetition->repetition_moment, + 'repetition_skip' => (int)$repetition->repetition_skip, + 'weekend' => (int)$repetition->weekend, + 'description' => $this->repository->repetitionDescription($repetition), + 'occurrences' => [], + ]; + + // get the (future) occurrences for this specific type of repetition: + $occurrences = $this->repository->getXOccurrences($repetition, $fromDate, 5); + /** @var Carbon $carbon */ + foreach ($occurrences as $carbon) { + $repetitionArray['occurrences'][] = $carbon->format('Y-m-d'); + } + + $return['recurrence_repetitions'][] = $repetitionArray; + } + unset($repetitionArray); + + // get all transactions: + /** @var RecurrenceTransaction $transaction */ + foreach ($recurrence->recurrenceTransactions as $transaction) { + $transactionArray = [ + 'currency_id' => $transaction->transaction_currency_id, + 'currency_code' => $transaction->transactionCurrency->code, + 'currency_symbol' => $transaction->transactionCurrency->symbol, + 'currency_dp' => $transaction->transactionCurrency->decimal_places, + 'foreign_currency_id' => $transaction->foreign_currency_id, + 'source_id' => $transaction->source_id, + 'source_name' => $transaction->sourceAccount->name, + 'destination_id' => $transaction->destination_id, + 'destination_name' => $transaction->destinationAccount->name, + 'amount' => $transaction->amount, + 'foreign_amount' => $transaction->foreign_amount, + 'description' => $transaction->description, + 'meta' => [], + ]; + if (null !== $transaction->foreign_currency_id) { + $transactionArray['foreign_currency_code'] = $transaction->foreignCurrency->code; + $transactionArray['foreign_currency_symbol'] = $transaction->foreignCurrency->symbol; + $transactionArray['foreign_currency_dp'] = $transaction->foreignCurrency->decimal_places; + } + + // get meta data for each transaction: + /** @var RecurrenceTransactionMeta $transactionMeta */ + foreach ($transaction->recurrenceTransactionMeta as $transactionMeta) { + $transactionMetaArray = [ + 'name' => $transactionMeta->name, + 'value' => $transactionMeta->value, + ]; + switch ($transactionMeta->name) { + default: + throw new FireflyException(sprintf('Recurrence transformer cannot handle transaction meta-field "%s"', $transactionMeta->name)); + case 'category_name': + /** @var CategoryFactory $factory */ + $factory = app(CategoryFactory::class); + $factory->setUser($recurrence->user); + $category = $factory->findOrCreate(null, $transactionMeta->value); + if (null !== $category) { + $transactionMetaArray['category_id'] = $category->id; + $transactionMetaArray['category_name'] = $category->name; + } + break; + case 'budget_id': + /** @var BudgetRepositoryInterface $repository */ + $repository = app(BudgetRepositoryInterface::class); + $budget = $repository->findNull((int)$transactionMeta->value); + if (null !== $budget) { + $transactionMetaArray['budget_id'] = $budget->id; + $transactionMetaArray['budget_name'] = $budget->name; + } + break; + } + // store transaction meta data in transaction + $transactionArray['meta'][] = $transactionMetaArray; + } + // store transaction in recurrence array. + $return['transactions'][] = $transactionArray; + } + // get all meta data for recurrence itself + /** @var RecurrenceMeta $recurrenceMeta */ + foreach ($recurrence->recurrenceMeta as $recurrenceMeta) { + $recurrenceMetaArray = [ + 'name' => $recurrenceMeta->name, + 'value' => $recurrenceMeta->value, + ]; + switch ($recurrenceMeta->name) { + default: + throw new FireflyException(sprintf('Recurrence transformer cannot handle meta-field "%s"', $recurrenceMeta->name)); + case 'tags': + $recurrenceMetaArray['tags'] = explode(',', $recurrenceMeta->value); + break; + case 'notes': + break; + case 'bill_id': + /** @var BillRepositoryInterface $repository */ + $repository = app(BillRepositoryInterface::class); + $bill = $repository->find((int)$recurrenceMeta->value); + if (null !== $bill) { + $recurrenceMetaArray['bill_id'] = $bill->id; + $recurrenceMetaArray['bill_name'] = $bill->name; + } + break; + case 'piggy_bank_id': + /** @var PiggyBankRepositoryInterface $repository */ + $repository = app(PiggyBankRepositoryInterface::class); + $piggy = $repository->findNull((int)$recurrenceMeta->value); + if (null !== $piggy) { + $recurrenceMetaArray['piggy_bank_id'] = $piggy->id; + $recurrenceMetaArray['piggy_bank_name'] = $piggy->name; + } + break; + } + // store meta date in recurring array + $return['meta'][] = $recurrenceMetaArray; + + } + + return $return; + } + +} \ No newline at end of file diff --git a/app/Transformers/RuleActionTransformer.php b/app/Transformers/RuleActionTransformer.php new file mode 100644 index 0000000000..b8b664dd49 --- /dev/null +++ b/app/Transformers/RuleActionTransformer.php @@ -0,0 +1,92 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\RuleAction; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class RuleActionTransformer + */ +class RuleActionTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = []; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Transform the rule action. + * + * @param RuleAction $ruleAction + * + * @return array + */ + public function transform(RuleAction $ruleAction): array + { + $data = [ + 'id' => (int)$ruleAction->id, + 'updated_at' => $ruleAction->updated_at->toAtomString(), + 'created_at' => $ruleAction->created_at->toAtomString(), + 'action_type' => $ruleAction->action_type, + 'action_value' => $ruleAction->action_value, + 'order' => $ruleAction->order, + 'active' => $ruleAction->active, + 'stop_processing' => $ruleAction->stop_processing, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/rule_triggers/' . $ruleAction->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Transformers/RuleGroupTransformer.php b/app/Transformers/RuleGroupTransformer.php new file mode 100644 index 0000000000..d749f09945 --- /dev/null +++ b/app/Transformers/RuleGroupTransformer.php @@ -0,0 +1,114 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + +use FireflyIII\Models\RuleGroup; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class RuleGroupTransformer + */ +class RuleGroupTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['user']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['user']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + public function includeRules(RuleGroup $ruleGroup): FractalCollection + { + return $this->collection($ruleGroup->rules, new RuleTransformer($this->parameters), 'rules'); + } + + /** + * Include the user. + * + * @param RuleGroup $ruleGroup + * + * @codeCoverageIgnore + * @return Item + */ + public function includeUser(RuleGroup $ruleGroup): Item + { + return $this->item($ruleGroup->user, new UserTransformer($this->parameters), 'users'); + } + + /** + * Transform the rule group + * + * @param RuleGroup $ruleGroup + * + * @return array + */ + public function transform(RuleGroup $ruleGroup): array + { + $data = [ + 'id' => (int)$ruleGroup->id, + 'updated_at' => $ruleGroup->updated_at->toAtomString(), + 'created_at' => $ruleGroup->created_at->toAtomString(), + 'title' => $ruleGroup->title, + 'text' => $ruleGroup->text, + 'order' => $ruleGroup->order, + 'active' => $ruleGroup->active, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/rule_groups/' . $ruleGroup->id, + ], + ], + ]; + + return $data; + } + + +} + + diff --git a/app/Transformers/RuleTransformer.php b/app/Transformers/RuleTransformer.php new file mode 100644 index 0000000000..89f8353aa0 --- /dev/null +++ b/app/Transformers/RuleTransformer.php @@ -0,0 +1,141 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\Rule; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class RuleTransformer + */ +class RuleTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['rule_group', 'rule_triggers', 'rule_actions', 'user']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['rule_group', 'rule_triggers', 'rule_actions']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * @param Rule $rule + * + * @return FractalCollection + */ + public function includeRuleActions(Rule $rule): FractalCollection + { + return $this->collection($rule->ruleActions, new RuleActionTransformer($this->parameters), 'rule_actions'); + } + + /** + * Include the rule group. + * + * @param Rule $rule + * + * @codeCoverageIgnore + * @return Item + */ + public function includeRuleGroup(Rule $rule): Item + { + return $this->item($rule->ruleGroup, new RuleGroupTransformer($this->parameters), 'rule_groups'); + } + + /** + * @param Rule $rule + * + * @return FractalCollection + */ + public function includeRuleTriggers(Rule $rule): FractalCollection + { + return $this->collection($rule->ruleTriggers, new RuleTriggerTransformer($this->parameters), 'rule_triggers'); + } + + /** + * Include the user. + * + * @param Rule $rule + * + * @codeCoverageIgnore + * @return Item + */ + public function includeUser(Rule $rule): Item + { + return $this->item($rule->user, new UserTransformer($this->parameters), 'users'); + } + + /** + * Transform the rule. + * + * @param Rule $rule + * + * @return array + */ + public function transform(Rule $rule): array + { + $data = [ + 'id' => (int)$rule->id, + 'updated_at' => $rule->updated_at->toAtomString(), + 'created_at' => $rule->created_at->toAtomString(), + 'title' => $rule->title, + 'text' => $rule->text, + 'order' => (int)$rule->order, + 'active' => $rule->active, + 'stop_processing' => $rule->stop_processing, + 'strict' => $rule->strict, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/rules/' . $rule->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Transformers/RuleTriggerTransformer.php b/app/Transformers/RuleTriggerTransformer.php new file mode 100644 index 0000000000..6c51c84bb3 --- /dev/null +++ b/app/Transformers/RuleTriggerTransformer.php @@ -0,0 +1,92 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\RuleTrigger; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class RuleTriggerTransformer + */ +class RuleTriggerTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = []; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Transform the rule trigger. + * + * @param RuleTrigger $ruleTrigger + * + * @return array + */ + public function transform(RuleTrigger $ruleTrigger): array + { + $data = [ + 'id' => (int)$ruleTrigger->id, + 'updated_at' => $ruleTrigger->updated_at->toAtomString(), + 'created_at' => $ruleTrigger->created_at->toAtomString(), + 'trigger_type' => $ruleTrigger->trigger_type, + 'trigger_value' => $ruleTrigger->trigger_value, + 'order' => $ruleTrigger->order, + 'active' => $ruleTrigger->active, + 'stop_processing' => $ruleTrigger->stop_processing, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/rule_triggers/' . $ruleTrigger->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/User.php b/app/User.php index 3e3150fd13..28525e3ab4 100644 --- a/app/User.php +++ b/app/User.php @@ -25,7 +25,24 @@ declare(strict_types=1); namespace FireflyIII; use FireflyIII\Events\RequestedNewPassword; +use FireflyIII\Models\Account; +use FireflyIII\Models\Attachment; +use FireflyIII\Models\AvailableBudget; +use FireflyIII\Models\Bill; +use FireflyIII\Models\Budget; +use FireflyIII\Models\Category; use FireflyIII\Models\CurrencyExchangeRate; +use FireflyIII\Models\ExportJob; +use FireflyIII\Models\ImportJob; +use FireflyIII\Models\PiggyBank; +use FireflyIII\Models\Preference; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\Role; +use FireflyIII\Models\Rule; +use FireflyIII\Models\RuleGroup; +use FireflyIII\Models\Tag; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; @@ -36,26 +53,11 @@ use Laravel\Passport\HasApiTokens; use Log; use Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\Tag; -use FireflyIII\Models\Rule; -use FireflyIII\Models\RuleGroup; -use FireflyIII\Models\Role; -use FireflyIII\Models\Preference; -use FireflyIII\Models\Account; -use FireflyIII\Models\PiggyBank; -use FireflyIII\Models\ImportJob; -use FireflyIII\Models\ExportJob; -use FireflyIII\Models\Category; -use FireflyIII\Models\Budget; -use FireflyIII\Models\Bill; -use FireflyIII\Models\AvailableBudget; -use FireflyIII\Models\Attachment; /** * Class User. - * @property int $id + * + * @property int $id * @property string $email */ class User extends Authenticatable @@ -290,6 +292,17 @@ class User extends Authenticatable return $this->hasMany(Preference::class); } + /** + * @codeCoverageIgnore + * Link to recurring transactions. + * + * @return HasMany + */ + public function recurrences(): HasMany + { + return $this->hasMany(Recurrence::class); + } + /** * @codeCoverageIgnore * Link to roles. diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index 1f9571e99d..15dcc33618 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -225,6 +225,8 @@ class FireflyValidator extends Validator } /** + * TODO lots of if-else because of API calls. + * * @param $attribute * * @return bool @@ -234,12 +236,27 @@ class FireflyValidator extends Validator // get the index from a string like "rule-action-value.2". $parts = explode('.', $attribute); $index = $parts[\count($parts) - 1]; + if ($index === 'value') { + // user is coming from API. + $index = $parts[\count($parts) - 2]; + } + $index = (int)$index; + + // get actions from $this->data + $actions = []; + if (isset($this->data['rule-action']) && \is_array($this->data['rule-action'])) { + $actions = $this->data['rule-action']; + } + if (isset($this->data['rule-actions']) && \is_array($this->data['rule-actions'])) { + $actions = $this->data['rule-actions']; + } + + // loop all rule-actions. // check if rule-action-value matches the thing. - - if (\is_array($this->data['rule-action'])) { - $name = $this->data['rule-action'][$index] ?? 'invalid'; - $value = $this->data['rule-action-value'][$index] ?? false; + if (\is_array($actions)) { + $name = $this->getRuleActionName($index); + $value = $this->getRuleActionValue($index); switch ($name) { default: @@ -271,6 +288,8 @@ class FireflyValidator extends Validator } /** + * TODO This method uses a lot of if-then to handle the API calls as well. Fix. + * * @param $attribute * * @return bool @@ -280,20 +299,60 @@ class FireflyValidator extends Validator // get the index from a string like "rule-trigger-value.2". $parts = explode('.', $attribute); $index = $parts[\count($parts) - 1]; + // if the index is not a number, then we might be dealing with an API $attribute + // which is formatted "rule-triggers.0.value" + if ($index === 'value') { + $index = $parts[\count($parts) - 2]; + } + $index = (int)$index; + + // get triggers from $this->data + $triggers = []; + if (isset($this->data['rule-trigger']) && \is_array($this->data['rule-trigger'])) { + $triggers = $this->data['rule-trigger']; + } + if (isset($this->data['rule-triggers']) && \is_array($this->data['rule-triggers'])) { + $triggers = $this->data['rule-triggers']; + } // loop all rule-triggers. // check if rule-value matches the thing. - if (\is_array($this->data['rule-trigger'])) { + if (\is_array($triggers)) { $name = $this->getRuleTriggerName($index); $value = $this->getRuleTriggerValue($index); // break on some easy checks: switch ($name) { case 'amount_less': + case 'amount_more': + case 'amount_exactly': $result = is_numeric($value); if (false === $result) { return false; } + break; + case 'from_account_starts': + case 'from_account_ends': + case 'from_account_is': + case 'from_account_contains': + case 'to_account_starts': + case 'to_account_ends': + case 'to_account_is': + case 'to_account_contains': + case 'description_starts': + case 'description_ends': + case 'description_contains': + case 'description_is': + case 'category_is': + case 'budget_is': + case 'tag_is': + case 'currency_is': + case 'notes_contain': + case 'notes_start': + case 'notes_end': + case 'notes_are': + return \strlen($value) > 0; + break; case 'transaction_type': $count = TransactionType::where('type', $value)->count(); @@ -489,23 +548,71 @@ class FireflyValidator extends Validator } /** + * TODO this method needs a lot of logic to be able to handle API calls. Fix that. + * * @param int $index * * @return string */ - private function getRuleTriggerName($index): string + private function getRuleActionName(int $index): string { - return $this->data['rule-trigger'][$index] ?? 'invalid'; + $name = $this->data['rule-action'][$index] ?? 'invalid'; + if (!isset($this->data['rule-action'][$index])) { + $name = $this->data['rule-actions'][$index]['name'] ?? 'invalid'; + } + + return $name; } /** + * TODO this method needs a lot of logic to be able to handle API calls. Fix that. + * * @param int $index * * @return string */ - private function getRuleTriggerValue($index): string + private function getRuleActionValue(int $index): string { - return $this->data['rule-trigger-value'][$index] ?? ''; + $value = $this->data['rule-action-value'][$index] ?? ''; + if (!isset($this->data['rule-action-value'][$index])) { + $value = $this->data['rule-actions'][$index]['value'] ?? ''; + } + + return $value; + } + + /** + * TODO this method needs a lot of logic to be able to handle API calls. Fix that. + * + * @param int $index + * + * @return string + */ + private function getRuleTriggerName(int $index): string + { + $name = $this->data['rule-trigger'][$index] ?? 'invalid'; + if (!isset($this->data['rule-trigger'][$index])) { + $name = $this->data['rule-triggers'][$index]['name'] ?? 'invalid'; + } + + return $name; + } + + /** + * TODO this method needs a lot of logic to be able to handle API calls. Fix that. + * + * @param int $index + * + * @return string + */ + private function getRuleTriggerValue(int $index): string + { + $value = $this->data['rule-trigger-value'][$index] ?? ''; + if (!isset($this->data['rule-trigger-value'][$index])) { + $value = $this->data['rule-triggers'][$index]['value'] ?? ''; + } + + return $value; } /** diff --git a/changelog.md b/changelog.md index d16497a54c..6358b2b40b 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,31 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.7.5] - 2018-07-02 +### Added +- A new feature called "recurring transactions" that will make Firefly III automatically create transactions for you. +- New API end points for attachments, available budgets, budgets, budget limits, categories, configuration, currency exchange rates, journal links, link types, piggy banks, preferences, recurring transactions, rules, rule groups and tags. +- Added support for YunoHost. + +### Changed +- The 2FA secret is visible so you can type it into 2FA apps. +- Bunq and Spectre imports will now ask to apply rules. +- Sandstorm users can now make API keys. + +### Fixed +- Various typo's in the English translations. [issue 1493](https://github.com/firefly-iii/firefly-iii/issues/1493) +- Bug where Spectre was never called [issue 1492](https://github.com/firefly-iii/firefly-iii/issues/1492) +- Clear cache after journal is created through API [issue 1483](https://github.com/firefly-iii/firefly-iii/issues/1483) +- Make sure docker directories exist [issue 1500](https://github.com/firefly-iii/firefly-iii/issues/1500) +- Broken link to bill edit [issue 1505](https://github.com/firefly-iii/firefly-iii/issues/1505) +- Several bugs in the editing of split transactions [issue 1509](https://github.com/firefly-iii/firefly-iii/issues/1509) +- Import routine ignored formatting of several date fields [issue 1510](https://github.com/firefly-iii/firefly-iii/issues/1510) +- Piggy bank events now show the correct currency [issue 1446](https://github.com/firefly-iii/firefly-iii/issues/1446) +- Inactive accounts are no longer suggested [issue 1463](https://github.com/firefly-iii/firefly-iii/issues/1463) +- Some income / expense charts are less confusing [issue 1518](https://github.com/firefly-iii/firefly-iii/issues/1518) +- Validation bug in multi-currency create view [issue 1521](https://github.com/firefly-iii/firefly-iii/issues/1521) +- Bug where imported transfers would be stored incorrectly. + ## [4.7.4] - 2015-06-03 ### Added - [Issue 1409](https://github.com/firefly-iii/firefly-iii/issues/1409), add Indian Rupee and explain that users can do this themselves [issue 1413](https://github.com/firefly-iii/firefly-iii/issues/1413) diff --git a/composer.json b/composer.json index 9abb0009b2..66b1ca985a 100644 --- a/composer.json +++ b/composer.json @@ -1,128 +1,129 @@ { - "name": "grumpydictator/firefly-iii", - "description": "Firefly III: a personal finances manager.", - "keywords": [ - "finance", - "finances", - "manager", - "management", - "euro", - "dollar", - "laravel", - "money", - "currency", - "financials", - "financial", - "budgets", - "administration", - "tool", - "tooling", - "help", - "helper", - "assistant", - "planning", - "organizing", - "bills", - "personal finance", - "budgets", - "budgeting", - "budgeting tool", - "budgeting application", - "transactions", - "self hosted", - "self-hosted", - "transfers", - "management" - ], - "license": "GPL-3.0-or-later", - "homepage": "https://github.com/firefly-iii/firefly-iii", - "type": "project", - "authors": [{ - "name": "James Cole", - "email": "thegrumpydictator@gmail.com", - "homepage": "https://github.com/firefly-iii", - "role": "Developer" - }], - "require": { - "php": ">=7.1.0", - "ext-bcmath": "*", - "ext-curl": "*", - "ext-gd": "*", - "ext-intl": "*", - "ext-xml": "*", - "ext-zip": "*", - "bacon/bacon-qr-code": "1.*", - "bunq/sdk_php": "dev-master#8c1faefc111d9b970168a1837ca165d854954941", - "davejamesmiller/laravel-breadcrumbs": "5.*", - "doctrine/dbal": "2.*", - "fideloper/proxy": "4.*", - "laravel/framework": "5.6.*", - "laravel/passport": "^5.0", - "laravelcollective/html": "5.6.*", - "league/commonmark": "0.*", - "league/csv": "9.*", - "league/fractal": "^0.17.0", - "pragmarx/google2fa": "3.*", - "pragmarx/google2fa-laravel": "0.*", - "rcrowe/twigbridge": "0.9.*", - "rmccue/requests": "1.*", - "twig/twig": "1.*" - }, - "require-dev": { - "barryvdh/laravel-ide-helper": "2.*", - "filp/whoops": "2.*", - "fzaninotto/faker": "1.*", - "johnkary/phpunit-speedtrap": "^3.0", - "mockery/mockery": "^1.0", - "php-coveralls/php-coveralls": "^2.0", - "phpunit/phpunit": "~7.0", - "roave/security-advisories": "dev-master" - }, - "autoload": { - "classmap": [ - "database/seeds", - "database/factories" - ], - "psr-4": { - "FireflyIII\\": "app/" - } - }, - "autoload-dev": { - "psr-4": { - "Tests\\": "tests/" - } - }, - "extra": { - "laravel": { - "dont-discover": [] - } - }, - "scripts": { - "pre-install-cmd": [ - "@php -r \"if (!(getenv('DYNO'))===false){file_exists('.env') || copy('.env.heroku', '.env');}\"" - ], - "post-root-package-install": [ - "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" - ], - "post-create-project-cmd": [ - "@php artisan key:generate" - ], - "post-autoload-dump": [ - "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump" - ], - "post-update-cmd": [ - "php artisan firefly:upgrade-database", - "php artisan firefly:verify", - "php artisan firefly:instructions update", - "php artisan passport:install" - ], - "post-install-cmd": [ - "php artisan firefly:instructions install" - ] - }, - "config": { - "preferred-install": "dist", - "sort-packages": true, - "optimize-autoloader": true - } + "name": "grumpydictator/firefly-iii", + "description": "Firefly III: a personal finances manager.", + "keywords": [ + "finance", + "finances", + "manager", + "management", + "euro", + "dollar", + "laravel", + "money", + "currency", + "financials", + "financial", + "budgets", + "administration", + "tool", + "tooling", + "help", + "helper", + "assistant", + "planning", + "organizing", + "bills", + "personal finance", + "budgets", + "budgeting", + "budgeting tool", + "budgeting application", + "transactions", + "self hosted", + "self-hosted", + "transfers", + "management" + ], + "license": "GPL-3.0-or-later", + "homepage": "https://github.com/firefly-iii/firefly-iii", + "type": "project", + "authors": [ + { + "name": "James Cole", + "email": "thegrumpydictator@gmail.com", + "homepage": "https://github.com/firefly-iii", + "role": "Developer" + } + ], + "require": { + "php": ">=7.1.0", + "ext-bcmath": "*", + "ext-curl": "*", + "ext-gd": "*", + "ext-intl": "*", + "ext-xml": "*", + "ext-zip": "*", + "bacon/bacon-qr-code": "1.*", + "bunq/sdk_php": "dev-master#8c1faefc111d9b970168a1837ca165d854954941", + "davejamesmiller/laravel-breadcrumbs": "5.*", + "doctrine/dbal": "2.*", + "fideloper/proxy": "4.*", + "laravel/framework": "5.6.*", + "laravel/passport": "^5.0", + "laravelcollective/html": "5.6.*", + "league/commonmark": "0.*", + "league/csv": "9.*", + "league/fractal": "^0.17.0", + "pragmarx/google2fa": "3.*", + "pragmarx/google2fa-laravel": "0.*", + "rcrowe/twigbridge": "0.9.*", + "twig/twig": "1.*" + }, + "require-dev": { + "barryvdh/laravel-ide-helper": "2.*", + "filp/whoops": "2.*", + "fzaninotto/faker": "1.*", + "johnkary/phpunit-speedtrap": "^3.0", + "mockery/mockery": "^1.0", + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "~7.0", + "roave/security-advisories": "dev-master" + }, + "autoload": { + "classmap": [ + "database/seeds", + "database/factories" + ], + "psr-4": { + "FireflyIII\\": "app/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "extra": { + "laravel": { + "dont-discover": [] + } + }, + "scripts": { + "pre-install-cmd": [ + "@php -r \"if (!(getenv('DYNO'))===false){file_exists('.env') || copy('.env.heroku', '.env');}\"" + ], + "post-root-package-install": [ + "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + ], + "post-create-project-cmd": [ + "@php artisan key:generate" + ], + "post-autoload-dump": [ + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump" + ], + "post-update-cmd": [ + "php artisan firefly:upgrade-database", + "php artisan firefly:verify", + "php artisan firefly:instructions update", + "php artisan passport:install" + ], + "post-install-cmd": [ + "php artisan firefly:instructions install" + ] + }, + "config": { + "preferred-install": "dist", + "sort-packages": true, + "optimize-autoloader": true + } } diff --git a/composer.lock b/composer.lock index e1f8241676..a37ccfe587 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": "312e75271d4570f83bc7554892a3b6ab", + "content-hash": "dcaf20ad3436c4fc4cbebeee09c9de1f", "packages": [ { "name": "bacon/bacon-qr-code", @@ -717,16 +717,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v2.1.0", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "3f00985deec8df53d4cc1e5c33619bda1ee309a5" + "reference": "92a2c3768d50e21a1f26a53cb795ce72806266c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/3f00985deec8df53d4cc1e5c33619bda1ee309a5", - "reference": "3f00985deec8df53d4cc1e5c33619bda1ee309a5", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/92a2c3768d50e21a1f26a53cb795ce72806266c5", + "reference": "92a2c3768d50e21a1f26a53cb795ce72806266c5", "shasum": "" }, "require": { @@ -762,7 +762,7 @@ "cron", "schedule" ], - "time": "2018-04-06T15:51:55+00:00" + "time": "2018-06-06T03:12:17+00:00" }, { "name": "egulias/email-validator", @@ -1150,16 +1150,16 @@ }, { "name": "laravel/framework", - "version": "v5.6.23", + "version": "v5.6.26", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "f547f0a71a12763d1adb8493237d541c9e3a5d10" + "reference": "7047df295e77cecb6a2f84736a732af66cc6789c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/f547f0a71a12763d1adb8493237d541c9e3a5d10", - "reference": "f547f0a71a12763d1adb8493237d541c9e3a5d10", + "url": "https://api.github.com/repos/laravel/framework/zipball/7047df295e77cecb6a2f84736a732af66cc6789c", + "reference": "7047df295e77cecb6a2f84736a732af66cc6789c", "shasum": "" }, "require": { @@ -1285,7 +1285,7 @@ "framework", "laravel" ], - "time": "2018-05-22T14:55:57+00:00" + "time": "2018-06-20T14:21:11+00:00" }, { "name": "laravel/passport", @@ -1358,16 +1358,16 @@ }, { "name": "laravelcollective/html", - "version": "v5.6.9", + "version": "v5.6.10", "source": { "type": "git", "url": "https://github.com/LaravelCollective/html.git", - "reference": "fda9d3dad85ecea609ef9c6323d6923536cf5643" + "reference": "974605fcd22a7e4d19f0b2ef635a0d1d7400387d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LaravelCollective/html/zipball/fda9d3dad85ecea609ef9c6323d6923536cf5643", - "reference": "fda9d3dad85ecea609ef9c6323d6923536cf5643", + "url": "https://api.github.com/repos/LaravelCollective/html/zipball/974605fcd22a7e4d19f0b2ef635a0d1d7400387d", + "reference": "974605fcd22a7e4d19f0b2ef635a0d1d7400387d", "shasum": "" }, "require": { @@ -1422,7 +1422,7 @@ ], "description": "HTML and Form Builders for the Laravel Framework", "homepage": "https://laravelcollective.com", - "time": "2018-05-30T16:09:07+00:00" + "time": "2018-06-18T15:04:16+00:00" }, { "name": "lcobucci/jwt", @@ -2079,16 +2079,16 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.12", + "version": "v2.0.15", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb" + "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/10bcb46e8f3d365170f6de9d05245aa066b81f09", + "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09", "shasum": "" }, "require": { @@ -2120,10 +2120,11 @@ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", "keywords": [ "csprng", + "polyfill", "pseudorandom", "random" ], - "time": "2018-04-04T21:24:14+00:00" + "time": "2018-06-08T15:26:40+00:00" }, { "name": "phpseclib/phpseclib", @@ -2695,67 +2696,18 @@ ], "time": "2018-02-08T15:59:23+00:00" }, - { - "name": "rmccue/requests", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/rmccue/Requests.git", - "reference": "87932f52ffad70504d93f04f15690cf16a089546" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rmccue/Requests/zipball/87932f52ffad70504d93f04f15690cf16a089546", - "reference": "87932f52ffad70504d93f04f15690cf16a089546", - "shasum": "" - }, - "require": { - "php": ">=5.2" - }, - "require-dev": { - "requests/test-server": "dev-master" - }, - "type": "library", - "autoload": { - "psr-0": { - "Requests": "library/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Ryan McCue", - "homepage": "http://ryanmccue.info" - } - ], - "description": "A HTTP library written in PHP, for human beings.", - "homepage": "http://github.com/rmccue/Requests", - "keywords": [ - "curl", - "fsockopen", - "http", - "idna", - "ipv6", - "iri", - "sockets" - ], - "time": "2016-10-13T00:11:37+00:00" - }, { "name": "swiftmailer/swiftmailer", - "version": "v6.0.2", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "412333372fb6c8ffb65496a2bbd7321af75733fc" + "reference": "0ff595e1d9d7d1c929b2a5f7b774bbcfbbd81587" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/412333372fb6c8ffb65496a2bbd7321af75733fc", - "reference": "412333372fb6c8ffb65496a2bbd7321af75733fc", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/0ff595e1d9d7d1c929b2a5f7b774bbcfbbd81587", + "reference": "0ff595e1d9d7d1c929b2a5f7b774bbcfbbd81587", "shasum": "" }, "require": { @@ -2766,10 +2718,14 @@ "mockery/mockery": "~0.9.1", "symfony/phpunit-bridge": "~3.3@dev" }, + "suggest": { + "ext-intl": "Needed to support internationalized email addresses", + "true/punycode": "Needed to support internationalized email addresses, if ext-intl is not installed" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.0-dev" + "dev-master": "6.1-dev" } }, "autoload": { @@ -2791,26 +2747,26 @@ } ], "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "http://swiftmailer.symfony.com", + "homepage": "https://swiftmailer.symfony.com", "keywords": [ "email", "mail", "mailer" ], - "time": "2017-09-30T22:39:41+00:00" + "time": "2018-07-02T20:24:38+00:00" }, { "name": "symfony/console", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2d5d973bf9933d46802b01010bd25c800c87c242" + "reference": "70591cda56b4b47c55776ac78e157c4bb6c8b43f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2d5d973bf9933d46802b01010bd25c800c87c242", - "reference": "2d5d973bf9933d46802b01010bd25c800c87c242", + "url": "https://api.github.com/repos/symfony/console/zipball/70591cda56b4b47c55776ac78e157c4bb6c8b43f", + "reference": "70591cda56b4b47c55776ac78e157c4bb6c8b43f", "shasum": "" }, "require": { @@ -2865,11 +2821,11 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-05-31T10:17:53+00:00" }, { "name": "symfony/css-selector", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -2922,16 +2878,16 @@ }, { "name": "symfony/debug", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b" + "reference": "dbe0fad88046a755dcf9379f2964c61a02f5ae3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/449f8b00b28ab6e6912c3e6b920406143b27193b", - "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b", + "url": "https://api.github.com/repos/symfony/debug/zipball/dbe0fad88046a755dcf9379f2964c61a02f5ae3d", + "reference": "dbe0fad88046a755dcf9379f2964c61a02f5ae3d", "shasum": "" }, "require": { @@ -2974,11 +2930,11 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-06-08T09:39:36+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -3041,16 +2997,16 @@ }, { "name": "symfony/finder", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238" + "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/087e2ee0d74464a4c6baac4e90417db7477dc238", - "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238", + "url": "https://api.github.com/repos/symfony/finder/zipball/84714b8417d19e4ba02ea78a41a975b3efaafddb", + "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb", "shasum": "" }, "require": { @@ -3086,20 +3042,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-06-19T21:38:16+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "a916c88390fb861ee21f12a92b107d51bb68af99" + "reference": "4f9c7cf962e635b0b26b14500ac046e07dbef7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a916c88390fb861ee21f12a92b107d51bb68af99", - "reference": "a916c88390fb861ee21f12a92b107d51bb68af99", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4f9c7cf962e635b0b26b14500ac046e07dbef7f3", + "reference": "4f9c7cf962e635b0b26b14500ac046e07dbef7f3", "shasum": "" }, "require": { @@ -3140,20 +3096,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-05-25T14:55:38+00:00" + "time": "2018-06-19T21:38:16+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90" + "reference": "29c094a1c4f8209b7e033f612cbbd69029e38955" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", - "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/29c094a1c4f8209b7e033f612cbbd69029e38955", + "reference": "29c094a1c4f8209b7e033f612cbbd69029e38955", "shasum": "" }, "require": { @@ -3161,13 +3117,13 @@ "psr/log": "~1.0", "symfony/debug": "~3.4|~4.0", "symfony/event-dispatcher": "~4.1", - "symfony/http-foundation": "~4.1", + "symfony/http-foundation": "^4.1.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/config": "<3.4", "symfony/dependency-injection": "<4.1", - "symfony/var-dumper": "<4.1", + "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" }, "provide": { @@ -3188,7 +3144,7 @@ "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", "symfony/translation": "~3.4|~4.0", - "symfony/var-dumper": "~4.1" + "symfony/var-dumper": "^4.1.1" }, "suggest": { "symfony/browser-kit": "", @@ -3227,7 +3183,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-05-30T12:52:34+00:00" + "time": "2018-06-25T13:06:45+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3508,16 +3464,16 @@ }, { "name": "symfony/process", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "73445bd33b0d337c060eef9652b94df72b6b3434" + "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/73445bd33b0d337c060eef9652b94df72b6b3434", - "reference": "73445bd33b0d337c060eef9652b94df72b6b3434", + "url": "https://api.github.com/repos/symfony/process/zipball/1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", + "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", "shasum": "" }, "require": { @@ -3553,7 +3509,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-05-31T10:17:53+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -3617,16 +3573,16 @@ }, { "name": "symfony/routing", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "180b51c66d10f09e562c9ebc395b39aacb2cf8a2" + "reference": "b38b9797327b26ea2e4146a40e6e2dc9820a6932" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/180b51c66d10f09e562c9ebc395b39aacb2cf8a2", - "reference": "180b51c66d10f09e562c9ebc395b39aacb2cf8a2", + "url": "https://api.github.com/repos/symfony/routing/zipball/b38b9797327b26ea2e4146a40e6e2dc9820a6932", + "reference": "b38b9797327b26ea2e4146a40e6e2dc9820a6932", "shasum": "" }, "require": { @@ -3639,7 +3595,6 @@ }, "require-dev": { "doctrine/annotations": "~1.0", - "doctrine/common": "~2.2", "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", @@ -3691,20 +3646,20 @@ "uri", "url" ], - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-06-19T21:38:16+00:00" }, { "name": "symfony/translation", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a" + "reference": "b6d8164085ee0b6debcd1b7a131fd6f63bb04854" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/16328f5b217cebc8dd4adfe4aeeaa8c377581f5a", - "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a", + "url": "https://api.github.com/repos/symfony/translation/zipball/b6d8164085ee0b6debcd1b7a131fd6f63bb04854", + "reference": "b6d8164085ee0b6debcd1b7a131fd6f63bb04854", "shasum": "" }, "require": { @@ -3760,20 +3715,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-06-22T08:59:39+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64" + "reference": "b2eebaec085d1f2cafbad7644733d494a3bbbc9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/bc88ad53e825ebacc7b190bbd360781fce381c64", - "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b2eebaec085d1f2cafbad7644733d494a3bbbc9b", + "reference": "b2eebaec085d1f2cafbad7644733d494a3bbbc9b", "shasum": "" }, "require": { @@ -3835,7 +3790,7 @@ "debug", "dump" ], - "time": "2018-04-29T07:56:09+00:00" + "time": "2018-06-23T12:23:56+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -3951,28 +3906,28 @@ }, { "name": "vlucas/phpdotenv", - "version": "v2.4.0", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c" + "reference": "6ae3e2e6494bb5e58c2decadafc3de7f1453f70a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", - "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/6ae3e2e6494bb5e58c2decadafc3de7f1453f70a", + "reference": "6ae3e2e6494bb5e58c2decadafc3de7f1453f70a", "shasum": "" }, "require": { "php": ">=5.3.9" }, "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.0" + "phpunit/phpunit": "^4.8.35 || ^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-master": "2.5-dev" } }, "autoload": { @@ -3982,7 +3937,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause-Attribution" + "BSD-3-Clause" ], "authors": [ { @@ -3997,20 +3952,20 @@ "env", "environment" ], - "time": "2016-09-01T10:05:43+00:00" + "time": "2018-07-01T10:25:50+00:00" }, { "name": "zendframework/zend-diactoros", - "version": "1.7.2", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "741e7a571836f038de731105f4742ca8a164e43a" + "reference": "11c9c1835e60eef6f9234377a480fcec096ebd9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/741e7a571836f038de731105f4742ca8a164e43a", - "reference": "741e7a571836f038de731105f4742ca8a164e43a", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/11c9c1835e60eef6f9234377a480fcec096ebd9e", + "reference": "11c9c1835e60eef6f9234377a480fcec096ebd9e", "shasum": "" }, "require": { @@ -4029,12 +3984,22 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev", - "dev-develop": "1.8.x-dev", + "dev-master": "1.8.x-dev", + "dev-develop": "1.9.x-dev", "dev-release-2.0": "2.0.x-dev" } }, "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], "psr-4": { "Zend\\Diactoros\\": "src/" } @@ -4050,7 +4015,7 @@ "psr", "psr-7" ], - "time": "2018-05-29T16:53:08+00:00" + "time": "2018-06-27T18:52:43+00:00" } ], "packages-dev": [ @@ -4232,16 +4197,16 @@ }, { "name": "filp/whoops", - "version": "2.1.14", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "c6081b8838686aa04f1e83ba7e91f78b7b2a23e6" + "reference": "181c4502d8f34db7aed7bfe88d4f87875b8e947a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/c6081b8838686aa04f1e83ba7e91f78b7b2a23e6", - "reference": "c6081b8838686aa04f1e83ba7e91f78b7b2a23e6", + "url": "https://api.github.com/repos/filp/whoops/zipball/181c4502d8f34db7aed7bfe88d4f87875b8e947a", + "reference": "181c4502d8f34db7aed7bfe88d4f87875b8e947a", "shasum": "" }, "require": { @@ -4249,9 +4214,9 @@ "psr/log": "^1.0.1" }, "require-dev": { - "mockery/mockery": "0.9.*", + "mockery/mockery": "^0.9 || ^1.0", "phpunit/phpunit": "^4.8.35 || ^5.7", - "symfony/var-dumper": "^2.6 || ^3.0" + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0" }, "suggest": { "symfony/var-dumper": "Pretty print complex values better with var-dumper available", @@ -4260,7 +4225,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -4289,7 +4254,7 @@ "throwable", "whoops" ], - "time": "2017-11-23T18:22:44+00:00" + "time": "2018-03-03T17:56:25+00:00" }, { "name": "fzaninotto/faker", @@ -4505,16 +4470,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.8.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "478465659fd987669df0bd8a9bf22a8710e5f1b6" + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/478465659fd987669df0bd8a9bf22a8710e5f1b6", - "reference": "478465659fd987669df0bd8a9bf22a8710e5f1b6", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", "shasum": "" }, "require": { @@ -4549,7 +4514,7 @@ "object", "object graph" ], - "time": "2018-05-29T17:25:09+00:00" + "time": "2018-06-11T23:09:50+00:00" }, { "name": "phar-io/manifest", @@ -5016,16 +4981,16 @@ }, { "name": "phpunit/php-file-iterator", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "e20525b0c2945c7c317fff95660698cb3d2a53bc" + "reference": "cecbc684605bb0cc288828eb5d65d93d5c676d3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/e20525b0c2945c7c317fff95660698cb3d2a53bc", - "reference": "e20525b0c2945c7c317fff95660698cb3d2a53bc", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cecbc684605bb0cc288828eb5d65d93d5c676d3c", + "reference": "cecbc684605bb0cc288828eb5d65d93d5c676d3c", "shasum": "" }, "require": { @@ -5059,7 +5024,7 @@ "filesystem", "iterator" ], - "time": "2018-05-28T12:13:49+00:00" + "time": "2018-06-11T11:44:00+00:00" }, { "name": "phpunit/php-text-template", @@ -5202,16 +5167,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.2.2", + "version": "7.2.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3cf0836680bf5c365c627e8566d46c9e1f544db9" + "reference": "400a3836ee549ae6f665323ac3f21e27eac7155f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3cf0836680bf5c365c627e8566d46c9e1f544db9", - "reference": "3cf0836680bf5c365c627e8566d46c9e1f544db9", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/400a3836ee549ae6f665323ac3f21e27eac7155f", + "reference": "400a3836ee549ae6f665323ac3f21e27eac7155f", "shasum": "" }, "require": { @@ -5227,7 +5192,7 @@ "php": "^7.1", "phpspec/prophecy": "^1.7", "phpunit/php-code-coverage": "^6.0.7", - "phpunit/php-file-iterator": "^2.0", + "phpunit/php-file-iterator": "^2.0.1", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^2.0", "sebastian/comparator": "^3.0", @@ -5282,7 +5247,7 @@ "testing", "xunit" ], - "time": "2018-06-01T07:54:27+00:00" + "time": "2018-06-21T13:13:39+00:00" }, { "name": "roave/security-advisories", @@ -5290,12 +5255,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "4d93302eb93402083f5abe72002fe8dc35e12dae" + "reference": "5c802c6300dca269edde06c6ae8c7eb561de3176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/4d93302eb93402083f5abe72002fe8dc35e12dae", - "reference": "4d93302eb93402083f5abe72002fe8dc35e12dae", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/5c802c6300dca269edde06c6ae8c7eb561de3176", + "reference": "5c802c6300dca269edde06c6ae8c7eb561de3176", "shasum": "" }, "conflict": { @@ -5346,7 +5311,7 @@ "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", "magento/magento1ce": ">=1.5.0.1,<1.9.3.2", "magento/magento1ee": ">=1.9,<1.14.3.2", - "magento/magento2ce": ">=2,<2.2", + "magento/magento2ce": ">=2,<2.3", "monolog/monolog": ">=1.8,<1.12", "namshi/jose": "<2.2", "onelogin/php-saml": "<2.10.4", @@ -5363,6 +5328,7 @@ "propel/propel1": ">=1,<=1.7.1", "pusher/pusher-php-server": "<2.2.1", "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", + "sensiolabs/connect": "<4.2.3", "shopware/shopware": "<5.3.7", "silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", @@ -5385,7 +5351,7 @@ "symfony/routing": ">=2,<2.0.19", "symfony/security": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", - "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.37|>=3,<3.3.17|>=3.4,<3.4.7|>=4,<4.0.7", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", @@ -5396,7 +5362,7 @@ "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", "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1,<2.1.2|>=2.1.0-beta1,<2.1.3", + "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2", "titon/framework": ">=0,<9.9.99", "twig/twig": "<1.20", "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.22|>=8,<8.7.5", @@ -5448,7 +5414,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-05-30T02:58:56+00:00" + "time": "2018-07-03T10:42:36+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -5497,16 +5463,16 @@ }, { "name": "sebastian/comparator", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5" + "reference": "591a30922f54656695e59b1f39501aec513403da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/ed5fd2281113729f1ebcc64d101ad66028aeb3d5", - "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/591a30922f54656695e59b1f39501aec513403da", + "reference": "591a30922f54656695e59b1f39501aec513403da", "shasum": "" }, "require": { @@ -5557,20 +5523,20 @@ "compare", "equality" ], - "time": "2018-04-18T13:33:00+00:00" + "time": "2018-06-14T15:05:28+00:00" }, { "name": "sebastian/diff", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8" + "reference": "366541b989927187c4ca70490a35615d3fef2dce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/e09160918c66281713f1c324c1f4c4c3037ba1e8", - "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/366541b989927187c4ca70490a35615d3fef2dce", + "reference": "366541b989927187c4ca70490a35615d3fef2dce", "shasum": "" }, "require": { @@ -5613,7 +5579,7 @@ "unidiff", "unified diff" ], - "time": "2018-02-01T13:45:15+00:00" + "time": "2018-06-10T07:54:39+00:00" }, { "name": "sebastian/environment", @@ -6015,7 +5981,7 @@ }, { "name": "symfony/class-loader", - "version": "v3.4.11", + "version": "v3.4.12", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", @@ -6071,16 +6037,16 @@ }, { "name": "symfony/config", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4" + "reference": "e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/5ceefc256caecc3e25147c4e5b933de71d0020c4", - "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4", + "url": "https://api.github.com/repos/symfony/config/zipball/e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5", + "reference": "e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5", "shasum": "" }, "require": { @@ -6130,11 +6096,11 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-06-20T11:15:17+00:00" }, { "name": "symfony/filesystem", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -6184,7 +6150,7 @@ }, { "name": "symfony/stopwatch", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -6233,7 +6199,7 @@ }, { "name": "symfony/yaml", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", diff --git a/config/app.php b/config/app.php index 953ed046ee..5127530c6d 100644 --- a/config/app.php +++ b/config/app.php @@ -98,6 +98,7 @@ return [ FireflyIII\Providers\SearchServiceProvider::class, FireflyIII\Providers\TagServiceProvider::class, FireflyIII\Providers\AdminServiceProvider::class, + FireflyIII\Providers\RecurringServiceProvider::class, ], diff --git a/config/firefly.php b/config/firefly.php index c0fe9ed9ea..3ce05abf00 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -83,16 +83,16 @@ use FireflyIII\TransactionRules\Triggers\UserAction; */ return [ - 'configuration' => [ + 'configuration' => [ 'single_user_mode' => true, 'is_demo_site' => false, ], - 'encryption' => null === env('USE_ENCRYPTION') || env('USE_ENCRYPTION') === true, - 'version' => '4.7.4', - 'api_version' => '0.3', - 'db_version' => 4, - 'maxUploadSize' => 15242880, - 'allowedMimes' => [ + 'encryption' => null === env('USE_ENCRYPTION') || env('USE_ENCRYPTION') === true, + 'version' => '4.7.5', + 'api_version' => '0.4', + 'db_version' => 4, + 'maxUploadSize' => 15242880, + 'allowedMimes' => [ /* plain files */ 'text/plain', @@ -154,8 +154,8 @@ return [ 'application/vnd.oasis.opendocument.database', 'application/vnd.oasis.opendocument.image', ], - 'list_length' => 10, - 'export_formats' => [ + 'list_length' => 10, + 'export_formats' => [ 'csv' => CsvExporter::class, ], 'default_export_format' => 'csv', @@ -260,6 +260,7 @@ return [ // models 'account' => \FireflyIII\Models\Account::class, 'attachment' => \FireflyIII\Models\Attachment::class, + 'availableBudget' => \FireflyIII\Models\AvailableBudget::class, 'bill' => \FireflyIII\Models\Bill::class, 'budget' => \FireflyIII\Models\Budget::class, 'budgetLimit' => \FireflyIII\Models\BudgetLimit::class, @@ -269,8 +270,10 @@ return [ 'journalLink' => \FireflyIII\Models\TransactionJournalLink::class, 'currency' => \FireflyIII\Models\TransactionCurrency::class, 'piggyBank' => \FireflyIII\Models\PiggyBank::class, + 'preference' => \FireflyIII\Models\Preference::class, 'tj' => \FireflyIII\Models\TransactionJournal::class, 'tag' => \FireflyIII\Models\Tag::class, + 'recurrence' => \FireflyIII\Models\Recurrence::class, 'rule' => \FireflyIII\Models\Rule::class, 'ruleGroup' => \FireflyIII\Models\RuleGroup::class, 'exportJob' => \FireflyIII\Models\ExportJob::class, @@ -279,7 +282,7 @@ return [ 'user' => \FireflyIII\User::class, // strings - 'import_provider' => \FireflyIII\Support\Binder\ImportProvider::class, + 'import_provider' => \FireflyIII\Support\Binder\ImportProvider::class, // dates 'start_date' => \FireflyIII\Support\Binder\Date::class, diff --git a/config/services.php b/config/services.php index 01c5a3d90e..d72eea32f3 100644 --- a/config/services.php +++ b/config/services.php @@ -51,10 +51,12 @@ return [ 'secret' => env('SPARKPOST_SECRET'), ], - 'stripe' => [ + 'stripe' => [ 'model' => FireflyIII\User::class, 'key' => env('STRIPE_KEY'), 'secret' => env('STRIPE_SECRET'), ], - + 'mandrill' => [ + 'secret' => env('MANDRILL_SECRET'), + ], ]; diff --git a/config/twigbridge.php b/config/twigbridge.php index f4a2c2167a..f6a6e22ae3 100644 --- a/config/twigbridge.php +++ b/config/twigbridge.php @@ -188,7 +188,8 @@ return [ 'is_safe' => [ 'date', 'text', 'select', 'balance', 'optionsList', 'checkbox', 'amount', 'tags', 'integer', 'textarea', 'location', 'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', 'password', 'nonSelectableBalance', 'nonSelectableAmount', - 'number', 'assetAccountList','amountNoCurrency','currencyList','ruleGroupList','assetAccountCheckList','ruleGroupListWithEmpty' + 'number', 'assetAccountList','amountNoCurrency','currencyList','ruleGroupList','assetAccountCheckList','ruleGroupListWithEmpty', + 'piggyBankList','currencyListEmpty','activeAssetAccountList' ], ], 'Form' => [ diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 04a90e6a2a..9a702f7221 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -46,6 +46,24 @@ $factory->define( } ); + +$factory->define( + FireflyIII\Models\Attachment::class, + function (Faker\Generator $faker) { + return [ + 'user_id' => 1, + 'attachable_id' => 1, + 'attachable_type' => \FireflyIII\Models\TransactionJournal::class, + 'md5' => md5($faker->words(6, true)), + 'mime' => 'text/plain', + 'size' => 1, + 'filename' => 'ok', + 'uploaded' => true, + + ]; + } +); + $factory->define( FireflyIII\Models\CurrencyExchangeRate::class, function (Faker\Generator $faker) { @@ -245,13 +263,13 @@ $factory->define( 'transaction_amount' => (string)$faker->randomFloat(2, -100, 100), 'destination_amount' => (string)$faker->randomFloat(2, -100, 100), 'opposing_account_id' => $faker->numberBetween(1, 10), - 'source_account_id' => $faker->numberBetween(1, 10), + 'source_id' => $faker->numberBetween(1, 10), 'opposing_account_name' => $faker->words(3, true), 'description' => $faker->words(3, true), - 'source_account_name' => $faker->words(3, true), - 'destination_account_id' => $faker->numberBetween(1, 10), + 'source_name' => $faker->words(3, true), + 'destination_id' => $faker->numberBetween(1, 10), 'date' => new Carbon, - 'destination_account_name' => $faker->words(3, true), + 'destination_name' => $faker->words(3, true), 'amount' => (string)$faker->randomFloat(2, -100, 100), 'budget_id' => 0, 'category' => $faker->words(3, true), diff --git a/database/migrations/2018_04_29_174524_changes_for_v474.php b/database/migrations/2018_04_29_174524_changes_for_v474.php index cdc6208f35..c9c79e6e82 100644 --- a/database/migrations/2018_04_29_174524_changes_for_v474.php +++ b/database/migrations/2018_04_29_174524_changes_for_v474.php @@ -37,7 +37,6 @@ class ChangesForV474 extends Migration */ public function down() { - // } /** diff --git a/database/migrations/2018_06_08_200526_changes_for_v475.php b/database/migrations/2018_06_08_200526_changes_for_v475.php new file mode 100644 index 0000000000..fa85ed5456 --- /dev/null +++ b/database/migrations/2018_06_08_200526_changes_for_v475.php @@ -0,0 +1,131 @@ +increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('user_id', false, true); + $table->integer('transaction_type_id', false, true); + + $table->string('title', 1024); + $table->text('description'); + + $table->date('first_date'); + $table->date('repeat_until')->nullable(); + $table->date('latest_date')->nullable(); + $table->smallInteger('repetitions', false, true); + + $table->boolean('apply_rules')->default(true); + $table->boolean('active')->default(true); + + // also separate: + // category, budget, tags, notes, bill, piggy bank + + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('transaction_type_id')->references('id')->on('transaction_types')->onDelete('cascade'); + } + ); + + Schema::create( + 'recurrences_transactions', function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('recurrence_id', false, true); + $table->integer('transaction_currency_id', false, true); + $table->integer('foreign_currency_id', false, true)->nullable(); + $table->integer('source_id', false, true); + $table->integer('destination_id', false, true); + + $table->decimal('amount', 22, 12); + $table->decimal('foreign_amount', 22, 12)->nullable(); + $table->string('description', 1024); + + + $table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade'); + $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); + $table->foreign('foreign_currency_id')->references('id')->on('transaction_currencies')->onDelete('set null'); + $table->foreign('source_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('destination_id')->references('id')->on('accounts')->onDelete('cascade'); + } + ); + + + Schema::create( + 'recurrences_repetitions', function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('recurrence_id', false, true); + $table->string('repetition_type', 50); + $table->string('repetition_moment', 50); + $table->smallInteger('repetition_skip', false, true); + $table->smallInteger('weekend', false, true); + + $table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade'); + } + ); + + Schema::create( + 'recurrences_meta', function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('recurrence_id', false, true); + + $table->string('name', 50); + $table->text('value'); + + $table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade'); + } + ); + + Schema::create( + 'rt_meta', function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('rt_id', false, true); + + $table->string('name', 50); + $table->text('value'); + + $table->foreign('rt_id')->references('id')->on('recurrences_transactions')->onDelete('cascade'); + } + ); + + + } +} diff --git a/phpunit.coverage.specific.xml b/phpunit.coverage.specific.xml index da49035fc3..9448630a43 100644 --- a/phpunit.coverage.specific.xml +++ b/phpunit.coverage.specific.xml @@ -30,12 +30,9 @@ app/Http/breadcrumbs.php - - vendor/ - - + diff --git a/phpunit.coverage.xml b/phpunit.coverage.xml index cfca4f71ee..7a3119d118 100644 --- a/phpunit.coverage.xml +++ b/phpunit.coverage.xml @@ -30,12 +30,9 @@ app/Http/breadcrumbs.php - - vendor/ - - + diff --git a/public/js/ff/charts.js b/public/js/ff/charts.js index 67e4805c53..275f3fb144 100644 --- a/public/js/ff/charts.js +++ b/public/js/ff/charts.js @@ -26,12 +26,12 @@ var allCharts = {}; */ var colourSet = [ [53, 124, 165], - [0, 141, 76], + [0, 141, 76], // green [219, 139, 11], - [202, 25, 90], + [202, 25, 90], // paars rood-ish #CA195A [85, 82, 153], [66, 133, 244], - [219, 68, 55], + [219, 68, 55], // red #DB4437 [244, 180, 0], [15, 157, 88], [171, 71, 188], @@ -205,6 +205,21 @@ function columnChart(URI, container) { } +/** + * + * @param URI + * @param container + */ +function columnChartCustomColours(URI, container) { + "use strict"; + var colorData = false; + var options = $.extend(true, {}, defaultChartOptions); + var chartType = 'bar'; + + drawAChart(URI, container, chartType, options, colorData); + +} + /** * * @param URI diff --git a/public/js/ff/firefly.js b/public/js/ff/firefly.js index 10ecacbcc3..ba48ecd4e6 100644 --- a/public/js/ff/firefly.js +++ b/public/js/ff/firefly.js @@ -83,6 +83,7 @@ $(function () { function currencySelect(e) { "use strict"; + console.log('In currencySelect() because somebody clicked a .currency-option.'); // clicked on var target = $(e.target); // target is the tag. @@ -105,6 +106,7 @@ function currencySelect(e) { var id = target.data('id'); // update the hidden input: + console.log('Updated ' + hiddenInputName + ' to ID ' + id); $('input[name="' + hiddenInputName + '"]').val(id); // update the symbol: diff --git a/public/js/ff/recurring/create.js b/public/js/ff/recurring/create.js new file mode 100644 index 0000000000..4d47190f70 --- /dev/null +++ b/public/js/ff/recurring/create.js @@ -0,0 +1,246 @@ +/* + * create.js + * Copyright (c) 2017 thegrumpydictator@gmail.com + * + * This file is part of Firefly III. + * + * Firefly III is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Firefly III is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Firefly III. If not, see . + */ + +/** global: Modernizr, currencies */ + +var calendar; + +$(document).ready(function () { + "use strict"; + if (!Modernizr.inputtypes.date) { + $('input[type="date"]').datepicker( + { + dateFormat: 'yy-mm-dd' + } + ); + } + initializeButtons(); + initializeAutoComplete(); + respondToFirstDateChange(); + respondToRepetitionEnd(); + $('.switch-button').on('click', switchTransactionType); + $('#ffInput_repetition_end').on('change', respondToRepetitionEnd); + $('#ffInput_first_date').on('change', respondToFirstDateChange); + + // create calendar on load: + calendar = $('#recurring_calendar').fullCalendar( + { + defaultDate: '2018-06-13', + editable: false, + height: 400, + width: 200, + contentHeight: 400, + aspectRatio: 1.25, + eventLimit: true, + eventSources: [], + }); + + $('#calendar-link').on('click', showRepCalendar); +}); + +/** + * + */ +function showRepCalendar() { + + // pre-append URL with repetition info: + var newEventsUri = eventsUri + '?type=' + $('#ffInput_repetition_type').val(); + newEventsUri += '&skip=' + $('#ffInput_skip').val(); + newEventsUri += '&ends=' + $('#ffInput_repetition_end').val(); + newEventsUri += '&end_date=' + $('#ffInput_repeat_until').val(); + newEventsUri += '&reps=' + $('#ffInput_repetitions').val(); + newEventsUri += '&first_date=' + $('#ffInput_first_date').val(); + newEventsUri += '&weekend=' + $('#ffInput_weekend').val(); + + // remove all event sources from calendar: + calendar.fullCalendar('removeEventSources'); + + // add a new one: + calendar.fullCalendar('addEventSource', newEventsUri); + $('#calendarModal').modal('show'); + + return false; +} + +function respondToRepetitionEnd() { + var obj = $('#ffInput_repetition_end'); + var value = obj.val(); + switch (value) { + case 'forever': + $('#repeat_until_holder').hide(); + $('#repetitions_holder').hide(); + break; + case 'until_date': + $('#repeat_until_holder').show(); + $('#repetitions_holder').hide(); + break; + case 'times': + $('#repeat_until_holder').hide(); + $('#repetitions_holder').show(); + break; + } + + +} + +function respondToFirstDateChange() { + var obj = $('#ffInput_first_date'); + var select = $('#ffInput_repetition_type'); + var date = obj.val(); + select.prop('disabled', true); + + // preselected value: + var preSelected = oldRepetitionType; + if(preSelected === '') { + preSelected = select.val(); + } + + $.getJSON(suggestUri, {date: date,pre_select: preSelected}).fail(function () { + console.error('Could not load repetition suggestions'); + alert('Could not load repetition suggestions'); + }).done(parseRepetitionSuggestions); +} + +function parseRepetitionSuggestions(data) { + + var select = $('#ffInput_repetition_type'); + select.empty(); + var opt; + for (var k in data) { + if (data.hasOwnProperty(k)) { + console.log('label: ' + data[k].label + ', selected: ' + data[k].selected); + opt = $('').val(k).attr('label', data[k].label).text(data[k].label); + if(data[k].selected) { + opt.attr('selected','selected'); + } + select.append(opt); + } + } + select.removeAttr('disabled'); +} + +function initializeAutoComplete() { + // auto complete things: + $.getJSON('json/tags').done(function (data) { + var opt = { + typeahead: { + source: data, + afterSelect: function () { + this.$element.val(""); + }, + autoSelect: false, + }, + autoSelect: false, + }; + + $('input[name="tags"]').tagsinput( + opt + ); + }); + + if ($('input[name="destination_name"]').length > 0) { + $.getJSON('json/expense-accounts').done(function (data) { + $('input[name="destination_name"]').typeahead({source: data, autoSelect: false}); + }); + } + + if ($('input[name="source_name"]').length > 0) { + $.getJSON('json/revenue-accounts').done(function (data) { + $('input[name="source_name"]').typeahead({source: data, autoSelect: false}); + }); + } + + $.getJSON('json/categories').done(function (data) { + $('input[name="category"]').typeahead({source: data, autoSelect: false}); + }); +} + +/** + * + * @param e + */ +function switchTransactionType(e) { + var target = $(e.target); + transactionType = target.data('value'); + initializeButtons(); + return false; +} + +/** + * Loop the three buttons and do some magic. + */ +function initializeButtons() { + console.log('Now in initializeButtons()'); + $.each($('.switch-button'), function (i, v) { + var btn = $(v); + console.log('Value is ' + btn.data('value')); + if (btn.data('value') === transactionType) { + btn.addClass('btn-info disabled').removeClass('btn-default'); + $('input[name="transaction_type"]').val(transactionType); + } else { + btn.removeClass('btn-info disabled').addClass('btn-default'); + } + }); + updateFormFields(); +} + +/** + * Hide and/or show stuff when switching: + */ +function updateFormFields() { + + if (transactionType === 'withdrawal') { + // hide source account name: + $('#source_name_holder').hide(); + + // show source account ID: + $('#source_id_holder').show(); + + // hide destination ID: + $('#destination_id_holder').hide(); + + // show destination name: + $('#destination_name_holder').show(); + + // show budget + $('#budget_id_holder').show(); + + // hide piggy bank: + $('#piggy_bank_id_holder').hide(); + } + + if (transactionType === 'deposit') { + $('#source_name_holder').show(); + $('#source_id_holder').hide(); + $('#destination_name_holder').hide(); + $('#destination_id_holder').show(); + $('#budget_id_holder').hide(); + $('#piggy_bank_id_holder').hide(); + } + + if (transactionType === 'transfer') { + $('#source_name_holder').hide(); + $('#source_id_holder').show(); + $('#destination_name_holder').hide(); + $('#destination_id_holder').show(); + $('#budget_id_holder').hide(); + $('#piggy_bank_id_holder').show(); + } +} diff --git a/public/js/ff/recurring/edit.js b/public/js/ff/recurring/edit.js new file mode 100644 index 0000000000..24ee67c1e8 --- /dev/null +++ b/public/js/ff/recurring/edit.js @@ -0,0 +1,247 @@ +/* + * edit.js + * Copyright (c) 2018 thegrumpydictator@gmail.com + * + * This file is part of Firefly III. + * + * Firefly III is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Firefly III is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Firefly III. If not, see . + */ + +/** global: Modernizr, currencies */ + +var calendar; + +$(document).ready(function () { + "use strict"; + if (!Modernizr.inputtypes.date) { + $('input[type="date"]').datepicker( + { + dateFormat: 'yy-mm-dd' + } + ); + } + initializeButtons(); + initializeAutoComplete(); + respondToFirstDateChange(); + respondToRepetitionEnd(); + $('.switch-button').on('click', switchTransactionType); + $('#ffInput_repetition_end').on('change', respondToRepetitionEnd); + $('#ffInput_first_date').on('change', respondToFirstDateChange); + + // create calendar on load: + calendar = $('#recurring_calendar').fullCalendar( + { + defaultDate: '2018-06-13', + editable: false, + height: 400, + width: 200, + contentHeight: 400, + aspectRatio: 1.25, + eventLimit: true, + eventSources: [], + }); + + $('#calendar-link').on('click', showRepCalendar); +}); + +/** + * + */ +function showRepCalendar() { + + // pre-append URL with repetition info: + var newEventsUri = eventsUri + '?type=' + $('#ffInput_repetition_type').val(); + newEventsUri += '&skip=' + $('#ffInput_skip').val(); + newEventsUri += '&ends=' + $('#ffInput_repetition_end').val(); + newEventsUri += '&end_date=' + $('#ffInput_repeat_until').val(); + newEventsUri += '&reps=' + $('#ffInput_repetitions').val(); + newEventsUri += '&first_date=' + $('#ffInput_first_date').val(); + newEventsUri += '&weekend=' + $('#ffInput_weekend').val(); + + // remove all event sources from calendar: + calendar.fullCalendar('removeEventSources'); + + // add a new one: + calendar.fullCalendar('addEventSource', newEventsUri); + $('#calendarModal').modal('show'); + + return false; +} + +function respondToRepetitionEnd() { + var obj = $('#ffInput_repetition_end'); + var value = obj.val(); + switch (value) { + case 'forever': + $('#repeat_until_holder').hide(); + $('#repetitions_holder').hide(); + break; + case 'until_date': + $('#repeat_until_holder').show(); + $('#repetitions_holder').hide(); + break; + case 'times': + $('#repeat_until_holder').hide(); + $('#repetitions_holder').show(); + break; + } + + +} + +function respondToFirstDateChange() { + // + var obj = $('#ffInput_first_date'); + var select = $('#ffInput_repetition_type'); + var date = obj.val(); + select.prop('disabled', true); + + // preselected value: + var preSelected = currentRepetitionType; + if(preSelected === '') { + preSelected = select.val(); + } + + $.getJSON(suggestUri, {date: date,pre_select: preSelected,past:true}).fail(function () { + console.error('Could not load repetition suggestions'); + alert('Could not load repetition suggestions'); + }).done(parseRepetitionSuggestions); +} + +function parseRepetitionSuggestions(data) { + + var select = $('#ffInput_repetition_type'); + select.empty(); + var opt; + for (var k in data) { + if (data.hasOwnProperty(k)) { + console.log('label: ' + data[k].label + ', selected: ' + data[k].selected); + opt = $('').val(k).attr('label', data[k].label).text(data[k].label); + if(data[k].selected) { + opt.attr('selected','selected'); + } + select.append(opt); + } + } + select.removeAttr('disabled'); +} + +function initializeAutoComplete() { + // auto complete things: + $.getJSON('json/tags').done(function (data) { + var opt = { + typeahead: { + source: data, + afterSelect: function () { + this.$element.val(""); + }, + autoSelect: false, + }, + autoSelect: false, + }; + + $('input[name="tags"]').tagsinput( + opt + ); + }); + + if ($('input[name="destination_name"]').length > 0) { + $.getJSON('json/expense-accounts').done(function (data) { + $('input[name="destination_name"]').typeahead({source: data, autoSelect: false}); + }); + } + + if ($('input[name="source_name"]').length > 0) { + $.getJSON('json/revenue-accounts').done(function (data) { + $('input[name="source_name"]').typeahead({source: data, autoSelect: false}); + }); + } + + $.getJSON('json/categories').done(function (data) { + $('input[name="category"]').typeahead({source: data, autoSelect: false}); + }); +} + +/** + * + * @param e + */ +function switchTransactionType(e) { + var target = $(e.target); + transactionType = target.data('value'); + initializeButtons(); + return false; +} + +/** + * Loop the three buttons and do some magic. + */ +function initializeButtons() { + console.log('Now in initializeButtons()'); + $.each($('.switch-button'), function (i, v) { + var btn = $(v); + console.log('Value is ' + btn.data('value')); + if (btn.data('value') === transactionType) { + btn.addClass('btn-info disabled').removeClass('btn-default'); + $('input[name="transaction_type"]').val(transactionType); + } else { + btn.removeClass('btn-info disabled').addClass('btn-default'); + } + }); + updateFormFields(); +} + +/** + * Hide and/or show stuff when switching: + */ +function updateFormFields() { + + if (transactionType === 'withdrawal') { + // hide source account name: + $('#source_name_holder').hide(); + + // show source account ID: + $('#source_id_holder').show(); + + // show destination name: + $('#destination_name_holder').show(); + + // hide destination ID: + $('#destination_id_holder').hide(); + + // show budget + $('#budget_id_holder').show(); + + // hide piggy bank: + $('#piggy_bank_id_holder').hide(); + } + + if (transactionType === 'deposit') { + $('#source_name_holder').show(); + $('#source_id_holder').hide(); + $('#destination_name_holder').hide(); + $('#destination_id_holder').show(); + $('#budget_id_holder').hide(); + $('#piggy_bank_id_holder').hide(); + } + + if (transactionType === 'transfer') { + $('#source_name_holder').hide(); + $('#source_id_holder').show(); + $('#destination_name_holder').hide(); + $('#destination_id_holder').show(); + $('#budget_id_holder').hide(); + $('#piggy_bank_id_holder').show(); + } +} diff --git a/public/js/ff/reports/default/multi-year.js b/public/js/ff/reports/default/multi-year.js index f2a56fd888..67dc295242 100644 --- a/public/js/ff/reports/default/multi-year.js +++ b/public/js/ff/reports/default/multi-year.js @@ -23,8 +23,8 @@ $(function () { "use strict"; lineChart(netWorthUri, 'net-worth'); - columnChart(opChartUri, 'income-expenses-chart'); - columnChart(sumChartUri, 'income-expenses-sum-chart'); + columnChartCustomColours(opChartUri, 'income-expenses-chart'); + columnChartCustomColours(sumChartUri, 'income-expenses-sum-chart'); loadAjaxPartial('budgetPeriodReport', budgetPeriodReportUri); loadAjaxPartial('categoryExpense', categoryExpenseUri); diff --git a/public/js/ff/reports/default/year.js b/public/js/ff/reports/default/year.js index 79887f7397..4504d3d810 100644 --- a/public/js/ff/reports/default/year.js +++ b/public/js/ff/reports/default/year.js @@ -23,8 +23,8 @@ $(function () { "use strict"; lineChart(netWorthUri, 'net-worth'); - columnChart(opChartUri, 'income-expenses-chart'); - columnChart(sumChartUri, 'income-expenses-sum-chart'); + columnChartCustomColours(opChartUri, 'income-expenses-chart'); + columnChartCustomColours(sumChartUri, 'income-expenses-sum-chart'); loadAjaxPartial('budgetPeriodReport', budgetPeriodReportUri); loadAjaxPartial('categoryExpense', categoryExpenseUri); diff --git a/public/js/ff/transactions/mass/edit.js b/public/js/ff/transactions/mass/edit.js index b267ab1bd0..9f98362fed 100644 --- a/public/js/ff/transactions/mass/edit.js +++ b/public/js/ff/transactions/mass/edit.js @@ -24,16 +24,16 @@ $(document).ready(function () { "use strict"; // destination account names: - if ($('input[name^="destination_account_name["]').length > 0) { + if ($('input[name^="destination_name["]').length > 0) { $.getJSON('json/expense-accounts').done(function (data) { - $('input[name^="destination_account_name["]').typeahead({source: data, autoSelect: false}); + $('input[name^="destination_name["]').typeahead({source: data, autoSelect: false}); }); } // source account name - if ($('input[name^="source_account_name["]').length > 0) { + if ($('input[name^="source_name["]').length > 0) { $.getJSON('json/revenue-accounts').done(function (data) { - $('input[name^="source_account_name["]').typeahead({source: data, autoSelect: false}); + $('input[name^="source_name["]').typeahead({source: data, autoSelect: false}); }); } diff --git a/public/js/ff/transactions/single/common.js b/public/js/ff/transactions/single/common.js index 47962ab6a8..e42850781f 100644 --- a/public/js/ff/transactions/single/common.js +++ b/public/js/ff/transactions/single/common.js @@ -65,15 +65,15 @@ function setCommonAutocomplete() { }); - if ($('input[name="destination_account_name"]').length > 0) { + if ($('input[name="destination_name"]').length > 0) { $.getJSON('json/expense-accounts').done(function (data) { - $('input[name="destination_account_name"]').typeahead({source: data, autoSelect: false}); + $('input[name="destination_name"]').typeahead({source: data, autoSelect: false}); }); } - if ($('input[name="source_account_name"]').length > 0) { + if ($('input[name="source_name"]').length > 0) { $.getJSON('json/revenue-accounts').done(function (data) { - $('input[name="source_account_name"]').typeahead({source: data, autoSelect: false}); + $('input[name="source_name"]').typeahead({source: data, autoSelect: false}); }); } @@ -92,11 +92,14 @@ function setCommonAutocomplete() { function selectsForeignCurrency() { console.log('In selectsForeignCurrency()'); var foreignCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val()); + console.log('Foreign currency ID is ' + foreignCurrencyId); var selectedAccountId = getAccountId(); var nativeCurrencyId = parseInt(accountInfo[selectedAccountId].preferredCurrency); - if (foreignCurrencyId !== nativeCurrencyId) { + console.log('Native currency ID is ' + nativeCurrencyId); + if (foreignCurrencyId !== nativeCurrencyId) { + console.log('These are not the same.'); // the input where the native amount is entered gets the symbol for the native currency: $('.non-selectable-currency-symbol').text(currencyInfo[nativeCurrencyId].symbol); @@ -105,18 +108,24 @@ function selectsForeignCurrency() { // both holders are shown to the user: $('#exchange_rate_instruction_holder').show(); + if(what !== 'transfer') { + console.log('Show native amount holder.'); $('#native_amount_holder').show(); + } // if possible the amount is already exchanged for the foreign currency convertForeignToNative(); } if (foreignCurrencyId === nativeCurrencyId) { + console.log('These are the same.'); $('#exchange_rate_instruction_holder').hide(); + console.log('Hide native amount holder (a)'); $('#native_amount_holder').hide(); // make all other inputs empty - $('input[name="destination_amount"]').val(""); + //console.log('Make destination_amount empty!'); + //$('input[name="destination_amount"]').val(""); $('input[name="native_amount"]').val(""); } @@ -160,8 +169,8 @@ function updateNativeAmount(data) { * Instructions for transfers */ function getTransferExchangeInstructions() { - var sourceAccount = $('select[name="source_account_id"]').val(); - var destAccount = $('select[name="destination_account_id"]').val(); + var sourceAccount = $('select[name="source_id"]').val(); + var destAccount = $('select[name="destination_id"]').val(); var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; var destinationCurrency = accountInfo[destAccount].preferredCurrency; @@ -180,8 +189,8 @@ function validateCurrencyForTransfer() { return; } $('#source_amount_holder').show(); - var sourceAccount = $('select[name="source_account_id"]').val(); - var destAccount = $('select[name="destination_account_id"]').val(); + var sourceAccount = $('select[name="source_id"]').val(); + var destAccount = $('select[name="destination_id"]').val(); var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; var sourceSymbol = currencyInfo[sourceCurrency].symbol; var destinationCurrency = accountInfo[destAccount].preferredCurrency; @@ -208,8 +217,8 @@ function validateCurrencyForTransfer() { * */ function convertSourceToDestination() { - var sourceAccount = $('select[name="source_account_id"]').val(); - var destAccount = $('select[name="destination_account_id"]').val(); + var sourceAccount = $('select[name="source_id"]').val(); + var destAccount = $('select[name="destination_id"]').val(); var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; var destinationCurrency = accountInfo[destAccount].preferredCurrency; diff --git a/public/js/ff/transactions/single/create.js b/public/js/ff/transactions/single/create.js index 189f7f7b08..47e045c22b 100644 --- a/public/js/ff/transactions/single/create.js +++ b/public/js/ff/transactions/single/create.js @@ -38,20 +38,17 @@ $(document).ready(function () { // when user changes source account or destination, native currency may be different. - $('select[name="source_account_id"]').on('change', function() { + $('select[name="source_id"]').on('change', function () { selectsDifferentSource(); // do something for transfers: validateCurrencyForTransfer(); }); - $('select[name="destination_account_id"]').on('change', function() { + $('select[name="destination_id"]').on('change', function () { selectsDifferentDestination(); // do something for transfers: validateCurrencyForTransfer(); }); - //$('select[name="source_account_id"]').on('change', updateNativeCurrency); - //$('select[name="destination_account_id"]').on('change', updateNativeCurrency); - // convert foreign currency to native currency (when input changes, exchange rate) $('#ffInput_amount').on('change', convertForeignToNative); @@ -60,6 +57,15 @@ $(document).ready(function () { // when user selects different currency, $('.currency-option').on('click', selectsForeignCurrency); + + + // overrule click on currency: + if(useAccountCurrency === false) { + $('.currency-option[data-id="' + overruleCurrency + '"]').click(); + $('[data-toggle="dropdown"]').parent().removeClass('open'); + } + + $('#ffInput_description').focus(); }); @@ -68,21 +74,23 @@ $(document).ready(function () { * and transfers. */ function selectsDifferentSource() { + console.log('Now in selectsDifferentSource()'); if (what === "deposit") { console.log('User is making a deposit. Don\'t bother with source.'); $('input[name="source_account_currency"]').val("0"); return; } // store original currency ID of the selected account in a separate var: - var sourceId = $('select[name="source_account_id"]').val(); + var sourceId = $('select[name="source_id"]').val(); var sourceCurrency = accountInfo[sourceId].preferredCurrency; $('input[name="source_account_currency"]').val(sourceCurrency); console.log('selectsDifferenctSource(): Set source account currency to ' + sourceCurrency); // change input thing: + console.log('Emulate click on .currency-option[data-id="' + sourceCurrency + '"]'); $('.currency-option[data-id="' + sourceCurrency + '"]').click(); $('[data-toggle="dropdown"]').parent().removeClass('open'); - $('select[name="source_account_id"]').focus(); + $('select[name="source_id"]').focus(); } /** @@ -96,7 +104,7 @@ function selectsDifferentDestination() { return; } // store original currency ID of the selected account in a separate var: - var destinationId = $('select[name="destination_account_id"]').val(); + var destinationId = $('select[name="destination_id"]').val(); var destinationCurrency = accountInfo[destinationId].preferredCurrency; $('input[name="destination_account_currency"]').val(destinationCurrency); console.log('selectsDifferentDestination(): Set destinationId account currency to ' + destinationCurrency); @@ -104,7 +112,7 @@ function selectsDifferentDestination() { // change input thing: $('.currency-option[data-id="' + destinationCurrency + '"]').click(); $('[data-toggle="dropdown"]').parent().removeClass('open'); - $('select[name="destination_account_id"]').focus(); + $('select[name="destination_id"]').focus(); } @@ -142,7 +150,7 @@ function updateLayout() { $('#subTitle').text(title[what]); $('.breadcrumb .active').text(breadcrumbs[what]); $('.breadcrumb li:nth-child(2)').html('' + middleCrumbName[what] + ''); - $('#transaction-btn').text(button[what]); + $('.transaction-btn').text(button[what]); } /** @@ -150,22 +158,23 @@ function updateLayout() { */ function updateForm() { "use strict"; + console.log('Now in updateForm()'); $('input[name="what"]').val(what); - var destName = $('#ffInput_destination_account_name'); - var srcName = $('#ffInput_source_account_name'); + var destName = $('#ffInput_destination_name'); + var srcName = $('#ffInput_source_name'); switch (what) { case 'withdrawal': // show source_id and dest_name - document.getElementById('source_account_id_holder').style.display = 'block'; - document.getElementById('destination_account_name_holder').style.display = 'block'; + document.getElementById('source_id_holder').style.display = 'block'; + document.getElementById('destination_name_holder').style.display = 'block'; // hide others: - document.getElementById('source_account_name_holder').style.display = 'none'; - document.getElementById('destination_account_id_holder').style.display = 'none'; + document.getElementById('source_name_holder').style.display = 'none'; + document.getElementById('destination_id_holder').style.display = 'none'; document.getElementById('budget_id_holder').style.display = 'block'; // hide piggy bank: @@ -185,12 +194,12 @@ function updateForm() { break; case 'deposit': // show source_name and dest_id: - document.getElementById('source_account_name_holder').style.display = 'block'; - document.getElementById('destination_account_id_holder').style.display = 'block'; + document.getElementById('source_name_holder').style.display = 'block'; + document.getElementById('destination_id_holder').style.display = 'block'; // hide others: - document.getElementById('source_account_id_holder').style.display = 'none'; - document.getElementById('destination_account_name_holder').style.display = 'none'; + document.getElementById('source_id_holder').style.display = 'none'; + document.getElementById('destination_name_holder').style.display = 'none'; // hide budget document.getElementById('budget_id_holder').style.display = 'none'; @@ -212,19 +221,19 @@ function updateForm() { break; case 'transfer': // show source_id and dest_id: - document.getElementById('source_account_id_holder').style.display = 'block'; - document.getElementById('destination_account_id_holder').style.display = 'block'; + document.getElementById('source_id_holder').style.display = 'block'; + document.getElementById('destination_id_holder').style.display = 'block'; // hide others: - document.getElementById('source_account_name_holder').style.display = 'none'; - document.getElementById('destination_account_name_holder').style.display = 'none'; + document.getElementById('source_name_holder').style.display = 'none'; + document.getElementById('destination_name_holder').style.display = 'none'; // hide budget document.getElementById('budget_id_holder').style.display = 'none'; // optional piggies var showPiggies = 'block'; - if (piggiesLength === 0) { + if ($('#ffInput_piggy_bank_id option').length === 0) { showPiggies = 'none'; } document.getElementById('piggy_bank_id_holder').style.display = showPiggies; @@ -233,7 +242,6 @@ function updateForm() { break; } // get instructions all the time. - //updateNativeCurrency(useAccountCurrency); console.log('End of update form'); selectsDifferentSource(); selectsDifferentDestination(); @@ -288,10 +296,10 @@ function clickButton(e) { */ function getAccountId() { if (what === "withdrawal") { - return $('select[name="source_account_id"]').val(); + return $('select[name="source_id"]').val(); } if (what === "deposit" || what === "transfer") { - return $('select[name="destination_account_id"]').val(); + return $('select[name="destination_id"]').val(); } return undefined; } diff --git a/public/js/ff/transactions/single/edit.js b/public/js/ff/transactions/single/edit.js index cf5248e021..30ce29ea8f 100644 --- a/public/js/ff/transactions/single/edit.js +++ b/public/js/ff/transactions/single/edit.js @@ -38,12 +38,12 @@ $(document).ready(function () { $('#ffInput_amount').on('change', convertForeignToNative); // respond to transfer changes: - $('#ffInput_source_account_id').on('change', function () { + $('#ffInput_source_id').on('change', function () { validateCurrencyForTransfer(); // update the two source account currency ID fields (initial value): initCurrencyIdValues(); }); - $('#ffInput_destination_account_id').on('change', function () { + $('#ffInput_destination_id').on('change', function () { validateCurrencyForTransfer(); // update the two source account currency ID fields (initial value): initCurrencyIdValues(); @@ -77,9 +77,9 @@ function initCurrencyIdValues() { $('input[name="destination_account_currency"]').val(currencyId); return; } - var sourceAccount = $('select[name="source_account_id"]').val(); + var sourceAccount = $('select[name="source_id"]').val(); console.log('Source account is ' + sourceAccount); - var destAccount = $('select[name="destination_account_id"]').val(); + var destAccount = $('select[name="destination_id"]').val(); console.log('Destination account is ' + destAccount); var sourceCurrency = parseInt(accountInfo[sourceAccount].preferredCurrency); @@ -134,10 +134,10 @@ function updateInitialPage() { function getAccountId() { console.log('in getAccountId()'); if (journal.transaction_type.type === "Withdrawal") { - return $('select[name="source_account_id"]').val(); + return $('select[name="source_id"]').val(); } if (journal.transaction_type.type === "Deposit") { - return $('select[name="destination_account_id"]').val(); + return $('select[name="destination_id"]').val(); } alert('Cannot handle ' + journal.transaction_type.type); diff --git a/public/js/ff/transactions/split/edit.js b/public/js/ff/transactions/split/edit.js index a99a39f651..f3178e0f4e 100644 --- a/public/js/ff/transactions/split/edit.js +++ b/public/js/ff/transactions/split/edit.js @@ -100,6 +100,12 @@ function removeDivRow(e) { } var row = $(e.target); var index = row.data('split'); + if (typeof index === 'undefined') { + var parent = row.parent(); + index = parent.data('split'); + console.log('Parent. ' + parent.className); + } + console.log('Split index is "' + index + '"'); $('div.split_row[data-split="' + index + '"]').remove(); @@ -185,15 +191,15 @@ function resetDivSplits() { var input = $(v); input.attr('name', 'transactions[' + i + '][transaction_description]'); }); - // ends with ][destination_account_name] + // ends with ][destination_name] $.each($('input[name$="][destination_name]"]'), function (i, v) { var input = $(v); - input.attr('name', 'transactions[' + i + '][destination_account_name]'); + input.attr('name', 'transactions[' + i + '][destination_name]'); }); - // ends with ][source_account_name] + // ends with ][source_name] $.each($('input[name$="][source_name]"]'), function (i, v) { var input = $(v); - input.attr('name', 'transactions[' + i + '][source_account_name]'); + input.attr('name', 'transactions[' + i + '][source_name]'); }); // ends with ][amount] $.each($('input[name$="][amount]"]'), function (i, v) { diff --git a/public/lib/fc/fullcalendar.css b/public/lib/fc/fullcalendar.css new file mode 100644 index 0000000000..dcbc999758 --- /dev/null +++ b/public/lib/fc/fullcalendar.css @@ -0,0 +1,1293 @@ +/*! + * FullCalendar v3.9.0 + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */ +.fc { + direction: ltr; + text-align: left; } + +.fc-rtl { + text-align: right; } + +body .fc { + /* extra precedence to overcome jqui */ + font-size: 1em; } + +/* Colors +--------------------------------------------------------------------------------------------------*/ +.fc-highlight { + /* when user is selecting cells */ + background: #bce8f1; + opacity: .3; } + +.fc-bgevent { + /* default look for background events */ + background: #8fdf82; + opacity: .3; } + +.fc-nonbusiness { + /* default look for non-business-hours areas */ + /* will inherit .fc-bgevent's styles */ + background: #d7d7d7; } + +/* Buttons (styled tags, normalized to work cross-browser) +--------------------------------------------------------------------------------------------------*/ +.fc button { + /* force height to include the border and padding */ + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + /* dimensions */ + margin: 0; + height: 2.1em; + padding: 0 .6em; + /* text & cursor */ + font-size: 1em; + /* normalize */ + white-space: nowrap; + cursor: pointer; } + +/* Firefox has an annoying inner border */ +.fc button::-moz-focus-inner { + margin: 0; + padding: 0; } + +.fc-state-default { + /* non-theme */ + border: 1px solid; } + +.fc-state-default.fc-corner-left { + /* non-theme */ + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; } + +.fc-state-default.fc-corner-right { + /* non-theme */ + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; } + +/* icons in buttons */ +.fc button .fc-icon { + /* non-theme */ + position: relative; + top: -0.05em; + /* seems to be a good adjustment across browsers */ + margin: 0 .2em; + vertical-align: middle; } + +/* + button states + borrowed from twitter bootstrap (http://twitter.github.com/bootstrap/) +*/ +.fc-state-default { + background-color: #f5f5f5; + background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); + background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); + background-repeat: repeat-x; + border-color: #e6e6e6 #e6e6e6 #bfbfbf; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + color: #333; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); } + +.fc-state-hover, +.fc-state-down, +.fc-state-active, +.fc-state-disabled { + color: #333333; + background-color: #e6e6e6; } + +.fc-state-hover { + color: #333333; + text-decoration: none; + background-position: 0 -15px; + -webkit-transition: background-position 0.1s linear; + -moz-transition: background-position 0.1s linear; + -o-transition: background-position 0.1s linear; + transition: background-position 0.1s linear; } + +.fc-state-down, +.fc-state-active { + background-color: #cccccc; + background-image: none; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); } + +.fc-state-disabled { + cursor: default; + background-image: none; + opacity: 0.65; + box-shadow: none; } + +/* Buttons Groups +--------------------------------------------------------------------------------------------------*/ +.fc-button-group { + display: inline-block; } + +/* +every button that is not first in a button group should scootch over one pixel and cover the +previous button's border... +*/ +.fc .fc-button-group > * { + /* extra precedence b/c buttons have margin set to zero */ + float: left; + margin: 0 0 0 -1px; } + +.fc .fc-button-group > :first-child { + /* same */ + margin-left: 0; } + +/* Popover +--------------------------------------------------------------------------------------------------*/ +.fc-popover { + position: absolute; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); } + +.fc-popover .fc-header { + /* TODO: be more consistent with fc-head/fc-body */ + padding: 2px 4px; } + +.fc-popover .fc-header .fc-title { + margin: 0 2px; } + +.fc-popover .fc-header .fc-close { + cursor: pointer; } + +.fc-ltr .fc-popover .fc-header .fc-title, +.fc-rtl .fc-popover .fc-header .fc-close { + float: left; } + +.fc-rtl .fc-popover .fc-header .fc-title, +.fc-ltr .fc-popover .fc-header .fc-close { + float: right; } + +/* Misc Reusable Components +--------------------------------------------------------------------------------------------------*/ +.fc-divider { + border-style: solid; + border-width: 1px; } + +hr.fc-divider { + height: 0; + margin: 0; + padding: 0 0 2px; + /* height is unreliable across browsers, so use padding */ + border-width: 1px 0; } + +.fc-clear { + clear: both; } + +.fc-bg, +.fc-bgevent-skeleton, +.fc-highlight-skeleton, +.fc-helper-skeleton { + /* these element should always cling to top-left/right corners */ + position: absolute; + top: 0; + left: 0; + right: 0; } + +.fc-bg { + bottom: 0; + /* strech bg to bottom edge */ } + +.fc-bg table { + height: 100%; + /* strech bg to bottom edge */ } + +/* Tables +--------------------------------------------------------------------------------------------------*/ +.fc table { + width: 100%; + box-sizing: border-box; + /* fix scrollbar issue in firefox */ + table-layout: fixed; + border-collapse: collapse; + border-spacing: 0; + font-size: 1em; + /* normalize cross-browser */ } + +.fc th { + text-align: center; } + +.fc th, +.fc td { + border-style: solid; + border-width: 1px; + padding: 0; + vertical-align: top; } + +.fc td.fc-today { + border-style: double; + /* overcome neighboring borders */ } + +/* Internal Nav Links +--------------------------------------------------------------------------------------------------*/ +a[data-goto] { + cursor: pointer; } + +a[data-goto]:hover { + text-decoration: underline; } + +/* Fake Table Rows +--------------------------------------------------------------------------------------------------*/ +.fc .fc-row { + /* extra precedence to overcome themes w/ .ui-widget-content forcing a 1px border */ + /* no visible border by default. but make available if need be (scrollbar width compensation) */ + border-style: solid; + border-width: 0; } + +.fc-row table { + /* don't put left/right border on anything within a fake row. + the outer tbody will worry about this */ + border-left: 0 hidden transparent; + border-right: 0 hidden transparent; + /* no bottom borders on rows */ + border-bottom: 0 hidden transparent; } + +.fc-row:first-child table { + border-top: 0 hidden transparent; + /* no top border on first row */ } + +/* Day Row (used within the header and the DayGrid) +--------------------------------------------------------------------------------------------------*/ +.fc-row { + position: relative; } + +.fc-row .fc-bg { + z-index: 1; } + +/* highlighting cells & background event skeleton */ +.fc-row .fc-bgevent-skeleton, +.fc-row .fc-highlight-skeleton { + bottom: 0; + /* stretch skeleton to bottom of row */ } + +.fc-row .fc-bgevent-skeleton table, +.fc-row .fc-highlight-skeleton table { + height: 100%; + /* stretch skeleton to bottom of row */ } + +.fc-row .fc-highlight-skeleton td, +.fc-row .fc-bgevent-skeleton td { + border-color: transparent; } + +.fc-row .fc-bgevent-skeleton { + z-index: 2; } + +.fc-row .fc-highlight-skeleton { + z-index: 3; } + +/* +row content (which contains day/week numbers and events) as well as "helper" (which contains +temporary rendered events). +*/ +.fc-row .fc-content-skeleton { + position: relative; + z-index: 4; + padding-bottom: 2px; + /* matches the space above the events */ } + +.fc-row .fc-helper-skeleton { + z-index: 5; } + +.fc .fc-row .fc-content-skeleton table, +.fc .fc-row .fc-content-skeleton td, +.fc .fc-row .fc-helper-skeleton td { + /* see-through to the background below */ + /* extra precedence to prevent theme-provided backgrounds */ + background: none; + /* in case s are globally styled */ + border-color: transparent; } + +.fc-row .fc-content-skeleton td, +.fc-row .fc-helper-skeleton td { + /* don't put a border between events and/or the day number */ + border-bottom: 0; } + +.fc-row .fc-content-skeleton tbody td, +.fc-row .fc-helper-skeleton tbody td { + /* don't put a border between event cells */ + border-top: 0; } + +/* Scrolling Container +--------------------------------------------------------------------------------------------------*/ +.fc-scroller { + -webkit-overflow-scrolling: touch; } + +/* TODO: move to agenda/basic */ +.fc-scroller > .fc-day-grid, +.fc-scroller > .fc-time-grid { + position: relative; + /* re-scope all positions */ + width: 100%; + /* hack to force re-sizing this inner element when scrollbars appear/disappear */ } + +/* Global Event Styles +--------------------------------------------------------------------------------------------------*/ +.fc-event { + position: relative; + /* for resize handle and other inner positioning */ + display: block; + /* make the tag block */ + font-size: .85em; + line-height: 1.3; + border-radius: 3px; + border: 1px solid #3a87ad; + /* default BORDER color */ } + +.fc-event, +.fc-event-dot { + background-color: #3a87ad; + /* default BACKGROUND color */ } + +.fc-event, +.fc-event:hover { + color: #fff; + /* default TEXT color */ + text-decoration: none; + /* if has an href */ } + +.fc-event[href], +.fc-event.fc-draggable { + cursor: pointer; + /* give events with links and draggable events a hand mouse pointer */ } + +.fc-not-allowed, +.fc-not-allowed .fc-event { + /* to override an event's custom cursor */ + cursor: not-allowed; } + +.fc-event .fc-bg { + /* the generic .fc-bg already does position */ + z-index: 1; + background: #fff; + opacity: .25; } + +.fc-event .fc-content { + position: relative; + z-index: 2; } + +/* resizer (cursor AND touch devices) */ +.fc-event .fc-resizer { + position: absolute; + z-index: 4; } + +/* resizer (touch devices) */ +.fc-event .fc-resizer { + display: none; } + +.fc-event.fc-allow-mouse-resize .fc-resizer, +.fc-event.fc-selected .fc-resizer { + /* only show when hovering or selected (with touch) */ + display: block; } + +/* hit area */ +.fc-event.fc-selected .fc-resizer:before { + /* 40x40 touch area */ + content: ""; + position: absolute; + z-index: 9999; + /* user of this util can scope within a lower z-index */ + top: 50%; + left: 50%; + width: 40px; + height: 40px; + margin-left: -20px; + margin-top: -20px; } + +/* Event Selection (only for touch devices) +--------------------------------------------------------------------------------------------------*/ +.fc-event.fc-selected { + z-index: 9999 !important; + /* overcomes inline z-index */ + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } + +.fc-event.fc-selected.fc-dragging { + box-shadow: 0 2px 7px rgba(0, 0, 0, 0.3); } + +/* Horizontal Events +--------------------------------------------------------------------------------------------------*/ +/* bigger touch area when selected */ +.fc-h-event.fc-selected:before { + content: ""; + position: absolute; + z-index: 3; + /* below resizers */ + top: -10px; + bottom: -10px; + left: 0; + right: 0; } + +/* events that are continuing to/from another week. kill rounded corners and butt up against edge */ +.fc-ltr .fc-h-event.fc-not-start, +.fc-rtl .fc-h-event.fc-not-end { + margin-left: 0; + border-left-width: 0; + padding-left: 1px; + /* replace the border with padding */ + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + +.fc-ltr .fc-h-event.fc-not-end, +.fc-rtl .fc-h-event.fc-not-start { + margin-right: 0; + border-right-width: 0; + padding-right: 1px; + /* replace the border with padding */ + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + +/* resizer (cursor AND touch devices) */ +/* left resizer */ +.fc-ltr .fc-h-event .fc-start-resizer, +.fc-rtl .fc-h-event .fc-end-resizer { + cursor: w-resize; + left: -1px; + /* overcome border */ } + +/* right resizer */ +.fc-ltr .fc-h-event .fc-end-resizer, +.fc-rtl .fc-h-event .fc-start-resizer { + cursor: e-resize; + right: -1px; + /* overcome border */ } + +/* resizer (mouse devices) */ +.fc-h-event.fc-allow-mouse-resize .fc-resizer { + width: 7px; + top: -1px; + /* overcome top border */ + bottom: -1px; + /* overcome bottom border */ } + +/* resizer (touch devices) */ +.fc-h-event.fc-selected .fc-resizer { + /* 8x8 little dot */ + border-radius: 4px; + border-width: 1px; + width: 6px; + height: 6px; + border-style: solid; + border-color: inherit; + background: #fff; + /* vertically center */ + top: 50%; + margin-top: -4px; } + +/* left resizer */ +.fc-ltr .fc-h-event.fc-selected .fc-start-resizer, +.fc-rtl .fc-h-event.fc-selected .fc-end-resizer { + margin-left: -4px; + /* centers the 8x8 dot on the left edge */ } + +/* right resizer */ +.fc-ltr .fc-h-event.fc-selected .fc-end-resizer, +.fc-rtl .fc-h-event.fc-selected .fc-start-resizer { + margin-right: -4px; + /* centers the 8x8 dot on the right edge */ } + +/* DayGrid events +---------------------------------------------------------------------------------------------------- +We use the full "fc-day-grid-event" class instead of using descendants because the event won't +be a descendant of the grid when it is being dragged. +*/ +.fc-day-grid-event { + margin: 1px 2px 0; + /* spacing between events and edges */ + padding: 0 1px; } + +tr:first-child > td > .fc-day-grid-event { + margin-top: 2px; + /* a little bit more space before the first event */ } + +.fc-day-grid-event.fc-selected:after { + content: ""; + position: absolute; + z-index: 1; + /* same z-index as fc-bg, behind text */ + /* overcome the borders */ + top: -1px; + right: -1px; + bottom: -1px; + left: -1px; + /* darkening effect */ + background: #000; + opacity: .25; } + +.fc-day-grid-event .fc-content { + /* force events to be one-line tall */ + white-space: nowrap; + overflow: hidden; } + +.fc-day-grid-event .fc-time { + font-weight: bold; } + +/* resizer (cursor devices) */ +/* left resizer */ +.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer, +.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer { + margin-left: -2px; + /* to the day cell's edge */ } + +/* right resizer */ +.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer, +.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer { + margin-right: -2px; + /* to the day cell's edge */ } + +/* Event Limiting +--------------------------------------------------------------------------------------------------*/ +/* "more" link that represents hidden events */ +a.fc-more { + margin: 1px 3px; + font-size: .85em; + cursor: pointer; + text-decoration: none; } + +a.fc-more:hover { + text-decoration: underline; } + +.fc-limited { + /* rows and cells that are hidden because of a "more" link */ + display: none; } + +/* popover that appears when "more" link is clicked */ +.fc-day-grid .fc-row { + z-index: 1; + /* make the "more" popover one higher than this */ } + +.fc-more-popover { + z-index: 2; + width: 220px; } + +.fc-more-popover .fc-event-container { + padding: 10px; } + +/* Now Indicator +--------------------------------------------------------------------------------------------------*/ +.fc-now-indicator { + position: absolute; + border: 0 solid red; } + +/* Utilities +--------------------------------------------------------------------------------------------------*/ +.fc-unselectable { + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; } + +/* +TODO: more distinction between this file and common.css +*/ +/* Colors +--------------------------------------------------------------------------------------------------*/ +.fc-unthemed th, +.fc-unthemed td, +.fc-unthemed thead, +.fc-unthemed tbody, +.fc-unthemed .fc-divider, +.fc-unthemed .fc-row, +.fc-unthemed .fc-content, +.fc-unthemed .fc-popover, +.fc-unthemed .fc-list-view, +.fc-unthemed .fc-list-heading td { + border-color: #ddd; } + +.fc-unthemed .fc-popover { + background-color: #fff; } + +.fc-unthemed .fc-divider, +.fc-unthemed .fc-popover .fc-header, +.fc-unthemed .fc-list-heading td { + background: #eee; } + +.fc-unthemed .fc-popover .fc-header .fc-close { + color: #666; } + +.fc-unthemed td.fc-today { + background: #fcf8e3; } + +.fc-unthemed .fc-disabled-day { + background: #d7d7d7; + opacity: .3; } + +/* Icons (inline elements with styled text that mock arrow icons) +--------------------------------------------------------------------------------------------------*/ +.fc-icon { + display: inline-block; + height: 1em; + line-height: 1em; + font-size: 1em; + text-align: center; + overflow: hidden; + font-family: "Courier New", Courier, monospace; + /* don't allow browser text-selection */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + +/* +Acceptable font-family overrides for individual icons: + "Arial", sans-serif + "Times New Roman", serif + +NOTE: use percentage font sizes or else old IE chokes +*/ +.fc-icon:after { + position: relative; } + +.fc-icon-left-single-arrow:after { + content: "\2039"; + font-weight: bold; + font-size: 200%; + top: -7%; } + +.fc-icon-right-single-arrow:after { + content: "\203A"; + font-weight: bold; + font-size: 200%; + top: -7%; } + +.fc-icon-left-double-arrow:after { + content: "\AB"; + font-size: 160%; + top: -7%; } + +.fc-icon-right-double-arrow:after { + content: "\BB"; + font-size: 160%; + top: -7%; } + +.fc-icon-left-triangle:after { + content: "\25C4"; + font-size: 125%; + top: 3%; } + +.fc-icon-right-triangle:after { + content: "\25BA"; + font-size: 125%; + top: 3%; } + +.fc-icon-down-triangle:after { + content: "\25BC"; + font-size: 125%; + top: 2%; } + +.fc-icon-x:after { + content: "\D7"; + font-size: 200%; + top: 6%; } + +/* Popover +--------------------------------------------------------------------------------------------------*/ +.fc-unthemed .fc-popover { + border-width: 1px; + border-style: solid; } + +.fc-unthemed .fc-popover .fc-header .fc-close { + font-size: .9em; + margin-top: 2px; } + +/* List View +--------------------------------------------------------------------------------------------------*/ +.fc-unthemed .fc-list-item:hover td { + background-color: #f5f5f5; } + +/* Colors +--------------------------------------------------------------------------------------------------*/ +.ui-widget .fc-disabled-day { + background-image: none; } + +/* Popover +--------------------------------------------------------------------------------------------------*/ +.fc-popover > .ui-widget-header + .ui-widget-content { + border-top: 0; + /* where they meet, let the header have the border */ } + +/* Global Event Styles +--------------------------------------------------------------------------------------------------*/ +.ui-widget .fc-event { + /* overpower jqui's styles on tags. TODO: more DRY */ + color: #fff; + /* default TEXT color */ + text-decoration: none; + /* if has an href */ + /* undo ui-widget-header bold */ + font-weight: normal; } + +/* TimeGrid axis running down the side (for both the all-day area and the slot area) +--------------------------------------------------------------------------------------------------*/ +.ui-widget td.fc-axis { + font-weight: normal; + /* overcome bold */ } + +/* TimeGrid Slats (lines that run horizontally) +--------------------------------------------------------------------------------------------------*/ +.fc-time-grid .fc-slats .ui-widget-content { + background: none; + /* see through to fc-bg */ } + +.fc.fc-bootstrap3 a { + text-decoration: none; } + +.fc.fc-bootstrap3 a[data-goto]:hover { + text-decoration: underline; } + +.fc-bootstrap3 hr.fc-divider { + border-color: inherit; } + +.fc-bootstrap3 .fc-today.alert { + border-radius: 0; } + +/* Popover +--------------------------------------------------------------------------------------------------*/ +.fc-bootstrap3 .fc-popover .panel-body { + padding: 0; } + +/* TimeGrid Slats (lines that run horizontally) +--------------------------------------------------------------------------------------------------*/ +.fc-bootstrap3 .fc-time-grid .fc-slats table { + /* some themes have background color. see through to slats */ + background: none; } + +.fc.fc-bootstrap4 a { + text-decoration: none; } + +.fc.fc-bootstrap4 a[data-goto]:hover { + text-decoration: underline; } + +.fc-bootstrap4 hr.fc-divider { + border-color: inherit; } + +.fc-bootstrap4 .fc-today.alert { + border-radius: 0; } + +.fc-bootstrap4 a.fc-event:not([href]):not([tabindex]) { + color: #fff; } + +.fc-bootstrap4 .fc-popover.card { + position: absolute; } + +/* Popover +--------------------------------------------------------------------------------------------------*/ +.fc-bootstrap4 .fc-popover .card-body { + padding: 0; } + +/* TimeGrid Slats (lines that run horizontally) +--------------------------------------------------------------------------------------------------*/ +.fc-bootstrap4 .fc-time-grid .fc-slats table { + /* some themes have background color. see through to slats */ + background: none; } + +/* Toolbar +--------------------------------------------------------------------------------------------------*/ +.fc-toolbar { + text-align: center; } + +.fc-toolbar.fc-header-toolbar { + margin-bottom: 1em; } + +.fc-toolbar.fc-footer-toolbar { + margin-top: 1em; } + +.fc-toolbar .fc-left { + float: left; } + +.fc-toolbar .fc-right { + float: right; } + +.fc-toolbar .fc-center { + display: inline-block; } + +/* the things within each left/right/center section */ +.fc .fc-toolbar > * > * { + /* extra precedence to override button border margins */ + float: left; + margin-left: .75em; } + +/* the first thing within each left/center/right section */ +.fc .fc-toolbar > * > :first-child { + /* extra precedence to override button border margins */ + margin-left: 0; } + +/* title text */ +.fc-toolbar h2 { + margin: 0; } + +/* button layering (for border precedence) */ +.fc-toolbar button { + position: relative; } + +.fc-toolbar .fc-state-hover, +.fc-toolbar .ui-state-hover { + z-index: 2; } + +.fc-toolbar .fc-state-down { + z-index: 3; } + +.fc-toolbar .fc-state-active, +.fc-toolbar .ui-state-active { + z-index: 4; } + +.fc-toolbar button:focus { + z-index: 5; } + +/* View Structure +--------------------------------------------------------------------------------------------------*/ +/* undo twitter bootstrap's box-sizing rules. normalizes positioning techniques */ +/* don't do this for the toolbar because we'll want bootstrap to style those buttons as some pt */ +.fc-view-container *, +.fc-view-container *:before, +.fc-view-container *:after { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; } + +.fc-view, +.fc-view > table { + /* so dragged elements can be above the view's main element */ + position: relative; + z-index: 1; } + +/* BasicView +--------------------------------------------------------------------------------------------------*/ +/* day row structure */ +.fc-basicWeek-view .fc-content-skeleton, +.fc-basicDay-view .fc-content-skeleton { + /* there may be week numbers in these views, so no padding-top */ + padding-bottom: 1em; + /* ensure a space at bottom of cell for user selecting/clicking */ } + +.fc-basic-view .fc-body .fc-row { + min-height: 4em; + /* ensure that all rows are at least this tall */ } + +/* a "rigid" row will take up a constant amount of height because content-skeleton is absolute */ +.fc-row.fc-rigid { + overflow: hidden; } + +.fc-row.fc-rigid .fc-content-skeleton { + position: absolute; + top: 0; + left: 0; + right: 0; } + +/* week and day number styling */ +.fc-day-top.fc-other-month { + opacity: 0.3; } + +.fc-basic-view .fc-week-number, +.fc-basic-view .fc-day-number { + padding: 2px; } + +.fc-basic-view th.fc-week-number, +.fc-basic-view th.fc-day-number { + padding: 0 2px; + /* column headers can't have as much v space */ } + +.fc-ltr .fc-basic-view .fc-day-top .fc-day-number { + float: right; } + +.fc-rtl .fc-basic-view .fc-day-top .fc-day-number { + float: left; } + +.fc-ltr .fc-basic-view .fc-day-top .fc-week-number { + float: left; + border-radius: 0 0 3px 0; } + +.fc-rtl .fc-basic-view .fc-day-top .fc-week-number { + float: right; + border-radius: 0 0 0 3px; } + +.fc-basic-view .fc-day-top .fc-week-number { + min-width: 1.5em; + text-align: center; + background-color: #f2f2f2; + color: #808080; } + +/* when week/day number have own column */ +.fc-basic-view td.fc-week-number { + text-align: center; } + +.fc-basic-view td.fc-week-number > * { + /* work around the way we do column resizing and ensure a minimum width */ + display: inline-block; + min-width: 1.25em; } + +/* AgendaView all-day area +--------------------------------------------------------------------------------------------------*/ +.fc-agenda-view .fc-day-grid { + position: relative; + z-index: 2; + /* so the "more.." popover will be over the time grid */ } + +.fc-agenda-view .fc-day-grid .fc-row { + min-height: 3em; + /* all-day section will never get shorter than this */ } + +.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton { + padding-bottom: 1em; + /* give space underneath events for clicking/selecting days */ } + +/* TimeGrid axis running down the side (for both the all-day area and the slot area) +--------------------------------------------------------------------------------------------------*/ +.fc .fc-axis { + /* .fc to overcome default cell styles */ + vertical-align: middle; + padding: 0 4px; + white-space: nowrap; } + +.fc-ltr .fc-axis { + text-align: right; } + +.fc-rtl .fc-axis { + text-align: left; } + +/* TimeGrid Structure +--------------------------------------------------------------------------------------------------*/ +.fc-time-grid-container, +.fc-time-grid { + /* so slats/bg/content/etc positions get scoped within here */ + position: relative; + z-index: 1; } + +.fc-time-grid { + min-height: 100%; + /* so if height setting is 'auto', .fc-bg stretches to fill height */ } + +.fc-time-grid table { + /* don't put outer borders on slats/bg/content/etc */ + border: 0 hidden transparent; } + +.fc-time-grid > .fc-bg { + z-index: 1; } + +.fc-time-grid .fc-slats, +.fc-time-grid > hr { + /* the AgendaView injects when grid is shorter than scroller */ + position: relative; + z-index: 2; } + +.fc-time-grid .fc-content-col { + position: relative; + /* because now-indicator lives directly inside */ } + +.fc-time-grid .fc-content-skeleton { + position: absolute; + z-index: 3; + top: 0; + left: 0; + right: 0; } + +/* divs within a cell within the fc-content-skeleton */ +.fc-time-grid .fc-business-container { + position: relative; + z-index: 1; } + +.fc-time-grid .fc-bgevent-container { + position: relative; + z-index: 2; } + +.fc-time-grid .fc-highlight-container { + position: relative; + z-index: 3; } + +.fc-time-grid .fc-event-container { + position: relative; + z-index: 4; } + +.fc-time-grid .fc-now-indicator-line { + z-index: 5; } + +.fc-time-grid .fc-helper-container { + /* also is fc-event-container */ + position: relative; + z-index: 6; } + +/* TimeGrid Slats (lines that run horizontally) +--------------------------------------------------------------------------------------------------*/ +.fc-time-grid .fc-slats td { + height: 1.5em; + border-bottom: 0; + /* each cell is responsible for its top border */ } + +.fc-time-grid .fc-slats .fc-minor td { + border-top-style: dotted; } + +/* TimeGrid Highlighting Slots +--------------------------------------------------------------------------------------------------*/ +.fc-time-grid .fc-highlight-container { + /* a div within a cell within the fc-highlight-skeleton */ + position: relative; + /* scopes the left/right of the fc-highlight to be in the column */ } + +.fc-time-grid .fc-highlight { + position: absolute; + left: 0; + right: 0; + /* top and bottom will be in by JS */ } + +/* TimeGrid Event Containment +--------------------------------------------------------------------------------------------------*/ +.fc-ltr .fc-time-grid .fc-event-container { + /* space on the sides of events for LTR (default) */ + margin: 0 2.5% 0 2px; } + +.fc-rtl .fc-time-grid .fc-event-container { + /* space on the sides of events for RTL */ + margin: 0 2px 0 2.5%; } + +.fc-time-grid .fc-event, +.fc-time-grid .fc-bgevent { + position: absolute; + z-index: 1; + /* scope inner z-index's */ } + +.fc-time-grid .fc-bgevent { + /* background events always span full width */ + left: 0; + right: 0; } + +/* Generic Vertical Event +--------------------------------------------------------------------------------------------------*/ +.fc-v-event.fc-not-start { + /* events that are continuing from another day */ + /* replace space made by the top border with padding */ + border-top-width: 0; + padding-top: 1px; + /* remove top rounded corners */ + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.fc-v-event.fc-not-end { + /* replace space made by the top border with padding */ + border-bottom-width: 0; + padding-bottom: 1px; + /* remove bottom rounded corners */ + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + +/* TimeGrid Event Styling +---------------------------------------------------------------------------------------------------- +We use the full "fc-time-grid-event" class instead of using descendants because the event won't +be a descendant of the grid when it is being dragged. +*/ +.fc-time-grid-event { + overflow: hidden; + /* don't let the bg flow over rounded corners */ } + +.fc-time-grid-event.fc-selected { + /* need to allow touch resizers to extend outside event's bounding box */ + /* common fc-selected styles hide the fc-bg, so don't need this anyway */ + overflow: visible; } + +.fc-time-grid-event.fc-selected .fc-bg { + display: none; + /* hide semi-white background, to appear darker */ } + +.fc-time-grid-event .fc-content { + overflow: hidden; + /* for when .fc-selected */ } + +.fc-time-grid-event .fc-time, +.fc-time-grid-event .fc-title { + padding: 0 1px; } + +.fc-time-grid-event .fc-time { + font-size: .85em; + white-space: nowrap; } + +/* short mode, where time and title are on the same line */ +.fc-time-grid-event.fc-short .fc-content { + /* don't wrap to second line (now that contents will be inline) */ + white-space: nowrap; } + +.fc-time-grid-event.fc-short .fc-time, +.fc-time-grid-event.fc-short .fc-title { + /* put the time and title on the same line */ + display: inline-block; + vertical-align: top; } + +.fc-time-grid-event.fc-short .fc-time span { + display: none; + /* don't display the full time text... */ } + +.fc-time-grid-event.fc-short .fc-time:before { + content: attr(data-start); + /* ...instead, display only the start time */ } + +.fc-time-grid-event.fc-short .fc-time:after { + content: "\A0-\A0"; + /* seperate with a dash, wrapped in nbsp's */ } + +.fc-time-grid-event.fc-short .fc-title { + font-size: .85em; + /* make the title text the same size as the time */ + padding: 0; + /* undo padding from above */ } + +/* resizer (cursor device) */ +.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer { + left: 0; + right: 0; + bottom: 0; + height: 8px; + overflow: hidden; + line-height: 8px; + font-size: 11px; + font-family: monospace; + text-align: center; + cursor: s-resize; } + +.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer:after { + content: "="; } + +/* resizer (touch device) */ +.fc-time-grid-event.fc-selected .fc-resizer { + /* 10x10 dot */ + border-radius: 5px; + border-width: 1px; + width: 8px; + height: 8px; + border-style: solid; + border-color: inherit; + background: #fff; + /* horizontally center */ + left: 50%; + margin-left: -5px; + /* center on the bottom edge */ + bottom: -5px; } + +/* Now Indicator +--------------------------------------------------------------------------------------------------*/ +.fc-time-grid .fc-now-indicator-line { + border-top-width: 1px; + left: 0; + right: 0; } + +/* arrow on axis */ +.fc-time-grid .fc-now-indicator-arrow { + margin-top: -5px; + /* vertically center on top coordinate */ } + +.fc-ltr .fc-time-grid .fc-now-indicator-arrow { + left: 0; + /* triangle pointing right... */ + border-width: 5px 0 5px 6px; + border-top-color: transparent; + border-bottom-color: transparent; } + +.fc-rtl .fc-time-grid .fc-now-indicator-arrow { + right: 0; + /* triangle pointing left... */ + border-width: 5px 6px 5px 0; + border-top-color: transparent; + border-bottom-color: transparent; } + +/* List View +--------------------------------------------------------------------------------------------------*/ +/* possibly reusable */ +.fc-event-dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 5px; } + +/* view wrapper */ +.fc-rtl .fc-list-view { + direction: rtl; + /* unlike core views, leverage browser RTL */ } + +.fc-list-view { + border-width: 1px; + border-style: solid; } + +/* table resets */ +.fc .fc-list-table { + table-layout: auto; + /* for shrinkwrapping cell content */ } + +.fc-list-table td { + border-width: 1px 0 0; + padding: 8px 14px; } + +.fc-list-table tr:first-child td { + border-top-width: 0; } + +/* day headings with the list */ +.fc-list-heading { + border-bottom-width: 1px; } + +.fc-list-heading td { + font-weight: bold; } + +.fc-ltr .fc-list-heading-main { + float: left; } + +.fc-ltr .fc-list-heading-alt { + float: right; } + +.fc-rtl .fc-list-heading-main { + float: right; } + +.fc-rtl .fc-list-heading-alt { + float: left; } + +/* event list items */ +.fc-list-item.fc-has-url { + cursor: pointer; + /* whole row will be clickable */ } + +.fc-list-item-marker, +.fc-list-item-time { + white-space: nowrap; + width: 1px; } + +/* make the dot closer to the event title */ +.fc-ltr .fc-list-item-marker { + padding-right: 0; } + +.fc-rtl .fc-list-item-marker { + padding-left: 0; } + +.fc-list-item-title a { + /* every event title cell has an tag */ + text-decoration: none; + color: inherit; } + +.fc-list-item-title a[href]:hover { + /* hover effect only on titles with hrefs */ + text-decoration: underline; } + +/* message when no events */ +.fc-list-empty-wrap2 { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; } + +.fc-list-empty-wrap1 { + width: 100%; + height: 100%; + display: table; } + +.fc-list-empty { + display: table-cell; + vertical-align: middle; + text-align: center; } + +.fc-unthemed .fc-list-empty { + /* theme will provide own background */ + background-color: #eee; } diff --git a/public/lib/fc/fullcalendar.js b/public/lib/fc/fullcalendar.js new file mode 100644 index 0000000000..f40962b87f --- /dev/null +++ b/public/lib/fc/fullcalendar.js @@ -0,0 +1,15010 @@ +/*! + * FullCalendar v3.9.0 + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(require("moment"), require("jquery")); + else if(typeof define === 'function' && define.amd) + define(["moment", "jquery"], factory); + else if(typeof exports === 'object') + exports["FullCalendar"] = factory(require("moment"), require("jquery")); + else + root["FullCalendar"] = factory(root["moment"], root["jQuery"]); +})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE_0__, __WEBPACK_EXTERNAL_MODULE_3__) { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 236); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_0__; + +/***/ }), +/* 1 */, +/* 2 */ +/***/ (function(module, exports) { + +/* +derived from: +https://github.com/Microsoft/tslib/blob/v1.6.0/tslib.js + +only include the helpers we need, to keep down filesize +*/ +var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) + if (b.hasOwnProperty(p)) + d[p] = b[p]; }; +exports.__extends = function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; + + +/***/ }), +/* 3 */ +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_3__; + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var moment = __webpack_require__(0); +var $ = __webpack_require__(3); +/* FullCalendar-specific DOM Utilities +----------------------------------------------------------------------------------------------------------------------*/ +// Given the scrollbar widths of some other container, create borders/margins on rowEls in order to match the left +// and right space that was offset by the scrollbars. A 1-pixel border first, then margin beyond that. +function compensateScroll(rowEls, scrollbarWidths) { + if (scrollbarWidths.left) { + rowEls.css({ + 'border-left-width': 1, + 'margin-left': scrollbarWidths.left - 1 + }); + } + if (scrollbarWidths.right) { + rowEls.css({ + 'border-right-width': 1, + 'margin-right': scrollbarWidths.right - 1 + }); + } +} +exports.compensateScroll = compensateScroll; +// Undoes compensateScroll and restores all borders/margins +function uncompensateScroll(rowEls) { + rowEls.css({ + 'margin-left': '', + 'margin-right': '', + 'border-left-width': '', + 'border-right-width': '' + }); +} +exports.uncompensateScroll = uncompensateScroll; +// Make the mouse cursor express that an event is not allowed in the current area +function disableCursor() { + $('body').addClass('fc-not-allowed'); +} +exports.disableCursor = disableCursor; +// Returns the mouse cursor to its original look +function enableCursor() { + $('body').removeClass('fc-not-allowed'); +} +exports.enableCursor = enableCursor; +// Given a total available height to fill, have `els` (essentially child rows) expand to accomodate. +// By default, all elements that are shorter than the recommended height are expanded uniformly, not considering +// any other els that are already too tall. if `shouldRedistribute` is on, it considers these tall rows and +// reduces the available height. +function distributeHeight(els, availableHeight, shouldRedistribute) { + // *FLOORING NOTE*: we floor in certain places because zoom can give inaccurate floating-point dimensions, + // and it is better to be shorter than taller, to avoid creating unnecessary scrollbars. + var minOffset1 = Math.floor(availableHeight / els.length); // for non-last element + var minOffset2 = Math.floor(availableHeight - minOffset1 * (els.length - 1)); // for last element *FLOORING NOTE* + var flexEls = []; // elements that are allowed to expand. array of DOM nodes + var flexOffsets = []; // amount of vertical space it takes up + var flexHeights = []; // actual css height + var usedHeight = 0; + undistributeHeight(els); // give all elements their natural height + // find elements that are below the recommended height (expandable). + // important to query for heights in a single first pass (to avoid reflow oscillation). + els.each(function (i, el) { + var minOffset = i === els.length - 1 ? minOffset2 : minOffset1; + var naturalOffset = $(el).outerHeight(true); + if (naturalOffset < minOffset) { + flexEls.push(el); + flexOffsets.push(naturalOffset); + flexHeights.push($(el).height()); + } + else { + // this element stretches past recommended height (non-expandable). mark the space as occupied. + usedHeight += naturalOffset; + } + }); + // readjust the recommended height to only consider the height available to non-maxed-out rows. + if (shouldRedistribute) { + availableHeight -= usedHeight; + minOffset1 = Math.floor(availableHeight / flexEls.length); + minOffset2 = Math.floor(availableHeight - minOffset1 * (flexEls.length - 1)); // *FLOORING NOTE* + } + // assign heights to all expandable elements + $(flexEls).each(function (i, el) { + var minOffset = i === flexEls.length - 1 ? minOffset2 : minOffset1; + var naturalOffset = flexOffsets[i]; + var naturalHeight = flexHeights[i]; + var newHeight = minOffset - (naturalOffset - naturalHeight); // subtract the margin/padding + if (naturalOffset < minOffset) { + $(el).height(newHeight); + } + }); +} +exports.distributeHeight = distributeHeight; +// Undoes distrubuteHeight, restoring all els to their natural height +function undistributeHeight(els) { + els.height(''); +} +exports.undistributeHeight = undistributeHeight; +// Given `els`, a jQuery set of cells, find the cell with the largest natural width and set the widths of all the +// cells to be that width. +// PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline +function matchCellWidths(els) { + var maxInnerWidth = 0; + els.find('> *').each(function (i, innerEl) { + var innerWidth = $(innerEl).outerWidth(); + if (innerWidth > maxInnerWidth) { + maxInnerWidth = innerWidth; + } + }); + maxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance + els.width(maxInnerWidth); + return maxInnerWidth; +} +exports.matchCellWidths = matchCellWidths; +// Given one element that resides inside another, +// Subtracts the height of the inner element from the outer element. +function subtractInnerElHeight(outerEl, innerEl) { + var both = outerEl.add(innerEl); + var diff; + // effin' IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked + both.css({ + position: 'relative', + left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll + }); + diff = outerEl.outerHeight() - innerEl.outerHeight(); // grab the dimensions + both.css({ position: '', left: '' }); // undo hack + return diff; +} +exports.subtractInnerElHeight = subtractInnerElHeight; +/* Element Geom Utilities +----------------------------------------------------------------------------------------------------------------------*/ +// borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51 +function getScrollParent(el) { + var position = el.css('position'); + var scrollParent = el.parents().filter(function () { + var parent = $(this); + return (/(auto|scroll)/).test(parent.css('overflow') + parent.css('overflow-y') + parent.css('overflow-x')); + }).eq(0); + return position === 'fixed' || !scrollParent.length ? $(el[0].ownerDocument || document) : scrollParent; +} +exports.getScrollParent = getScrollParent; +// Queries the outer bounding area of a jQuery element. +// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive). +// Origin is optional. +function getOuterRect(el, origin) { + var offset = el.offset(); + var left = offset.left - (origin ? origin.left : 0); + var top = offset.top - (origin ? origin.top : 0); + return { + left: left, + right: left + el.outerWidth(), + top: top, + bottom: top + el.outerHeight() + }; +} +exports.getOuterRect = getOuterRect; +// Queries the area within the margin/border/scrollbars of a jQuery element. Does not go within the padding. +// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive). +// Origin is optional. +// WARNING: given element can't have borders +// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser. +function getClientRect(el, origin) { + var offset = el.offset(); + var scrollbarWidths = getScrollbarWidths(el); + var left = offset.left + getCssFloat(el, 'border-left-width') + scrollbarWidths.left - (origin ? origin.left : 0); + var top = offset.top + getCssFloat(el, 'border-top-width') + scrollbarWidths.top - (origin ? origin.top : 0); + return { + left: left, + right: left + el[0].clientWidth, + top: top, + bottom: top + el[0].clientHeight // clientHeight includes padding but NOT scrollbars + }; +} +exports.getClientRect = getClientRect; +// Queries the area within the margin/border/padding of a jQuery element. Assumed not to have scrollbars. +// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive). +// Origin is optional. +function getContentRect(el, origin) { + var offset = el.offset(); // just outside of border, margin not included + var left = offset.left + getCssFloat(el, 'border-left-width') + getCssFloat(el, 'padding-left') - + (origin ? origin.left : 0); + var top = offset.top + getCssFloat(el, 'border-top-width') + getCssFloat(el, 'padding-top') - + (origin ? origin.top : 0); + return { + left: left, + right: left + el.width(), + top: top, + bottom: top + el.height() + }; +} +exports.getContentRect = getContentRect; +// Returns the computed left/right/top/bottom scrollbar widths for the given jQuery element. +// WARNING: given element can't have borders (which will cause offsetWidth/offsetHeight to be larger). +// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser. +function getScrollbarWidths(el) { + var leftRightWidth = el[0].offsetWidth - el[0].clientWidth; + var bottomWidth = el[0].offsetHeight - el[0].clientHeight; + var widths; + leftRightWidth = sanitizeScrollbarWidth(leftRightWidth); + bottomWidth = sanitizeScrollbarWidth(bottomWidth); + widths = { left: 0, right: 0, top: 0, bottom: bottomWidth }; + if (getIsLeftRtlScrollbars() && el.css('direction') === 'rtl') { + widths.left = leftRightWidth; + } + else { + widths.right = leftRightWidth; + } + return widths; +} +exports.getScrollbarWidths = getScrollbarWidths; +// The scrollbar width computations in getScrollbarWidths are sometimes flawed when it comes to +// retina displays, rounding, and IE11. Massage them into a usable value. +function sanitizeScrollbarWidth(width) { + width = Math.max(0, width); // no negatives + width = Math.round(width); + return width; +} +// Logic for determining if, when the element is right-to-left, the scrollbar appears on the left side +var _isLeftRtlScrollbars = null; +function getIsLeftRtlScrollbars() { + if (_isLeftRtlScrollbars === null) { + _isLeftRtlScrollbars = computeIsLeftRtlScrollbars(); + } + return _isLeftRtlScrollbars; +} +function computeIsLeftRtlScrollbars() { + var el = $('') + .css({ + position: 'absolute', + top: -1000, + left: 0, + border: 0, + padding: 0, + overflow: 'scroll', + direction: 'rtl' + }) + .appendTo('body'); + var innerEl = el.children(); + var res = innerEl.offset().left > el.offset().left; // is the inner div shifted to accommodate a left scrollbar? + el.remove(); + return res; +} +// Retrieves a jQuery element's computed CSS value as a floating-point number. +// If the queried value is non-numeric (ex: IE can return "medium" for border width), will just return zero. +function getCssFloat(el, prop) { + return parseFloat(el.css(prop)) || 0; +} +/* Mouse / Touch Utilities +----------------------------------------------------------------------------------------------------------------------*/ +// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) +function isPrimaryMouseButton(ev) { + return ev.which === 1 && !ev.ctrlKey; +} +exports.isPrimaryMouseButton = isPrimaryMouseButton; +function getEvX(ev) { + var touches = ev.originalEvent.touches; + // on mobile FF, pageX for touch events is present, but incorrect, + // so, look at touch coordinates first. + if (touches && touches.length) { + return touches[0].pageX; + } + return ev.pageX; +} +exports.getEvX = getEvX; +function getEvY(ev) { + var touches = ev.originalEvent.touches; + // on mobile FF, pageX for touch events is present, but incorrect, + // so, look at touch coordinates first. + if (touches && touches.length) { + return touches[0].pageY; + } + return ev.pageY; +} +exports.getEvY = getEvY; +function getEvIsTouch(ev) { + return /^touch/.test(ev.type); +} +exports.getEvIsTouch = getEvIsTouch; +function preventSelection(el) { + el.addClass('fc-unselectable') + .on('selectstart', preventDefault); +} +exports.preventSelection = preventSelection; +function allowSelection(el) { + el.removeClass('fc-unselectable') + .off('selectstart', preventDefault); +} +exports.allowSelection = allowSelection; +// Stops a mouse/touch event from doing it's native browser action +function preventDefault(ev) { + ev.preventDefault(); +} +exports.preventDefault = preventDefault; +/* General Geometry Utils +----------------------------------------------------------------------------------------------------------------------*/ +// Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false +function intersectRects(rect1, rect2) { + var res = { + left: Math.max(rect1.left, rect2.left), + right: Math.min(rect1.right, rect2.right), + top: Math.max(rect1.top, rect2.top), + bottom: Math.min(rect1.bottom, rect2.bottom) + }; + if (res.left < res.right && res.top < res.bottom) { + return res; + } + return false; +} +exports.intersectRects = intersectRects; +// Returns a new point that will have been moved to reside within the given rectangle +function constrainPoint(point, rect) { + return { + left: Math.min(Math.max(point.left, rect.left), rect.right), + top: Math.min(Math.max(point.top, rect.top), rect.bottom) + }; +} +exports.constrainPoint = constrainPoint; +// Returns a point that is the center of the given rectangle +function getRectCenter(rect) { + return { + left: (rect.left + rect.right) / 2, + top: (rect.top + rect.bottom) / 2 + }; +} +exports.getRectCenter = getRectCenter; +// Subtracts point2's coordinates from point1's coordinates, returning a delta +function diffPoints(point1, point2) { + return { + left: point1.left - point2.left, + top: point1.top - point2.top + }; +} +exports.diffPoints = diffPoints; +/* Object Ordering by Field +----------------------------------------------------------------------------------------------------------------------*/ +function parseFieldSpecs(input) { + var specs = []; + var tokens = []; + var i; + var token; + if (typeof input === 'string') { + tokens = input.split(/\s*,\s*/); + } + else if (typeof input === 'function') { + tokens = [input]; + } + else if ($.isArray(input)) { + tokens = input; + } + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + if (typeof token === 'string') { + specs.push(token.charAt(0) === '-' ? + { field: token.substring(1), order: -1 } : + { field: token, order: 1 }); + } + else if (typeof token === 'function') { + specs.push({ func: token }); + } + } + return specs; +} +exports.parseFieldSpecs = parseFieldSpecs; +function compareByFieldSpecs(obj1, obj2, fieldSpecs, obj1fallback, obj2fallback) { + var i; + var cmp; + for (i = 0; i < fieldSpecs.length; i++) { + cmp = compareByFieldSpec(obj1, obj2, fieldSpecs[i], obj1fallback, obj2fallback); + if (cmp) { + return cmp; + } + } + return 0; +} +exports.compareByFieldSpecs = compareByFieldSpecs; +function compareByFieldSpec(obj1, obj2, fieldSpec, obj1fallback, obj2fallback) { + if (fieldSpec.func) { + return fieldSpec.func(obj1, obj2); + } + var val1 = obj1[fieldSpec.field]; + var val2 = obj2[fieldSpec.field]; + if (val1 == null && obj1fallback) { + val1 = obj1fallback[fieldSpec.field]; + } + if (val2 == null && obj2fallback) { + val2 = obj2fallback[fieldSpec.field]; + } + return flexibleCompare(val1, val2) * (fieldSpec.order || 1); +} +exports.compareByFieldSpec = compareByFieldSpec; +function flexibleCompare(a, b) { + if (!a && !b) { + return 0; + } + if (b == null) { + return -1; + } + if (a == null) { + return 1; + } + if ($.type(a) === 'string' || $.type(b) === 'string') { + return String(a).localeCompare(String(b)); + } + return a - b; +} +exports.flexibleCompare = flexibleCompare; +/* Date Utilities +----------------------------------------------------------------------------------------------------------------------*/ +exports.dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; +exports.unitsDesc = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond']; // descending +// Diffs the two moments into a Duration where full-days are recorded first, then the remaining time. +// Moments will have their timezones normalized. +function diffDayTime(a, b) { + return moment.duration({ + days: a.clone().stripTime().diff(b.clone().stripTime(), 'days'), + ms: a.time() - b.time() // time-of-day from day start. disregards timezone + }); +} +exports.diffDayTime = diffDayTime; +// Diffs the two moments via their start-of-day (regardless of timezone). Produces whole-day durations. +function diffDay(a, b) { + return moment.duration({ + days: a.clone().stripTime().diff(b.clone().stripTime(), 'days') + }); +} +exports.diffDay = diffDay; +// Diffs two moments, producing a duration, made of a whole-unit-increment of the given unit. Uses rounding. +function diffByUnit(a, b, unit) { + return moment.duration(Math.round(a.diff(b, unit, true)), // returnFloat=true + unit); +} +exports.diffByUnit = diffByUnit; +// Computes the unit name of the largest whole-unit period of time. +// For example, 48 hours will be "days" whereas 49 hours will be "hours". +// Accepts start/end, a range object, or an original duration object. +function computeGreatestUnit(start, end) { + var i; + var unit; + var val; + for (i = 0; i < exports.unitsDesc.length; i++) { + unit = exports.unitsDesc[i]; + val = computeRangeAs(unit, start, end); + if (val >= 1 && isInt(val)) { + break; + } + } + return unit; // will be "milliseconds" if nothing else matches +} +exports.computeGreatestUnit = computeGreatestUnit; +// like computeGreatestUnit, but has special abilities to interpret the source input for clues +function computeDurationGreatestUnit(duration, durationInput) { + var unit = computeGreatestUnit(duration); + // prevent days:7 from being interpreted as a week + if (unit === 'week' && typeof durationInput === 'object' && durationInput.days) { + unit = 'day'; + } + return unit; +} +exports.computeDurationGreatestUnit = computeDurationGreatestUnit; +// Computes the number of units (like "hours") in the given range. +// Range can be a {start,end} object, separate start/end args, or a Duration. +// Results are based on Moment's .as() and .diff() methods, so results can depend on internal handling +// of month-diffing logic (which tends to vary from version to version). +function computeRangeAs(unit, start, end) { + if (end != null) { + return end.diff(start, unit, true); + } + else if (moment.isDuration(start)) { + return start.as(unit); + } + else { + return start.end.diff(start.start, unit, true); + } +} +// Intelligently divides a range (specified by a start/end params) by a duration +function divideRangeByDuration(start, end, dur) { + var months; + if (durationHasTime(dur)) { + return (end - start) / dur; + } + months = dur.asMonths(); + if (Math.abs(months) >= 1 && isInt(months)) { + return end.diff(start, 'months', true) / months; + } + return end.diff(start, 'days', true) / dur.asDays(); +} +exports.divideRangeByDuration = divideRangeByDuration; +// Intelligently divides one duration by another +function divideDurationByDuration(dur1, dur2) { + var months1; + var months2; + if (durationHasTime(dur1) || durationHasTime(dur2)) { + return dur1 / dur2; + } + months1 = dur1.asMonths(); + months2 = dur2.asMonths(); + if (Math.abs(months1) >= 1 && isInt(months1) && + Math.abs(months2) >= 1 && isInt(months2)) { + return months1 / months2; + } + return dur1.asDays() / dur2.asDays(); +} +exports.divideDurationByDuration = divideDurationByDuration; +// Intelligently multiplies a duration by a number +function multiplyDuration(dur, n) { + var months; + if (durationHasTime(dur)) { + return moment.duration(dur * n); + } + months = dur.asMonths(); + if (Math.abs(months) >= 1 && isInt(months)) { + return moment.duration({ months: months * n }); + } + return moment.duration({ days: dur.asDays() * n }); +} +exports.multiplyDuration = multiplyDuration; +// Returns a boolean about whether the given duration has any time parts (hours/minutes/seconds/ms) +function durationHasTime(dur) { + return Boolean(dur.hours() || dur.minutes() || dur.seconds() || dur.milliseconds()); +} +exports.durationHasTime = durationHasTime; +function isNativeDate(input) { + return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date; +} +exports.isNativeDate = isNativeDate; +// Returns a boolean about whether the given input is a time string, like "06:40:00" or "06:00" +function isTimeString(str) { + return typeof str === 'string' && + /^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(str); +} +exports.isTimeString = isTimeString; +/* Logging and Debug +----------------------------------------------------------------------------------------------------------------------*/ +function log() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var console = window.console; + if (console && console.log) { + return console.log.apply(console, args); + } +} +exports.log = log; +function warn() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var console = window.console; + if (console && console.warn) { + return console.warn.apply(console, args); + } + else { + return log.apply(null, args); + } +} +exports.warn = warn; +/* General Utilities +----------------------------------------------------------------------------------------------------------------------*/ +var hasOwnPropMethod = {}.hasOwnProperty; +// Merges an array of objects into a single object. +// The second argument allows for an array of property names who's object values will be merged together. +function mergeProps(propObjs, complexProps) { + var dest = {}; + var i; + var name; + var complexObjs; + var j; + var val; + var props; + if (complexProps) { + for (i = 0; i < complexProps.length; i++) { + name = complexProps[i]; + complexObjs = []; + // collect the trailing object values, stopping when a non-object is discovered + for (j = propObjs.length - 1; j >= 0; j--) { + val = propObjs[j][name]; + if (typeof val === 'object') { + complexObjs.unshift(val); + } + else if (val !== undefined) { + dest[name] = val; // if there were no objects, this value will be used + break; + } + } + // if the trailing values were objects, use the merged value + if (complexObjs.length) { + dest[name] = mergeProps(complexObjs); + } + } + } + // copy values into the destination, going from last to first + for (i = propObjs.length - 1; i >= 0; i--) { + props = propObjs[i]; + for (name in props) { + if (!(name in dest)) { + dest[name] = props[name]; + } + } + } + return dest; +} +exports.mergeProps = mergeProps; +function copyOwnProps(src, dest) { + for (var name_1 in src) { + if (hasOwnProp(src, name_1)) { + dest[name_1] = src[name_1]; + } + } +} +exports.copyOwnProps = copyOwnProps; +function hasOwnProp(obj, name) { + return hasOwnPropMethod.call(obj, name); +} +exports.hasOwnProp = hasOwnProp; +function applyAll(functions, thisObj, args) { + if ($.isFunction(functions)) { + functions = [functions]; + } + if (functions) { + var i = void 0; + var ret = void 0; + for (i = 0; i < functions.length; i++) { + ret = functions[i].apply(thisObj, args) || ret; + } + return ret; + } +} +exports.applyAll = applyAll; +function removeMatching(array, testFunc) { + var removeCnt = 0; + var i = 0; + while (i < array.length) { + if (testFunc(array[i])) { + array.splice(i, 1); + removeCnt++; + } + else { + i++; + } + } + return removeCnt; +} +exports.removeMatching = removeMatching; +function removeExact(array, exactVal) { + var removeCnt = 0; + var i = 0; + while (i < array.length) { + if (array[i] === exactVal) { + array.splice(i, 1); + removeCnt++; + } + else { + i++; + } + } + return removeCnt; +} +exports.removeExact = removeExact; +function isArraysEqual(a0, a1) { + var len = a0.length; + var i; + if (len == null || len !== a1.length) { + return false; + } + for (i = 0; i < len; i++) { + if (a0[i] !== a1[i]) { + return false; + } + } + return true; +} +exports.isArraysEqual = isArraysEqual; +function firstDefined() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + for (var i = 0; i < args.length; i++) { + if (args[i] !== undefined) { + return args[i]; + } + } +} +exports.firstDefined = firstDefined; +function htmlEscape(s) { + return (s + '').replace(/&/g, '&') + .replace(//g, '>') + .replace(/'/g, ''') + .replace(/"/g, '"') + .replace(/\n/g, ''); +} +exports.htmlEscape = htmlEscape; +function stripHtmlEntities(text) { + return text.replace(/&.*?;/g, ''); +} +exports.stripHtmlEntities = stripHtmlEntities; +// Given a hash of CSS properties, returns a string of CSS. +// Uses property names as-is (no camel-case conversion). Will not make statements for null/undefined values. +function cssToStr(cssProps) { + var statements = []; + $.each(cssProps, function (name, val) { + if (val != null) { + statements.push(name + ':' + val); + } + }); + return statements.join(';'); +} +exports.cssToStr = cssToStr; +// Given an object hash of HTML attribute names to values, +// generates a string that can be injected between < > in HTML +function attrsToStr(attrs) { + var parts = []; + $.each(attrs, function (name, val) { + if (val != null) { + parts.push(name + '="' + htmlEscape(val) + '"'); + } + }); + return parts.join(' '); +} +exports.attrsToStr = attrsToStr; +function capitaliseFirstLetter(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} +exports.capitaliseFirstLetter = capitaliseFirstLetter; +function compareNumbers(a, b) { + return a - b; +} +exports.compareNumbers = compareNumbers; +function isInt(n) { + return n % 1 === 0; +} +exports.isInt = isInt; +// Returns a method bound to the given object context. +// Just like one of the jQuery.proxy signatures, but without the undesired behavior of treating the same method with +// different contexts as identical when binding/unbinding events. +function proxy(obj, methodName) { + var method = obj[methodName]; + return function () { + return method.apply(obj, arguments); + }; +} +exports.proxy = proxy; +// Returns a function, that, as long as it continues to be invoked, will not +// be triggered. The function will be called after it stops being called for +// N milliseconds. If `immediate` is passed, trigger the function on the +// leading edge, instead of the trailing. +// https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714 +function debounce(func, wait, immediate) { + if (immediate === void 0) { immediate = false; } + var timeout; + var args; + var context; + var timestamp; + var result; + var later = function () { + var last = +new Date() - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } + else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + context = args = null; + } + } + }; + return function () { + context = this; + args = arguments; + timestamp = +new Date(); + var callNow = immediate && !timeout; + if (!timeout) { + timeout = setTimeout(later, wait); + } + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + return result; + }; +} +exports.debounce = debounce; + + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var moment = __webpack_require__(0); +var moment_ext_1 = __webpack_require__(10); +var UnzonedRange = /** @class */ (function () { + function UnzonedRange(startInput, endInput) { + // TODO: move these into footprint. + // Especially, doesn't make sense for null startMs/endMs. + this.isStart = true; + this.isEnd = true; + if (moment.isMoment(startInput)) { + startInput = startInput.clone().stripZone(); + } + if (moment.isMoment(endInput)) { + endInput = endInput.clone().stripZone(); + } + if (startInput) { + this.startMs = startInput.valueOf(); + } + if (endInput) { + this.endMs = endInput.valueOf(); + } + } + /* + SIDEEFFECT: will mutate eventRanges. + Will return a new array result. + Only works for non-open-ended ranges. + */ + UnzonedRange.invertRanges = function (ranges, constraintRange) { + var invertedRanges = []; + var startMs = constraintRange.startMs; // the end of the previous range. the start of the new range + var i; + var dateRange; + // ranges need to be in order. required for our date-walking algorithm + ranges.sort(compareUnzonedRanges); + for (i = 0; i < ranges.length; i++) { + dateRange = ranges[i]; + // add the span of time before the event (if there is any) + if (dateRange.startMs > startMs) { + invertedRanges.push(new UnzonedRange(startMs, dateRange.startMs)); + } + if (dateRange.endMs > startMs) { + startMs = dateRange.endMs; + } + } + // add the span of time after the last event (if there is any) + if (startMs < constraintRange.endMs) { + invertedRanges.push(new UnzonedRange(startMs, constraintRange.endMs)); + } + return invertedRanges; + }; + UnzonedRange.prototype.intersect = function (otherRange) { + var startMs = this.startMs; + var endMs = this.endMs; + var newRange = null; + if (otherRange.startMs != null) { + if (startMs == null) { + startMs = otherRange.startMs; + } + else { + startMs = Math.max(startMs, otherRange.startMs); + } + } + if (otherRange.endMs != null) { + if (endMs == null) { + endMs = otherRange.endMs; + } + else { + endMs = Math.min(endMs, otherRange.endMs); + } + } + if (startMs == null || endMs == null || startMs < endMs) { + newRange = new UnzonedRange(startMs, endMs); + newRange.isStart = this.isStart && startMs === this.startMs; + newRange.isEnd = this.isEnd && endMs === this.endMs; + } + return newRange; + }; + UnzonedRange.prototype.intersectsWith = function (otherRange) { + return (this.endMs == null || otherRange.startMs == null || this.endMs > otherRange.startMs) && + (this.startMs == null || otherRange.endMs == null || this.startMs < otherRange.endMs); + }; + UnzonedRange.prototype.containsRange = function (innerRange) { + return (this.startMs == null || (innerRange.startMs != null && innerRange.startMs >= this.startMs)) && + (this.endMs == null || (innerRange.endMs != null && innerRange.endMs <= this.endMs)); + }; + // `date` can be a moment, a Date, or a millisecond time. + UnzonedRange.prototype.containsDate = function (date) { + var ms = date.valueOf(); + return (this.startMs == null || ms >= this.startMs) && + (this.endMs == null || ms < this.endMs); + }; + // If the given date is not within the given range, move it inside. + // (If it's past the end, make it one millisecond before the end). + // `date` can be a moment, a Date, or a millisecond time. + // Returns a MS-time. + UnzonedRange.prototype.constrainDate = function (date) { + var ms = date.valueOf(); + if (this.startMs != null && ms < this.startMs) { + ms = this.startMs; + } + if (this.endMs != null && ms >= this.endMs) { + ms = this.endMs - 1; + } + return ms; + }; + UnzonedRange.prototype.equals = function (otherRange) { + return this.startMs === otherRange.startMs && this.endMs === otherRange.endMs; + }; + UnzonedRange.prototype.clone = function () { + var range = new UnzonedRange(this.startMs, this.endMs); + range.isStart = this.isStart; + range.isEnd = this.isEnd; + return range; + }; + // Returns an ambig-zoned moment from startMs. + // BEWARE: returned moment is not localized. + // Formatting and start-of-week will be default. + UnzonedRange.prototype.getStart = function () { + if (this.startMs != null) { + return moment_ext_1.default.utc(this.startMs).stripZone(); + } + return null; + }; + // Returns an ambig-zoned moment from startMs. + // BEWARE: returned moment is not localized. + // Formatting and start-of-week will be default. + UnzonedRange.prototype.getEnd = function () { + if (this.endMs != null) { + return moment_ext_1.default.utc(this.endMs).stripZone(); + } + return null; + }; + UnzonedRange.prototype.as = function (unit) { + return moment.utc(this.endMs).diff(moment.utc(this.startMs), unit, true); + }; + return UnzonedRange; +}()); +exports.default = UnzonedRange; +/* +Only works for non-open-ended ranges. +*/ +function compareUnzonedRanges(range1, range2) { + return range1.startMs - range2.startMs; // earlier ranges go first +} + + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var ParsableModelMixin_1 = __webpack_require__(208); +var Class_1 = __webpack_require__(33); +var EventDefParser_1 = __webpack_require__(49); +var EventSource = /** @class */ (function (_super) { + tslib_1.__extends(EventSource, _super); + // can we do away with calendar? at least for the abstract? + // useful for buildEventDef + function EventSource(calendar) { + var _this = _super.call(this) || this; + _this.calendar = calendar; + _this.className = []; + _this.uid = String(EventSource.uuid++); + return _this; + } + /* + rawInput can be any data type! + */ + EventSource.parse = function (rawInput, calendar) { + var source = new this(calendar); + if (typeof rawInput === 'object') { + if (source.applyProps(rawInput)) { + return source; + } + } + return false; + }; + EventSource.normalizeId = function (id) { + if (id) { + return String(id); + } + return null; + }; + EventSource.prototype.fetch = function (start, end, timezone) { + // subclasses must implement. must return a promise. + }; + EventSource.prototype.removeEventDefsById = function (eventDefId) { + // optional for subclasses to implement + }; + EventSource.prototype.removeAllEventDefs = function () { + // optional for subclasses to implement + }; + /* + For compairing/matching + */ + EventSource.prototype.getPrimitive = function (otherSource) { + // subclasses must implement + }; + EventSource.prototype.parseEventDefs = function (rawEventDefs) { + var i; + var eventDef; + var eventDefs = []; + for (i = 0; i < rawEventDefs.length; i++) { + eventDef = this.parseEventDef(rawEventDefs[i]); + if (eventDef) { + eventDefs.push(eventDef); + } + } + return eventDefs; + }; + EventSource.prototype.parseEventDef = function (rawInput) { + var calendarTransform = this.calendar.opt('eventDataTransform'); + var sourceTransform = this.eventDataTransform; + if (calendarTransform) { + rawInput = calendarTransform(rawInput, this.calendar); + } + if (sourceTransform) { + rawInput = sourceTransform(rawInput, this.calendar); + } + return EventDefParser_1.default.parse(rawInput, this); + }; + EventSource.prototype.applyManualStandardProps = function (rawProps) { + if (rawProps.id != null) { + this.id = EventSource.normalizeId(rawProps.id); + } + // TODO: converge with EventDef + if ($.isArray(rawProps.className)) { + this.className = rawProps.className; + } + else if (typeof rawProps.className === 'string') { + this.className = rawProps.className.split(/\s+/); + } + return true; + }; + EventSource.uuid = 0; + EventSource.defineStandardProps = ParsableModelMixin_1.default.defineStandardProps; + EventSource.copyVerbatimStandardProps = ParsableModelMixin_1.default.copyVerbatimStandardProps; + return EventSource; +}(Class_1.default)); +exports.default = EventSource; +ParsableModelMixin_1.default.mixInto(EventSource); +// Parsing +// --------------------------------------------------------------------------------------------------------------------- +EventSource.defineStandardProps({ + // manually process... + id: false, + className: false, + // automatically transfer... + color: true, + backgroundColor: true, + borderColor: true, + textColor: true, + editable: true, + startEditable: true, + durationEditable: true, + rendering: true, + overlap: true, + constraint: true, + allDayDefault: true, + eventDataTransform: true +}); + + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + +/* +Utility methods for easily listening to events on another object, +and more importantly, easily unlistening from them. + +USAGE: + import { default as ListenerMixin, ListenerInterface } from './ListenerMixin' +in class: + listenTo: ListenerInterface['listenTo'] + stopListeningTo: ListenerInterface['stopListeningTo'] +after class: + ListenerMixin.mixInto(TheClass) +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var Mixin_1 = __webpack_require__(14); +var guid = 0; +var ListenerMixin = /** @class */ (function (_super) { + tslib_1.__extends(ListenerMixin, _super); + function ListenerMixin() { + return _super !== null && _super.apply(this, arguments) || this; + } + /* + Given an `other` object that has on/off methods, bind the given `callback` to an event by the given name. + The `callback` will be called with the `this` context of the object that .listenTo is being called on. + Can be called: + .listenTo(other, eventName, callback) + OR + .listenTo(other, { + eventName1: callback1, + eventName2: callback2 + }) + */ + ListenerMixin.prototype.listenTo = function (other, arg, callback) { + if (typeof arg === 'object') { + for (var eventName in arg) { + if (arg.hasOwnProperty(eventName)) { + this.listenTo(other, eventName, arg[eventName]); + } + } + } + else if (typeof arg === 'string') { + other.on(arg + '.' + this.getListenerNamespace(), // use event namespacing to identify this object + $.proxy(callback, this) // always use `this` context + // the usually-undesired jQuery guid behavior doesn't matter, + // because we always unbind via namespace + ); + } + }; + /* + Causes the current object to stop listening to events on the `other` object. + `eventName` is optional. If omitted, will stop listening to ALL events on `other`. + */ + ListenerMixin.prototype.stopListeningTo = function (other, eventName) { + other.off((eventName || '') + '.' + this.getListenerNamespace()); + }; + /* + Returns a string, unique to this object, to be used for event namespacing + */ + ListenerMixin.prototype.getListenerNamespace = function () { + if (this.listenerId == null) { + this.listenerId = guid++; + } + return '_listener' + this.listenerId; + }; + return ListenerMixin; +}(Mixin_1.default)); +exports.default = ListenerMixin; + + +/***/ }), +/* 8 */, +/* 9 */, +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var moment = __webpack_require__(0); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/; +var ambigTimeOrZoneRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/; +var newMomentProto = moment.fn; // where we will attach our new methods +exports.newMomentProto = newMomentProto; +var oldMomentProto = $.extend({}, newMomentProto); // copy of original moment methods +exports.oldMomentProto = oldMomentProto; +// tell momentjs to transfer these properties upon clone +var momentProperties = moment.momentProperties; +momentProperties.push('_fullCalendar'); +momentProperties.push('_ambigTime'); +momentProperties.push('_ambigZone'); +/* +Call this if you want Moment's original format method to be used +*/ +function oldMomentFormat(mom, formatStr) { + return oldMomentProto.format.call(mom, formatStr); // oldMomentProto defined in moment-ext.js +} +exports.oldMomentFormat = oldMomentFormat; +// Creating +// ------------------------------------------------------------------------------------------------- +// Creates a new moment, similar to the vanilla moment(...) constructor, but with +// extra features (ambiguous time, enhanced formatting). When given an existing moment, +// it will function as a clone (and retain the zone of the moment). Anything else will +// result in a moment in the local zone. +var momentExt = function () { + return makeMoment(arguments); +}; +exports.default = momentExt; +// Sames as momentExt, but forces the resulting moment to be in the UTC timezone. +momentExt.utc = function () { + var mom = makeMoment(arguments, true); + // Force it into UTC because makeMoment doesn't guarantee it + // (if given a pre-existing moment for example) + if (mom.hasTime()) { + mom.utc(); + } + return mom; +}; +// Same as momentExt, but when given an ISO8601 string, the timezone offset is preserved. +// ISO8601 strings with no timezone offset will become ambiguously zoned. +momentExt.parseZone = function () { + return makeMoment(arguments, true, true); +}; +// Builds an enhanced moment from args. When given an existing moment, it clones. When given a +// native Date, or called with no arguments (the current time), the resulting moment will be local. +// Anything else needs to be "parsed" (a string or an array), and will be affected by: +// parseAsUTC - if there is no zone information, should we parse the input in UTC? +// parseZone - if there is zone information, should we force the zone of the moment? +function makeMoment(args, parseAsUTC, parseZone) { + if (parseAsUTC === void 0) { parseAsUTC = false; } + if (parseZone === void 0) { parseZone = false; } + var input = args[0]; + var isSingleString = args.length === 1 && typeof input === 'string'; + var isAmbigTime; + var isAmbigZone; + var ambigMatch; + var mom; + if (moment.isMoment(input) || util_1.isNativeDate(input) || input === undefined) { + mom = moment.apply(null, args); + } + else { + isAmbigTime = false; + isAmbigZone = false; + if (isSingleString) { + if (ambigDateOfMonthRegex.test(input)) { + // accept strings like '2014-05', but convert to the first of the month + input += '-01'; + args = [input]; // for when we pass it on to moment's constructor + isAmbigTime = true; + isAmbigZone = true; + } + else if ((ambigMatch = ambigTimeOrZoneRegex.exec(input))) { + isAmbigTime = !ambigMatch[5]; // no time part? + isAmbigZone = true; + } + } + else if ($.isArray(input)) { + // arrays have no timezone information, so assume ambiguous zone + isAmbigZone = true; + } + // otherwise, probably a string with a format + if (parseAsUTC || isAmbigTime) { + mom = moment.utc.apply(moment, args); + } + else { + mom = moment.apply(null, args); + } + if (isAmbigTime) { + mom._ambigTime = true; + mom._ambigZone = true; // ambiguous time always means ambiguous zone + } + else if (parseZone) { + if (isAmbigZone) { + mom._ambigZone = true; + } + else if (isSingleString) { + mom.utcOffset(input); // if not a valid zone, will assign UTC + } + } + } + mom._fullCalendar = true; // flag for extended functionality + return mom; +} +// Week Number +// ------------------------------------------------------------------------------------------------- +// Returns the week number, considering the locale's custom week number calcuation +// `weeks` is an alias for `week` +newMomentProto.week = newMomentProto.weeks = function (input) { + var weekCalc = this._locale._fullCalendar_weekCalc; + if (input == null && typeof weekCalc === 'function') { + return weekCalc(this); + } + else if (weekCalc === 'ISO') { + return oldMomentProto.isoWeek.apply(this, arguments); // ISO getter/setter + } + return oldMomentProto.week.apply(this, arguments); // local getter/setter +}; +// Time-of-day +// ------------------------------------------------------------------------------------------------- +// GETTER +// Returns a Duration with the hours/minutes/seconds/ms values of the moment. +// If the moment has an ambiguous time, a duration of 00:00 will be returned. +// +// SETTER +// You can supply a Duration, a Moment, or a Duration-like argument. +// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous. +newMomentProto.time = function (time) { + // Fallback to the original method (if there is one) if this moment wasn't created via FullCalendar. + // `time` is a generic enough method name where this precaution is necessary to avoid collisions w/ other plugins. + if (!this._fullCalendar) { + return oldMomentProto.time.apply(this, arguments); + } + if (time == null) { + return moment.duration({ + hours: this.hours(), + minutes: this.minutes(), + seconds: this.seconds(), + milliseconds: this.milliseconds() + }); + } + else { + this._ambigTime = false; // mark that the moment now has a time + if (!moment.isDuration(time) && !moment.isMoment(time)) { + time = moment.duration(time); + } + // The day value should cause overflow (so 24 hours becomes 00:00:00 of next day). + // Only for Duration times, not Moment times. + var dayHours = 0; + if (moment.isDuration(time)) { + dayHours = Math.floor(time.asDays()) * 24; + } + // We need to set the individual fields. + // Can't use startOf('day') then add duration. In case of DST at start of day. + return this.hours(dayHours + time.hours()) + .minutes(time.minutes()) + .seconds(time.seconds()) + .milliseconds(time.milliseconds()); + } +}; +// Converts the moment to UTC, stripping out its time-of-day and timezone offset, +// but preserving its YMD. A moment with a stripped time will display no time +// nor timezone offset when .format() is called. +newMomentProto.stripTime = function () { + if (!this._ambigTime) { + this.utc(true); // keepLocalTime=true (for keeping *date* value) + // set time to zero + this.set({ + hours: 0, + minutes: 0, + seconds: 0, + ms: 0 + }); + // Mark the time as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(), + // which clears all ambig flags. + this._ambigTime = true; + this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset + } + return this; // for chaining +}; +// Returns if the moment has a non-ambiguous time (boolean) +newMomentProto.hasTime = function () { + return !this._ambigTime; +}; +// Timezone +// ------------------------------------------------------------------------------------------------- +// Converts the moment to UTC, stripping out its timezone offset, but preserving its +// YMD and time-of-day. A moment with a stripped timezone offset will display no +// timezone offset when .format() is called. +newMomentProto.stripZone = function () { + var wasAmbigTime; + if (!this._ambigZone) { + wasAmbigTime = this._ambigTime; + this.utc(true); // keepLocalTime=true (for keeping date and time values) + // the above call to .utc()/.utcOffset() unfortunately might clear the ambig flags, so restore + this._ambigTime = wasAmbigTime || false; + // Mark the zone as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(), + // which clears the ambig flags. + this._ambigZone = true; + } + return this; // for chaining +}; +// Returns of the moment has a non-ambiguous timezone offset (boolean) +newMomentProto.hasZone = function () { + return !this._ambigZone; +}; +// implicitly marks a zone +newMomentProto.local = function (keepLocalTime) { + // for when converting from ambiguously-zoned to local, + // keep the time values when converting from UTC -> local + oldMomentProto.local.call(this, this._ambigZone || keepLocalTime); + // ensure non-ambiguous + // this probably already happened via local() -> utcOffset(), but don't rely on Moment's internals + this._ambigTime = false; + this._ambigZone = false; + return this; // for chaining +}; +// implicitly marks a zone +newMomentProto.utc = function (keepLocalTime) { + oldMomentProto.utc.call(this, keepLocalTime); + // ensure non-ambiguous + // this probably already happened via utc() -> utcOffset(), but don't rely on Moment's internals + this._ambigTime = false; + this._ambigZone = false; + return this; +}; +// implicitly marks a zone (will probably get called upon .utc() and .local()) +newMomentProto.utcOffset = function (tzo) { + if (tzo != null) { + // these assignments needs to happen before the original zone method is called. + // I forget why, something to do with a browser crash. + this._ambigTime = false; + this._ambigZone = false; + } + return oldMomentProto.utcOffset.apply(this, arguments); +}; + + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + +/* +USAGE: + import { default as EmitterMixin, EmitterInterface } from './EmitterMixin' +in class: + on: EmitterInterface['on'] + one: EmitterInterface['one'] + off: EmitterInterface['off'] + trigger: EmitterInterface['trigger'] + triggerWith: EmitterInterface['triggerWith'] + hasHandlers: EmitterInterface['hasHandlers'] +after class: + EmitterMixin.mixInto(TheClass) +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var Mixin_1 = __webpack_require__(14); +var EmitterMixin = /** @class */ (function (_super) { + tslib_1.__extends(EmitterMixin, _super); + function EmitterMixin() { + return _super !== null && _super.apply(this, arguments) || this; + } + // jQuery-ification via $(this) allows a non-DOM object to have + // the same event handling capabilities (including namespaces). + EmitterMixin.prototype.on = function (types, handler) { + $(this).on(types, this._prepareIntercept(handler)); + return this; // for chaining + }; + EmitterMixin.prototype.one = function (types, handler) { + $(this).one(types, this._prepareIntercept(handler)); + return this; // for chaining + }; + EmitterMixin.prototype._prepareIntercept = function (handler) { + // handlers are always called with an "event" object as their first param. + // sneak the `this` context and arguments into the extra parameter object + // and forward them on to the original handler. + var intercept = function (ev, extra) { + return handler.apply(extra.context || this, extra.args || []); + }; + // mimick jQuery's internal "proxy" system (risky, I know) + // causing all functions with the same .guid to appear to be the same. + // https://github.com/jquery/jquery/blob/2.2.4/src/core.js#L448 + // this is needed for calling .off with the original non-intercept handler. + if (!handler.guid) { + handler.guid = $.guid++; + } + intercept.guid = handler.guid; + return intercept; + }; + EmitterMixin.prototype.off = function (types, handler) { + $(this).off(types, handler); + return this; // for chaining + }; + EmitterMixin.prototype.trigger = function (types) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + // pass in "extra" info to the intercept + $(this).triggerHandler(types, { args: args }); + return this; // for chaining + }; + EmitterMixin.prototype.triggerWith = function (types, context, args) { + // `triggerHandler` is less reliant on the DOM compared to `trigger`. + // pass in "extra" info to the intercept. + $(this).triggerHandler(types, { context: context, args: args }); + return this; // for chaining + }; + EmitterMixin.prototype.hasHandlers = function (type) { + var hash = $._data(this, 'events'); // http://blog.jquery.com/2012/08/09/jquery-1-8-released/ + return hash && hash[type] && hash[type].length > 0; + }; + return EmitterMixin; +}(Mixin_1.default)); +exports.default = EmitterMixin; + + +/***/ }), +/* 12 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); +/* +Meant to be immutable +*/ +var ComponentFootprint = /** @class */ (function () { + function ComponentFootprint(unzonedRange, isAllDay) { + this.isAllDay = false; // component can choose to ignore this + this.unzonedRange = unzonedRange; + this.isAllDay = isAllDay; + } + /* + Only works for non-open-ended ranges. + */ + ComponentFootprint.prototype.toLegacy = function (calendar) { + return { + start: calendar.msToMoment(this.unzonedRange.startMs, this.isAllDay), + end: calendar.msToMoment(this.unzonedRange.endMs, this.isAllDay) + }; + }; + return ComponentFootprint; +}()); +exports.default = ComponentFootprint; + + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var EventDef_1 = __webpack_require__(34); +var EventInstance_1 = __webpack_require__(209); +var EventDateProfile_1 = __webpack_require__(17); +var SingleEventDef = /** @class */ (function (_super) { + tslib_1.__extends(SingleEventDef, _super); + function SingleEventDef() { + return _super !== null && _super.apply(this, arguments) || this; + } + /* + Will receive start/end params, but will be ignored. + */ + SingleEventDef.prototype.buildInstances = function () { + return [this.buildInstance()]; + }; + SingleEventDef.prototype.buildInstance = function () { + return new EventInstance_1.default(this, // definition + this.dateProfile); + }; + SingleEventDef.prototype.isAllDay = function () { + return this.dateProfile.isAllDay(); + }; + SingleEventDef.prototype.clone = function () { + var def = _super.prototype.clone.call(this); + def.dateProfile = this.dateProfile; + return def; + }; + SingleEventDef.prototype.rezone = function () { + var calendar = this.source.calendar; + var dateProfile = this.dateProfile; + this.dateProfile = new EventDateProfile_1.default(calendar.moment(dateProfile.start), dateProfile.end ? calendar.moment(dateProfile.end) : null, calendar); + }; + /* + NOTE: if super-method fails, should still attempt to apply + */ + SingleEventDef.prototype.applyManualStandardProps = function (rawProps) { + var superSuccess = _super.prototype.applyManualStandardProps.call(this, rawProps); + var dateProfile = EventDateProfile_1.default.parse(rawProps, this.source); // returns null on failure + if (dateProfile) { + this.dateProfile = dateProfile; + // make sure `date` shows up in the legacy event objects as-is + if (rawProps.date != null) { + this.miscProps.date = rawProps.date; + } + return superSuccess; + } + else { + return false; + } + }; + return SingleEventDef; +}(EventDef_1.default)); +exports.default = SingleEventDef; +// Parsing +// --------------------------------------------------------------------------------------------------------------------- +SingleEventDef.defineStandardProps({ + start: false, + date: false, + end: false, + allDay: false +}); + + +/***/ }), +/* 14 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var Mixin = /** @class */ (function () { + function Mixin() { + } + Mixin.mixInto = function (destClass) { + var _this = this; + Object.getOwnPropertyNames(this.prototype).forEach(function (name) { + if (!destClass.prototype[name]) { + destClass.prototype[name] = _this.prototype[name]; + } + }); + }; + /* + will override existing methods + TODO: remove! not used anymore + */ + Mixin.mixOver = function (destClass) { + var _this = this; + Object.getOwnPropertyNames(this.prototype).forEach(function (name) { + destClass.prototype[name] = _this.prototype[name]; + }); + }; + return Mixin; +}()); +exports.default = Mixin; + + +/***/ }), +/* 15 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var Interaction = /** @class */ (function () { + function Interaction(component) { + this.view = component._getView(); + this.component = component; + } + Interaction.prototype.opt = function (name) { + return this.view.opt(name); + }; + Interaction.prototype.end = function () { + // subclasses can implement + }; + return Interaction; +}()); +exports.default = Interaction; + + +/***/ }), +/* 16 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +exports.version = '3.9.0'; +// When introducing internal API incompatibilities (where fullcalendar plugins would break), +// the minor version of the calendar should be upped (ex: 2.7.2 -> 2.8.0) +// and the below integer should be incremented. +exports.internalApiVersion = 12; +var util_1 = __webpack_require__(4); +exports.applyAll = util_1.applyAll; +exports.debounce = util_1.debounce; +exports.isInt = util_1.isInt; +exports.htmlEscape = util_1.htmlEscape; +exports.cssToStr = util_1.cssToStr; +exports.proxy = util_1.proxy; +exports.capitaliseFirstLetter = util_1.capitaliseFirstLetter; +exports.getOuterRect = util_1.getOuterRect; +exports.getClientRect = util_1.getClientRect; +exports.getContentRect = util_1.getContentRect; +exports.getScrollbarWidths = util_1.getScrollbarWidths; +exports.preventDefault = util_1.preventDefault; +exports.parseFieldSpecs = util_1.parseFieldSpecs; +exports.compareByFieldSpecs = util_1.compareByFieldSpecs; +exports.compareByFieldSpec = util_1.compareByFieldSpec; +exports.flexibleCompare = util_1.flexibleCompare; +exports.computeGreatestUnit = util_1.computeGreatestUnit; +exports.divideRangeByDuration = util_1.divideRangeByDuration; +exports.divideDurationByDuration = util_1.divideDurationByDuration; +exports.multiplyDuration = util_1.multiplyDuration; +exports.durationHasTime = util_1.durationHasTime; +exports.log = util_1.log; +exports.warn = util_1.warn; +exports.removeExact = util_1.removeExact; +exports.intersectRects = util_1.intersectRects; +var date_formatting_1 = __webpack_require__(47); +exports.formatDate = date_formatting_1.formatDate; +exports.formatRange = date_formatting_1.formatRange; +exports.queryMostGranularFormatUnit = date_formatting_1.queryMostGranularFormatUnit; +var locale_1 = __webpack_require__(31); +exports.datepickerLocale = locale_1.datepickerLocale; +exports.locale = locale_1.locale; +var moment_ext_1 = __webpack_require__(10); +exports.moment = moment_ext_1.default; +var EmitterMixin_1 = __webpack_require__(11); +exports.EmitterMixin = EmitterMixin_1.default; +var ListenerMixin_1 = __webpack_require__(7); +exports.ListenerMixin = ListenerMixin_1.default; +var Model_1 = __webpack_require__(48); +exports.Model = Model_1.default; +var Constraints_1 = __webpack_require__(207); +exports.Constraints = Constraints_1.default; +var UnzonedRange_1 = __webpack_require__(5); +exports.UnzonedRange = UnzonedRange_1.default; +var ComponentFootprint_1 = __webpack_require__(12); +exports.ComponentFootprint = ComponentFootprint_1.default; +var BusinessHourGenerator_1 = __webpack_require__(212); +exports.BusinessHourGenerator = BusinessHourGenerator_1.default; +var EventDef_1 = __webpack_require__(34); +exports.EventDef = EventDef_1.default; +var EventDefMutation_1 = __webpack_require__(37); +exports.EventDefMutation = EventDefMutation_1.default; +var EventSourceParser_1 = __webpack_require__(38); +exports.EventSourceParser = EventSourceParser_1.default; +var EventSource_1 = __webpack_require__(6); +exports.EventSource = EventSource_1.default; +var ThemeRegistry_1 = __webpack_require__(51); +exports.defineThemeSystem = ThemeRegistry_1.defineThemeSystem; +var EventInstanceGroup_1 = __webpack_require__(18); +exports.EventInstanceGroup = EventInstanceGroup_1.default; +var ArrayEventSource_1 = __webpack_require__(52); +exports.ArrayEventSource = ArrayEventSource_1.default; +var FuncEventSource_1 = __webpack_require__(215); +exports.FuncEventSource = FuncEventSource_1.default; +var JsonFeedEventSource_1 = __webpack_require__(216); +exports.JsonFeedEventSource = JsonFeedEventSource_1.default; +var EventFootprint_1 = __webpack_require__(36); +exports.EventFootprint = EventFootprint_1.default; +var Class_1 = __webpack_require__(33); +exports.Class = Class_1.default; +var Mixin_1 = __webpack_require__(14); +exports.Mixin = Mixin_1.default; +var CoordCache_1 = __webpack_require__(53); +exports.CoordCache = CoordCache_1.default; +var DragListener_1 = __webpack_require__(54); +exports.DragListener = DragListener_1.default; +var Promise_1 = __webpack_require__(20); +exports.Promise = Promise_1.default; +var TaskQueue_1 = __webpack_require__(217); +exports.TaskQueue = TaskQueue_1.default; +var RenderQueue_1 = __webpack_require__(218); +exports.RenderQueue = RenderQueue_1.default; +var Scroller_1 = __webpack_require__(39); +exports.Scroller = Scroller_1.default; +var Theme_1 = __webpack_require__(19); +exports.Theme = Theme_1.default; +var DateComponent_1 = __webpack_require__(219); +exports.DateComponent = DateComponent_1.default; +var InteractiveDateComponent_1 = __webpack_require__(40); +exports.InteractiveDateComponent = InteractiveDateComponent_1.default; +var Calendar_1 = __webpack_require__(220); +exports.Calendar = Calendar_1.default; +var View_1 = __webpack_require__(41); +exports.View = View_1.default; +var ViewRegistry_1 = __webpack_require__(22); +exports.defineView = ViewRegistry_1.defineView; +exports.getViewConfig = ViewRegistry_1.getViewConfig; +var DayTableMixin_1 = __webpack_require__(55); +exports.DayTableMixin = DayTableMixin_1.default; +var BusinessHourRenderer_1 = __webpack_require__(56); +exports.BusinessHourRenderer = BusinessHourRenderer_1.default; +var EventRenderer_1 = __webpack_require__(42); +exports.EventRenderer = EventRenderer_1.default; +var FillRenderer_1 = __webpack_require__(57); +exports.FillRenderer = FillRenderer_1.default; +var HelperRenderer_1 = __webpack_require__(58); +exports.HelperRenderer = HelperRenderer_1.default; +var ExternalDropping_1 = __webpack_require__(222); +exports.ExternalDropping = ExternalDropping_1.default; +var EventResizing_1 = __webpack_require__(223); +exports.EventResizing = EventResizing_1.default; +var EventPointing_1 = __webpack_require__(59); +exports.EventPointing = EventPointing_1.default; +var EventDragging_1 = __webpack_require__(224); +exports.EventDragging = EventDragging_1.default; +var DateSelecting_1 = __webpack_require__(225); +exports.DateSelecting = DateSelecting_1.default; +var StandardInteractionsMixin_1 = __webpack_require__(60); +exports.StandardInteractionsMixin = StandardInteractionsMixin_1.default; +var AgendaView_1 = __webpack_require__(226); +exports.AgendaView = AgendaView_1.default; +var TimeGrid_1 = __webpack_require__(227); +exports.TimeGrid = TimeGrid_1.default; +var DayGrid_1 = __webpack_require__(61); +exports.DayGrid = DayGrid_1.default; +var BasicView_1 = __webpack_require__(62); +exports.BasicView = BasicView_1.default; +var MonthView_1 = __webpack_require__(229); +exports.MonthView = MonthView_1.default; +var ListView_1 = __webpack_require__(230); +exports.ListView = ListView_1.default; + + +/***/ }), +/* 17 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var UnzonedRange_1 = __webpack_require__(5); +/* +Meant to be immutable +*/ +var EventDateProfile = /** @class */ (function () { + function EventDateProfile(start, end, calendar) { + this.start = start; + this.end = end || null; + this.unzonedRange = this.buildUnzonedRange(calendar); + } + /* + Needs an EventSource object + */ + EventDateProfile.parse = function (rawProps, source) { + var startInput = rawProps.start || rawProps.date; + var endInput = rawProps.end; + if (!startInput) { + return false; + } + var calendar = source.calendar; + var start = calendar.moment(startInput); + var end = endInput ? calendar.moment(endInput) : null; + var forcedAllDay = rawProps.allDay; + var forceEventDuration = calendar.opt('forceEventDuration'); + if (!start.isValid()) { + return false; + } + if (end && (!end.isValid() || !end.isAfter(start))) { + end = null; + } + if (forcedAllDay == null) { + forcedAllDay = source.allDayDefault; + if (forcedAllDay == null) { + forcedAllDay = calendar.opt('allDayDefault'); + } + } + if (forcedAllDay === true) { + start.stripTime(); + if (end) { + end.stripTime(); + } + } + else if (forcedAllDay === false) { + if (!start.hasTime()) { + start.time(0); + } + if (end && !end.hasTime()) { + end.time(0); + } + } + if (!end && forceEventDuration) { + end = calendar.getDefaultEventEnd(!start.hasTime(), start); + } + return new EventDateProfile(start, end, calendar); + }; + EventDateProfile.isStandardProp = function (propName) { + return propName === 'start' || propName === 'date' || propName === 'end' || propName === 'allDay'; + }; + EventDateProfile.prototype.isAllDay = function () { + return !(this.start.hasTime() || (this.end && this.end.hasTime())); + }; + /* + Needs a Calendar object + */ + EventDateProfile.prototype.buildUnzonedRange = function (calendar) { + var startMs = this.start.clone().stripZone().valueOf(); + var endMs = this.getEnd(calendar).stripZone().valueOf(); + return new UnzonedRange_1.default(startMs, endMs); + }; + /* + Needs a Calendar object + */ + EventDateProfile.prototype.getEnd = function (calendar) { + return this.end ? + this.end.clone() : + // derive the end from the start and allDay. compute allDay if necessary + calendar.getDefaultEventEnd(this.isAllDay(), this.start); + }; + return EventDateProfile; +}()); +exports.default = EventDateProfile; + + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var UnzonedRange_1 = __webpack_require__(5); +var util_1 = __webpack_require__(35); +var EventRange_1 = __webpack_require__(211); +/* +It's expected that there will be at least one EventInstance, +OR that an explicitEventDef is assigned. +*/ +var EventInstanceGroup = /** @class */ (function () { + function EventInstanceGroup(eventInstances) { + this.eventInstances = eventInstances || []; + } + EventInstanceGroup.prototype.getAllEventRanges = function (constraintRange) { + if (constraintRange) { + return this.sliceNormalRenderRanges(constraintRange); + } + else { + return this.eventInstances.map(util_1.eventInstanceToEventRange); + } + }; + EventInstanceGroup.prototype.sliceRenderRanges = function (constraintRange) { + if (this.isInverse()) { + return this.sliceInverseRenderRanges(constraintRange); + } + else { + return this.sliceNormalRenderRanges(constraintRange); + } + }; + EventInstanceGroup.prototype.sliceNormalRenderRanges = function (constraintRange) { + var eventInstances = this.eventInstances; + var i; + var eventInstance; + var slicedRange; + var slicedEventRanges = []; + for (i = 0; i < eventInstances.length; i++) { + eventInstance = eventInstances[i]; + slicedRange = eventInstance.dateProfile.unzonedRange.intersect(constraintRange); + if (slicedRange) { + slicedEventRanges.push(new EventRange_1.default(slicedRange, eventInstance.def, eventInstance)); + } + } + return slicedEventRanges; + }; + EventInstanceGroup.prototype.sliceInverseRenderRanges = function (constraintRange) { + var unzonedRanges = this.eventInstances.map(util_1.eventInstanceToUnzonedRange); + var ownerDef = this.getEventDef(); + unzonedRanges = UnzonedRange_1.default.invertRanges(unzonedRanges, constraintRange); + return unzonedRanges.map(function (unzonedRange) { + return new EventRange_1.default(unzonedRange, ownerDef); // don't give an EventInstance + }); + }; + EventInstanceGroup.prototype.isInverse = function () { + return this.getEventDef().hasInverseRendering(); + }; + EventInstanceGroup.prototype.getEventDef = function () { + return this.explicitEventDef || this.eventInstances[0].def; + }; + return EventInstanceGroup; +}()); +exports.default = EventInstanceGroup; + + +/***/ }), +/* 19 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var Theme = /** @class */ (function () { + function Theme(optionsManager) { + this.optionsManager = optionsManager; + this.processIconOverride(); + } + Theme.prototype.processIconOverride = function () { + if (this.iconOverrideOption) { + this.setIconOverride(this.optionsManager.get(this.iconOverrideOption)); + } + }; + Theme.prototype.setIconOverride = function (iconOverrideHash) { + var iconClassesCopy; + var buttonName; + if ($.isPlainObject(iconOverrideHash)) { + iconClassesCopy = $.extend({}, this.iconClasses); + for (buttonName in iconOverrideHash) { + iconClassesCopy[buttonName] = this.applyIconOverridePrefix(iconOverrideHash[buttonName]); + } + this.iconClasses = iconClassesCopy; + } + else if (iconOverrideHash === false) { + this.iconClasses = {}; + } + }; + Theme.prototype.applyIconOverridePrefix = function (className) { + var prefix = this.iconOverridePrefix; + if (prefix && className.indexOf(prefix) !== 0) { + className = prefix + className; + } + return className; + }; + Theme.prototype.getClass = function (key) { + return this.classes[key] || ''; + }; + Theme.prototype.getIconClass = function (buttonName) { + var className = this.iconClasses[buttonName]; + if (className) { + return this.baseIconClass + ' ' + className; + } + return ''; + }; + Theme.prototype.getCustomButtonIconClass = function (customButtonProps) { + var className; + if (this.iconOverrideCustomButtonOption) { + className = customButtonProps[this.iconOverrideCustomButtonOption]; + if (className) { + return this.baseIconClass + ' ' + this.applyIconOverridePrefix(className); + } + } + return ''; + }; + return Theme; +}()); +exports.default = Theme; +Theme.prototype.classes = {}; +Theme.prototype.iconClasses = {}; +Theme.prototype.baseIconClass = ''; +Theme.prototype.iconOverridePrefix = ''; + + +/***/ }), +/* 20 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var PromiseStub = { + construct: function (executor) { + var deferred = $.Deferred(); + var promise = deferred.promise(); + if (typeof executor === 'function') { + executor(function (val) { + deferred.resolve(val); + attachImmediatelyResolvingThen(promise, val); + }, function () { + deferred.reject(); + attachImmediatelyRejectingThen(promise); + }); + } + return promise; + }, + resolve: function (val) { + var deferred = $.Deferred().resolve(val); + var promise = deferred.promise(); + attachImmediatelyResolvingThen(promise, val); + return promise; + }, + reject: function () { + var deferred = $.Deferred().reject(); + var promise = deferred.promise(); + attachImmediatelyRejectingThen(promise); + return promise; + } +}; +exports.default = PromiseStub; +function attachImmediatelyResolvingThen(promise, val) { + promise.then = function (onResolve) { + if (typeof onResolve === 'function') { + return PromiseStub.resolve(onResolve(val)); + } + return promise; + }; +} +function attachImmediatelyRejectingThen(promise) { + promise.then = function (onResolve, onReject) { + if (typeof onReject === 'function') { + onReject(); + } + return promise; + }; +} + + +/***/ }), +/* 21 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var exportHooks = __webpack_require__(16); +var EmitterMixin_1 = __webpack_require__(11); +var ListenerMixin_1 = __webpack_require__(7); +exportHooks.touchMouseIgnoreWait = 500; +var globalEmitter = null; +var neededCount = 0; +/* +Listens to document and window-level user-interaction events, like touch events and mouse events, +and fires these events as-is to whoever is observing a GlobalEmitter. +Best when used as a singleton via GlobalEmitter.get() + +Normalizes mouse/touch events. For examples: +- ignores the the simulated mouse events that happen after a quick tap: mousemove+mousedown+mouseup+click +- compensates for various buggy scenarios where a touchend does not fire +*/ +var GlobalEmitter = /** @class */ (function () { + function GlobalEmitter() { + this.isTouching = false; + this.mouseIgnoreDepth = 0; + } + // gets the singleton + GlobalEmitter.get = function () { + if (!globalEmitter) { + globalEmitter = new GlobalEmitter(); + globalEmitter.bind(); + } + return globalEmitter; + }; + // called when an object knows it will need a GlobalEmitter in the near future. + GlobalEmitter.needed = function () { + GlobalEmitter.get(); // ensures globalEmitter + neededCount++; + }; + // called when the object that originally called needed() doesn't need a GlobalEmitter anymore. + GlobalEmitter.unneeded = function () { + neededCount--; + if (!neededCount) { + globalEmitter.unbind(); + globalEmitter = null; + } + }; + GlobalEmitter.prototype.bind = function () { + var _this = this; + this.listenTo($(document), { + touchstart: this.handleTouchStart, + touchcancel: this.handleTouchCancel, + touchend: this.handleTouchEnd, + mousedown: this.handleMouseDown, + mousemove: this.handleMouseMove, + mouseup: this.handleMouseUp, + click: this.handleClick, + selectstart: this.handleSelectStart, + contextmenu: this.handleContextMenu + }); + // because we need to call preventDefault + // because https://www.chromestatus.com/features/5093566007214080 + // TODO: investigate performance because this is a global handler + window.addEventListener('touchmove', this.handleTouchMoveProxy = function (ev) { + _this.handleTouchMove($.Event(ev)); + }, { passive: false } // allows preventDefault() + ); + // attach a handler to get called when ANY scroll action happens on the page. + // this was impossible to do with normal on/off because 'scroll' doesn't bubble. + // http://stackoverflow.com/a/32954565/96342 + window.addEventListener('scroll', this.handleScrollProxy = function (ev) { + _this.handleScroll($.Event(ev)); + }, true // useCapture + ); + }; + GlobalEmitter.prototype.unbind = function () { + this.stopListeningTo($(document)); + window.removeEventListener('touchmove', this.handleTouchMoveProxy); + window.removeEventListener('scroll', this.handleScrollProxy, true // useCapture + ); + }; + // Touch Handlers + // ----------------------------------------------------------------------------------------------------------------- + GlobalEmitter.prototype.handleTouchStart = function (ev) { + // if a previous touch interaction never ended with a touchend, then implicitly end it, + // but since a new touch interaction is about to begin, don't start the mouse ignore period. + this.stopTouch(ev, true); // skipMouseIgnore=true + this.isTouching = true; + this.trigger('touchstart', ev); + }; + GlobalEmitter.prototype.handleTouchMove = function (ev) { + if (this.isTouching) { + this.trigger('touchmove', ev); + } + }; + GlobalEmitter.prototype.handleTouchCancel = function (ev) { + if (this.isTouching) { + this.trigger('touchcancel', ev); + // Have touchcancel fire an artificial touchend. That way, handlers won't need to listen to both. + // If touchend fires later, it won't have any effect b/c isTouching will be false. + this.stopTouch(ev); + } + }; + GlobalEmitter.prototype.handleTouchEnd = function (ev) { + this.stopTouch(ev); + }; + // Mouse Handlers + // ----------------------------------------------------------------------------------------------------------------- + GlobalEmitter.prototype.handleMouseDown = function (ev) { + if (!this.shouldIgnoreMouse()) { + this.trigger('mousedown', ev); + } + }; + GlobalEmitter.prototype.handleMouseMove = function (ev) { + if (!this.shouldIgnoreMouse()) { + this.trigger('mousemove', ev); + } + }; + GlobalEmitter.prototype.handleMouseUp = function (ev) { + if (!this.shouldIgnoreMouse()) { + this.trigger('mouseup', ev); + } + }; + GlobalEmitter.prototype.handleClick = function (ev) { + if (!this.shouldIgnoreMouse()) { + this.trigger('click', ev); + } + }; + // Misc Handlers + // ----------------------------------------------------------------------------------------------------------------- + GlobalEmitter.prototype.handleSelectStart = function (ev) { + this.trigger('selectstart', ev); + }; + GlobalEmitter.prototype.handleContextMenu = function (ev) { + this.trigger('contextmenu', ev); + }; + GlobalEmitter.prototype.handleScroll = function (ev) { + this.trigger('scroll', ev); + }; + // Utils + // ----------------------------------------------------------------------------------------------------------------- + GlobalEmitter.prototype.stopTouch = function (ev, skipMouseIgnore) { + if (skipMouseIgnore === void 0) { skipMouseIgnore = false; } + if (this.isTouching) { + this.isTouching = false; + this.trigger('touchend', ev); + if (!skipMouseIgnore) { + this.startTouchMouseIgnore(); + } + } + }; + GlobalEmitter.prototype.startTouchMouseIgnore = function () { + var _this = this; + var wait = exportHooks.touchMouseIgnoreWait; + if (wait) { + this.mouseIgnoreDepth++; + setTimeout(function () { + _this.mouseIgnoreDepth--; + }, wait); + } + }; + GlobalEmitter.prototype.shouldIgnoreMouse = function () { + return this.isTouching || Boolean(this.mouseIgnoreDepth); + }; + return GlobalEmitter; +}()); +exports.default = GlobalEmitter; +ListenerMixin_1.default.mixInto(GlobalEmitter); +EmitterMixin_1.default.mixInto(GlobalEmitter); + + +/***/ }), +/* 22 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var exportHooks = __webpack_require__(16); +exports.viewHash = {}; +exportHooks.views = exports.viewHash; +function defineView(viewName, viewConfig) { + exports.viewHash[viewName] = viewConfig; +} +exports.defineView = defineView; +function getViewConfig(viewName) { + return exports.viewHash[viewName]; +} +exports.getViewConfig = getViewConfig; + + +/***/ }), +/* 23 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var util_1 = __webpack_require__(4); +var DragListener_1 = __webpack_require__(54); +/* Tracks mouse movements over a component and raises events about which hit the mouse is over. +------------------------------------------------------------------------------------------------------------------------ +options: +- subjectEl +- subjectCenter +*/ +var HitDragListener = /** @class */ (function (_super) { + tslib_1.__extends(HitDragListener, _super); + function HitDragListener(component, options) { + var _this = _super.call(this, options) || this; + _this.component = component; + return _this; + } + // Called when drag listening starts (but a real drag has not necessarily began). + // ev might be undefined if dragging was started manually. + HitDragListener.prototype.handleInteractionStart = function (ev) { + var subjectEl = this.subjectEl; + var subjectRect; + var origPoint; + var point; + this.component.hitsNeeded(); + this.computeScrollBounds(); // for autoscroll + if (ev) { + origPoint = { left: util_1.getEvX(ev), top: util_1.getEvY(ev) }; + point = origPoint; + // constrain the point to bounds of the element being dragged + if (subjectEl) { + subjectRect = util_1.getOuterRect(subjectEl); // used for centering as well + point = util_1.constrainPoint(point, subjectRect); + } + this.origHit = this.queryHit(point.left, point.top); + // treat the center of the subject as the collision point? + if (subjectEl && this.options.subjectCenter) { + // only consider the area the subject overlaps the hit. best for large subjects. + // TODO: skip this if hit didn't supply left/right/top/bottom + if (this.origHit) { + subjectRect = util_1.intersectRects(this.origHit, subjectRect) || + subjectRect; // in case there is no intersection + } + point = util_1.getRectCenter(subjectRect); + } + this.coordAdjust = util_1.diffPoints(point, origPoint); // point - origPoint + } + else { + this.origHit = null; + this.coordAdjust = null; + } + // call the super-method. do it after origHit has been computed + _super.prototype.handleInteractionStart.call(this, ev); + }; + // Called when the actual drag has started + HitDragListener.prototype.handleDragStart = function (ev) { + var hit; + _super.prototype.handleDragStart.call(this, ev); + // might be different from this.origHit if the min-distance is large + hit = this.queryHit(util_1.getEvX(ev), util_1.getEvY(ev)); + // report the initial hit the mouse is over + // especially important if no min-distance and drag starts immediately + if (hit) { + this.handleHitOver(hit); + } + }; + // Called when the drag moves + HitDragListener.prototype.handleDrag = function (dx, dy, ev) { + var hit; + _super.prototype.handleDrag.call(this, dx, dy, ev); + hit = this.queryHit(util_1.getEvX(ev), util_1.getEvY(ev)); + if (!isHitsEqual(hit, this.hit)) { + if (this.hit) { + this.handleHitOut(); + } + if (hit) { + this.handleHitOver(hit); + } + } + }; + // Called when dragging has been stopped + HitDragListener.prototype.handleDragEnd = function (ev) { + this.handleHitDone(); + _super.prototype.handleDragEnd.call(this, ev); + }; + // Called when a the mouse has just moved over a new hit + HitDragListener.prototype.handleHitOver = function (hit) { + var isOrig = isHitsEqual(hit, this.origHit); + this.hit = hit; + this.trigger('hitOver', this.hit, isOrig, this.origHit); + }; + // Called when the mouse has just moved out of a hit + HitDragListener.prototype.handleHitOut = function () { + if (this.hit) { + this.trigger('hitOut', this.hit); + this.handleHitDone(); + this.hit = null; + } + }; + // Called after a hitOut. Also called before a dragStop + HitDragListener.prototype.handleHitDone = function () { + if (this.hit) { + this.trigger('hitDone', this.hit); + } + }; + // Called when the interaction ends, whether there was a real drag or not + HitDragListener.prototype.handleInteractionEnd = function (ev, isCancelled) { + _super.prototype.handleInteractionEnd.call(this, ev, isCancelled); + this.origHit = null; + this.hit = null; + this.component.hitsNotNeeded(); + }; + // Called when scrolling has stopped, whether through auto scroll, or the user scrolling + HitDragListener.prototype.handleScrollEnd = function () { + _super.prototype.handleScrollEnd.call(this); + // hits' absolute positions will be in new places after a user's scroll. + // HACK for recomputing. + if (this.isDragging) { + this.component.releaseHits(); + this.component.prepareHits(); + } + }; + // Gets the hit underneath the coordinates for the given mouse event + HitDragListener.prototype.queryHit = function (left, top) { + if (this.coordAdjust) { + left += this.coordAdjust.left; + top += this.coordAdjust.top; + } + return this.component.queryHit(left, top); + }; + return HitDragListener; +}(DragListener_1.default)); +exports.default = HitDragListener; +// Returns `true` if the hits are identically equal. `false` otherwise. Must be from the same component. +// Two null values will be considered equal, as two "out of the component" states are the same. +function isHitsEqual(hit0, hit1) { + if (!hit0 && !hit1) { + return true; + } + if (hit0 && hit1) { + return hit0.component === hit1.component && + isHitPropsWithin(hit0, hit1) && + isHitPropsWithin(hit1, hit0); // ensures all props are identical + } + return false; +} +// Returns true if all of subHit's non-standard properties are within superHit +function isHitPropsWithin(subHit, superHit) { + for (var propName in subHit) { + if (!/^(component|left|right|top|bottom)$/.test(propName)) { + if (subHit[propName] !== superHit[propName]) { + return false; + } + } + } + return true; +} + + +/***/ }), +/* 24 */, +/* 25 */, +/* 26 */, +/* 27 */, +/* 28 */, +/* 29 */, +/* 30 */, +/* 31 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var moment = __webpack_require__(0); +var exportHooks = __webpack_require__(16); +var options_1 = __webpack_require__(32); +var util_1 = __webpack_require__(4); +exports.localeOptionHash = {}; +exportHooks.locales = exports.localeOptionHash; +// NOTE: can't guarantee any of these computations will run because not every locale has datepicker +// configs, so make sure there are English fallbacks for these in the defaults file. +var dpComputableOptions = { + buttonText: function (dpOptions) { + return { + // the translations sometimes wrongly contain HTML entities + prev: util_1.stripHtmlEntities(dpOptions.prevText), + next: util_1.stripHtmlEntities(dpOptions.nextText), + today: util_1.stripHtmlEntities(dpOptions.currentText) + }; + }, + // Produces format strings like "MMMM YYYY" -> "September 2014" + monthYearFormat: function (dpOptions) { + return dpOptions.showMonthAfterYear ? + 'YYYY[' + dpOptions.yearSuffix + '] MMMM' : + 'MMMM YYYY[' + dpOptions.yearSuffix + ']'; + } +}; +var momComputableOptions = { + // Produces format strings like "ddd M/D" -> "Fri 9/15" + dayOfMonthFormat: function (momOptions, fcOptions) { + var format = momOptions.longDateFormat('l'); // for the format like "M/D/YYYY" + // strip the year off the edge, as well as other misc non-whitespace chars + format = format.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g, ''); + if (fcOptions.isRTL) { + format += ' ddd'; // for RTL, add day-of-week to end + } + else { + format = 'ddd ' + format; // for LTR, add day-of-week to beginning + } + return format; + }, + // Produces format strings like "h:mma" -> "6:00pm" + mediumTimeFormat: function (momOptions) { + return momOptions.longDateFormat('LT') + .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand + }, + // Produces format strings like "h(:mm)a" -> "6pm" / "6:30pm" + smallTimeFormat: function (momOptions) { + return momOptions.longDateFormat('LT') + .replace(':mm', '(:mm)') + .replace(/(\Wmm)$/, '($1)') // like above, but for foreign locales + .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand + }, + // Produces format strings like "h(:mm)t" -> "6p" / "6:30p" + extraSmallTimeFormat: function (momOptions) { + return momOptions.longDateFormat('LT') + .replace(':mm', '(:mm)') + .replace(/(\Wmm)$/, '($1)') // like above, but for foreign locales + .replace(/\s*a$/i, 't'); // convert to AM/PM/am/pm to lowercase one-letter. remove any spaces beforehand + }, + // Produces format strings like "ha" / "H" -> "6pm" / "18" + hourFormat: function (momOptions) { + return momOptions.longDateFormat('LT') + .replace(':mm', '') + .replace(/(\Wmm)$/, '') // like above, but for foreign locales + .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand + }, + // Produces format strings like "h:mm" -> "6:30" (with no AM/PM) + noMeridiemTimeFormat: function (momOptions) { + return momOptions.longDateFormat('LT') + .replace(/\s*a$/i, ''); // remove trailing AM/PM + } +}; +// options that should be computed off live calendar options (considers override options) +// TODO: best place for this? related to locale? +// TODO: flipping text based on isRTL is a bad idea because the CSS `direction` might want to handle it +var instanceComputableOptions = { + // Produces format strings for results like "Mo 16" + smallDayDateFormat: function (options) { + return options.isRTL ? + 'D dd' : + 'dd D'; + }, + // Produces format strings for results like "Wk 5" + weekFormat: function (options) { + return options.isRTL ? + 'w[ ' + options.weekNumberTitle + ']' : + '[' + options.weekNumberTitle + ' ]w'; + }, + // Produces format strings for results like "Wk5" + smallWeekFormat: function (options) { + return options.isRTL ? + 'w[' + options.weekNumberTitle + ']' : + '[' + options.weekNumberTitle + ']w'; + } +}; +// TODO: make these computable properties in optionsManager +function populateInstanceComputableOptions(options) { + $.each(instanceComputableOptions, function (name, func) { + if (options[name] == null) { + options[name] = func(options); + } + }); +} +exports.populateInstanceComputableOptions = populateInstanceComputableOptions; +// Initialize jQuery UI datepicker translations while using some of the translations +// Will set this as the default locales for datepicker. +function datepickerLocale(localeCode, dpLocaleCode, dpOptions) { + // get the FullCalendar internal option hash for this locale. create if necessary + var fcOptions = exports.localeOptionHash[localeCode] || (exports.localeOptionHash[localeCode] = {}); + // transfer some simple options from datepicker to fc + fcOptions.isRTL = dpOptions.isRTL; + fcOptions.weekNumberTitle = dpOptions.weekHeader; + // compute some more complex options from datepicker + $.each(dpComputableOptions, function (name, func) { + fcOptions[name] = func(dpOptions); + }); + var jqDatePicker = $.datepicker; + // is jQuery UI Datepicker is on the page? + if (jqDatePicker) { + // Register the locale data. + // FullCalendar and MomentJS use locale codes like "pt-br" but Datepicker + // does it like "pt-BR" or if it doesn't have the locale, maybe just "pt". + // Make an alias so the locale can be referenced either way. + jqDatePicker.regional[dpLocaleCode] = + jqDatePicker.regional[localeCode] = // alias + dpOptions; + // Alias 'en' to the default locale data. Do this every time. + jqDatePicker.regional.en = jqDatePicker.regional['']; + // Set as Datepicker's global defaults. + jqDatePicker.setDefaults(dpOptions); + } +} +exports.datepickerLocale = datepickerLocale; +// Sets FullCalendar-specific translations. Will set the locales as the global default. +function locale(localeCode, newFcOptions) { + var fcOptions; + var momOptions; + // get the FullCalendar internal option hash for this locale. create if necessary + fcOptions = exports.localeOptionHash[localeCode] || (exports.localeOptionHash[localeCode] = {}); + // provided new options for this locales? merge them in + if (newFcOptions) { + fcOptions = exports.localeOptionHash[localeCode] = options_1.mergeOptions([fcOptions, newFcOptions]); + } + // compute locale options that weren't defined. + // always do this. newFcOptions can be undefined when initializing from i18n file, + // so no way to tell if this is an initialization or a default-setting. + momOptions = getMomentLocaleData(localeCode); // will fall back to en + $.each(momComputableOptions, function (name, func) { + if (fcOptions[name] == null) { + fcOptions[name] = (func)(momOptions, fcOptions); + } + }); + // set it as the default locale for FullCalendar + options_1.globalDefaults.locale = localeCode; +} +exports.locale = locale; +// Returns moment's internal locale data. If doesn't exist, returns English. +function getMomentLocaleData(localeCode) { + return moment.localeData(localeCode) || moment.localeData('en'); +} +exports.getMomentLocaleData = getMomentLocaleData; +// Initialize English by forcing computation of moment-derived options. +// Also, sets it as the default. +locale('en', options_1.englishDefaults); + + +/***/ }), +/* 32 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var util_1 = __webpack_require__(4); +exports.globalDefaults = { + titleRangeSeparator: ' \u2013 ', + monthYearFormat: 'MMMM YYYY', + defaultTimedEventDuration: '02:00:00', + defaultAllDayEventDuration: { days: 1 }, + forceEventDuration: false, + nextDayThreshold: '09:00:00', + // display + columnHeader: true, + defaultView: 'month', + aspectRatio: 1.35, + header: { + left: 'title', + center: '', + right: 'today prev,next' + }, + weekends: true, + weekNumbers: false, + weekNumberTitle: 'W', + weekNumberCalculation: 'local', + // editable: false, + // nowIndicator: false, + scrollTime: '06:00:00', + minTime: '00:00:00', + maxTime: '24:00:00', + showNonCurrentDates: true, + // event ajax + lazyFetching: true, + startParam: 'start', + endParam: 'end', + timezoneParam: 'timezone', + timezone: false, + // allDayDefault: undefined, + // locale + locale: null, + isRTL: false, + buttonText: { + prev: 'prev', + next: 'next', + prevYear: 'prev year', + nextYear: 'next year', + year: 'year', + today: 'today', + month: 'month', + week: 'week', + day: 'day' + }, + // buttonIcons: null, + allDayText: 'all-day', + // allows setting a min-height to the event segment to prevent short events overlapping each other + agendaEventMinHeight: 0, + // jquery-ui theming + theme: false, + // themeButtonIcons: null, + // eventResizableFromStart: false, + dragOpacity: .75, + dragRevertDuration: 500, + dragScroll: true, + // selectable: false, + unselectAuto: true, + // selectMinDistance: 0, + dropAccept: '*', + eventOrder: 'title', + // eventRenderWait: null, + eventLimit: false, + eventLimitText: 'more', + eventLimitClick: 'popover', + dayPopoverFormat: 'LL', + handleWindowResize: true, + windowResizeDelay: 100, + longPressDelay: 1000 +}; +exports.englishDefaults = { + dayPopoverFormat: 'dddd, MMMM D' +}; +exports.rtlDefaults = { + header: { + left: 'next,prev today', + center: '', + right: 'title' + }, + buttonIcons: { + prev: 'right-single-arrow', + next: 'left-single-arrow', + prevYear: 'right-double-arrow', + nextYear: 'left-double-arrow' + }, + themeButtonIcons: { + prev: 'circle-triangle-e', + next: 'circle-triangle-w', + nextYear: 'seek-prev', + prevYear: 'seek-next' + } +}; +var complexOptions = [ + 'header', + 'footer', + 'buttonText', + 'buttonIcons', + 'themeButtonIcons' +]; +// Merges an array of option objects into a single object +function mergeOptions(optionObjs) { + return util_1.mergeProps(optionObjs, complexOptions); +} +exports.mergeOptions = mergeOptions; + + +/***/ }), +/* 33 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var util_1 = __webpack_require__(4); +// Class that all other classes will inherit from +var Class = /** @class */ (function () { + function Class() { + } + // Called on a class to create a subclass. + // LIMITATION: cannot provide a constructor! + Class.extend = function (members) { + var SubClass = /** @class */ (function (_super) { + tslib_1.__extends(SubClass, _super); + function SubClass() { + return _super !== null && _super.apply(this, arguments) || this; + } + return SubClass; + }(this)); + util_1.copyOwnProps(members, SubClass.prototype); + return SubClass; + }; + // Adds new member variables/methods to the class's prototype. + // Can be called with another class, or a plain object hash containing new members. + Class.mixin = function (members) { + util_1.copyOwnProps(members, this.prototype); + }; + return Class; +}()); +exports.default = Class; + + +/***/ }), +/* 34 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var ParsableModelMixin_1 = __webpack_require__(208); +var EventDef = /** @class */ (function () { + function EventDef(source) { + this.source = source; + this.className = []; + this.miscProps = {}; + } + EventDef.parse = function (rawInput, source) { + var def = new this(source); + if (def.applyProps(rawInput)) { + return def; + } + return false; + }; + EventDef.normalizeId = function (id) { + return String(id); + }; + EventDef.generateId = function () { + return '_fc' + (EventDef.uuid++); + }; + EventDef.prototype.clone = function () { + var copy = new this.constructor(this.source); + copy.id = this.id; + copy.rawId = this.rawId; + copy.uid = this.uid; // not really unique anymore :( + EventDef.copyVerbatimStandardProps(this, copy); + copy.className = this.className.slice(); // copy + copy.miscProps = $.extend({}, this.miscProps); + return copy; + }; + EventDef.prototype.hasInverseRendering = function () { + return this.getRendering() === 'inverse-background'; + }; + EventDef.prototype.hasBgRendering = function () { + var rendering = this.getRendering(); + return rendering === 'inverse-background' || rendering === 'background'; + }; + EventDef.prototype.getRendering = function () { + if (this.rendering != null) { + return this.rendering; + } + return this.source.rendering; + }; + EventDef.prototype.getConstraint = function () { + if (this.constraint != null) { + return this.constraint; + } + if (this.source.constraint != null) { + return this.source.constraint; + } + return this.source.calendar.opt('eventConstraint'); // what about View option? + }; + EventDef.prototype.getOverlap = function () { + if (this.overlap != null) { + return this.overlap; + } + if (this.source.overlap != null) { + return this.source.overlap; + } + return this.source.calendar.opt('eventOverlap'); // what about View option? + }; + EventDef.prototype.isStartExplicitlyEditable = function () { + if (this.startEditable != null) { + return this.startEditable; + } + return this.source.startEditable; + }; + EventDef.prototype.isDurationExplicitlyEditable = function () { + if (this.durationEditable != null) { + return this.durationEditable; + } + return this.source.durationEditable; + }; + EventDef.prototype.isExplicitlyEditable = function () { + if (this.editable != null) { + return this.editable; + } + return this.source.editable; + }; + EventDef.prototype.toLegacy = function () { + var obj = $.extend({}, this.miscProps); + obj._id = this.uid; + obj.source = this.source; + obj.className = this.className.slice(); // copy + obj.allDay = this.isAllDay(); + if (this.rawId != null) { + obj.id = this.rawId; + } + EventDef.copyVerbatimStandardProps(this, obj); + return obj; + }; + EventDef.prototype.applyManualStandardProps = function (rawProps) { + if (rawProps.id != null) { + this.id = EventDef.normalizeId((this.rawId = rawProps.id)); + } + else { + this.id = EventDef.generateId(); + } + if (rawProps._id != null) { + this.uid = String(rawProps._id); + } + else { + this.uid = EventDef.generateId(); + } + // TODO: converge with EventSource + if ($.isArray(rawProps.className)) { + this.className = rawProps.className; + } + if (typeof rawProps.className === 'string') { + this.className = rawProps.className.split(/\s+/); + } + return true; + }; + EventDef.prototype.applyMiscProps = function (rawProps) { + $.extend(this.miscProps, rawProps); + }; + EventDef.uuid = 0; + EventDef.defineStandardProps = ParsableModelMixin_1.default.defineStandardProps; + EventDef.copyVerbatimStandardProps = ParsableModelMixin_1.default.copyVerbatimStandardProps; + return EventDef; +}()); +exports.default = EventDef; +ParsableModelMixin_1.default.mixInto(EventDef); +EventDef.defineStandardProps({ + // not automatically assigned (`false`) + _id: false, + id: false, + className: false, + source: false, + // automatically assigned (`true`) + title: true, + url: true, + rendering: true, + constraint: true, + overlap: true, + editable: true, + startEditable: true, + durationEditable: true, + color: true, + backgroundColor: true, + borderColor: true, + textColor: true +}); + + +/***/ }), +/* 35 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var EventRange_1 = __webpack_require__(211); +var EventFootprint_1 = __webpack_require__(36); +var ComponentFootprint_1 = __webpack_require__(12); +function eventDefsToEventInstances(eventDefs, unzonedRange) { + var eventInstances = []; + var i; + for (i = 0; i < eventDefs.length; i++) { + eventInstances.push.apply(eventInstances, // append + eventDefs[i].buildInstances(unzonedRange)); + } + return eventInstances; +} +exports.eventDefsToEventInstances = eventDefsToEventInstances; +function eventInstanceToEventRange(eventInstance) { + return new EventRange_1.default(eventInstance.dateProfile.unzonedRange, eventInstance.def, eventInstance); +} +exports.eventInstanceToEventRange = eventInstanceToEventRange; +function eventRangeToEventFootprint(eventRange) { + return new EventFootprint_1.default(new ComponentFootprint_1.default(eventRange.unzonedRange, eventRange.eventDef.isAllDay()), eventRange.eventDef, eventRange.eventInstance // might not exist + ); +} +exports.eventRangeToEventFootprint = eventRangeToEventFootprint; +function eventInstanceToUnzonedRange(eventInstance) { + return eventInstance.dateProfile.unzonedRange; +} +exports.eventInstanceToUnzonedRange = eventInstanceToUnzonedRange; +function eventFootprintToComponentFootprint(eventFootprint) { + return eventFootprint.componentFootprint; +} +exports.eventFootprintToComponentFootprint = eventFootprintToComponentFootprint; + + +/***/ }), +/* 36 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var EventFootprint = /** @class */ (function () { + function EventFootprint(componentFootprint, eventDef, eventInstance) { + this.componentFootprint = componentFootprint; + this.eventDef = eventDef; + if (eventInstance) { + this.eventInstance = eventInstance; + } + } + EventFootprint.prototype.getEventLegacy = function () { + return (this.eventInstance || this.eventDef).toLegacy(); + }; + return EventFootprint; +}()); +exports.default = EventFootprint; + + +/***/ }), +/* 37 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var util_1 = __webpack_require__(4); +var EventDateProfile_1 = __webpack_require__(17); +var EventDef_1 = __webpack_require__(34); +var EventDefDateMutation_1 = __webpack_require__(50); +var SingleEventDef_1 = __webpack_require__(13); +var EventDefMutation = /** @class */ (function () { + function EventDefMutation() { + } + EventDefMutation.createFromRawProps = function (eventInstance, rawProps, largeUnit) { + var eventDef = eventInstance.def; + var dateProps = {}; + var standardProps = {}; + var miscProps = {}; + var verbatimStandardProps = {}; + var eventDefId = null; + var className = null; + var propName; + var dateProfile; + var dateMutation; + var defMutation; + for (propName in rawProps) { + if (EventDateProfile_1.default.isStandardProp(propName)) { + dateProps[propName] = rawProps[propName]; + } + else if (eventDef.isStandardProp(propName)) { + standardProps[propName] = rawProps[propName]; + } + else if (eventDef.miscProps[propName] !== rawProps[propName]) { + miscProps[propName] = rawProps[propName]; + } + } + dateProfile = EventDateProfile_1.default.parse(dateProps, eventDef.source); + if (dateProfile) { + dateMutation = EventDefDateMutation_1.default.createFromDiff(eventInstance.dateProfile, dateProfile, largeUnit); + } + if (standardProps.id !== eventDef.id) { + eventDefId = standardProps.id; // only apply if there's a change + } + if (!util_1.isArraysEqual(standardProps.className, eventDef.className)) { + className = standardProps.className; // only apply if there's a change + } + EventDef_1.default.copyVerbatimStandardProps(standardProps, // src + verbatimStandardProps // dest + ); + defMutation = new EventDefMutation(); + defMutation.eventDefId = eventDefId; + defMutation.className = className; + defMutation.verbatimStandardProps = verbatimStandardProps; + defMutation.miscProps = miscProps; + if (dateMutation) { + defMutation.dateMutation = dateMutation; + } + return defMutation; + }; + /* + eventDef assumed to be a SingleEventDef. + returns an undo function. + */ + EventDefMutation.prototype.mutateSingle = function (eventDef) { + var origDateProfile; + if (this.dateMutation) { + origDateProfile = eventDef.dateProfile; + eventDef.dateProfile = this.dateMutation.buildNewDateProfile(origDateProfile, eventDef.source.calendar); + } + // can't undo + // TODO: more DRY with EventDef::applyManualStandardProps + if (this.eventDefId != null) { + eventDef.id = EventDef_1.default.normalizeId((eventDef.rawId = this.eventDefId)); + } + // can't undo + // TODO: more DRY with EventDef::applyManualStandardProps + if (this.className) { + eventDef.className = this.className; + } + // can't undo + if (this.verbatimStandardProps) { + SingleEventDef_1.default.copyVerbatimStandardProps(this.verbatimStandardProps, // src + eventDef // dest + ); + } + // can't undo + if (this.miscProps) { + eventDef.applyMiscProps(this.miscProps); + } + if (origDateProfile) { + return function () { + eventDef.dateProfile = origDateProfile; + }; + } + else { + return function () { }; + } + }; + EventDefMutation.prototype.setDateMutation = function (dateMutation) { + if (dateMutation && !dateMutation.isEmpty()) { + this.dateMutation = dateMutation; + } + else { + this.dateMutation = null; + } + }; + EventDefMutation.prototype.isEmpty = function () { + return !this.dateMutation; + }; + return EventDefMutation; +}()); +exports.default = EventDefMutation; + + +/***/ }), +/* 38 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = { + sourceClasses: [], + registerClass: function (EventSourceClass) { + this.sourceClasses.unshift(EventSourceClass); // give highest priority + }, + parse: function (rawInput, calendar) { + var sourceClasses = this.sourceClasses; + var i; + var eventSource; + for (i = 0; i < sourceClasses.length; i++) { + eventSource = sourceClasses[i].parse(rawInput, calendar); + if (eventSource) { + return eventSource; + } + } + } +}; + + +/***/ }), +/* 39 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var Class_1 = __webpack_require__(33); +/* +Embodies a div that has potential scrollbars +*/ +var Scroller = /** @class */ (function (_super) { + tslib_1.__extends(Scroller, _super); + function Scroller(options) { + var _this = _super.call(this) || this; + options = options || {}; + _this.overflowX = options.overflowX || options.overflow || 'auto'; + _this.overflowY = options.overflowY || options.overflow || 'auto'; + return _this; + } + Scroller.prototype.render = function () { + this.el = this.renderEl(); + this.applyOverflow(); + }; + Scroller.prototype.renderEl = function () { + return (this.scrollEl = $('')); + }; + // sets to natural height, unlocks overflow + Scroller.prototype.clear = function () { + this.setHeight('auto'); + this.applyOverflow(); + }; + Scroller.prototype.destroy = function () { + this.el.remove(); + }; + // Overflow + // ----------------------------------------------------------------------------------------------------------------- + Scroller.prototype.applyOverflow = function () { + this.scrollEl.css({ + 'overflow-x': this.overflowX, + 'overflow-y': this.overflowY + }); + }; + // Causes any 'auto' overflow values to resolves to 'scroll' or 'hidden'. + // Useful for preserving scrollbar widths regardless of future resizes. + // Can pass in scrollbarWidths for optimization. + Scroller.prototype.lockOverflow = function (scrollbarWidths) { + var overflowX = this.overflowX; + var overflowY = this.overflowY; + scrollbarWidths = scrollbarWidths || this.getScrollbarWidths(); + if (overflowX === 'auto') { + overflowX = (scrollbarWidths.top || scrollbarWidths.bottom || // horizontal scrollbars? + // OR scrolling pane with massless scrollbars? + this.scrollEl[0].scrollWidth - 1 > this.scrollEl[0].clientWidth) ? 'scroll' : 'hidden'; + } + if (overflowY === 'auto') { + overflowY = (scrollbarWidths.left || scrollbarWidths.right || // vertical scrollbars? + // OR scrolling pane with massless scrollbars? + this.scrollEl[0].scrollHeight - 1 > this.scrollEl[0].clientHeight) ? 'scroll' : 'hidden'; + } + this.scrollEl.css({ 'overflow-x': overflowX, 'overflow-y': overflowY }); + }; + // Getters / Setters + // ----------------------------------------------------------------------------------------------------------------- + Scroller.prototype.setHeight = function (height) { + this.scrollEl.height(height); + }; + Scroller.prototype.getScrollTop = function () { + return this.scrollEl.scrollTop(); + }; + Scroller.prototype.setScrollTop = function (top) { + this.scrollEl.scrollTop(top); + }; + Scroller.prototype.getClientWidth = function () { + return this.scrollEl[0].clientWidth; + }; + Scroller.prototype.getClientHeight = function () { + return this.scrollEl[0].clientHeight; + }; + Scroller.prototype.getScrollbarWidths = function () { + return util_1.getScrollbarWidths(this.scrollEl); + }; + return Scroller; +}(Class_1.default)); +exports.default = Scroller; + + +/***/ }), +/* 40 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var DateComponent_1 = __webpack_require__(219); +var GlobalEmitter_1 = __webpack_require__(21); +var InteractiveDateComponent = /** @class */ (function (_super) { + tslib_1.__extends(InteractiveDateComponent, _super); + function InteractiveDateComponent(_view, _options) { + var _this = _super.call(this, _view, _options) || this; + // self-config, overridable by subclasses + _this.segSelector = '.fc-event-container > *'; // what constitutes an event element? + if (_this.dateSelectingClass) { + _this.dateClicking = new _this.dateClickingClass(_this); + } + if (_this.dateSelectingClass) { + _this.dateSelecting = new _this.dateSelectingClass(_this); + } + if (_this.eventPointingClass) { + _this.eventPointing = new _this.eventPointingClass(_this); + } + if (_this.eventDraggingClass && _this.eventPointing) { + _this.eventDragging = new _this.eventDraggingClass(_this, _this.eventPointing); + } + if (_this.eventResizingClass && _this.eventPointing) { + _this.eventResizing = new _this.eventResizingClass(_this, _this.eventPointing); + } + if (_this.externalDroppingClass) { + _this.externalDropping = new _this.externalDroppingClass(_this); + } + return _this; + } + // Sets the container element that the view should render inside of, does global DOM-related initializations, + // and renders all the non-date-related content inside. + InteractiveDateComponent.prototype.setElement = function (el) { + _super.prototype.setElement.call(this, el); + if (this.dateClicking) { + this.dateClicking.bindToEl(el); + } + if (this.dateSelecting) { + this.dateSelecting.bindToEl(el); + } + this.bindAllSegHandlersToEl(el); + }; + InteractiveDateComponent.prototype.removeElement = function () { + this.endInteractions(); + _super.prototype.removeElement.call(this); + }; + InteractiveDateComponent.prototype.executeEventUnrender = function () { + this.endInteractions(); + _super.prototype.executeEventUnrender.call(this); + }; + InteractiveDateComponent.prototype.bindGlobalHandlers = function () { + _super.prototype.bindGlobalHandlers.call(this); + if (this.externalDropping) { + this.externalDropping.bindToDocument(); + } + }; + InteractiveDateComponent.prototype.unbindGlobalHandlers = function () { + _super.prototype.unbindGlobalHandlers.call(this); + if (this.externalDropping) { + this.externalDropping.unbindFromDocument(); + } + }; + InteractiveDateComponent.prototype.bindDateHandlerToEl = function (el, name, handler) { + var _this = this; + // attach a handler to the grid's root element. + // jQuery will take care of unregistering them when removeElement gets called. + this.el.on(name, function (ev) { + if (!$(ev.target).is(_this.segSelector + ':not(.fc-helper),' + // directly on an event element + _this.segSelector + ':not(.fc-helper) *,' + // within an event element + '.fc-more,' + // a "more.." link + 'a[data-goto]' // a clickable nav link + )) { + return handler.call(_this, ev); + } + }); + }; + InteractiveDateComponent.prototype.bindAllSegHandlersToEl = function (el) { + [ + this.eventPointing, + this.eventDragging, + this.eventResizing + ].forEach(function (eventInteraction) { + if (eventInteraction) { + eventInteraction.bindToEl(el); + } + }); + }; + InteractiveDateComponent.prototype.bindSegHandlerToEl = function (el, name, handler) { + var _this = this; + el.on(name, this.segSelector, function (ev) { + var segEl = $(ev.currentTarget); + if (!segEl.is('.fc-helper')) { + var seg = segEl.data('fc-seg'); // grab segment data. put there by View::renderEventsPayload + if (seg && !_this.shouldIgnoreEventPointing()) { + return handler.call(_this, seg, ev); // context will be the Grid + } + } + }); + }; + InteractiveDateComponent.prototype.shouldIgnoreMouse = function () { + // HACK + // This will still work even though bindDateHandlerToEl doesn't use GlobalEmitter. + return GlobalEmitter_1.default.get().shouldIgnoreMouse(); + }; + InteractiveDateComponent.prototype.shouldIgnoreTouch = function () { + var view = this._getView(); + // On iOS (and Android?) when a new selection is initiated overtop another selection, + // the touchend never fires because the elements gets removed mid-touch-interaction (my theory). + // HACK: simply don't allow this to happen. + // ALSO: prevent selection when an *event* is already raised. + return view.isSelected || view.selectedEvent; + }; + InteractiveDateComponent.prototype.shouldIgnoreEventPointing = function () { + // only call the handlers if there is not a drag/resize in progress + return (this.eventDragging && this.eventDragging.isDragging) || + (this.eventResizing && this.eventResizing.isResizing); + }; + InteractiveDateComponent.prototype.canStartSelection = function (seg, ev) { + return util_1.getEvIsTouch(ev) && + !this.canStartResize(seg, ev) && + (this.isEventDefDraggable(seg.footprint.eventDef) || + this.isEventDefResizable(seg.footprint.eventDef)); + }; + InteractiveDateComponent.prototype.canStartDrag = function (seg, ev) { + return !this.canStartResize(seg, ev) && + this.isEventDefDraggable(seg.footprint.eventDef); + }; + InteractiveDateComponent.prototype.canStartResize = function (seg, ev) { + var view = this._getView(); + var eventDef = seg.footprint.eventDef; + return (!util_1.getEvIsTouch(ev) || view.isEventDefSelected(eventDef)) && + this.isEventDefResizable(eventDef) && + $(ev.target).is('.fc-resizer'); + }; + // Kills all in-progress dragging. + // Useful for when public API methods that result in re-rendering are invoked during a drag. + // Also useful for when touch devices misbehave and don't fire their touchend. + InteractiveDateComponent.prototype.endInteractions = function () { + [ + this.dateClicking, + this.dateSelecting, + this.eventPointing, + this.eventDragging, + this.eventResizing + ].forEach(function (interaction) { + if (interaction) { + interaction.end(); + } + }); + }; + // Event Drag-n-Drop + // --------------------------------------------------------------------------------------------------------------- + // Computes if the given event is allowed to be dragged by the user + InteractiveDateComponent.prototype.isEventDefDraggable = function (eventDef) { + return this.isEventDefStartEditable(eventDef); + }; + InteractiveDateComponent.prototype.isEventDefStartEditable = function (eventDef) { + var isEditable = eventDef.isStartExplicitlyEditable(); + if (isEditable == null) { + isEditable = this.opt('eventStartEditable'); + if (isEditable == null) { + isEditable = this.isEventDefGenerallyEditable(eventDef); + } + } + return isEditable; + }; + InteractiveDateComponent.prototype.isEventDefGenerallyEditable = function (eventDef) { + var isEditable = eventDef.isExplicitlyEditable(); + if (isEditable == null) { + isEditable = this.opt('editable'); + } + return isEditable; + }; + // Event Resizing + // --------------------------------------------------------------------------------------------------------------- + // Computes if the given event is allowed to be resized from its starting edge + InteractiveDateComponent.prototype.isEventDefResizableFromStart = function (eventDef) { + return this.opt('eventResizableFromStart') && this.isEventDefResizable(eventDef); + }; + // Computes if the given event is allowed to be resized from its ending edge + InteractiveDateComponent.prototype.isEventDefResizableFromEnd = function (eventDef) { + return this.isEventDefResizable(eventDef); + }; + // Computes if the given event is allowed to be resized by the user at all + InteractiveDateComponent.prototype.isEventDefResizable = function (eventDef) { + var isResizable = eventDef.isDurationExplicitlyEditable(); + if (isResizable == null) { + isResizable = this.opt('eventDurationEditable'); + if (isResizable == null) { + isResizable = this.isEventDefGenerallyEditable(eventDef); + } + } + return isResizable; + }; + // Event Mutation / Constraints + // --------------------------------------------------------------------------------------------------------------- + // Diffs the two dates, returning a duration, based on granularity of the grid + // TODO: port isTimeScale into this system? + InteractiveDateComponent.prototype.diffDates = function (a, b) { + if (this.largeUnit) { + return util_1.diffByUnit(a, b, this.largeUnit); + } + else { + return util_1.diffDayTime(a, b); + } + }; + // is it allowed, in relation to the view's validRange? + // NOTE: very similar to isExternalInstanceGroupAllowed + InteractiveDateComponent.prototype.isEventInstanceGroupAllowed = function (eventInstanceGroup) { + var view = this._getView(); + var dateProfile = this.dateProfile; + var eventFootprints = this.eventRangesToEventFootprints(eventInstanceGroup.getAllEventRanges()); + var i; + for (i = 0; i < eventFootprints.length; i++) { + // TODO: just use getAllEventRanges directly + if (!dateProfile.validUnzonedRange.containsRange(eventFootprints[i].componentFootprint.unzonedRange)) { + return false; + } + } + return view.calendar.constraints.isEventInstanceGroupAllowed(eventInstanceGroup); + }; + // NOTE: very similar to isEventInstanceGroupAllowed + // when it's a completely anonymous external drag, no event. + InteractiveDateComponent.prototype.isExternalInstanceGroupAllowed = function (eventInstanceGroup) { + var view = this._getView(); + var dateProfile = this.dateProfile; + var eventFootprints = this.eventRangesToEventFootprints(eventInstanceGroup.getAllEventRanges()); + var i; + for (i = 0; i < eventFootprints.length; i++) { + if (!dateProfile.validUnzonedRange.containsRange(eventFootprints[i].componentFootprint.unzonedRange)) { + return false; + } + } + for (i = 0; i < eventFootprints.length; i++) { + // treat it as a selection + // TODO: pass in eventInstanceGroup instead + // because we don't want calendar's constraint system to depend on a component's + // determination of footprints. + if (!view.calendar.constraints.isSelectionFootprintAllowed(eventFootprints[i].componentFootprint)) { + return false; + } + } + return true; + }; + return InteractiveDateComponent; +}(DateComponent_1.default)); +exports.default = InteractiveDateComponent; + + +/***/ }), +/* 41 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var moment = __webpack_require__(0); +var util_1 = __webpack_require__(4); +var RenderQueue_1 = __webpack_require__(218); +var DateProfileGenerator_1 = __webpack_require__(221); +var InteractiveDateComponent_1 = __webpack_require__(40); +var GlobalEmitter_1 = __webpack_require__(21); +var UnzonedRange_1 = __webpack_require__(5); +/* An abstract class from which other views inherit from +----------------------------------------------------------------------------------------------------------------------*/ +var View = /** @class */ (function (_super) { + tslib_1.__extends(View, _super); + function View(calendar, viewSpec) { + var _this = _super.call(this, null, viewSpec.options) || this; + _this.batchRenderDepth = 0; + _this.isSelected = false; // boolean whether a range of time is user-selected or not + _this.calendar = calendar; + _this.viewSpec = viewSpec; + // shortcuts + _this.type = viewSpec.type; + // .name is deprecated + _this.name = _this.type; + _this.initRenderQueue(); + _this.initHiddenDays(); + _this.dateProfileGenerator = new _this.dateProfileGeneratorClass(_this); + _this.bindBaseRenderHandlers(); + _this.eventOrderSpecs = util_1.parseFieldSpecs(_this.opt('eventOrder')); + // legacy + if (_this['initialize']) { + _this['initialize'](); + } + return _this; + } + View.prototype._getView = function () { + return this; + }; + // Retrieves an option with the given name + View.prototype.opt = function (name) { + return this.options[name]; + }; + /* Render Queue + ------------------------------------------------------------------------------------------------------------------*/ + View.prototype.initRenderQueue = function () { + this.renderQueue = new RenderQueue_1.default({ + event: this.opt('eventRenderWait') + }); + this.renderQueue.on('start', this.onRenderQueueStart.bind(this)); + this.renderQueue.on('stop', this.onRenderQueueStop.bind(this)); + this.on('before:change', this.startBatchRender); + this.on('change', this.stopBatchRender); + }; + View.prototype.onRenderQueueStart = function () { + this.calendar.freezeContentHeight(); + this.addScroll(this.queryScroll()); + }; + View.prototype.onRenderQueueStop = function () { + if (this.calendar.updateViewSize()) { + this.popScroll(); + } + this.calendar.thawContentHeight(); + }; + View.prototype.startBatchRender = function () { + if (!(this.batchRenderDepth++)) { + this.renderQueue.pause(); + } + }; + View.prototype.stopBatchRender = function () { + if (!(--this.batchRenderDepth)) { + this.renderQueue.resume(); + } + }; + View.prototype.requestRender = function (func, namespace, actionType) { + this.renderQueue.queue(func, namespace, actionType); + }; + // given func will auto-bind to `this` + View.prototype.whenSizeUpdated = function (func) { + if (this.renderQueue.isRunning) { + this.renderQueue.one('stop', func.bind(this)); + } + else { + func.call(this); + } + }; + /* Title and Date Formatting + ------------------------------------------------------------------------------------------------------------------*/ + // Computes what the title at the top of the calendar should be for this view + View.prototype.computeTitle = function (dateProfile) { + var unzonedRange; + // for views that span a large unit of time, show the proper interval, ignoring stray days before and after + if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) { + unzonedRange = dateProfile.currentUnzonedRange; + } + else { + unzonedRange = dateProfile.activeUnzonedRange; + } + return this.formatRange({ + start: this.calendar.msToMoment(unzonedRange.startMs, dateProfile.isRangeAllDay), + end: this.calendar.msToMoment(unzonedRange.endMs, dateProfile.isRangeAllDay) + }, dateProfile.isRangeAllDay, this.opt('titleFormat') || this.computeTitleFormat(dateProfile), this.opt('titleRangeSeparator')); + }; + // Generates the format string that should be used to generate the title for the current date range. + // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`. + View.prototype.computeTitleFormat = function (dateProfile) { + var currentRangeUnit = dateProfile.currentRangeUnit; + if (currentRangeUnit === 'year') { + return 'YYYY'; + } + else if (currentRangeUnit === 'month') { + return this.opt('monthYearFormat'); // like "September 2014" + } + else if (dateProfile.currentUnzonedRange.as('days') > 1) { + return 'll'; // multi-day range. shorter, like "Sep 9 - 10 2014" + } + else { + return 'LL'; // one day. longer, like "September 9 2014" + } + }; + // Date Setting/Unsetting + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.setDate = function (date) { + var currentDateProfile = this.get('dateProfile'); + var newDateProfile = this.dateProfileGenerator.build(date, undefined, true); // forceToValid=true + if (!currentDateProfile || + !currentDateProfile.activeUnzonedRange.equals(newDateProfile.activeUnzonedRange)) { + this.set('dateProfile', newDateProfile); + } + }; + View.prototype.unsetDate = function () { + this.unset('dateProfile'); + }; + // Event Data + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.fetchInitialEvents = function (dateProfile) { + var calendar = this.calendar; + var forceAllDay = dateProfile.isRangeAllDay && !this.usesMinMaxTime; + return calendar.requestEvents(calendar.msToMoment(dateProfile.activeUnzonedRange.startMs, forceAllDay), calendar.msToMoment(dateProfile.activeUnzonedRange.endMs, forceAllDay)); + }; + View.prototype.bindEventChanges = function () { + this.listenTo(this.calendar, 'eventsReset', this.resetEvents); // TODO: make this a real event + }; + View.prototype.unbindEventChanges = function () { + this.stopListeningTo(this.calendar, 'eventsReset'); + }; + View.prototype.setEvents = function (eventsPayload) { + this.set('currentEvents', eventsPayload); + this.set('hasEvents', true); + }; + View.prototype.unsetEvents = function () { + this.unset('currentEvents'); + this.unset('hasEvents'); + }; + View.prototype.resetEvents = function (eventsPayload) { + this.startBatchRender(); + this.unsetEvents(); + this.setEvents(eventsPayload); + this.stopBatchRender(); + }; + // Date High-level Rendering + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.requestDateRender = function (dateProfile) { + var _this = this; + this.requestRender(function () { + _this.executeDateRender(dateProfile); + }, 'date', 'init'); + }; + View.prototype.requestDateUnrender = function () { + var _this = this; + this.requestRender(function () { + _this.executeDateUnrender(); + }, 'date', 'destroy'); + }; + // if dateProfile not specified, uses current + View.prototype.executeDateRender = function (dateProfile) { + _super.prototype.executeDateRender.call(this, dateProfile); + if (this['render']) { + this['render'](); // TODO: deprecate + } + this.trigger('datesRendered'); + this.addScroll({ isDateInit: true }); + this.startNowIndicator(); // shouldn't render yet because updateSize will be called soon + }; + View.prototype.executeDateUnrender = function () { + this.unselect(); + this.stopNowIndicator(); + this.trigger('before:datesUnrendered'); + if (this['destroy']) { + this['destroy'](); // TODO: deprecate + } + _super.prototype.executeDateUnrender.call(this); + }; + // "Base" rendering + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.bindBaseRenderHandlers = function () { + var _this = this; + this.on('datesRendered', function () { + _this.whenSizeUpdated(_this.triggerViewRender); + }); + this.on('before:datesUnrendered', function () { + _this.triggerViewDestroy(); + }); + }; + View.prototype.triggerViewRender = function () { + this.publiclyTrigger('viewRender', { + context: this, + args: [this, this.el] + }); + }; + View.prototype.triggerViewDestroy = function () { + this.publiclyTrigger('viewDestroy', { + context: this, + args: [this, this.el] + }); + }; + // Event High-level Rendering + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.requestEventsRender = function (eventsPayload) { + var _this = this; + this.requestRender(function () { + _this.executeEventRender(eventsPayload); + _this.whenSizeUpdated(_this.triggerAfterEventsRendered); + }, 'event', 'init'); + }; + View.prototype.requestEventsUnrender = function () { + var _this = this; + this.requestRender(function () { + _this.triggerBeforeEventsDestroyed(); + _this.executeEventUnrender(); + }, 'event', 'destroy'); + }; + // Business Hour High-level Rendering + // ----------------------------------------------------------------------------------------------------------------- + View.prototype.requestBusinessHoursRender = function (businessHourGenerator) { + var _this = this; + this.requestRender(function () { + _this.renderBusinessHours(businessHourGenerator); + }, 'businessHours', 'init'); + }; + View.prototype.requestBusinessHoursUnrender = function () { + var _this = this; + this.requestRender(function () { + _this.unrenderBusinessHours(); + }, 'businessHours', 'destroy'); + }; + // Misc view rendering utils + // ----------------------------------------------------------------------------------------------------------------- + // Binds DOM handlers to elements that reside outside the view container, such as the document + View.prototype.bindGlobalHandlers = function () { + _super.prototype.bindGlobalHandlers.call(this); + this.listenTo(GlobalEmitter_1.default.get(), { + touchstart: this.processUnselect, + mousedown: this.handleDocumentMousedown + }); + }; + // Unbinds DOM handlers from elements that reside outside the view container + View.prototype.unbindGlobalHandlers = function () { + _super.prototype.unbindGlobalHandlers.call(this); + this.stopListeningTo(GlobalEmitter_1.default.get()); + }; + /* Now Indicator + ------------------------------------------------------------------------------------------------------------------*/ + // Immediately render the current time indicator and begins re-rendering it at an interval, + // which is defined by this.getNowIndicatorUnit(). + // TODO: somehow do this for the current whole day's background too + View.prototype.startNowIndicator = function () { + var _this = this; + var unit; + var update; + var delay; // ms wait value + if (this.opt('nowIndicator')) { + unit = this.getNowIndicatorUnit(); + if (unit) { + update = util_1.proxy(this, 'updateNowIndicator'); // bind to `this` + this.initialNowDate = this.calendar.getNow(); + this.initialNowQueriedMs = new Date().valueOf(); + // wait until the beginning of the next interval + delay = this.initialNowDate.clone().startOf(unit).add(1, unit).valueOf() - this.initialNowDate.valueOf(); + this.nowIndicatorTimeoutID = setTimeout(function () { + _this.nowIndicatorTimeoutID = null; + update(); + delay = +moment.duration(1, unit); + delay = Math.max(100, delay); // prevent too frequent + _this.nowIndicatorIntervalID = setInterval(update, delay); // update every interval + }, delay); + } + // rendering will be initiated in updateSize + } + }; + // rerenders the now indicator, computing the new current time from the amount of time that has passed + // since the initial getNow call. + View.prototype.updateNowIndicator = function () { + if (this.isDatesRendered && + this.initialNowDate // activated before? + ) { + this.unrenderNowIndicator(); // won't unrender if unnecessary + this.renderNowIndicator(this.initialNowDate.clone().add(new Date().valueOf() - this.initialNowQueriedMs) // add ms + ); + this.isNowIndicatorRendered = true; + } + }; + // Immediately unrenders the view's current time indicator and stops any re-rendering timers. + // Won't cause side effects if indicator isn't rendered. + View.prototype.stopNowIndicator = function () { + if (this.isNowIndicatorRendered) { + if (this.nowIndicatorTimeoutID) { + clearTimeout(this.nowIndicatorTimeoutID); + this.nowIndicatorTimeoutID = null; + } + if (this.nowIndicatorIntervalID) { + clearInterval(this.nowIndicatorIntervalID); + this.nowIndicatorIntervalID = null; + } + this.unrenderNowIndicator(); + this.isNowIndicatorRendered = false; + } + }; + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + View.prototype.updateSize = function (totalHeight, isAuto, isResize) { + if (this['setHeight']) { + this['setHeight'](totalHeight, isAuto); + } + else { + _super.prototype.updateSize.call(this, totalHeight, isAuto, isResize); + } + this.updateNowIndicator(); + }; + /* Scroller + ------------------------------------------------------------------------------------------------------------------*/ + View.prototype.addScroll = function (scroll) { + var queuedScroll = this.queuedScroll || (this.queuedScroll = {}); + $.extend(queuedScroll, scroll); + }; + View.prototype.popScroll = function () { + this.applyQueuedScroll(); + this.queuedScroll = null; + }; + View.prototype.applyQueuedScroll = function () { + if (this.queuedScroll) { + this.applyScroll(this.queuedScroll); + } + }; + View.prototype.queryScroll = function () { + var scroll = {}; + if (this.isDatesRendered) { + $.extend(scroll, this.queryDateScroll()); + } + return scroll; + }; + View.prototype.applyScroll = function (scroll) { + if (scroll.isDateInit && this.isDatesRendered) { + $.extend(scroll, this.computeInitialDateScroll()); + } + if (this.isDatesRendered) { + this.applyDateScroll(scroll); + } + }; + View.prototype.computeInitialDateScroll = function () { + return {}; // subclasses must implement + }; + View.prototype.queryDateScroll = function () { + return {}; // subclasses must implement + }; + View.prototype.applyDateScroll = function (scroll) { + // subclasses must implement + }; + /* Event Drag-n-Drop + ------------------------------------------------------------------------------------------------------------------*/ + View.prototype.reportEventDrop = function (eventInstance, eventMutation, el, ev) { + var eventManager = this.calendar.eventManager; + var undoFunc = eventManager.mutateEventsWithId(eventInstance.def.id, eventMutation); + var dateMutation = eventMutation.dateMutation; + // update the EventInstance, for handlers + if (dateMutation) { + eventInstance.dateProfile = dateMutation.buildNewDateProfile(eventInstance.dateProfile, this.calendar); + } + this.triggerEventDrop(eventInstance, + // a drop doesn't necessarily mean a date mutation (ex: resource change) + (dateMutation && dateMutation.dateDelta) || moment.duration(), undoFunc, el, ev); + }; + // Triggers event-drop handlers that have subscribed via the API + View.prototype.triggerEventDrop = function (eventInstance, dateDelta, undoFunc, el, ev) { + this.publiclyTrigger('eventDrop', { + context: el[0], + args: [ + eventInstance.toLegacy(), + dateDelta, + undoFunc, + ev, + {}, + this + ] + }); + }; + /* External Element Drag-n-Drop + ------------------------------------------------------------------------------------------------------------------*/ + // Must be called when an external element, via jQuery UI, has been dropped onto the calendar. + // `meta` is the parsed data that has been embedded into the dragging event. + // `dropLocation` is an object that contains the new zoned start/end/allDay values for the event. + View.prototype.reportExternalDrop = function (singleEventDef, isEvent, isSticky, el, ev, ui) { + if (isEvent) { + this.calendar.eventManager.addEventDef(singleEventDef, isSticky); + } + this.triggerExternalDrop(singleEventDef, isEvent, el, ev, ui); + }; + // Triggers external-drop handlers that have subscribed via the API + View.prototype.triggerExternalDrop = function (singleEventDef, isEvent, el, ev, ui) { + // trigger 'drop' regardless of whether element represents an event + this.publiclyTrigger('drop', { + context: el[0], + args: [ + singleEventDef.dateProfile.start.clone(), + ev, + ui, + this + ] + }); + if (isEvent) { + // signal an external event landed + this.publiclyTrigger('eventReceive', { + context: this, + args: [ + singleEventDef.buildInstance().toLegacy(), + this + ] + }); + } + }; + /* Event Resizing + ------------------------------------------------------------------------------------------------------------------*/ + // Must be called when an event in the view has been resized to a new length + View.prototype.reportEventResize = function (eventInstance, eventMutation, el, ev) { + var eventManager = this.calendar.eventManager; + var undoFunc = eventManager.mutateEventsWithId(eventInstance.def.id, eventMutation); + // update the EventInstance, for handlers + eventInstance.dateProfile = eventMutation.dateMutation.buildNewDateProfile(eventInstance.dateProfile, this.calendar); + this.triggerEventResize(eventInstance, eventMutation.dateMutation.endDelta, undoFunc, el, ev); + }; + // Triggers event-resize handlers that have subscribed via the API + View.prototype.triggerEventResize = function (eventInstance, durationDelta, undoFunc, el, ev) { + this.publiclyTrigger('eventResize', { + context: el[0], + args: [ + eventInstance.toLegacy(), + durationDelta, + undoFunc, + ev, + {}, + this + ] + }); + }; + /* Selection (time range) + ------------------------------------------------------------------------------------------------------------------*/ + // Selects a date span on the view. `start` and `end` are both Moments. + // `ev` is the native mouse event that begin the interaction. + View.prototype.select = function (footprint, ev) { + this.unselect(ev); + this.renderSelectionFootprint(footprint); + this.reportSelection(footprint, ev); + }; + View.prototype.renderSelectionFootprint = function (footprint) { + if (this['renderSelection']) { + this['renderSelection'](footprint.toLegacy(this.calendar)); + } + else { + _super.prototype.renderSelectionFootprint.call(this, footprint); + } + }; + // Called when a new selection is made. Updates internal state and triggers handlers. + View.prototype.reportSelection = function (footprint, ev) { + this.isSelected = true; + this.triggerSelect(footprint, ev); + }; + // Triggers handlers to 'select' + View.prototype.triggerSelect = function (footprint, ev) { + var dateProfile = this.calendar.footprintToDateProfile(footprint); // abuse of "Event"DateProfile? + this.publiclyTrigger('select', { + context: this, + args: [ + dateProfile.start, + dateProfile.end, + ev, + this + ] + }); + }; + // Undoes a selection. updates in the internal state and triggers handlers. + // `ev` is the native mouse event that began the interaction. + View.prototype.unselect = function (ev) { + if (this.isSelected) { + this.isSelected = false; + if (this['destroySelection']) { + this['destroySelection'](); // TODO: deprecate + } + this.unrenderSelection(); + this.publiclyTrigger('unselect', { + context: this, + args: [ev, this] + }); + } + }; + /* Event Selection + ------------------------------------------------------------------------------------------------------------------*/ + View.prototype.selectEventInstance = function (eventInstance) { + if (!this.selectedEventInstance || + this.selectedEventInstance !== eventInstance) { + this.unselectEventInstance(); + this.getEventSegs().forEach(function (seg) { + if (seg.footprint.eventInstance === eventInstance && + seg.el // necessary? + ) { + seg.el.addClass('fc-selected'); + } + }); + this.selectedEventInstance = eventInstance; + } + }; + View.prototype.unselectEventInstance = function () { + if (this.selectedEventInstance) { + this.getEventSegs().forEach(function (seg) { + if (seg.el) { + seg.el.removeClass('fc-selected'); + } + }); + this.selectedEventInstance = null; + } + }; + View.prototype.isEventDefSelected = function (eventDef) { + // event references might change on refetchEvents(), while selectedEventInstance doesn't, + // so compare IDs + return this.selectedEventInstance && this.selectedEventInstance.def.id === eventDef.id; + }; + /* Mouse / Touch Unselecting (time range & event unselection) + ------------------------------------------------------------------------------------------------------------------*/ + // TODO: move consistently to down/start or up/end? + // TODO: don't kill previous selection if touch scrolling + View.prototype.handleDocumentMousedown = function (ev) { + if (util_1.isPrimaryMouseButton(ev)) { + this.processUnselect(ev); + } + }; + View.prototype.processUnselect = function (ev) { + this.processRangeUnselect(ev); + this.processEventUnselect(ev); + }; + View.prototype.processRangeUnselect = function (ev) { + var ignore; + // is there a time-range selection? + if (this.isSelected && this.opt('unselectAuto')) { + // only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element + ignore = this.opt('unselectCancel'); + if (!ignore || !$(ev.target).closest(ignore).length) { + this.unselect(ev); + } + } + }; + View.prototype.processEventUnselect = function (ev) { + if (this.selectedEventInstance) { + if (!$(ev.target).closest('.fc-selected').length) { + this.unselectEventInstance(); + } + } + }; + /* Triggers + ------------------------------------------------------------------------------------------------------------------*/ + View.prototype.triggerBaseRendered = function () { + this.publiclyTrigger('viewRender', { + context: this, + args: [this, this.el] + }); + }; + View.prototype.triggerBaseUnrendered = function () { + this.publiclyTrigger('viewDestroy', { + context: this, + args: [this, this.el] + }); + }; + // Triggers handlers to 'dayClick' + // Span has start/end of the clicked area. Only the start is useful. + View.prototype.triggerDayClick = function (footprint, dayEl, ev) { + var dateProfile = this.calendar.footprintToDateProfile(footprint); // abuse of "Event"DateProfile? + this.publiclyTrigger('dayClick', { + context: dayEl, + args: [dateProfile.start, ev, this] + }); + }; + /* Date Utils + ------------------------------------------------------------------------------------------------------------------*/ + // For DateComponent::getDayClasses + View.prototype.isDateInOtherMonth = function (date, dateProfile) { + return false; + }; + // Arguments after name will be forwarded to a hypothetical function value + // WARNING: passed-in arguments will be given to generator functions as-is and can cause side-effects. + // Always clone your objects if you fear mutation. + View.prototype.getUnzonedRangeOption = function (name) { + var val = this.opt(name); + if (typeof val === 'function') { + val = val.apply(null, Array.prototype.slice.call(arguments, 1)); + } + if (val) { + return this.calendar.parseUnzonedRange(val); + } + }; + /* Hidden Days + ------------------------------------------------------------------------------------------------------------------*/ + // Initializes internal variables related to calculating hidden days-of-week + View.prototype.initHiddenDays = function () { + var hiddenDays = this.opt('hiddenDays') || []; // array of day-of-week indices that are hidden + var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) + var dayCnt = 0; + var i; + if (this.opt('weekends') === false) { + hiddenDays.push(0, 6); // 0=sunday, 6=saturday + } + for (i = 0; i < 7; i++) { + if (!(isHiddenDayHash[i] = $.inArray(i, hiddenDays) !== -1)) { + dayCnt++; + } + } + if (!dayCnt) { + throw new Error('invalid hiddenDays'); // all days were hidden? bad. + } + this.isHiddenDayHash = isHiddenDayHash; + }; + // Remove days from the beginning and end of the range that are computed as hidden. + // If the whole range is trimmed off, returns null + View.prototype.trimHiddenDays = function (inputUnzonedRange) { + var start = inputUnzonedRange.getStart(); + var end = inputUnzonedRange.getEnd(); + if (start) { + start = this.skipHiddenDays(start); + } + if (end) { + end = this.skipHiddenDays(end, -1, true); + } + if (start === null || end === null || start < end) { + return new UnzonedRange_1.default(start, end); + } + return null; + }; + // Is the current day hidden? + // `day` is a day-of-week index (0-6), or a Moment + View.prototype.isHiddenDay = function (day) { + if (moment.isMoment(day)) { + day = day.day(); + } + return this.isHiddenDayHash[day]; + }; + // Incrementing the current day until it is no longer a hidden day, returning a copy. + // DOES NOT CONSIDER validUnzonedRange! + // If the initial value of `date` is not a hidden day, don't do anything. + // Pass `isExclusive` as `true` if you are dealing with an end date. + // `inc` defaults to `1` (increment one day forward each time) + View.prototype.skipHiddenDays = function (date, inc, isExclusive) { + if (inc === void 0) { inc = 1; } + if (isExclusive === void 0) { isExclusive = false; } + var out = date.clone(); + while (this.isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7]) { + out.add(inc, 'days'); + } + return out; + }; + return View; +}(InteractiveDateComponent_1.default)); +exports.default = View; +View.prototype.usesMinMaxTime = false; +View.prototype.dateProfileGeneratorClass = DateProfileGenerator_1.default; +View.watch('displayingDates', ['isInDom', 'dateProfile'], function (deps) { + this.requestDateRender(deps.dateProfile); +}, function () { + this.requestDateUnrender(); +}); +View.watch('displayingBusinessHours', ['displayingDates', 'businessHourGenerator'], function (deps) { + this.requestBusinessHoursRender(deps.businessHourGenerator); +}, function () { + this.requestBusinessHoursUnrender(); +}); +View.watch('initialEvents', ['dateProfile'], function (deps) { + return this.fetchInitialEvents(deps.dateProfile); +}); +View.watch('bindingEvents', ['initialEvents'], function (deps) { + this.setEvents(deps.initialEvents); + this.bindEventChanges(); +}, function () { + this.unbindEventChanges(); + this.unsetEvents(); +}); +View.watch('displayingEvents', ['displayingDates', 'hasEvents'], function () { + this.requestEventsRender(this.get('currentEvents')); +}, function () { + this.requestEventsUnrender(); +}); +View.watch('title', ['dateProfile'], function (deps) { + return (this.title = this.computeTitle(deps.dateProfile)); // assign to View for legacy reasons +}); +View.watch('legacyDateProps', ['dateProfile'], function (deps) { + var calendar = this.calendar; + var dateProfile = deps.dateProfile; + // DEPRECATED, but we need to keep it updated... + this.start = calendar.msToMoment(dateProfile.activeUnzonedRange.startMs, dateProfile.isRangeAllDay); + this.end = calendar.msToMoment(dateProfile.activeUnzonedRange.endMs, dateProfile.isRangeAllDay); + this.intervalStart = calendar.msToMoment(dateProfile.currentUnzonedRange.startMs, dateProfile.isRangeAllDay); + this.intervalEnd = calendar.msToMoment(dateProfile.currentUnzonedRange.endMs, dateProfile.isRangeAllDay); +}); + + +/***/ }), +/* 42 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var EventRenderer = /** @class */ (function () { + function EventRenderer(component, fillRenderer) { + this.view = component._getView(); + this.component = component; + this.fillRenderer = fillRenderer; + } + EventRenderer.prototype.opt = function (name) { + return this.view.opt(name); + }; + // Updates values that rely on options and also relate to range + EventRenderer.prototype.rangeUpdated = function () { + var displayEventTime; + var displayEventEnd; + this.eventTimeFormat = + this.opt('eventTimeFormat') || + this.opt('timeFormat') || // deprecated + this.computeEventTimeFormat(); + displayEventTime = this.opt('displayEventTime'); + if (displayEventTime == null) { + displayEventTime = this.computeDisplayEventTime(); // might be based off of range + } + displayEventEnd = this.opt('displayEventEnd'); + if (displayEventEnd == null) { + displayEventEnd = this.computeDisplayEventEnd(); // might be based off of range + } + this.displayEventTime = displayEventTime; + this.displayEventEnd = displayEventEnd; + }; + EventRenderer.prototype.render = function (eventsPayload) { + var dateProfile = this.component._getDateProfile(); + var eventDefId; + var instanceGroup; + var eventRanges; + var bgRanges = []; + var fgRanges = []; + for (eventDefId in eventsPayload) { + instanceGroup = eventsPayload[eventDefId]; + eventRanges = instanceGroup.sliceRenderRanges(dateProfile.activeUnzonedRange); + if (instanceGroup.getEventDef().hasBgRendering()) { + bgRanges.push.apply(bgRanges, eventRanges); + } + else { + fgRanges.push.apply(fgRanges, eventRanges); + } + } + this.renderBgRanges(bgRanges); + this.renderFgRanges(fgRanges); + }; + EventRenderer.prototype.unrender = function () { + this.unrenderBgRanges(); + this.unrenderFgRanges(); + }; + EventRenderer.prototype.renderFgRanges = function (eventRanges) { + var eventFootprints = this.component.eventRangesToEventFootprints(eventRanges); + var segs = this.component.eventFootprintsToSegs(eventFootprints); + // render an `.el` on each seg + // returns a subset of the segs. segs that were actually rendered + segs = this.renderFgSegEls(segs); + if (this.renderFgSegs(segs) !== false) { + this.fgSegs = segs; + } + }; + EventRenderer.prototype.unrenderFgRanges = function () { + this.unrenderFgSegs(this.fgSegs || []); + this.fgSegs = null; + }; + EventRenderer.prototype.renderBgRanges = function (eventRanges) { + var eventFootprints = this.component.eventRangesToEventFootprints(eventRanges); + var segs = this.component.eventFootprintsToSegs(eventFootprints); + if (this.renderBgSegs(segs) !== false) { + this.bgSegs = segs; + } + }; + EventRenderer.prototype.unrenderBgRanges = function () { + this.unrenderBgSegs(); + this.bgSegs = null; + }; + EventRenderer.prototype.getSegs = function () { + return (this.bgSegs || []).concat(this.fgSegs || []); + }; + // Renders foreground event segments onto the grid + EventRenderer.prototype.renderFgSegs = function (segs) { + // subclasses must implement + // segs already has rendered els, and has been filtered. + return false; // signal failure if not implemented + }; + // Unrenders all currently rendered foreground segments + EventRenderer.prototype.unrenderFgSegs = function (segs) { + // subclasses must implement + }; + EventRenderer.prototype.renderBgSegs = function (segs) { + var _this = this; + if (this.fillRenderer) { + this.fillRenderer.renderSegs('bgEvent', segs, { + getClasses: function (seg) { + return _this.getBgClasses(seg.footprint.eventDef); + }, + getCss: function (seg) { + return { + 'background-color': _this.getBgColor(seg.footprint.eventDef) + }; + }, + filterEl: function (seg, el) { + return _this.filterEventRenderEl(seg.footprint, el); + } + }); + } + else { + return false; // signal failure if no fillRenderer + } + }; + EventRenderer.prototype.unrenderBgSegs = function () { + if (this.fillRenderer) { + this.fillRenderer.unrender('bgEvent'); + } + }; + // Renders and assigns an `el` property for each foreground event segment. + // Only returns segments that successfully rendered. + EventRenderer.prototype.renderFgSegEls = function (segs, disableResizing) { + var _this = this; + if (disableResizing === void 0) { disableResizing = false; } + var hasEventRenderHandlers = this.view.hasPublicHandlers('eventRender'); + var html = ''; + var renderedSegs = []; + var i; + if (segs.length) { + // build a large concatenation of event segment HTML + for (i = 0; i < segs.length; i++) { + this.beforeFgSegHtml(segs[i]); + html += this.fgSegHtml(segs[i], disableResizing); + } + // Grab individual elements from the combined HTML string. Use each as the default rendering. + // Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false. + $(html).each(function (i, node) { + var seg = segs[i]; + var el = $(node); + if (hasEventRenderHandlers) { + el = _this.filterEventRenderEl(seg.footprint, el); + } + if (el) { + el.data('fc-seg', seg); // used by handlers + seg.el = el; + renderedSegs.push(seg); + } + }); + } + return renderedSegs; + }; + EventRenderer.prototype.beforeFgSegHtml = function (seg) { + }; + // Generates the HTML for the default rendering of a foreground event segment. Used by renderFgSegEls() + EventRenderer.prototype.fgSegHtml = function (seg, disableResizing) { + // subclasses should implement + }; + // Generic utility for generating the HTML classNames for an event segment's element + EventRenderer.prototype.getSegClasses = function (seg, isDraggable, isResizable) { + var classes = [ + 'fc-event', + seg.isStart ? 'fc-start' : 'fc-not-start', + seg.isEnd ? 'fc-end' : 'fc-not-end' + ].concat(this.getClasses(seg.footprint.eventDef)); + if (isDraggable) { + classes.push('fc-draggable'); + } + if (isResizable) { + classes.push('fc-resizable'); + } + // event is currently selected? attach a className. + if (this.view.isEventDefSelected(seg.footprint.eventDef)) { + classes.push('fc-selected'); + } + return classes; + }; + // Given an event and the default element used for rendering, returns the element that should actually be used. + // Basically runs events and elements through the eventRender hook. + EventRenderer.prototype.filterEventRenderEl = function (eventFootprint, el) { + var legacy = eventFootprint.getEventLegacy(); + var custom = this.view.publiclyTrigger('eventRender', { + context: legacy, + args: [legacy, el, this.view] + }); + if (custom === false) { + el = null; + } + else if (custom && custom !== true) { + el = $(custom); + } + return el; + }; + // Compute the text that should be displayed on an event's element. + // `range` can be the Event object itself, or something range-like, with at least a `start`. + // If event times are disabled, or the event has no time, will return a blank string. + // If not specified, formatStr will default to the eventTimeFormat setting, + // and displayEnd will default to the displayEventEnd setting. + EventRenderer.prototype.getTimeText = function (eventFootprint, formatStr, displayEnd) { + return this._getTimeText(eventFootprint.eventInstance.dateProfile.start, eventFootprint.eventInstance.dateProfile.end, eventFootprint.componentFootprint.isAllDay, formatStr, displayEnd); + }; + EventRenderer.prototype._getTimeText = function (start, end, isAllDay, formatStr, displayEnd) { + if (formatStr == null) { + formatStr = this.eventTimeFormat; + } + if (displayEnd == null) { + displayEnd = this.displayEventEnd; + } + if (this.displayEventTime && !isAllDay) { + if (displayEnd && end) { + return this.view.formatRange({ start: start, end: end }, false, // allDay + formatStr); + } + else { + return start.format(formatStr); + } + } + return ''; + }; + EventRenderer.prototype.computeEventTimeFormat = function () { + return this.opt('smallTimeFormat'); + }; + EventRenderer.prototype.computeDisplayEventTime = function () { + return true; + }; + EventRenderer.prototype.computeDisplayEventEnd = function () { + return true; + }; + EventRenderer.prototype.getBgClasses = function (eventDef) { + var classNames = this.getClasses(eventDef); + classNames.push('fc-bgevent'); + return classNames; + }; + EventRenderer.prototype.getClasses = function (eventDef) { + var objs = this.getStylingObjs(eventDef); + var i; + var classNames = []; + for (i = 0; i < objs.length; i++) { + classNames.push.apply(// append + classNames, objs[i].eventClassName || objs[i].className || []); + } + return classNames; + }; + // Utility for generating event skin-related CSS properties + EventRenderer.prototype.getSkinCss = function (eventDef) { + return { + 'background-color': this.getBgColor(eventDef), + 'border-color': this.getBorderColor(eventDef), + color: this.getTextColor(eventDef) + }; + }; + // Queries for caller-specified color, then falls back to default + EventRenderer.prototype.getBgColor = function (eventDef) { + var objs = this.getStylingObjs(eventDef); + var i; + var val; + for (i = 0; i < objs.length && !val; i++) { + val = objs[i].eventBackgroundColor || objs[i].eventColor || + objs[i].backgroundColor || objs[i].color; + } + if (!val) { + val = this.opt('eventBackgroundColor') || this.opt('eventColor'); + } + return val; + }; + // Queries for caller-specified color, then falls back to default + EventRenderer.prototype.getBorderColor = function (eventDef) { + var objs = this.getStylingObjs(eventDef); + var i; + var val; + for (i = 0; i < objs.length && !val; i++) { + val = objs[i].eventBorderColor || objs[i].eventColor || + objs[i].borderColor || objs[i].color; + } + if (!val) { + val = this.opt('eventBorderColor') || this.opt('eventColor'); + } + return val; + }; + // Queries for caller-specified color, then falls back to default + EventRenderer.prototype.getTextColor = function (eventDef) { + var objs = this.getStylingObjs(eventDef); + var i; + var val; + for (i = 0; i < objs.length && !val; i++) { + val = objs[i].eventTextColor || + objs[i].textColor; + } + if (!val) { + val = this.opt('eventTextColor'); + } + return val; + }; + EventRenderer.prototype.getStylingObjs = function (eventDef) { + var objs = this.getFallbackStylingObjs(eventDef); + objs.unshift(eventDef); + return objs; + }; + EventRenderer.prototype.getFallbackStylingObjs = function (eventDef) { + return [eventDef.source]; + }; + EventRenderer.prototype.sortEventSegs = function (segs) { + segs.sort(util_1.proxy(this, 'compareEventSegs')); + }; + // A cmp function for determining which segments should take visual priority + EventRenderer.prototype.compareEventSegs = function (seg1, seg2) { + var f1 = seg1.footprint; + var f2 = seg2.footprint; + var cf1 = f1.componentFootprint; + var cf2 = f2.componentFootprint; + var r1 = cf1.unzonedRange; + var r2 = cf2.unzonedRange; + return r1.startMs - r2.startMs || // earlier events go first + (r2.endMs - r2.startMs) - (r1.endMs - r1.startMs) || // tie? longer events go first + cf2.isAllDay - cf1.isAllDay || // tie? put all-day events first (booleans cast to 0/1) + util_1.compareByFieldSpecs(f1.eventDef, f2.eventDef, this.view.eventOrderSpecs, f1.eventDef.miscProps, f2.eventDef.miscProps); + }; + return EventRenderer; +}()); +exports.default = EventRenderer; + + +/***/ }), +/* 43 */, +/* 44 */, +/* 45 */, +/* 46 */, +/* 47 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var moment_ext_1 = __webpack_require__(10); +// Plugin +// ------------------------------------------------------------------------------------------------- +moment_ext_1.newMomentProto.format = function () { + if (this._fullCalendar && arguments[0]) { + return formatDate(this, arguments[0]); // our extended formatting + } + if (this._ambigTime) { + return moment_ext_1.oldMomentFormat(englishMoment(this), 'YYYY-MM-DD'); + } + if (this._ambigZone) { + return moment_ext_1.oldMomentFormat(englishMoment(this), 'YYYY-MM-DD[T]HH:mm:ss'); + } + if (this._fullCalendar) { + // moment.format() doesn't ensure english, but we want to. + return moment_ext_1.oldMomentFormat(englishMoment(this)); + } + return moment_ext_1.oldMomentProto.format.apply(this, arguments); +}; +moment_ext_1.newMomentProto.toISOString = function () { + if (this._ambigTime) { + return moment_ext_1.oldMomentFormat(englishMoment(this), 'YYYY-MM-DD'); + } + if (this._ambigZone) { + return moment_ext_1.oldMomentFormat(englishMoment(this), 'YYYY-MM-DD[T]HH:mm:ss'); + } + if (this._fullCalendar) { + // depending on browser, moment might not output english. ensure english. + // https://github.com/moment/moment/blob/2.18.1/src/lib/moment/format.js#L22 + return moment_ext_1.oldMomentProto.toISOString.apply(englishMoment(this), arguments); + } + return moment_ext_1.oldMomentProto.toISOString.apply(this, arguments); +}; +function englishMoment(mom) { + if (mom.locale() !== 'en') { + return mom.clone().locale('en'); + } + return mom; +} +// Config +// --------------------------------------------------------------------------------------------------------------------- +/* +Inserted between chunks in the fake ("intermediate") formatting string. +Important that it passes as whitespace (\s) because moment often identifies non-standalone months +via a regexp with an \s. +*/ +var PART_SEPARATOR = '\u000b'; // vertical tab +/* +Inserted as the first character of a literal-text chunk to indicate that the literal text is not actually literal text, +but rather, a "special" token that has custom rendering (see specialTokens map). +*/ +var SPECIAL_TOKEN_MARKER = '\u001f'; // information separator 1 +/* +Inserted at the beginning and end of a span of text that must have non-zero numeric characters. +Handling of these markers is done in a post-processing step at the very end of text rendering. +*/ +var MAYBE_MARKER = '\u001e'; // information separator 2 +var MAYBE_REGEXP = new RegExp(MAYBE_MARKER + '([^' + MAYBE_MARKER + ']*)' + MAYBE_MARKER, 'g'); // must be global +/* +Addition formatting tokens we want recognized +*/ +var specialTokens = { + t: function (date) { + return moment_ext_1.oldMomentFormat(date, 'a').charAt(0); + }, + T: function (date) { + return moment_ext_1.oldMomentFormat(date, 'A').charAt(0); + } +}; +/* +The first characters of formatting tokens for units that are 1 day or larger. +`value` is for ranking relative size (lower means bigger). +`unit` is a normalized unit, used for comparing moments. +*/ +var largeTokenMap = { + Y: { value: 1, unit: 'year' }, + M: { value: 2, unit: 'month' }, + W: { value: 3, unit: 'week' }, + w: { value: 3, unit: 'week' }, + D: { value: 4, unit: 'day' }, + d: { value: 4, unit: 'day' } // day of week +}; +// Single Date Formatting +// --------------------------------------------------------------------------------------------------------------------- +/* +Formats `date` with a Moment formatting string, but allow our non-zero areas and special token +*/ +function formatDate(date, formatStr) { + return renderFakeFormatString(getParsedFormatString(formatStr).fakeFormatString, date); +} +exports.formatDate = formatDate; +// Date Range Formatting +// ------------------------------------------------------------------------------------------------- +// TODO: make it work with timezone offset +/* +Using a formatting string meant for a single date, generate a range string, like +"Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ. +If the dates are the same as far as the format string is concerned, just return a single +rendering of one date, without any separator. +*/ +function formatRange(date1, date2, formatStr, separator, isRTL) { + var localeData; + date1 = moment_ext_1.default.parseZone(date1); + date2 = moment_ext_1.default.parseZone(date2); + localeData = date1.localeData(); + // Expand localized format strings, like "LL" -> "MMMM D YYYY". + // BTW, this is not important for `formatDate` because it is impossible to put custom tokens + // or non-zero areas in Moment's localized format strings. + formatStr = localeData.longDateFormat(formatStr) || formatStr; + return renderParsedFormat(getParsedFormatString(formatStr), date1, date2, separator || ' - ', isRTL); +} +exports.formatRange = formatRange; +/* +Renders a range with an already-parsed format string. +*/ +function renderParsedFormat(parsedFormat, date1, date2, separator, isRTL) { + var sameUnits = parsedFormat.sameUnits; + var unzonedDate1 = date1.clone().stripZone(); // for same-unit comparisons + var unzonedDate2 = date2.clone().stripZone(); // " + var renderedParts1 = renderFakeFormatStringParts(parsedFormat.fakeFormatString, date1); + var renderedParts2 = renderFakeFormatStringParts(parsedFormat.fakeFormatString, date2); + var leftI; + var leftStr = ''; + var rightI; + var rightStr = ''; + var middleI; + var middleStr1 = ''; + var middleStr2 = ''; + var middleStr = ''; + // Start at the leftmost side of the formatting string and continue until you hit a token + // that is not the same between dates. + for (leftI = 0; leftI < sameUnits.length && (!sameUnits[leftI] || unzonedDate1.isSame(unzonedDate2, sameUnits[leftI])); leftI++) { + leftStr += renderedParts1[leftI]; + } + // Similarly, start at the rightmost side of the formatting string and move left + for (rightI = sameUnits.length - 1; rightI > leftI && (!sameUnits[rightI] || unzonedDate1.isSame(unzonedDate2, sameUnits[rightI])); rightI--) { + // If current chunk is on the boundary of unique date-content, and is a special-case + // date-formatting postfix character, then don't consume it. Consider it unique date-content. + // TODO: make configurable + if (rightI - 1 === leftI && renderedParts1[rightI] === '.') { + break; + } + rightStr = renderedParts1[rightI] + rightStr; + } + // The area in the middle is different for both of the dates. + // Collect them distinctly so we can jam them together later. + for (middleI = leftI; middleI <= rightI; middleI++) { + middleStr1 += renderedParts1[middleI]; + middleStr2 += renderedParts2[middleI]; + } + if (middleStr1 || middleStr2) { + if (isRTL) { + middleStr = middleStr2 + separator + middleStr1; + } + else { + middleStr = middleStr1 + separator + middleStr2; + } + } + return processMaybeMarkers(leftStr + middleStr + rightStr); +} +// Format String Parsing +// --------------------------------------------------------------------------------------------------------------------- +var parsedFormatStrCache = {}; +/* +Returns a parsed format string, leveraging a cache. +*/ +function getParsedFormatString(formatStr) { + return parsedFormatStrCache[formatStr] || + (parsedFormatStrCache[formatStr] = parseFormatString(formatStr)); +} +/* +Parses a format string into the following: +- fakeFormatString: a momentJS formatting string, littered with special control characters that get post-processed. +- sameUnits: for every part in fakeFormatString, if the part is a token, the value will be a unit string (like "day"), + that indicates how similar a range's start & end must be in order to share the same formatted text. + If not a token, then the value is null. + Always a flat array (not nested liked "chunks"). +*/ +function parseFormatString(formatStr) { + var chunks = chunkFormatString(formatStr); + return { + fakeFormatString: buildFakeFormatString(chunks), + sameUnits: buildSameUnits(chunks) + }; +} +/* +Break the formatting string into an array of chunks. +A 'maybe' chunk will have nested chunks. +*/ +function chunkFormatString(formatStr) { + var chunks = []; + var match; + // TODO: more descrimination + // \4 is a backreference to the first character of a multi-character set. + var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g; + while ((match = chunker.exec(formatStr))) { + if (match[1]) { + chunks.push.apply(chunks, // append + splitStringLiteral(match[1])); + } + else if (match[2]) { + chunks.push({ maybe: chunkFormatString(match[2]) }); + } + else if (match[3]) { + chunks.push({ token: match[3] }); + } + else if (match[5]) { + chunks.push.apply(chunks, // append + splitStringLiteral(match[5])); + } + } + return chunks; +} +/* +Potentially splits a literal-text string into multiple parts. For special cases. +*/ +function splitStringLiteral(s) { + if (s === '. ') { + return ['.', ' ']; // for locales with periods bound to the end of each year/month/date + } + else { + return [s]; + } +} +/* +Given chunks parsed from a real format string, generate a fake (aka "intermediate") format string with special control +characters that will eventually be given to moment for formatting, and then post-processed. +*/ +function buildFakeFormatString(chunks) { + var parts = []; + var i; + var chunk; + for (i = 0; i < chunks.length; i++) { + chunk = chunks[i]; + if (typeof chunk === 'string') { + parts.push('[' + chunk + ']'); + } + else if (chunk.token) { + if (chunk.token in specialTokens) { + parts.push(SPECIAL_TOKEN_MARKER + // useful during post-processing + '[' + chunk.token + ']' // preserve as literal text + ); + } + else { + parts.push(chunk.token); // unprotected text implies a format string + } + } + else if (chunk.maybe) { + parts.push(MAYBE_MARKER + // useful during post-processing + buildFakeFormatString(chunk.maybe) + + MAYBE_MARKER); + } + } + return parts.join(PART_SEPARATOR); +} +/* +Given parsed chunks from a real formatting string, generates an array of unit strings (like "day") that indicate +in which regard two dates must be similar in order to share range formatting text. +The `chunks` can be nested (because of "maybe" chunks), however, the returned array will be flat. +*/ +function buildSameUnits(chunks) { + var units = []; + var i; + var chunk; + var tokenInfo; + for (i = 0; i < chunks.length; i++) { + chunk = chunks[i]; + if (chunk.token) { + tokenInfo = largeTokenMap[chunk.token.charAt(0)]; + units.push(tokenInfo ? tokenInfo.unit : 'second'); // default to a very strict same-second + } + else if (chunk.maybe) { + units.push.apply(units, // append + buildSameUnits(chunk.maybe)); + } + else { + units.push(null); + } + } + return units; +} +// Rendering to text +// --------------------------------------------------------------------------------------------------------------------- +/* +Formats a date with a fake format string, post-processes the control characters, then returns. +*/ +function renderFakeFormatString(fakeFormatString, date) { + return processMaybeMarkers(renderFakeFormatStringParts(fakeFormatString, date).join('')); +} +/* +Formats a date into parts that will have been post-processed, EXCEPT for the "maybe" markers. +*/ +function renderFakeFormatStringParts(fakeFormatString, date) { + var parts = []; + var fakeRender = moment_ext_1.oldMomentFormat(date, fakeFormatString); + var fakeParts = fakeRender.split(PART_SEPARATOR); + var i; + var fakePart; + for (i = 0; i < fakeParts.length; i++) { + fakePart = fakeParts[i]; + if (fakePart.charAt(0) === SPECIAL_TOKEN_MARKER) { + parts.push( + // the literal string IS the token's name. + // call special token's registered function. + specialTokens[fakePart.substring(1)](date)); + } + else { + parts.push(fakePart); + } + } + return parts; +} +/* +Accepts an almost-finally-formatted string and processes the "maybe" control characters, returning a new string. +*/ +function processMaybeMarkers(s) { + return s.replace(MAYBE_REGEXP, function (m0, m1) { + if (m1.match(/[1-9]/)) { + return m1; + } + else { + return ''; + } + }); +} +// Misc Utils +// ------------------------------------------------------------------------------------------------- +/* +Returns a unit string, either 'year', 'month', 'day', or null for the most granular formatting token in the string. +*/ +function queryMostGranularFormatUnit(formatStr) { + var chunks = chunkFormatString(formatStr); + var i; + var chunk; + var candidate; + var best; + for (i = 0; i < chunks.length; i++) { + chunk = chunks[i]; + if (chunk.token) { + candidate = largeTokenMap[chunk.token.charAt(0)]; + if (candidate) { + if (!best || candidate.value > best.value) { + best = candidate; + } + } + } + } + if (best) { + return best.unit; + } + return null; +} +exports.queryMostGranularFormatUnit = queryMostGranularFormatUnit; + + +/***/ }), +/* 48 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var Class_1 = __webpack_require__(33); +var EmitterMixin_1 = __webpack_require__(11); +var ListenerMixin_1 = __webpack_require__(7); +var Model = /** @class */ (function (_super) { + tslib_1.__extends(Model, _super); + function Model() { + var _this = _super.call(this) || this; + _this._watchers = {}; + _this._props = {}; + _this.applyGlobalWatchers(); + _this.constructed(); + return _this; + } + Model.watch = function (name) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + // subclasses should make a masked-copy of the superclass's map + // TODO: write test + if (!this.prototype.hasOwnProperty('_globalWatchArgs')) { + this.prototype._globalWatchArgs = Object.create(this.prototype._globalWatchArgs); + } + this.prototype._globalWatchArgs[name] = args; + }; + Model.prototype.constructed = function () { + // useful for monkeypatching. TODO: BaseClass? + }; + Model.prototype.applyGlobalWatchers = function () { + var map = this._globalWatchArgs; + var name; + for (name in map) { + this.watch.apply(this, [name].concat(map[name])); + } + }; + Model.prototype.has = function (name) { + return name in this._props; + }; + Model.prototype.get = function (name) { + if (name === undefined) { + return this._props; + } + return this._props[name]; + }; + Model.prototype.set = function (name, val) { + var newProps; + if (typeof name === 'string') { + newProps = {}; + newProps[name] = val === undefined ? null : val; + } + else { + newProps = name; + } + this.setProps(newProps); + }; + Model.prototype.reset = function (newProps) { + var oldProps = this._props; + var changeset = {}; // will have undefined's to signal unsets + var name; + for (name in oldProps) { + changeset[name] = undefined; + } + for (name in newProps) { + changeset[name] = newProps[name]; + } + this.setProps(changeset); + }; + Model.prototype.unset = function (name) { + var newProps = {}; + var names; + var i; + if (typeof name === 'string') { + names = [name]; + } + else { + names = name; + } + for (i = 0; i < names.length; i++) { + newProps[names[i]] = undefined; + } + this.setProps(newProps); + }; + Model.prototype.setProps = function (newProps) { + var changedProps = {}; + var changedCnt = 0; + var name; + var val; + for (name in newProps) { + val = newProps[name]; + // a change in value? + // if an object, don't check equality, because might have been mutated internally. + // TODO: eventually enforce immutability. + if (typeof val === 'object' || + val !== this._props[name]) { + changedProps[name] = val; + changedCnt++; + } + } + if (changedCnt) { + this.trigger('before:batchChange', changedProps); + for (name in changedProps) { + val = changedProps[name]; + this.trigger('before:change', name, val); + this.trigger('before:change:' + name, val); + } + for (name in changedProps) { + val = changedProps[name]; + if (val === undefined) { + delete this._props[name]; + } + else { + this._props[name] = val; + } + this.trigger('change:' + name, val); + this.trigger('change', name, val); + } + this.trigger('batchChange', changedProps); + } + }; + Model.prototype.watch = function (name, depList, startFunc, stopFunc) { + var _this = this; + this.unwatch(name); + this._watchers[name] = this._watchDeps(depList, function (deps) { + var res = startFunc.call(_this, deps); + if (res && res.then) { + _this.unset(name); // put in an unset state while resolving + res.then(function (val) { + _this.set(name, val); + }); + } + else { + _this.set(name, res); + } + }, function (deps) { + _this.unset(name); + if (stopFunc) { + stopFunc.call(_this, deps); + } + }); + }; + Model.prototype.unwatch = function (name) { + var watcher = this._watchers[name]; + if (watcher) { + delete this._watchers[name]; + watcher.teardown(); + } + }; + Model.prototype._watchDeps = function (depList, startFunc, stopFunc) { + var _this = this; + var queuedChangeCnt = 0; + var depCnt = depList.length; + var satisfyCnt = 0; + var values = {}; // what's passed as the `deps` arguments + var bindTuples = []; // array of [ eventName, handlerFunc ] arrays + var isCallingStop = false; + var onBeforeDepChange = function (depName, val, isOptional) { + queuedChangeCnt++; + if (queuedChangeCnt === 1) { + if (satisfyCnt === depCnt) { + isCallingStop = true; + stopFunc(values); + isCallingStop = false; + } + } + }; + var onDepChange = function (depName, val, isOptional) { + if (val === undefined) { + // required dependency that was previously set? + if (!isOptional && values[depName] !== undefined) { + satisfyCnt--; + } + delete values[depName]; + } + else { + // required dependency that was previously unset? + if (!isOptional && values[depName] === undefined) { + satisfyCnt++; + } + values[depName] = val; + } + queuedChangeCnt--; + if (!queuedChangeCnt) { + // now finally satisfied or satisfied all along? + if (satisfyCnt === depCnt) { + // if the stopFunc initiated another value change, ignore it. + // it will be processed by another change event anyway. + if (!isCallingStop) { + startFunc(values); + } + } + } + }; + // intercept for .on() that remembers handlers + var bind = function (eventName, handler) { + _this.on(eventName, handler); + bindTuples.push([eventName, handler]); + }; + // listen to dependency changes + depList.forEach(function (depName) { + var isOptional = false; + if (depName.charAt(0) === '?') { + depName = depName.substring(1); + isOptional = true; + } + bind('before:change:' + depName, function (val) { + onBeforeDepChange(depName, val, isOptional); + }); + bind('change:' + depName, function (val) { + onDepChange(depName, val, isOptional); + }); + }); + // process current dependency values + depList.forEach(function (depName) { + var isOptional = false; + if (depName.charAt(0) === '?') { + depName = depName.substring(1); + isOptional = true; + } + if (_this.has(depName)) { + values[depName] = _this.get(depName); + satisfyCnt++; + } + else if (isOptional) { + satisfyCnt++; + } + }); + // initially satisfied + if (satisfyCnt === depCnt) { + startFunc(values); + } + return { + teardown: function () { + // remove all handlers + for (var i = 0; i < bindTuples.length; i++) { + _this.off(bindTuples[i][0], bindTuples[i][1]); + } + bindTuples = null; + // was satisfied, so call stopFunc + if (satisfyCnt === depCnt) { + stopFunc(); + } + }, + flash: function () { + if (satisfyCnt === depCnt) { + stopFunc(); + startFunc(values); + } + } + }; + }; + Model.prototype.flash = function (name) { + var watcher = this._watchers[name]; + if (watcher) { + watcher.flash(); + } + }; + return Model; +}(Class_1.default)); +exports.default = Model; +Model.prototype._globalWatchArgs = {}; // mutation protection in Model.watch +EmitterMixin_1.default.mixInto(Model); +ListenerMixin_1.default.mixInto(Model); + + +/***/ }), +/* 49 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var moment = __webpack_require__(0); +var util_1 = __webpack_require__(4); +var SingleEventDef_1 = __webpack_require__(13); +var RecurringEventDef_1 = __webpack_require__(210); +exports.default = { + parse: function (eventInput, source) { + if (util_1.isTimeString(eventInput.start) || moment.isDuration(eventInput.start) || + util_1.isTimeString(eventInput.end) || moment.isDuration(eventInput.end)) { + return RecurringEventDef_1.default.parse(eventInput, source); + } + else { + return SingleEventDef_1.default.parse(eventInput, source); + } + } +}; + + +/***/ }), +/* 50 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var util_1 = __webpack_require__(4); +var EventDateProfile_1 = __webpack_require__(17); +var EventDefDateMutation = /** @class */ (function () { + function EventDefDateMutation() { + this.clearEnd = false; + this.forceTimed = false; + this.forceAllDay = false; + } + EventDefDateMutation.createFromDiff = function (dateProfile0, dateProfile1, largeUnit) { + var clearEnd = dateProfile0.end && !dateProfile1.end; + var forceTimed = dateProfile0.isAllDay() && !dateProfile1.isAllDay(); + var forceAllDay = !dateProfile0.isAllDay() && dateProfile1.isAllDay(); + var dateDelta; + var endDiff; + var endDelta; + var mutation; + // subtracts the dates in the appropriate way, returning a duration + function subtractDates(date1, date0) { + if (largeUnit) { + return util_1.diffByUnit(date1, date0, largeUnit); // poorly named + } + else if (dateProfile1.isAllDay()) { + return util_1.diffDay(date1, date0); // poorly named + } + else { + return util_1.diffDayTime(date1, date0); // poorly named + } + } + dateDelta = subtractDates(dateProfile1.start, dateProfile0.start); + if (dateProfile1.end) { + // use unzonedRanges because dateProfile0.end might be null + endDiff = subtractDates(dateProfile1.unzonedRange.getEnd(), dateProfile0.unzonedRange.getEnd()); + endDelta = endDiff.subtract(dateDelta); + } + mutation = new EventDefDateMutation(); + mutation.clearEnd = clearEnd; + mutation.forceTimed = forceTimed; + mutation.forceAllDay = forceAllDay; + mutation.setDateDelta(dateDelta); + mutation.setEndDelta(endDelta); + return mutation; + }; + /* + returns an undo function. + */ + EventDefDateMutation.prototype.buildNewDateProfile = function (eventDateProfile, calendar) { + var start = eventDateProfile.start.clone(); + var end = null; + var shouldRezone = false; + if (eventDateProfile.end && !this.clearEnd) { + end = eventDateProfile.end.clone(); + } + else if (this.endDelta && !end) { + end = calendar.getDefaultEventEnd(eventDateProfile.isAllDay(), start); + } + if (this.forceTimed) { + shouldRezone = true; + if (!start.hasTime()) { + start.time(0); + } + if (end && !end.hasTime()) { + end.time(0); + } + } + else if (this.forceAllDay) { + if (start.hasTime()) { + start.stripTime(); + } + if (end && end.hasTime()) { + end.stripTime(); + } + } + if (this.dateDelta) { + shouldRezone = true; + start.add(this.dateDelta); + if (end) { + end.add(this.dateDelta); + } + } + // do this before adding startDelta to start, so we can work off of start + if (this.endDelta) { + shouldRezone = true; + end.add(this.endDelta); + } + if (this.startDelta) { + shouldRezone = true; + start.add(this.startDelta); + } + if (shouldRezone) { + start = calendar.applyTimezone(start); + if (end) { + end = calendar.applyTimezone(end); + } + } + // TODO: okay to access calendar option? + if (!end && calendar.opt('forceEventDuration')) { + end = calendar.getDefaultEventEnd(eventDateProfile.isAllDay(), start); + } + return new EventDateProfile_1.default(start, end, calendar); + }; + EventDefDateMutation.prototype.setDateDelta = function (dateDelta) { + if (dateDelta && dateDelta.valueOf()) { + this.dateDelta = dateDelta; + } + else { + this.dateDelta = null; + } + }; + EventDefDateMutation.prototype.setStartDelta = function (startDelta) { + if (startDelta && startDelta.valueOf()) { + this.startDelta = startDelta; + } + else { + this.startDelta = null; + } + }; + EventDefDateMutation.prototype.setEndDelta = function (endDelta) { + if (endDelta && endDelta.valueOf()) { + this.endDelta = endDelta; + } + else { + this.endDelta = null; + } + }; + EventDefDateMutation.prototype.isEmpty = function () { + return !this.clearEnd && !this.forceTimed && !this.forceAllDay && + !this.dateDelta && !this.startDelta && !this.endDelta; + }; + return EventDefDateMutation; +}()); +exports.default = EventDefDateMutation; + + +/***/ }), +/* 51 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var StandardTheme_1 = __webpack_require__(213); +var JqueryUiTheme_1 = __webpack_require__(214); +var themeClassHash = {}; +function defineThemeSystem(themeName, themeClass) { + themeClassHash[themeName] = themeClass; +} +exports.defineThemeSystem = defineThemeSystem; +function getThemeSystemClass(themeSetting) { + if (!themeSetting) { + return StandardTheme_1.default; + } + else if (themeSetting === true) { + return JqueryUiTheme_1.default; + } + else { + return themeClassHash[themeSetting]; + } +} +exports.getThemeSystemClass = getThemeSystemClass; + + +/***/ }), +/* 52 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var Promise_1 = __webpack_require__(20); +var EventSource_1 = __webpack_require__(6); +var SingleEventDef_1 = __webpack_require__(13); +var ArrayEventSource = /** @class */ (function (_super) { + tslib_1.__extends(ArrayEventSource, _super); + function ArrayEventSource(calendar) { + var _this = _super.call(this, calendar) || this; + _this.eventDefs = []; // for if setRawEventDefs is never called + return _this; + } + ArrayEventSource.parse = function (rawInput, calendar) { + var rawProps; + // normalize raw input + if ($.isArray(rawInput.events)) { + rawProps = rawInput; + } + else if ($.isArray(rawInput)) { + rawProps = { events: rawInput }; + } + if (rawProps) { + return EventSource_1.default.parse.call(this, rawProps, calendar); + } + return false; + }; + ArrayEventSource.prototype.setRawEventDefs = function (rawEventDefs) { + this.rawEventDefs = rawEventDefs; + this.eventDefs = this.parseEventDefs(rawEventDefs); + }; + ArrayEventSource.prototype.fetch = function (start, end, timezone) { + var eventDefs = this.eventDefs; + var i; + if (this.currentTimezone != null && + this.currentTimezone !== timezone) { + for (i = 0; i < eventDefs.length; i++) { + if (eventDefs[i] instanceof SingleEventDef_1.default) { + eventDefs[i].rezone(); + } + } + } + this.currentTimezone = timezone; + return Promise_1.default.resolve(eventDefs); + }; + ArrayEventSource.prototype.addEventDef = function (eventDef) { + this.eventDefs.push(eventDef); + }; + /* + eventDefId already normalized to a string + */ + ArrayEventSource.prototype.removeEventDefsById = function (eventDefId) { + return util_1.removeMatching(this.eventDefs, function (eventDef) { + return eventDef.id === eventDefId; + }); + }; + ArrayEventSource.prototype.removeAllEventDefs = function () { + this.eventDefs = []; + }; + ArrayEventSource.prototype.getPrimitive = function () { + return this.rawEventDefs; + }; + ArrayEventSource.prototype.applyManualStandardProps = function (rawProps) { + var superSuccess = _super.prototype.applyManualStandardProps.call(this, rawProps); + this.setRawEventDefs(rawProps.events); + return superSuccess; + }; + return ArrayEventSource; +}(EventSource_1.default)); +exports.default = ArrayEventSource; +ArrayEventSource.defineStandardProps({ + events: false // don't automatically transfer +}); + + +/***/ }), +/* 53 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +/* +A cache for the left/right/top/bottom/width/height values for one or more elements. +Works with both offset (from topleft document) and position (from offsetParent). + +options: +- els +- isHorizontal +- isVertical +*/ +var CoordCache = /** @class */ (function () { + function CoordCache(options) { + this.isHorizontal = false; // whether to query for left/right/width + this.isVertical = false; // whether to query for top/bottom/height + this.els = $(options.els); + this.isHorizontal = options.isHorizontal; + this.isVertical = options.isVertical; + this.forcedOffsetParentEl = options.offsetParent ? $(options.offsetParent) : null; + } + // Queries the els for coordinates and stores them. + // Call this method before using and of the get* methods below. + CoordCache.prototype.build = function () { + var offsetParentEl = this.forcedOffsetParentEl; + if (!offsetParentEl && this.els.length > 0) { + offsetParentEl = this.els.eq(0).offsetParent(); + } + this.origin = offsetParentEl ? + offsetParentEl.offset() : + null; + this.boundingRect = this.queryBoundingRect(); + if (this.isHorizontal) { + this.buildElHorizontals(); + } + if (this.isVertical) { + this.buildElVerticals(); + } + }; + // Destroys all internal data about coordinates, freeing memory + CoordCache.prototype.clear = function () { + this.origin = null; + this.boundingRect = null; + this.lefts = null; + this.rights = null; + this.tops = null; + this.bottoms = null; + }; + // When called, if coord caches aren't built, builds them + CoordCache.prototype.ensureBuilt = function () { + if (!this.origin) { + this.build(); + } + }; + // Populates the left/right internal coordinate arrays + CoordCache.prototype.buildElHorizontals = function () { + var lefts = []; + var rights = []; + this.els.each(function (i, node) { + var el = $(node); + var left = el.offset().left; + var width = el.outerWidth(); + lefts.push(left); + rights.push(left + width); + }); + this.lefts = lefts; + this.rights = rights; + }; + // Populates the top/bottom internal coordinate arrays + CoordCache.prototype.buildElVerticals = function () { + var tops = []; + var bottoms = []; + this.els.each(function (i, node) { + var el = $(node); + var top = el.offset().top; + var height = el.outerHeight(); + tops.push(top); + bottoms.push(top + height); + }); + this.tops = tops; + this.bottoms = bottoms; + }; + // Given a left offset (from document left), returns the index of the el that it horizontally intersects. + // If no intersection is made, returns undefined. + CoordCache.prototype.getHorizontalIndex = function (leftOffset) { + this.ensureBuilt(); + var lefts = this.lefts; + var rights = this.rights; + var len = lefts.length; + var i; + for (i = 0; i < len; i++) { + if (leftOffset >= lefts[i] && leftOffset < rights[i]) { + return i; + } + } + }; + // Given a top offset (from document top), returns the index of the el that it vertically intersects. + // If no intersection is made, returns undefined. + CoordCache.prototype.getVerticalIndex = function (topOffset) { + this.ensureBuilt(); + var tops = this.tops; + var bottoms = this.bottoms; + var len = tops.length; + var i; + for (i = 0; i < len; i++) { + if (topOffset >= tops[i] && topOffset < bottoms[i]) { + return i; + } + } + }; + // Gets the left offset (from document left) of the element at the given index + CoordCache.prototype.getLeftOffset = function (leftIndex) { + this.ensureBuilt(); + return this.lefts[leftIndex]; + }; + // Gets the left position (from offsetParent left) of the element at the given index + CoordCache.prototype.getLeftPosition = function (leftIndex) { + this.ensureBuilt(); + return this.lefts[leftIndex] - this.origin.left; + }; + // Gets the right offset (from document left) of the element at the given index. + // This value is NOT relative to the document's right edge, like the CSS concept of "right" would be. + CoordCache.prototype.getRightOffset = function (leftIndex) { + this.ensureBuilt(); + return this.rights[leftIndex]; + }; + // Gets the right position (from offsetParent left) of the element at the given index. + // This value is NOT relative to the offsetParent's right edge, like the CSS concept of "right" would be. + CoordCache.prototype.getRightPosition = function (leftIndex) { + this.ensureBuilt(); + return this.rights[leftIndex] - this.origin.left; + }; + // Gets the width of the element at the given index + CoordCache.prototype.getWidth = function (leftIndex) { + this.ensureBuilt(); + return this.rights[leftIndex] - this.lefts[leftIndex]; + }; + // Gets the top offset (from document top) of the element at the given index + CoordCache.prototype.getTopOffset = function (topIndex) { + this.ensureBuilt(); + return this.tops[topIndex]; + }; + // Gets the top position (from offsetParent top) of the element at the given position + CoordCache.prototype.getTopPosition = function (topIndex) { + this.ensureBuilt(); + return this.tops[topIndex] - this.origin.top; + }; + // Gets the bottom offset (from the document top) of the element at the given index. + // This value is NOT relative to the offsetParent's bottom edge, like the CSS concept of "bottom" would be. + CoordCache.prototype.getBottomOffset = function (topIndex) { + this.ensureBuilt(); + return this.bottoms[topIndex]; + }; + // Gets the bottom position (from the offsetParent top) of the element at the given index. + // This value is NOT relative to the offsetParent's bottom edge, like the CSS concept of "bottom" would be. + CoordCache.prototype.getBottomPosition = function (topIndex) { + this.ensureBuilt(); + return this.bottoms[topIndex] - this.origin.top; + }; + // Gets the height of the element at the given index + CoordCache.prototype.getHeight = function (topIndex) { + this.ensureBuilt(); + return this.bottoms[topIndex] - this.tops[topIndex]; + }; + // Bounding Rect + // TODO: decouple this from CoordCache + // Compute and return what the elements' bounding rectangle is, from the user's perspective. + // Right now, only returns a rectangle if constrained by an overflow:scroll element. + // Returns null if there are no elements + CoordCache.prototype.queryBoundingRect = function () { + var scrollParentEl; + if (this.els.length > 0) { + scrollParentEl = util_1.getScrollParent(this.els.eq(0)); + if (!scrollParentEl.is(document)) { + return util_1.getClientRect(scrollParentEl); + } + } + return null; + }; + CoordCache.prototype.isPointInBounds = function (leftOffset, topOffset) { + return this.isLeftInBounds(leftOffset) && this.isTopInBounds(topOffset); + }; + CoordCache.prototype.isLeftInBounds = function (leftOffset) { + return !this.boundingRect || (leftOffset >= this.boundingRect.left && leftOffset < this.boundingRect.right); + }; + CoordCache.prototype.isTopInBounds = function (topOffset) { + return !this.boundingRect || (topOffset >= this.boundingRect.top && topOffset < this.boundingRect.bottom); + }; + return CoordCache; +}()); +exports.default = CoordCache; + + +/***/ }), +/* 54 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var ListenerMixin_1 = __webpack_require__(7); +var GlobalEmitter_1 = __webpack_require__(21); +/* Tracks a drag's mouse movement, firing various handlers +----------------------------------------------------------------------------------------------------------------------*/ +// TODO: use Emitter +var DragListener = /** @class */ (function () { + function DragListener(options) { + this.isInteracting = false; + this.isDistanceSurpassed = false; + this.isDelayEnded = false; + this.isDragging = false; + this.isTouch = false; + this.isGeneric = false; // initiated by 'dragstart' (jqui) + this.shouldCancelTouchScroll = true; + this.scrollAlwaysKills = false; + this.isAutoScroll = false; + // defaults + this.scrollSensitivity = 30; // pixels from edge for scrolling to start + this.scrollSpeed = 200; // pixels per second, at maximum speed + this.scrollIntervalMs = 50; // millisecond wait between scroll increment + this.options = options || {}; + } + // Interaction (high-level) + // ----------------------------------------------------------------------------------------------------------------- + DragListener.prototype.startInteraction = function (ev, extraOptions) { + if (extraOptions === void 0) { extraOptions = {}; } + if (ev.type === 'mousedown') { + if (GlobalEmitter_1.default.get().shouldIgnoreMouse()) { + return; + } + else if (!util_1.isPrimaryMouseButton(ev)) { + return; + } + else { + ev.preventDefault(); // prevents native selection in most browsers + } + } + if (!this.isInteracting) { + // process options + this.delay = util_1.firstDefined(extraOptions.delay, this.options.delay, 0); + this.minDistance = util_1.firstDefined(extraOptions.distance, this.options.distance, 0); + this.subjectEl = this.options.subjectEl; + util_1.preventSelection($('body')); + this.isInteracting = true; + this.isTouch = util_1.getEvIsTouch(ev); + this.isGeneric = ev.type === 'dragstart'; + this.isDelayEnded = false; + this.isDistanceSurpassed = false; + this.originX = util_1.getEvX(ev); + this.originY = util_1.getEvY(ev); + this.scrollEl = util_1.getScrollParent($(ev.target)); + this.bindHandlers(); + this.initAutoScroll(); + this.handleInteractionStart(ev); + this.startDelay(ev); + if (!this.minDistance) { + this.handleDistanceSurpassed(ev); + } + } + }; + DragListener.prototype.handleInteractionStart = function (ev) { + this.trigger('interactionStart', ev); + }; + DragListener.prototype.endInteraction = function (ev, isCancelled) { + if (this.isInteracting) { + this.endDrag(ev); + if (this.delayTimeoutId) { + clearTimeout(this.delayTimeoutId); + this.delayTimeoutId = null; + } + this.destroyAutoScroll(); + this.unbindHandlers(); + this.isInteracting = false; + this.handleInteractionEnd(ev, isCancelled); + util_1.allowSelection($('body')); + } + }; + DragListener.prototype.handleInteractionEnd = function (ev, isCancelled) { + this.trigger('interactionEnd', ev, isCancelled || false); + }; + // Binding To DOM + // ----------------------------------------------------------------------------------------------------------------- + DragListener.prototype.bindHandlers = function () { + // some browsers (Safari in iOS 10) don't allow preventDefault on touch events that are bound after touchstart, + // so listen to the GlobalEmitter singleton, which is always bound, instead of the document directly. + var globalEmitter = GlobalEmitter_1.default.get(); + if (this.isGeneric) { + this.listenTo($(document), { + drag: this.handleMove, + dragstop: this.endInteraction + }); + } + else if (this.isTouch) { + this.listenTo(globalEmitter, { + touchmove: this.handleTouchMove, + touchend: this.endInteraction, + scroll: this.handleTouchScroll + }); + } + else { + this.listenTo(globalEmitter, { + mousemove: this.handleMouseMove, + mouseup: this.endInteraction + }); + } + this.listenTo(globalEmitter, { + selectstart: util_1.preventDefault, + contextmenu: util_1.preventDefault // long taps would open menu on Chrome dev tools + }); + }; + DragListener.prototype.unbindHandlers = function () { + this.stopListeningTo(GlobalEmitter_1.default.get()); + this.stopListeningTo($(document)); // for isGeneric + }; + // Drag (high-level) + // ----------------------------------------------------------------------------------------------------------------- + // extraOptions ignored if drag already started + DragListener.prototype.startDrag = function (ev, extraOptions) { + this.startInteraction(ev, extraOptions); // ensure interaction began + if (!this.isDragging) { + this.isDragging = true; + this.handleDragStart(ev); + } + }; + DragListener.prototype.handleDragStart = function (ev) { + this.trigger('dragStart', ev); + }; + DragListener.prototype.handleMove = function (ev) { + var dx = util_1.getEvX(ev) - this.originX; + var dy = util_1.getEvY(ev) - this.originY; + var minDistance = this.minDistance; + var distanceSq; // current distance from the origin, squared + if (!this.isDistanceSurpassed) { + distanceSq = dx * dx + dy * dy; + if (distanceSq >= minDistance * minDistance) { + this.handleDistanceSurpassed(ev); + } + } + if (this.isDragging) { + this.handleDrag(dx, dy, ev); + } + }; + // Called while the mouse is being moved and when we know a legitimate drag is taking place + DragListener.prototype.handleDrag = function (dx, dy, ev) { + this.trigger('drag', dx, dy, ev); + this.updateAutoScroll(ev); // will possibly cause scrolling + }; + DragListener.prototype.endDrag = function (ev) { + if (this.isDragging) { + this.isDragging = false; + this.handleDragEnd(ev); + } + }; + DragListener.prototype.handleDragEnd = function (ev) { + this.trigger('dragEnd', ev); + }; + // Delay + // ----------------------------------------------------------------------------------------------------------------- + DragListener.prototype.startDelay = function (initialEv) { + var _this = this; + if (this.delay) { + this.delayTimeoutId = setTimeout(function () { + _this.handleDelayEnd(initialEv); + }, this.delay); + } + else { + this.handleDelayEnd(initialEv); + } + }; + DragListener.prototype.handleDelayEnd = function (initialEv) { + this.isDelayEnded = true; + if (this.isDistanceSurpassed) { + this.startDrag(initialEv); + } + }; + // Distance + // ----------------------------------------------------------------------------------------------------------------- + DragListener.prototype.handleDistanceSurpassed = function (ev) { + this.isDistanceSurpassed = true; + if (this.isDelayEnded) { + this.startDrag(ev); + } + }; + // Mouse / Touch + // ----------------------------------------------------------------------------------------------------------------- + DragListener.prototype.handleTouchMove = function (ev) { + // prevent inertia and touchmove-scrolling while dragging + if (this.isDragging && this.shouldCancelTouchScroll) { + ev.preventDefault(); + } + this.handleMove(ev); + }; + DragListener.prototype.handleMouseMove = function (ev) { + this.handleMove(ev); + }; + // Scrolling (unrelated to auto-scroll) + // ----------------------------------------------------------------------------------------------------------------- + DragListener.prototype.handleTouchScroll = function (ev) { + // if the drag is being initiated by touch, but a scroll happens before + // the drag-initiating delay is over, cancel the drag + if (!this.isDragging || this.scrollAlwaysKills) { + this.endInteraction(ev, true); // isCancelled=true + } + }; + // Utils + // ----------------------------------------------------------------------------------------------------------------- + // Triggers a callback. Calls a function in the option hash of the same name. + // Arguments beyond the first `name` are forwarded on. + DragListener.prototype.trigger = function (name) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + if (this.options[name]) { + this.options[name].apply(this, args); + } + // makes _methods callable by event name. TODO: kill this + if (this['_' + name]) { + this['_' + name].apply(this, args); + } + }; + // Auto-scroll + // ----------------------------------------------------------------------------------------------------------------- + DragListener.prototype.initAutoScroll = function () { + var scrollEl = this.scrollEl; + this.isAutoScroll = + this.options.scroll && + scrollEl && + !scrollEl.is(window) && + !scrollEl.is(document); + if (this.isAutoScroll) { + // debounce makes sure rapid calls don't happen + this.listenTo(scrollEl, 'scroll', util_1.debounce(this.handleDebouncedScroll, 100)); + } + }; + DragListener.prototype.destroyAutoScroll = function () { + this.endAutoScroll(); // kill any animation loop + // remove the scroll handler if there is a scrollEl + if (this.isAutoScroll) { + this.stopListeningTo(this.scrollEl, 'scroll'); // will probably get removed by unbindHandlers too :( + } + }; + // Computes and stores the bounding rectangle of scrollEl + DragListener.prototype.computeScrollBounds = function () { + if (this.isAutoScroll) { + this.scrollBounds = util_1.getOuterRect(this.scrollEl); + // TODO: use getClientRect in future. but prevents auto scrolling when on top of scrollbars + } + }; + // Called when the dragging is in progress and scrolling should be updated + DragListener.prototype.updateAutoScroll = function (ev) { + var sensitivity = this.scrollSensitivity; + var bounds = this.scrollBounds; + var topCloseness; + var bottomCloseness; + var leftCloseness; + var rightCloseness; + var topVel = 0; + var leftVel = 0; + if (bounds) { + // compute closeness to edges. valid range is from 0.0 - 1.0 + topCloseness = (sensitivity - (util_1.getEvY(ev) - bounds.top)) / sensitivity; + bottomCloseness = (sensitivity - (bounds.bottom - util_1.getEvY(ev))) / sensitivity; + leftCloseness = (sensitivity - (util_1.getEvX(ev) - bounds.left)) / sensitivity; + rightCloseness = (sensitivity - (bounds.right - util_1.getEvX(ev))) / sensitivity; + // translate vertical closeness into velocity. + // mouse must be completely in bounds for velocity to happen. + if (topCloseness >= 0 && topCloseness <= 1) { + topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up + } + else if (bottomCloseness >= 0 && bottomCloseness <= 1) { + topVel = bottomCloseness * this.scrollSpeed; + } + // translate horizontal closeness into velocity + if (leftCloseness >= 0 && leftCloseness <= 1) { + leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left + } + else if (rightCloseness >= 0 && rightCloseness <= 1) { + leftVel = rightCloseness * this.scrollSpeed; + } + } + this.setScrollVel(topVel, leftVel); + }; + // Sets the speed-of-scrolling for the scrollEl + DragListener.prototype.setScrollVel = function (topVel, leftVel) { + this.scrollTopVel = topVel; + this.scrollLeftVel = leftVel; + this.constrainScrollVel(); // massages into realistic values + // if there is non-zero velocity, and an animation loop hasn't already started, then START + if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) { + this.scrollIntervalId = setInterval(util_1.proxy(this, 'scrollIntervalFunc'), // scope to `this` + this.scrollIntervalMs); + } + }; + // Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way + DragListener.prototype.constrainScrollVel = function () { + var el = this.scrollEl; + if (this.scrollTopVel < 0) { + if (el.scrollTop() <= 0) { + this.scrollTopVel = 0; + } + } + else if (this.scrollTopVel > 0) { + if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { + this.scrollTopVel = 0; + } + } + if (this.scrollLeftVel < 0) { + if (el.scrollLeft() <= 0) { + this.scrollLeftVel = 0; + } + } + else if (this.scrollLeftVel > 0) { + if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { + this.scrollLeftVel = 0; + } + } + }; + // This function gets called during every iteration of the scrolling animation loop + DragListener.prototype.scrollIntervalFunc = function () { + var el = this.scrollEl; + var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by + // change the value of scrollEl's scroll + if (this.scrollTopVel) { + el.scrollTop(el.scrollTop() + this.scrollTopVel * frac); + } + if (this.scrollLeftVel) { + el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac); + } + this.constrainScrollVel(); // since the scroll values changed, recompute the velocities + // if scrolled all the way, which causes the vels to be zero, stop the animation loop + if (!this.scrollTopVel && !this.scrollLeftVel) { + this.endAutoScroll(); + } + }; + // Kills any existing scrolling animation loop + DragListener.prototype.endAutoScroll = function () { + if (this.scrollIntervalId) { + clearInterval(this.scrollIntervalId); + this.scrollIntervalId = null; + this.handleScrollEnd(); + } + }; + // Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce) + DragListener.prototype.handleDebouncedScroll = function () { + // recompute all coordinates, but *only* if this is *not* part of our scrolling animation + if (!this.scrollIntervalId) { + this.handleScrollEnd(); + } + }; + DragListener.prototype.handleScrollEnd = function () { + // Called when scrolling has stopped, whether through auto scroll, or the user scrolling + }; + return DragListener; +}()); +exports.default = DragListener; +ListenerMixin_1.default.mixInto(DragListener); + + +/***/ }), +/* 55 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var util_1 = __webpack_require__(4); +var Mixin_1 = __webpack_require__(14); +/* +A set of rendering and date-related methods for a visual component comprised of one or more rows of day columns. +Prerequisite: the object being mixed into needs to be a *Grid* +*/ +var DayTableMixin = /** @class */ (function (_super) { + tslib_1.__extends(DayTableMixin, _super); + function DayTableMixin() { + return _super !== null && _super.apply(this, arguments) || this; + } + // Populates internal variables used for date calculation and rendering + DayTableMixin.prototype.updateDayTable = function () { + var t = this; + var view = t.view; + var calendar = view.calendar; + var date = calendar.msToUtcMoment(t.dateProfile.renderUnzonedRange.startMs, true); + var end = calendar.msToUtcMoment(t.dateProfile.renderUnzonedRange.endMs, true); + var dayIndex = -1; + var dayIndices = []; + var dayDates = []; + var daysPerRow; + var firstDay; + var rowCnt; + while (date.isBefore(end)) { + if (view.isHiddenDay(date)) { + dayIndices.push(dayIndex + 0.5); // mark that it's between indices + } + else { + dayIndex++; + dayIndices.push(dayIndex); + dayDates.push(date.clone()); + } + date.add(1, 'days'); + } + if (this.breakOnWeeks) { + // count columns until the day-of-week repeats + firstDay = dayDates[0].day(); + for (daysPerRow = 1; daysPerRow < dayDates.length; daysPerRow++) { + if (dayDates[daysPerRow].day() === firstDay) { + break; + } + } + rowCnt = Math.ceil(dayDates.length / daysPerRow); + } + else { + rowCnt = 1; + daysPerRow = dayDates.length; + } + this.dayDates = dayDates; + this.dayIndices = dayIndices; + this.daysPerRow = daysPerRow; + this.rowCnt = rowCnt; + this.updateDayTableCols(); + }; + // Computes and assigned the colCnt property and updates any options that may be computed from it + DayTableMixin.prototype.updateDayTableCols = function () { + this.colCnt = this.computeColCnt(); + this.colHeadFormat = + this.opt('columnHeaderFormat') || + this.opt('columnFormat') || // deprecated + this.computeColHeadFormat(); + }; + // Determines how many columns there should be in the table + DayTableMixin.prototype.computeColCnt = function () { + return this.daysPerRow; + }; + // Computes the ambiguously-timed moment for the given cell + DayTableMixin.prototype.getCellDate = function (row, col) { + return this.dayDates[this.getCellDayIndex(row, col)].clone(); + }; + // Computes the ambiguously-timed date range for the given cell + DayTableMixin.prototype.getCellRange = function (row, col) { + var start = this.getCellDate(row, col); + var end = start.clone().add(1, 'days'); + return { start: start, end: end }; + }; + // Returns the number of day cells, chronologically, from the first of the grid (0-based) + DayTableMixin.prototype.getCellDayIndex = function (row, col) { + return row * this.daysPerRow + this.getColDayIndex(col); + }; + // Returns the numner of day cells, chronologically, from the first cell in *any given row* + DayTableMixin.prototype.getColDayIndex = function (col) { + if (this.isRTL) { + return this.colCnt - 1 - col; + } + else { + return col; + } + }; + // Given a date, returns its chronolocial cell-index from the first cell of the grid. + // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets. + // If before the first offset, returns a negative number. + // If after the last offset, returns an offset past the last cell offset. + // Only works for *start* dates of cells. Will not work for exclusive end dates for cells. + DayTableMixin.prototype.getDateDayIndex = function (date) { + var dayIndices = this.dayIndices; + var dayOffset = date.diff(this.dayDates[0], 'days'); + if (dayOffset < 0) { + return dayIndices[0] - 1; + } + else if (dayOffset >= dayIndices.length) { + return dayIndices[dayIndices.length - 1] + 1; + } + else { + return dayIndices[dayOffset]; + } + }; + /* Options + ------------------------------------------------------------------------------------------------------------------*/ + // Computes a default column header formatting string if `colFormat` is not explicitly defined + DayTableMixin.prototype.computeColHeadFormat = function () { + // if more than one week row, or if there are a lot of columns with not much space, + // put just the day numbers will be in each cell + if (this.rowCnt > 1 || this.colCnt > 10) { + return 'ddd'; // "Sat" + } + else if (this.colCnt > 1) { + return this.opt('dayOfMonthFormat'); // "Sat 12/10" + } + else { + return 'dddd'; // "Saturday" + } + }; + /* Slicing + ------------------------------------------------------------------------------------------------------------------*/ + // Slices up a date range into a segment for every week-row it intersects with + DayTableMixin.prototype.sliceRangeByRow = function (unzonedRange) { + var daysPerRow = this.daysPerRow; + var normalRange = this.view.computeDayRange(unzonedRange); // make whole-day range, considering nextDayThreshold + var rangeFirst = this.getDateDayIndex(normalRange.start); // inclusive first index + var rangeLast = this.getDateDayIndex(normalRange.end.clone().subtract(1, 'days')); // inclusive last index + var segs = []; + var row; + var rowFirst; + var rowLast; // inclusive day-index range for current row + var segFirst; + var segLast; // inclusive day-index range for segment + for (row = 0; row < this.rowCnt; row++) { + rowFirst = row * daysPerRow; + rowLast = rowFirst + daysPerRow - 1; + // intersect segment's offset range with the row's + segFirst = Math.max(rangeFirst, rowFirst); + segLast = Math.min(rangeLast, rowLast); + // deal with in-between indices + segFirst = Math.ceil(segFirst); // in-between starts round to next cell + segLast = Math.floor(segLast); // in-between ends round to prev cell + if (segFirst <= segLast) { + segs.push({ + row: row, + // normalize to start of row + firstRowDayIndex: segFirst - rowFirst, + lastRowDayIndex: segLast - rowFirst, + // must be matching integers to be the segment's start/end + isStart: segFirst === rangeFirst, + isEnd: segLast === rangeLast + }); + } + } + return segs; + }; + // Slices up a date range into a segment for every day-cell it intersects with. + // TODO: make more DRY with sliceRangeByRow somehow. + DayTableMixin.prototype.sliceRangeByDay = function (unzonedRange) { + var daysPerRow = this.daysPerRow; + var normalRange = this.view.computeDayRange(unzonedRange); // make whole-day range, considering nextDayThreshold + var rangeFirst = this.getDateDayIndex(normalRange.start); // inclusive first index + var rangeLast = this.getDateDayIndex(normalRange.end.clone().subtract(1, 'days')); // inclusive last index + var segs = []; + var row; + var rowFirst; + var rowLast; // inclusive day-index range for current row + var i; + var segFirst; + var segLast; // inclusive day-index range for segment + for (row = 0; row < this.rowCnt; row++) { + rowFirst = row * daysPerRow; + rowLast = rowFirst + daysPerRow - 1; + for (i = rowFirst; i <= rowLast; i++) { + // intersect segment's offset range with the row's + segFirst = Math.max(rangeFirst, i); + segLast = Math.min(rangeLast, i); + // deal with in-between indices + segFirst = Math.ceil(segFirst); // in-between starts round to next cell + segLast = Math.floor(segLast); // in-between ends round to prev cell + if (segFirst <= segLast) { + segs.push({ + row: row, + // normalize to start of row + firstRowDayIndex: segFirst - rowFirst, + lastRowDayIndex: segLast - rowFirst, + // must be matching integers to be the segment's start/end + isStart: segFirst === rangeFirst, + isEnd: segLast === rangeLast + }); + } + } + } + return segs; + }; + /* Header Rendering + ------------------------------------------------------------------------------------------------------------------*/ + DayTableMixin.prototype.renderHeadHtml = function () { + var theme = this.view.calendar.theme; + return '' + + '' + + '' + + '' + + this.renderHeadTrHtml() + + '' + + '' + + ''; + }; + DayTableMixin.prototype.renderHeadIntroHtml = function () { + return this.renderIntroHtml(); // fall back to generic + }; + DayTableMixin.prototype.renderHeadTrHtml = function () { + return '' + + '' + + (this.isRTL ? '' : this.renderHeadIntroHtml()) + + this.renderHeadDateCellsHtml() + + (this.isRTL ? this.renderHeadIntroHtml() : '') + + ''; + }; + DayTableMixin.prototype.renderHeadDateCellsHtml = function () { + var htmls = []; + var col; + var date; + for (col = 0; col < this.colCnt; col++) { + date = this.getCellDate(0, col); + htmls.push(this.renderHeadDateCellHtml(date)); + } + return htmls.join(''); + }; + // TODO: when internalApiVersion, accept an object for HTML attributes + // (colspan should be no different) + DayTableMixin.prototype.renderHeadDateCellHtml = function (date, colspan, otherAttrs) { + var t = this; + var view = t.view; + var isDateValid = t.dateProfile.activeUnzonedRange.containsDate(date); // TODO: called too frequently. cache somehow. + var classNames = [ + 'fc-day-header', + view.calendar.theme.getClass('widgetHeader') + ]; + var innerHtml; + if (typeof t.opt('columnHeaderHtml') === 'function') { + innerHtml = t.opt('columnHeaderHtml')(date); + } + else if (typeof t.opt('columnHeaderText') === 'function') { + innerHtml = util_1.htmlEscape(t.opt('columnHeaderText')(date)); + } + else { + innerHtml = util_1.htmlEscape(date.format(t.colHeadFormat)); + } + // if only one row of days, the classNames on the header can represent the specific days beneath + if (t.rowCnt === 1) { + classNames = classNames.concat( + // includes the day-of-week class + // noThemeHighlight=true (don't highlight the header) + t.getDayClasses(date, true)); + } + else { + classNames.push('fc-' + util_1.dayIDs[date.day()]); // only add the day-of-week class + } + return '' + + ' 1 ? + ' colspan="' + colspan + '"' : + '') + + (otherAttrs ? + ' ' + otherAttrs : + '') + + '>' + + (isDateValid ? + // don't make a link if the heading could represent multiple days, or if there's only one day (forceOff) + view.buildGotoAnchorHtml({ date: date, forceOff: t.rowCnt > 1 || t.colCnt === 1 }, innerHtml) : + // if not valid, display text, but no link + innerHtml) + + ''; + }; + /* Background Rendering + ------------------------------------------------------------------------------------------------------------------*/ + DayTableMixin.prototype.renderBgTrHtml = function (row) { + return '' + + '' + + (this.isRTL ? '' : this.renderBgIntroHtml(row)) + + this.renderBgCellsHtml(row) + + (this.isRTL ? this.renderBgIntroHtml(row) : '') + + ''; + }; + DayTableMixin.prototype.renderBgIntroHtml = function (row) { + return this.renderIntroHtml(); // fall back to generic + }; + DayTableMixin.prototype.renderBgCellsHtml = function (row) { + var htmls = []; + var col; + var date; + for (col = 0; col < this.colCnt; col++) { + date = this.getCellDate(row, col); + htmls.push(this.renderBgCellHtml(date)); + } + return htmls.join(''); + }; + DayTableMixin.prototype.renderBgCellHtml = function (date, otherAttrs) { + var t = this; + var view = t.view; + var isDateValid = t.dateProfile.activeUnzonedRange.containsDate(date); // TODO: called too frequently. cache somehow. + var classes = t.getDayClasses(date); + classes.unshift('fc-day', view.calendar.theme.getClass('widgetContent')); + return ''; + }; + /* Generic + ------------------------------------------------------------------------------------------------------------------*/ + DayTableMixin.prototype.renderIntroHtml = function () { + // Generates the default HTML intro for any row. User classes should override + }; + // TODO: a generic method for dealing with , RTL, intro + // when increment internalApiVersion + // wrapTr (scheduler) + /* Utils + ------------------------------------------------------------------------------------------------------------------*/ + // Applies the generic "intro" and "outro" HTML to the given cells. + // Intro means the leftmost cell when the calendar is LTR and the rightmost cell when RTL. Vice-versa for outro. + DayTableMixin.prototype.bookendCells = function (trEl) { + var introHtml = this.renderIntroHtml(); + if (introHtml) { + if (this.isRTL) { + trEl.append(introHtml); + } + else { + trEl.prepend(introHtml); + } + } + }; + return DayTableMixin; +}(Mixin_1.default)); +exports.default = DayTableMixin; + + +/***/ }), +/* 56 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var BusinessHourRenderer = /** @class */ (function () { + /* + component implements: + - eventRangesToEventFootprints + - eventFootprintsToSegs + */ + function BusinessHourRenderer(component, fillRenderer) { + this.component = component; + this.fillRenderer = fillRenderer; + } + BusinessHourRenderer.prototype.render = function (businessHourGenerator) { + var component = this.component; + var unzonedRange = component._getDateProfile().activeUnzonedRange; + var eventInstanceGroup = businessHourGenerator.buildEventInstanceGroup(component.hasAllDayBusinessHours, unzonedRange); + var eventFootprints = eventInstanceGroup ? + component.eventRangesToEventFootprints(eventInstanceGroup.sliceRenderRanges(unzonedRange)) : + []; + this.renderEventFootprints(eventFootprints); + }; + BusinessHourRenderer.prototype.renderEventFootprints = function (eventFootprints) { + var segs = this.component.eventFootprintsToSegs(eventFootprints); + this.renderSegs(segs); + this.segs = segs; + }; + BusinessHourRenderer.prototype.renderSegs = function (segs) { + if (this.fillRenderer) { + this.fillRenderer.renderSegs('businessHours', segs, { + getClasses: function (seg) { + return ['fc-nonbusiness', 'fc-bgevent']; + } + }); + } + }; + BusinessHourRenderer.prototype.unrender = function () { + if (this.fillRenderer) { + this.fillRenderer.unrender('businessHours'); + } + this.segs = null; + }; + BusinessHourRenderer.prototype.getSegs = function () { + return this.segs || []; + }; + return BusinessHourRenderer; +}()); +exports.default = BusinessHourRenderer; + + +/***/ }), +/* 57 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var FillRenderer = /** @class */ (function () { + function FillRenderer(component) { + this.fillSegTag = 'div'; + this.component = component; + this.elsByFill = {}; + } + FillRenderer.prototype.renderFootprint = function (type, componentFootprint, props) { + this.renderSegs(type, this.component.componentFootprintToSegs(componentFootprint), props); + }; + FillRenderer.prototype.renderSegs = function (type, segs, props) { + var els; + segs = this.buildSegEls(type, segs, props); // assignes `.el` to each seg. returns successfully rendered segs + els = this.attachSegEls(type, segs); + if (els) { + this.reportEls(type, els); + } + return segs; + }; + // Unrenders a specific type of fill that is currently rendered on the grid + FillRenderer.prototype.unrender = function (type) { + var el = this.elsByFill[type]; + if (el) { + el.remove(); + delete this.elsByFill[type]; + } + }; + // Renders and assigns an `el` property for each fill segment. Generic enough to work with different types. + // Only returns segments that successfully rendered. + FillRenderer.prototype.buildSegEls = function (type, segs, props) { + var _this = this; + var html = ''; + var renderedSegs = []; + var i; + if (segs.length) { + // build a large concatenation of segment HTML + for (i = 0; i < segs.length; i++) { + html += this.buildSegHtml(type, segs[i], props); + } + // Grab individual elements from the combined HTML string. Use each as the default rendering. + // Then, compute the 'el' for each segment. + $(html).each(function (i, node) { + var seg = segs[i]; + var el = $(node); + // allow custom filter methods per-type + if (props.filterEl) { + el = props.filterEl(seg, el); + } + if (el) { + el = $(el); // allow custom filter to return raw DOM node + // correct element type? (would be bad if a non-TD were inserted into a table for example) + if (el.is(_this.fillSegTag)) { + seg.el = el; + renderedSegs.push(seg); + } + } + }); + } + return renderedSegs; + }; + // Builds the HTML needed for one fill segment. Generic enough to work with different types. + FillRenderer.prototype.buildSegHtml = function (type, seg, props) { + // custom hooks per-type + var classes = props.getClasses ? props.getClasses(seg) : []; + var css = util_1.cssToStr(props.getCss ? props.getCss(seg) : {}); + return '<' + this.fillSegTag + + (classes.length ? ' class="' + classes.join(' ') + '"' : '') + + (css ? ' style="' + css + '"' : '') + + ' />'; + }; + // Should return wrapping DOM structure + FillRenderer.prototype.attachSegEls = function (type, segs) { + // subclasses must implement + }; + FillRenderer.prototype.reportEls = function (type, nodes) { + if (this.elsByFill[type]) { + this.elsByFill[type] = this.elsByFill[type].add(nodes); + } + else { + this.elsByFill[type] = $(nodes); + } + }; + return FillRenderer; +}()); +exports.default = FillRenderer; + + +/***/ }), +/* 58 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var SingleEventDef_1 = __webpack_require__(13); +var EventFootprint_1 = __webpack_require__(36); +var EventSource_1 = __webpack_require__(6); +var HelperRenderer = /** @class */ (function () { + function HelperRenderer(component, eventRenderer) { + this.view = component._getView(); + this.component = component; + this.eventRenderer = eventRenderer; + } + HelperRenderer.prototype.renderComponentFootprint = function (componentFootprint) { + this.renderEventFootprints([ + this.fabricateEventFootprint(componentFootprint) + ]); + }; + HelperRenderer.prototype.renderEventDraggingFootprints = function (eventFootprints, sourceSeg, isTouch) { + this.renderEventFootprints(eventFootprints, sourceSeg, 'fc-dragging', isTouch ? null : this.view.opt('dragOpacity')); + }; + HelperRenderer.prototype.renderEventResizingFootprints = function (eventFootprints, sourceSeg, isTouch) { + this.renderEventFootprints(eventFootprints, sourceSeg, 'fc-resizing'); + }; + HelperRenderer.prototype.renderEventFootprints = function (eventFootprints, sourceSeg, extraClassNames, opacity) { + var segs = this.component.eventFootprintsToSegs(eventFootprints); + var classNames = 'fc-helper ' + (extraClassNames || ''); + var i; + // assigns each seg's el and returns a subset of segs that were rendered + segs = this.eventRenderer.renderFgSegEls(segs); + for (i = 0; i < segs.length; i++) { + segs[i].el.addClass(classNames); + } + if (opacity != null) { + for (i = 0; i < segs.length; i++) { + segs[i].el.css('opacity', opacity); + } + } + this.helperEls = this.renderSegs(segs, sourceSeg); + }; + /* + Must return all mock event elements + */ + HelperRenderer.prototype.renderSegs = function (segs, sourceSeg) { + // Subclasses must implement + }; + HelperRenderer.prototype.unrender = function () { + if (this.helperEls) { + this.helperEls.remove(); + this.helperEls = null; + } + }; + HelperRenderer.prototype.fabricateEventFootprint = function (componentFootprint) { + var calendar = this.view.calendar; + var eventDateProfile = calendar.footprintToDateProfile(componentFootprint); + var dummyEvent = new SingleEventDef_1.default(new EventSource_1.default(calendar)); + var dummyInstance; + dummyEvent.dateProfile = eventDateProfile; + dummyInstance = dummyEvent.buildInstance(); + return new EventFootprint_1.default(componentFootprint, dummyEvent, dummyInstance); + }; + return HelperRenderer; +}()); +exports.default = HelperRenderer; + + +/***/ }), +/* 59 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var GlobalEmitter_1 = __webpack_require__(21); +var Interaction_1 = __webpack_require__(15); +var EventPointing = /** @class */ (function (_super) { + tslib_1.__extends(EventPointing, _super); + function EventPointing() { + return _super !== null && _super.apply(this, arguments) || this; + } + /* + component must implement: + - publiclyTrigger + */ + EventPointing.prototype.bindToEl = function (el) { + var component = this.component; + component.bindSegHandlerToEl(el, 'click', this.handleClick.bind(this)); + component.bindSegHandlerToEl(el, 'mouseenter', this.handleMouseover.bind(this)); + component.bindSegHandlerToEl(el, 'mouseleave', this.handleMouseout.bind(this)); + }; + EventPointing.prototype.handleClick = function (seg, ev) { + var res = this.component.publiclyTrigger('eventClick', { + context: seg.el[0], + args: [seg.footprint.getEventLegacy(), ev, this.view] + }); + if (res === false) { + ev.preventDefault(); + } + }; + // Updates internal state and triggers handlers for when an event element is moused over + EventPointing.prototype.handleMouseover = function (seg, ev) { + if (!GlobalEmitter_1.default.get().shouldIgnoreMouse() && + !this.mousedOverSeg) { + this.mousedOverSeg = seg; + // TODO: move to EventSelecting's responsibility + if (this.view.isEventDefResizable(seg.footprint.eventDef)) { + seg.el.addClass('fc-allow-mouse-resize'); + } + this.component.publiclyTrigger('eventMouseover', { + context: seg.el[0], + args: [seg.footprint.getEventLegacy(), ev, this.view] + }); + } + }; + // Updates internal state and triggers handlers for when an event element is moused out. + // Can be given no arguments, in which case it will mouseout the segment that was previously moused over. + EventPointing.prototype.handleMouseout = function (seg, ev) { + if (this.mousedOverSeg) { + this.mousedOverSeg = null; + // TODO: move to EventSelecting's responsibility + if (this.view.isEventDefResizable(seg.footprint.eventDef)) { + seg.el.removeClass('fc-allow-mouse-resize'); + } + this.component.publiclyTrigger('eventMouseout', { + context: seg.el[0], + args: [ + seg.footprint.getEventLegacy(), + ev || {}, + this.view + ] + }); + } + }; + EventPointing.prototype.end = function () { + if (this.mousedOverSeg) { + this.handleMouseout(this.mousedOverSeg); + } + }; + return EventPointing; +}(Interaction_1.default)); +exports.default = EventPointing; + + +/***/ }), +/* 60 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var Mixin_1 = __webpack_require__(14); +var DateClicking_1 = __webpack_require__(245); +var DateSelecting_1 = __webpack_require__(225); +var EventPointing_1 = __webpack_require__(59); +var EventDragging_1 = __webpack_require__(224); +var EventResizing_1 = __webpack_require__(223); +var ExternalDropping_1 = __webpack_require__(222); +var StandardInteractionsMixin = /** @class */ (function (_super) { + tslib_1.__extends(StandardInteractionsMixin, _super); + function StandardInteractionsMixin() { + return _super !== null && _super.apply(this, arguments) || this; + } + return StandardInteractionsMixin; +}(Mixin_1.default)); +exports.default = StandardInteractionsMixin; +StandardInteractionsMixin.prototype.dateClickingClass = DateClicking_1.default; +StandardInteractionsMixin.prototype.dateSelectingClass = DateSelecting_1.default; +StandardInteractionsMixin.prototype.eventPointingClass = EventPointing_1.default; +StandardInteractionsMixin.prototype.eventDraggingClass = EventDragging_1.default; +StandardInteractionsMixin.prototype.eventResizingClass = EventResizing_1.default; +StandardInteractionsMixin.prototype.externalDroppingClass = ExternalDropping_1.default; + + +/***/ }), +/* 61 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var CoordCache_1 = __webpack_require__(53); +var Popover_1 = __webpack_require__(249); +var UnzonedRange_1 = __webpack_require__(5); +var ComponentFootprint_1 = __webpack_require__(12); +var EventFootprint_1 = __webpack_require__(36); +var BusinessHourRenderer_1 = __webpack_require__(56); +var StandardInteractionsMixin_1 = __webpack_require__(60); +var InteractiveDateComponent_1 = __webpack_require__(40); +var DayTableMixin_1 = __webpack_require__(55); +var DayGridEventRenderer_1 = __webpack_require__(250); +var DayGridHelperRenderer_1 = __webpack_require__(251); +var DayGridFillRenderer_1 = __webpack_require__(252); +/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week. +----------------------------------------------------------------------------------------------------------------------*/ +var DayGrid = /** @class */ (function (_super) { + tslib_1.__extends(DayGrid, _super); + function DayGrid(view) { + var _this = _super.call(this, view) || this; + _this.cellWeekNumbersVisible = false; // display week numbers in day cell? + _this.bottomCoordPadding = 0; // hack for extending the hit area for the last row of the coordinate grid + // isRigid determines whether the individual rows should ignore the contents and be a constant height. + // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient. + _this.isRigid = false; + _this.hasAllDayBusinessHours = true; + return _this; + } + // Slices up the given span (unzoned start/end with other misc data) into an array of segments + DayGrid.prototype.componentFootprintToSegs = function (componentFootprint) { + var segs = this.sliceRangeByRow(componentFootprint.unzonedRange); + var i; + var seg; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + if (this.isRTL) { + seg.leftCol = this.daysPerRow - 1 - seg.lastRowDayIndex; + seg.rightCol = this.daysPerRow - 1 - seg.firstRowDayIndex; + } + else { + seg.leftCol = seg.firstRowDayIndex; + seg.rightCol = seg.lastRowDayIndex; + } + } + return segs; + }; + /* Date Rendering + ------------------------------------------------------------------------------------------------------------------*/ + DayGrid.prototype.renderDates = function (dateProfile) { + this.dateProfile = dateProfile; + this.updateDayTable(); + this.renderGrid(); + }; + DayGrid.prototype.unrenderDates = function () { + this.removeSegPopover(); + }; + // Renders the rows and columns into the component's `this.el`, which should already be assigned. + DayGrid.prototype.renderGrid = function () { + var view = this.view; + var rowCnt = this.rowCnt; + var colCnt = this.colCnt; + var html = ''; + var row; + var col; + if (this.headContainerEl) { + this.headContainerEl.html(this.renderHeadHtml()); + } + for (row = 0; row < rowCnt; row++) { + html += this.renderDayRowHtml(row, this.isRigid); + } + this.el.html(html); + this.rowEls = this.el.find('.fc-row'); + this.cellEls = this.el.find('.fc-day, .fc-disabled-day'); + this.rowCoordCache = new CoordCache_1.default({ + els: this.rowEls, + isVertical: true + }); + this.colCoordCache = new CoordCache_1.default({ + els: this.cellEls.slice(0, this.colCnt), + isHorizontal: true + }); + // trigger dayRender with each cell's element + for (row = 0; row < rowCnt; row++) { + for (col = 0; col < colCnt; col++) { + this.publiclyTrigger('dayRender', { + context: view, + args: [ + this.getCellDate(row, col), + this.getCellEl(row, col), + view + ] + }); + } + } + }; + // Generates the HTML for a single row, which is a div that wraps a table. + // `row` is the row number. + DayGrid.prototype.renderDayRowHtml = function (row, isRigid) { + var theme = this.view.calendar.theme; + var classes = ['fc-row', 'fc-week', theme.getClass('dayRow')]; + if (isRigid) { + classes.push('fc-rigid'); + } + return '' + + '' + + '' + + '' + + this.renderBgTrHtml(row) + + '' + + '' + + '' + + '' + + (this.getIsNumbersVisible() ? + '' + + this.renderNumberTrHtml(row) + + '' : + '') + + '' + + '' + + ''; + }; + DayGrid.prototype.getIsNumbersVisible = function () { + return this.getIsDayNumbersVisible() || this.cellWeekNumbersVisible; + }; + DayGrid.prototype.getIsDayNumbersVisible = function () { + return this.rowCnt > 1; + }; + /* Grid Number Rendering + ------------------------------------------------------------------------------------------------------------------*/ + DayGrid.prototype.renderNumberTrHtml = function (row) { + return '' + + '' + + (this.isRTL ? '' : this.renderNumberIntroHtml(row)) + + this.renderNumberCellsHtml(row) + + (this.isRTL ? this.renderNumberIntroHtml(row) : '') + + ''; + }; + DayGrid.prototype.renderNumberIntroHtml = function (row) { + return this.renderIntroHtml(); + }; + DayGrid.prototype.renderNumberCellsHtml = function (row) { + var htmls = []; + var col; + var date; + for (col = 0; col < this.colCnt; col++) { + date = this.getCellDate(row, col); + htmls.push(this.renderNumberCellHtml(date)); + } + return htmls.join(''); + }; + // Generates the HTML for the s of the "number" row in the DayGrid's content skeleton. + // The number row will only exist if either day numbers or week numbers are turned on. + DayGrid.prototype.renderNumberCellHtml = function (date) { + var view = this.view; + var html = ''; + var isDateValid = this.dateProfile.activeUnzonedRange.containsDate(date); // TODO: called too frequently. cache somehow. + var isDayNumberVisible = this.getIsDayNumbersVisible() && isDateValid; + var classes; + var weekCalcFirstDoW; + if (!isDayNumberVisible && !this.cellWeekNumbersVisible) { + // no numbers in day cell (week number must be along the side) + return ''; // will create an empty space above events :( + } + classes = this.getDayClasses(date); + classes.unshift('fc-day-top'); + if (this.cellWeekNumbersVisible) { + // To determine the day of week number change under ISO, we cannot + // rely on moment.js methods such as firstDayOfWeek() or weekday(), + // because they rely on the locale's dow (possibly overridden by + // our firstDay option), which may not be Monday. We cannot change + // dow, because that would affect the calendar start day as well. + if (date._locale._fullCalendar_weekCalc === 'ISO') { + weekCalcFirstDoW = 1; // Monday by ISO 8601 definition + } + else { + weekCalcFirstDoW = date._locale.firstDayOfWeek(); + } + } + html += ''; + if (this.cellWeekNumbersVisible && (date.day() === weekCalcFirstDoW)) { + html += view.buildGotoAnchorHtml({ date: date, type: 'week' }, { 'class': 'fc-week-number' }, date.format('w') // inner HTML + ); + } + if (isDayNumberVisible) { + html += view.buildGotoAnchorHtml(date, { 'class': 'fc-day-number' }, date.format('D') // inner HTML + ); + } + html += ''; + return html; + }; + /* Hit System + ------------------------------------------------------------------------------------------------------------------*/ + DayGrid.prototype.prepareHits = function () { + this.colCoordCache.build(); + this.rowCoordCache.build(); + this.rowCoordCache.bottoms[this.rowCnt - 1] += this.bottomCoordPadding; // hack + }; + DayGrid.prototype.releaseHits = function () { + this.colCoordCache.clear(); + this.rowCoordCache.clear(); + }; + DayGrid.prototype.queryHit = function (leftOffset, topOffset) { + if (this.colCoordCache.isLeftInBounds(leftOffset) && this.rowCoordCache.isTopInBounds(topOffset)) { + var col = this.colCoordCache.getHorizontalIndex(leftOffset); + var row = this.rowCoordCache.getVerticalIndex(topOffset); + if (row != null && col != null) { + return this.getCellHit(row, col); + } + } + }; + DayGrid.prototype.getHitFootprint = function (hit) { + var range = this.getCellRange(hit.row, hit.col); + return new ComponentFootprint_1.default(new UnzonedRange_1.default(range.start, range.end), true // all-day? + ); + }; + DayGrid.prototype.getHitEl = function (hit) { + return this.getCellEl(hit.row, hit.col); + }; + /* Cell System + ------------------------------------------------------------------------------------------------------------------*/ + // FYI: the first column is the leftmost column, regardless of date + DayGrid.prototype.getCellHit = function (row, col) { + return { + row: row, + col: col, + component: this, + left: this.colCoordCache.getLeftOffset(col), + right: this.colCoordCache.getRightOffset(col), + top: this.rowCoordCache.getTopOffset(row), + bottom: this.rowCoordCache.getBottomOffset(row) + }; + }; + DayGrid.prototype.getCellEl = function (row, col) { + return this.cellEls.eq(row * this.colCnt + col); + }; + /* Event Rendering + ------------------------------------------------------------------------------------------------------------------*/ + // Unrenders all events currently rendered on the grid + DayGrid.prototype.executeEventUnrender = function () { + this.removeSegPopover(); // removes the "more.." events popover + _super.prototype.executeEventUnrender.call(this); + }; + // Retrieves all rendered segment objects currently rendered on the grid + DayGrid.prototype.getOwnEventSegs = function () { + // append the segments from the "more..." popover + return _super.prototype.getOwnEventSegs.call(this).concat(this.popoverSegs || []); + }; + /* Event Drag Visualization + ------------------------------------------------------------------------------------------------------------------*/ + // Renders a visual indication of an event or external element being dragged. + // `eventLocation` has zoned start and end (optional) + DayGrid.prototype.renderDrag = function (eventFootprints, seg, isTouch) { + var i; + for (i = 0; i < eventFootprints.length; i++) { + this.renderHighlight(eventFootprints[i].componentFootprint); + } + // render drags from OTHER components as helpers + if (eventFootprints.length && seg && seg.component !== this) { + this.helperRenderer.renderEventDraggingFootprints(eventFootprints, seg, isTouch); + return true; // signal helpers rendered + } + }; + // Unrenders any visual indication of a hovering event + DayGrid.prototype.unrenderDrag = function () { + this.unrenderHighlight(); + this.helperRenderer.unrender(); + }; + /* Event Resize Visualization + ------------------------------------------------------------------------------------------------------------------*/ + // Renders a visual indication of an event being resized + DayGrid.prototype.renderEventResize = function (eventFootprints, seg, isTouch) { + var i; + for (i = 0; i < eventFootprints.length; i++) { + this.renderHighlight(eventFootprints[i].componentFootprint); + } + this.helperRenderer.renderEventResizingFootprints(eventFootprints, seg, isTouch); + }; + // Unrenders a visual indication of an event being resized + DayGrid.prototype.unrenderEventResize = function () { + this.unrenderHighlight(); + this.helperRenderer.unrender(); + }; + /* More+ Link Popover + ------------------------------------------------------------------------------------------------------------------*/ + DayGrid.prototype.removeSegPopover = function () { + if (this.segPopover) { + this.segPopover.hide(); // in handler, will call segPopover's removeElement + } + }; + // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid. + // `levelLimit` can be false (don't limit), a number, or true (should be computed). + DayGrid.prototype.limitRows = function (levelLimit) { + var rowStructs = this.eventRenderer.rowStructs || []; + var row; // row # + var rowLevelLimit; + for (row = 0; row < rowStructs.length; row++) { + this.unlimitRow(row); + if (!levelLimit) { + rowLevelLimit = false; + } + else if (typeof levelLimit === 'number') { + rowLevelLimit = levelLimit; + } + else { + rowLevelLimit = this.computeRowLevelLimit(row); + } + if (rowLevelLimit !== false) { + this.limitRow(row, rowLevelLimit); + } + } + }; + // Computes the number of levels a row will accomodate without going outside its bounds. + // Assumes the row is "rigid" (maintains a constant height regardless of what is inside). + // `row` is the row number. + DayGrid.prototype.computeRowLevelLimit = function (row) { + var rowEl = this.rowEls.eq(row); // the containing "fake" row div + var rowHeight = rowEl.height(); // TODO: cache somehow? + var trEls = this.eventRenderer.rowStructs[row].tbodyEl.children(); + var i; + var trEl; + var trHeight; + function iterInnerHeights(i, childNode) { + trHeight = Math.max(trHeight, $(childNode).outerHeight()); + } + // Reveal one level at a time and stop when we find one out of bounds + for (i = 0; i < trEls.length; i++) { + trEl = trEls.eq(i).removeClass('fc-limited'); // reset to original state (reveal) + // with rowspans>1 and IE8, trEl.outerHeight() would return the height of the largest cell, + // so instead, find the tallest inner content element. + trHeight = 0; + trEl.find('> td > :first-child').each(iterInnerHeights); + if (trEl.position().top + trHeight > rowHeight) { + return i; + } + } + return false; // should not limit at all + }; + // Limits the given grid row to the maximum number of levels and injects "more" links if necessary. + // `row` is the row number. + // `levelLimit` is a number for the maximum (inclusive) number of levels allowed. + DayGrid.prototype.limitRow = function (row, levelLimit) { + var _this = this; + var rowStruct = this.eventRenderer.rowStructs[row]; + var moreNodes = []; // array of "more" links and DOM nodes + var col = 0; // col #, left-to-right (not chronologically) + var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right + var cellMatrix; // a matrix (by level, then column) of all jQuery elements in the row + var limitedNodes; // array of temporarily hidden level and segment DOM nodes + var i; + var seg; + var segsBelow; // array of segment objects below `seg` in the current `col` + var totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies + var colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column) + var td; + var rowspan; + var segMoreNodes; // array of "more" cells that will stand-in for the current seg's cell + var j; + var moreTd; + var moreWrap; + var moreLink; + // Iterates through empty level cells and places "more" links inside if need be + var emptyCellsUntil = function (endCol) { + while (col < endCol) { + segsBelow = _this.getCellSegs(row, col, levelLimit); + if (segsBelow.length) { + td = cellMatrix[levelLimit - 1][col]; + moreLink = _this.renderMoreLink(row, col, segsBelow); + moreWrap = $('').append(moreLink); + td.append(moreWrap); + moreNodes.push(moreWrap[0]); + } + col++; + } + }; + if (levelLimit && levelLimit < rowStruct.segLevels.length) { + levelSegs = rowStruct.segLevels[levelLimit - 1]; + cellMatrix = rowStruct.cellMatrix; + limitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level elements past the limit + .addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array + // iterate though segments in the last allowable level + for (i = 0; i < levelSegs.length; i++) { + seg = levelSegs[i]; + emptyCellsUntil(seg.leftCol); // process empty cells before the segment + // determine *all* segments below `seg` that occupy the same columns + colSegsBelow = []; + totalSegsBelow = 0; + while (col <= seg.rightCol) { + segsBelow = this.getCellSegs(row, col, levelLimit); + colSegsBelow.push(segsBelow); + totalSegsBelow += segsBelow.length; + col++; + } + if (totalSegsBelow) { + td = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell + rowspan = td.attr('rowspan') || 1; + segMoreNodes = []; + // make a replacement for each column the segment occupies. will be one for each colspan + for (j = 0; j < colSegsBelow.length; j++) { + moreTd = $('').attr('rowspan', rowspan); + segsBelow = colSegsBelow[j]; + moreLink = this.renderMoreLink(row, seg.leftCol + j, [seg].concat(segsBelow) // count seg as hidden too + ); + moreWrap = $('').append(moreLink); + moreTd.append(moreWrap); + segMoreNodes.push(moreTd[0]); + moreNodes.push(moreTd[0]); + } + td.addClass('fc-limited').after($(segMoreNodes)); // hide original and inject replacements + limitedNodes.push(td[0]); + } + } + emptyCellsUntil(this.colCnt); // finish off the level + rowStruct.moreEls = $(moreNodes); // for easy undoing later + rowStruct.limitedEls = $(limitedNodes); // for easy undoing later + } + }; + // Reveals all levels and removes all "more"-related elements for a grid's row. + // `row` is a row number. + DayGrid.prototype.unlimitRow = function (row) { + var rowStruct = this.eventRenderer.rowStructs[row]; + if (rowStruct.moreEls) { + rowStruct.moreEls.remove(); + rowStruct.moreEls = null; + } + if (rowStruct.limitedEls) { + rowStruct.limitedEls.removeClass('fc-limited'); + rowStruct.limitedEls = null; + } + }; + // Renders an element that represents hidden event element for a cell. + // Responsible for attaching click handler as well. + DayGrid.prototype.renderMoreLink = function (row, col, hiddenSegs) { + var _this = this; + var view = this.view; + return $('') + .text(this.getMoreLinkText(hiddenSegs.length)) + .on('click', function (ev) { + var clickOption = _this.opt('eventLimitClick'); + var date = _this.getCellDate(row, col); + var moreEl = $(ev.currentTarget); + var dayEl = _this.getCellEl(row, col); + var allSegs = _this.getCellSegs(row, col); + // rescope the segments to be within the cell's date + var reslicedAllSegs = _this.resliceDaySegs(allSegs, date); + var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date); + if (typeof clickOption === 'function') { + // the returned value can be an atomic option + clickOption = _this.publiclyTrigger('eventLimitClick', { + context: view, + args: [ + { + date: date.clone(), + dayEl: dayEl, + moreEl: moreEl, + segs: reslicedAllSegs, + hiddenSegs: reslicedHiddenSegs + }, + ev, + view + ] + }); + } + if (clickOption === 'popover') { + _this.showSegPopover(row, col, moreEl, reslicedAllSegs); + } + else if (typeof clickOption === 'string') { + view.calendar.zoomTo(date, clickOption); + } + }); + }; + // Reveals the popover that displays all events within a cell + DayGrid.prototype.showSegPopover = function (row, col, moreLink, segs) { + var _this = this; + var view = this.view; + var moreWrap = moreLink.parent(); // the wrapper around the + var topEl; // the element we want to match the top coordinate of + var options; + if (this.rowCnt === 1) { + topEl = view.el; // will cause the popover to cover any sort of header + } + else { + topEl = this.rowEls.eq(row); // will align with top of row + } + options = { + className: 'fc-more-popover ' + view.calendar.theme.getClass('popover'), + content: this.renderSegPopoverContent(row, col, segs), + parentEl: view.el, + top: topEl.offset().top, + autoHide: true, + viewportConstrain: this.opt('popoverViewportConstrain'), + hide: function () { + // kill everything when the popover is hidden + // notify events to be removed + if (_this.popoverSegs) { + _this.triggerBeforeEventSegsDestroyed(_this.popoverSegs); + } + _this.segPopover.removeElement(); + _this.segPopover = null; + _this.popoverSegs = null; + } + }; + // Determine horizontal coordinate. + // We use the moreWrap instead of the to avoid border confusion. + if (this.isRTL) { + options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border + } + else { + options.left = moreWrap.offset().left - 1; // -1 to be over cell border + } + this.segPopover = new Popover_1.default(options); + this.segPopover.show(); + // the popover doesn't live within the grid's container element, and thus won't get the event + // delegated-handlers for free. attach event-related handlers to the popover. + this.bindAllSegHandlersToEl(this.segPopover.el); + this.triggerAfterEventSegsRendered(segs); + }; + // Builds the inner DOM contents of the segment popover + DayGrid.prototype.renderSegPopoverContent = function (row, col, segs) { + var view = this.view; + var theme = view.calendar.theme; + var title = this.getCellDate(row, col).format(this.opt('dayPopoverFormat')); + var content = $('' + + '' + + '' + + util_1.htmlEscape(title) + + '' + + '' + + '' + + '' + + '' + + ''); + var segContainer = content.find('.fc-event-container'); + var i; + // render each seg's `el` and only return the visible segs + segs = this.eventRenderer.renderFgSegEls(segs, true); // disableResizing=true + this.popoverSegs = segs; + for (i = 0; i < segs.length; i++) { + // because segments in the popover are not part of a grid coordinate system, provide a hint to any + // grids that want to do drag-n-drop about which cell it came from + this.hitsNeeded(); + segs[i].hit = this.getCellHit(row, col); + this.hitsNotNeeded(); + segContainer.append(segs[i].el); + } + return content; + }; + // Given the events within an array of segment objects, reslice them to be in a single day + DayGrid.prototype.resliceDaySegs = function (segs, dayDate) { + var dayStart = dayDate.clone(); + var dayEnd = dayStart.clone().add(1, 'days'); + var dayRange = new UnzonedRange_1.default(dayStart, dayEnd); + var newSegs = []; + var i; + var seg; + var slicedRange; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + slicedRange = seg.footprint.componentFootprint.unzonedRange.intersect(dayRange); + if (slicedRange) { + newSegs.push($.extend({}, seg, { + footprint: new EventFootprint_1.default(new ComponentFootprint_1.default(slicedRange, seg.footprint.componentFootprint.isAllDay), seg.footprint.eventDef, seg.footprint.eventInstance), + isStart: seg.isStart && slicedRange.isStart, + isEnd: seg.isEnd && slicedRange.isEnd + })); + } + } + // force an order because eventsToSegs doesn't guarantee one + // TODO: research if still needed + this.eventRenderer.sortEventSegs(newSegs); + return newSegs; + }; + // Generates the text that should be inside a "more" link, given the number of events it represents + DayGrid.prototype.getMoreLinkText = function (num) { + var opt = this.opt('eventLimitText'); + if (typeof opt === 'function') { + return opt(num); + } + else { + return '+' + num + ' ' + opt; + } + }; + // Returns segments within a given cell. + // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs. + DayGrid.prototype.getCellSegs = function (row, col, startLevel) { + var segMatrix = this.eventRenderer.rowStructs[row].segMatrix; + var level = startLevel || 0; + var segs = []; + var seg; + while (level < segMatrix.length) { + seg = segMatrix[level][col]; + if (seg) { + segs.push(seg); + } + level++; + } + return segs; + }; + return DayGrid; +}(InteractiveDateComponent_1.default)); +exports.default = DayGrid; +DayGrid.prototype.eventRendererClass = DayGridEventRenderer_1.default; +DayGrid.prototype.businessHourRendererClass = BusinessHourRenderer_1.default; +DayGrid.prototype.helperRendererClass = DayGridHelperRenderer_1.default; +DayGrid.prototype.fillRendererClass = DayGridFillRenderer_1.default; +StandardInteractionsMixin_1.default.mixInto(DayGrid); +DayTableMixin_1.default.mixInto(DayGrid); + + +/***/ }), +/* 62 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var Scroller_1 = __webpack_require__(39); +var View_1 = __webpack_require__(41); +var BasicViewDateProfileGenerator_1 = __webpack_require__(228); +var DayGrid_1 = __webpack_require__(61); +/* An abstract class for the "basic" views, as well as month view. Renders one or more rows of day cells. +----------------------------------------------------------------------------------------------------------------------*/ +// It is a manager for a DayGrid subcomponent, which does most of the heavy lifting. +// It is responsible for managing width/height. +var BasicView = /** @class */ (function (_super) { + tslib_1.__extends(BasicView, _super); + function BasicView(calendar, viewSpec) { + var _this = _super.call(this, calendar, viewSpec) || this; + _this.dayGrid = _this.instantiateDayGrid(); + _this.dayGrid.isRigid = _this.hasRigidRows(); + if (_this.opt('weekNumbers')) { + if (_this.opt('weekNumbersWithinDays')) { + _this.dayGrid.cellWeekNumbersVisible = true; + _this.dayGrid.colWeekNumbersVisible = false; + } + else { + _this.dayGrid.cellWeekNumbersVisible = false; + _this.dayGrid.colWeekNumbersVisible = true; + } + } + _this.addChild(_this.dayGrid); + _this.scroller = new Scroller_1.default({ + overflowX: 'hidden', + overflowY: 'auto' + }); + return _this; + } + // Generates the DayGrid object this view needs. Draws from this.dayGridClass + BasicView.prototype.instantiateDayGrid = function () { + // generate a subclass on the fly with BasicView-specific behavior + // TODO: cache this subclass + var subclass = makeDayGridSubclass(this.dayGridClass); + return new subclass(this); + }; + BasicView.prototype.executeDateRender = function (dateProfile) { + this.dayGrid.breakOnWeeks = /year|month|week/.test(dateProfile.currentRangeUnit); + _super.prototype.executeDateRender.call(this, dateProfile); + }; + BasicView.prototype.renderSkeleton = function () { + var dayGridContainerEl; + var dayGridEl; + this.el.addClass('fc-basic-view').html(this.renderSkeletonHtml()); + this.scroller.render(); + dayGridContainerEl = this.scroller.el.addClass('fc-day-grid-container'); + dayGridEl = $('').appendTo(dayGridContainerEl); + this.el.find('.fc-body > tr > td').append(dayGridContainerEl); + this.dayGrid.headContainerEl = this.el.find('.fc-head-container'); + this.dayGrid.setElement(dayGridEl); + }; + BasicView.prototype.unrenderSkeleton = function () { + this.dayGrid.removeElement(); + this.scroller.destroy(); + }; + // Builds the HTML skeleton for the view. + // The day-grid component will render inside of a container defined by this HTML. + BasicView.prototype.renderSkeletonHtml = function () { + var theme = this.calendar.theme; + return '' + + '' + + (this.opt('columnHeader') ? + '' + + '' + + ' ' + + '' + + '' : + '') + + '' + + '' + + '' + + '' + + '' + + ''; + }; + // Generates an HTML attribute string for setting the width of the week number column, if it is known + BasicView.prototype.weekNumberStyleAttr = function () { + if (this.weekNumberWidth != null) { + return 'style="width:' + this.weekNumberWidth + 'px"'; + } + return ''; + }; + // Determines whether each row should have a constant height + BasicView.prototype.hasRigidRows = function () { + var eventLimit = this.opt('eventLimit'); + return eventLimit && typeof eventLimit !== 'number'; + }; + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + // Refreshes the horizontal dimensions of the view + BasicView.prototype.updateSize = function (totalHeight, isAuto, isResize) { + var eventLimit = this.opt('eventLimit'); + var headRowEl = this.dayGrid.headContainerEl.find('.fc-row'); + var scrollerHeight; + var scrollbarWidths; + // hack to give the view some height prior to dayGrid's columns being rendered + // TODO: separate setting height from scroller VS dayGrid. + if (!this.dayGrid.rowEls) { + if (!isAuto) { + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.scroller.setHeight(scrollerHeight); + } + return; + } + _super.prototype.updateSize.call(this, totalHeight, isAuto, isResize); + if (this.dayGrid.colWeekNumbersVisible) { + // Make sure all week number cells running down the side have the same width. + // Record the width for cells created later. + this.weekNumberWidth = util_1.matchCellWidths(this.el.find('.fc-week-number')); + } + // reset all heights to be natural + this.scroller.clear(); + util_1.uncompensateScroll(headRowEl); + this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed + // is the event limit a constant level number? + if (eventLimit && typeof eventLimit === 'number') { + this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after + } + // distribute the height to the rows + // (totalHeight is a "recommended" value if isAuto) + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.setGridHeight(scrollerHeight, isAuto); + // is the event limit dynamically calculated? + if (eventLimit && typeof eventLimit !== 'number') { + this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set + } + if (!isAuto) { + this.scroller.setHeight(scrollerHeight); + scrollbarWidths = this.scroller.getScrollbarWidths(); + if (scrollbarWidths.left || scrollbarWidths.right) { + util_1.compensateScroll(headRowEl, scrollbarWidths); + // doing the scrollbar compensation might have created text overflow which created more height. redo + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.scroller.setHeight(scrollerHeight); + } + // guarantees the same scrollbar widths + this.scroller.lockOverflow(scrollbarWidths); + } + }; + // given a desired total height of the view, returns what the height of the scroller should be + BasicView.prototype.computeScrollerHeight = function (totalHeight) { + return totalHeight - + util_1.subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller + }; + // Sets the height of just the DayGrid component in this view + BasicView.prototype.setGridHeight = function (height, isAuto) { + if (isAuto) { + util_1.undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding + } + else { + util_1.distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows + } + }; + /* Scroll + ------------------------------------------------------------------------------------------------------------------*/ + BasicView.prototype.computeInitialDateScroll = function () { + return { top: 0 }; + }; + BasicView.prototype.queryDateScroll = function () { + return { top: this.scroller.getScrollTop() }; + }; + BasicView.prototype.applyDateScroll = function (scroll) { + if (scroll.top !== undefined) { + this.scroller.setScrollTop(scroll.top); + } + }; + return BasicView; +}(View_1.default)); +exports.default = BasicView; +BasicView.prototype.dateProfileGeneratorClass = BasicViewDateProfileGenerator_1.default; +BasicView.prototype.dayGridClass = DayGrid_1.default; +// customize the rendering behavior of BasicView's dayGrid +function makeDayGridSubclass(SuperClass) { + return /** @class */ (function (_super) { + tslib_1.__extends(SubClass, _super); + function SubClass() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.colWeekNumbersVisible = false; // display week numbers along the side? + return _this; + } + // Generates the HTML that will go before the day-of week header cells + SubClass.prototype.renderHeadIntroHtml = function () { + var view = this.view; + if (this.colWeekNumbersVisible) { + return '' + + '' + + '' + // needed for matchCellWidths + util_1.htmlEscape(this.opt('weekNumberTitle')) + + '' + + ''; + } + return ''; + }; + // Generates the HTML that will go before content-skeleton cells that display the day/week numbers + SubClass.prototype.renderNumberIntroHtml = function (row) { + var view = this.view; + var weekStart = this.getCellDate(row, 0); + if (this.colWeekNumbersVisible) { + return '' + + '' + + view.buildGotoAnchorHtml(// aside from link, important for matchCellWidths + { date: weekStart, type: 'week', forceOff: this.colCnt === 1 }, weekStart.format('w') // inner HTML + ) + + ''; + } + return ''; + }; + // Generates the HTML that goes before the day bg cells for each day-row + SubClass.prototype.renderBgIntroHtml = function () { + var view = this.view; + if (this.colWeekNumbersVisible) { + return ''; + } + return ''; + }; + // Generates the HTML that goes before every other type of row generated by DayGrid. + // Affects helper-skeleton and highlight-skeleton rows. + SubClass.prototype.renderIntroHtml = function () { + var view = this.view; + if (this.colWeekNumbersVisible) { + return ''; + } + return ''; + }; + SubClass.prototype.getIsNumbersVisible = function () { + return DayGrid_1.default.prototype.getIsNumbersVisible.apply(this, arguments) || this.colWeekNumbersVisible; + }; + return SubClass; + }(SuperClass)); +} + + +/***/ }), +/* 63 */, +/* 64 */, +/* 65 */, +/* 66 */, +/* 67 */, +/* 68 */, +/* 69 */, +/* 70 */, +/* 71 */, +/* 72 */, +/* 73 */, +/* 74 */, +/* 75 */, +/* 76 */, +/* 77 */, +/* 78 */, +/* 79 */, +/* 80 */, +/* 81 */, +/* 82 */, +/* 83 */, +/* 84 */, +/* 85 */, +/* 86 */, +/* 87 */, +/* 88 */, +/* 89 */, +/* 90 */, +/* 91 */, +/* 92 */, +/* 93 */, +/* 94 */, +/* 95 */, +/* 96 */, +/* 97 */, +/* 98 */, +/* 99 */, +/* 100 */, +/* 101 */, +/* 102 */, +/* 103 */, +/* 104 */, +/* 105 */, +/* 106 */, +/* 107 */, +/* 108 */, +/* 109 */, +/* 110 */, +/* 111 */, +/* 112 */, +/* 113 */, +/* 114 */, +/* 115 */, +/* 116 */, +/* 117 */, +/* 118 */, +/* 119 */, +/* 120 */, +/* 121 */, +/* 122 */, +/* 123 */, +/* 124 */, +/* 125 */, +/* 126 */, +/* 127 */, +/* 128 */, +/* 129 */, +/* 130 */, +/* 131 */, +/* 132 */, +/* 133 */, +/* 134 */, +/* 135 */, +/* 136 */, +/* 137 */, +/* 138 */, +/* 139 */, +/* 140 */, +/* 141 */, +/* 142 */, +/* 143 */, +/* 144 */, +/* 145 */, +/* 146 */, +/* 147 */, +/* 148 */, +/* 149 */, +/* 150 */, +/* 151 */, +/* 152 */, +/* 153 */, +/* 154 */, +/* 155 */, +/* 156 */, +/* 157 */, +/* 158 */, +/* 159 */, +/* 160 */, +/* 161 */, +/* 162 */, +/* 163 */, +/* 164 */, +/* 165 */, +/* 166 */, +/* 167 */, +/* 168 */, +/* 169 */, +/* 170 */, +/* 171 */, +/* 172 */, +/* 173 */, +/* 174 */, +/* 175 */, +/* 176 */, +/* 177 */, +/* 178 */, +/* 179 */, +/* 180 */, +/* 181 */, +/* 182 */, +/* 183 */, +/* 184 */, +/* 185 */, +/* 186 */, +/* 187 */, +/* 188 */, +/* 189 */, +/* 190 */, +/* 191 */, +/* 192 */, +/* 193 */, +/* 194 */, +/* 195 */, +/* 196 */, +/* 197 */, +/* 198 */, +/* 199 */, +/* 200 */, +/* 201 */, +/* 202 */, +/* 203 */, +/* 204 */, +/* 205 */, +/* 206 */, +/* 207 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var UnzonedRange_1 = __webpack_require__(5); +var ComponentFootprint_1 = __webpack_require__(12); +var EventDefParser_1 = __webpack_require__(49); +var EventSource_1 = __webpack_require__(6); +var util_1 = __webpack_require__(35); +var Constraints = /** @class */ (function () { + function Constraints(eventManager, _calendar) { + this.eventManager = eventManager; + this._calendar = _calendar; + } + Constraints.prototype.opt = function (name) { + return this._calendar.opt(name); + }; + /* + determines if eventInstanceGroup is allowed, + in relation to other EVENTS and business hours. + */ + Constraints.prototype.isEventInstanceGroupAllowed = function (eventInstanceGroup) { + var eventDef = eventInstanceGroup.getEventDef(); + var eventFootprints = this.eventRangesToEventFootprints(eventInstanceGroup.getAllEventRanges()); + var i; + var peerEventInstances = this.getPeerEventInstances(eventDef); + var peerEventRanges = peerEventInstances.map(util_1.eventInstanceToEventRange); + var peerEventFootprints = this.eventRangesToEventFootprints(peerEventRanges); + var constraintVal = eventDef.getConstraint(); + var overlapVal = eventDef.getOverlap(); + var eventAllowFunc = this.opt('eventAllow'); + for (i = 0; i < eventFootprints.length; i++) { + if (!this.isFootprintAllowed(eventFootprints[i].componentFootprint, peerEventFootprints, constraintVal, overlapVal, eventFootprints[i].eventInstance)) { + return false; + } + } + if (eventAllowFunc) { + for (i = 0; i < eventFootprints.length; i++) { + if (eventAllowFunc(eventFootprints[i].componentFootprint.toLegacy(this._calendar), eventFootprints[i].getEventLegacy()) === false) { + return false; + } + } + } + return true; + }; + Constraints.prototype.getPeerEventInstances = function (eventDef) { + return this.eventManager.getEventInstancesWithoutId(eventDef.id); + }; + Constraints.prototype.isSelectionFootprintAllowed = function (componentFootprint) { + var peerEventInstances = this.eventManager.getEventInstances(); + var peerEventRanges = peerEventInstances.map(util_1.eventInstanceToEventRange); + var peerEventFootprints = this.eventRangesToEventFootprints(peerEventRanges); + var selectAllowFunc; + if (this.isFootprintAllowed(componentFootprint, peerEventFootprints, this.opt('selectConstraint'), this.opt('selectOverlap'))) { + selectAllowFunc = this.opt('selectAllow'); + if (selectAllowFunc) { + return selectAllowFunc(componentFootprint.toLegacy(this._calendar)) !== false; + } + else { + return true; + } + } + return false; + }; + Constraints.prototype.isFootprintAllowed = function (componentFootprint, peerEventFootprints, constraintVal, overlapVal, subjectEventInstance // optional + ) { + var constraintFootprints; // ComponentFootprint[] + var overlapEventFootprints; // EventFootprint[] + if (constraintVal != null) { + constraintFootprints = this.constraintValToFootprints(constraintVal, componentFootprint.isAllDay); + if (!this.isFootprintWithinConstraints(componentFootprint, constraintFootprints)) { + return false; + } + } + overlapEventFootprints = this.collectOverlapEventFootprints(peerEventFootprints, componentFootprint); + if (overlapVal === false) { + if (overlapEventFootprints.length) { + return false; + } + } + else if (typeof overlapVal === 'function') { + if (!isOverlapsAllowedByFunc(overlapEventFootprints, overlapVal, subjectEventInstance)) { + return false; + } + } + if (subjectEventInstance) { + if (!isOverlapEventInstancesAllowed(overlapEventFootprints, subjectEventInstance)) { + return false; + } + } + return true; + }; + // Constraint + // ------------------------------------------------------------------------------------------------ + Constraints.prototype.isFootprintWithinConstraints = function (componentFootprint, constraintFootprints) { + var i; + for (i = 0; i < constraintFootprints.length; i++) { + if (this.footprintContainsFootprint(constraintFootprints[i], componentFootprint)) { + return true; + } + } + return false; + }; + Constraints.prototype.constraintValToFootprints = function (constraintVal, isAllDay) { + var eventInstances; + if (constraintVal === 'businessHours') { + return this.buildCurrentBusinessFootprints(isAllDay); + } + else if (typeof constraintVal === 'object') { + eventInstances = this.parseEventDefToInstances(constraintVal); // handles recurring events + if (!eventInstances) { + return this.parseFootprints(constraintVal); + } + else { + return this.eventInstancesToFootprints(eventInstances); + } + } + else if (constraintVal != null) { + eventInstances = this.eventManager.getEventInstancesWithId(constraintVal); + return this.eventInstancesToFootprints(eventInstances); + } + }; + // returns ComponentFootprint[] + // uses current view's range + Constraints.prototype.buildCurrentBusinessFootprints = function (isAllDay) { + var view = this._calendar.view; + var businessHourGenerator = view.get('businessHourGenerator'); + var unzonedRange = view.dateProfile.activeUnzonedRange; + var eventInstanceGroup = businessHourGenerator.buildEventInstanceGroup(isAllDay, unzonedRange); + if (eventInstanceGroup) { + return this.eventInstancesToFootprints(eventInstanceGroup.eventInstances); + } + else { + return []; + } + }; + // conversion util + Constraints.prototype.eventInstancesToFootprints = function (eventInstances) { + var eventRanges = eventInstances.map(util_1.eventInstanceToEventRange); + var eventFootprints = this.eventRangesToEventFootprints(eventRanges); + return eventFootprints.map(util_1.eventFootprintToComponentFootprint); + }; + // Overlap + // ------------------------------------------------------------------------------------------------ + Constraints.prototype.collectOverlapEventFootprints = function (peerEventFootprints, targetFootprint) { + var overlapEventFootprints = []; + var i; + for (i = 0; i < peerEventFootprints.length; i++) { + if (this.footprintsIntersect(targetFootprint, peerEventFootprints[i].componentFootprint)) { + overlapEventFootprints.push(peerEventFootprints[i]); + } + } + return overlapEventFootprints; + }; + // Conversion: eventDefs -> eventInstances -> eventRanges -> eventFootprints -> componentFootprints + // ------------------------------------------------------------------------------------------------ + // NOTE: this might seem like repetitive code with the Grid class, however, this code is related to + // constraints whereas the Grid code is related to rendering. Each approach might want to convert + // eventRanges -> eventFootprints in a different way. Regardless, there are opportunities to make + // this more DRY. + /* + Returns false on invalid input. + */ + Constraints.prototype.parseEventDefToInstances = function (eventInput) { + var eventManager = this.eventManager; + var eventDef = EventDefParser_1.default.parse(eventInput, new EventSource_1.default(this._calendar)); + if (!eventDef) { + return false; + } + return eventDef.buildInstances(eventManager.currentPeriod.unzonedRange); + }; + Constraints.prototype.eventRangesToEventFootprints = function (eventRanges) { + var i; + var eventFootprints = []; + for (i = 0; i < eventRanges.length; i++) { + eventFootprints.push.apply(// footprints + eventFootprints, this.eventRangeToEventFootprints(eventRanges[i])); + } + return eventFootprints; + }; + Constraints.prototype.eventRangeToEventFootprints = function (eventRange) { + return [util_1.eventRangeToEventFootprint(eventRange)]; + }; + /* + Parses footprints directly. + Very similar to EventDateProfile::parse :( + */ + Constraints.prototype.parseFootprints = function (rawInput) { + var start; + var end; + if (rawInput.start) { + start = this._calendar.moment(rawInput.start); + if (!start.isValid()) { + start = null; + } + } + if (rawInput.end) { + end = this._calendar.moment(rawInput.end); + if (!end.isValid()) { + end = null; + } + } + return [ + new ComponentFootprint_1.default(new UnzonedRange_1.default(start, end), (start && !start.hasTime()) || (end && !end.hasTime()) // isAllDay + ) + ]; + }; + // Footprint Utils + // ---------------------------------------------------------------------------------------- + Constraints.prototype.footprintContainsFootprint = function (outerFootprint, innerFootprint) { + return outerFootprint.unzonedRange.containsRange(innerFootprint.unzonedRange); + }; + Constraints.prototype.footprintsIntersect = function (footprint0, footprint1) { + return footprint0.unzonedRange.intersectsWith(footprint1.unzonedRange); + }; + return Constraints; +}()); +exports.default = Constraints; +// optional subjectEventInstance +function isOverlapsAllowedByFunc(overlapEventFootprints, overlapFunc, subjectEventInstance) { + var i; + for (i = 0; i < overlapEventFootprints.length; i++) { + if (!overlapFunc(overlapEventFootprints[i].eventInstance.toLegacy(), subjectEventInstance ? subjectEventInstance.toLegacy() : null)) { + return false; + } + } + return true; +} +function isOverlapEventInstancesAllowed(overlapEventFootprints, subjectEventInstance) { + var subjectLegacyInstance = subjectEventInstance.toLegacy(); + var i; + var overlapEventInstance; + var overlapEventDef; + var overlapVal; + for (i = 0; i < overlapEventFootprints.length; i++) { + overlapEventInstance = overlapEventFootprints[i].eventInstance; + overlapEventDef = overlapEventInstance.def; + // don't need to pass in calendar, because don't want to consider global eventOverlap property, + // because we already considered that earlier in the process. + overlapVal = overlapEventDef.getOverlap(); + if (overlapVal === false) { + return false; + } + else if (typeof overlapVal === 'function') { + if (!overlapVal(overlapEventInstance.toLegacy(), subjectLegacyInstance)) { + return false; + } + } + } + return true; +} + + +/***/ }), +/* 208 */ +/***/ (function(module, exports, __webpack_require__) { + +/* +USAGE: + import { default as ParsableModelMixin, ParsableModelInterface } from './ParsableModelMixin' +in class: + applyProps: ParsableModelInterface['applyProps'] + applyManualStandardProps: ParsableModelInterface['applyManualStandardProps'] + applyMiscProps: ParsableModelInterface['applyMiscProps'] + isStandardProp: ParsableModelInterface['isStandardProp'] + static defineStandardProps = ParsableModelMixin.defineStandardProps + static copyVerbatimStandardProps = ParsableModelMixin.copyVerbatimStandardProps +after class: + ParsableModelMixin.mixInto(TheClass) +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var util_1 = __webpack_require__(4); +var Mixin_1 = __webpack_require__(14); +var ParsableModelMixin = /** @class */ (function (_super) { + tslib_1.__extends(ParsableModelMixin, _super); + function ParsableModelMixin() { + return _super !== null && _super.apply(this, arguments) || this; + } + ParsableModelMixin.defineStandardProps = function (propDefs) { + var proto = this.prototype; + if (!proto.hasOwnProperty('standardPropMap')) { + proto.standardPropMap = Object.create(proto.standardPropMap); + } + util_1.copyOwnProps(propDefs, proto.standardPropMap); + }; + ParsableModelMixin.copyVerbatimStandardProps = function (src, dest) { + var map = this.prototype.standardPropMap; + var propName; + for (propName in map) { + if (src[propName] != null && // in the src object? + map[propName] === true // false means "copy verbatim" + ) { + dest[propName] = src[propName]; + } + } + }; + /* + Returns true/false for success. + Meant to be only called ONCE, at object creation. + */ + ParsableModelMixin.prototype.applyProps = function (rawProps) { + var standardPropMap = this.standardPropMap; + var manualProps = {}; + var miscProps = {}; + var propName; + for (propName in rawProps) { + if (standardPropMap[propName] === true) { + this[propName] = rawProps[propName]; + } + else if (standardPropMap[propName] === false) { + manualProps[propName] = rawProps[propName]; + } + else { + miscProps[propName] = rawProps[propName]; + } + } + this.applyMiscProps(miscProps); + return this.applyManualStandardProps(manualProps); + }; + /* + If subclasses override, they must call this supermethod and return the boolean response. + Meant to be only called ONCE, at object creation. + */ + ParsableModelMixin.prototype.applyManualStandardProps = function (rawProps) { + return true; + }; + /* + Can be called even after initial object creation. + */ + ParsableModelMixin.prototype.applyMiscProps = function (rawProps) { + // subclasses can implement + }; + /* + TODO: why is this a method when defineStandardProps is static + */ + ParsableModelMixin.prototype.isStandardProp = function (propName) { + return propName in this.standardPropMap; + }; + return ParsableModelMixin; +}(Mixin_1.default)); +exports.default = ParsableModelMixin; +ParsableModelMixin.prototype.standardPropMap = {}; // will be cloned by defineStandardProps + + +/***/ }), +/* 209 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var EventInstance = /** @class */ (function () { + function EventInstance(def, dateProfile) { + this.def = def; + this.dateProfile = dateProfile; + } + EventInstance.prototype.toLegacy = function () { + var dateProfile = this.dateProfile; + var obj = this.def.toLegacy(); + obj.start = dateProfile.start.clone(); + obj.end = dateProfile.end ? dateProfile.end.clone() : null; + return obj; + }; + return EventInstance; +}()); +exports.default = EventInstance; + + +/***/ }), +/* 210 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var moment = __webpack_require__(0); +var EventDef_1 = __webpack_require__(34); +var EventInstance_1 = __webpack_require__(209); +var EventDateProfile_1 = __webpack_require__(17); +var RecurringEventDef = /** @class */ (function (_super) { + tslib_1.__extends(RecurringEventDef, _super); + function RecurringEventDef() { + return _super !== null && _super.apply(this, arguments) || this; + } + RecurringEventDef.prototype.isAllDay = function () { + return !this.startTime && !this.endTime; + }; + RecurringEventDef.prototype.buildInstances = function (unzonedRange) { + var calendar = this.source.calendar; + var unzonedDate = unzonedRange.getStart(); + var unzonedEnd = unzonedRange.getEnd(); + var zonedDayStart; + var instanceStart; + var instanceEnd; + var instances = []; + while (unzonedDate.isBefore(unzonedEnd)) { + // if everyday, or this particular day-of-week + if (!this.dowHash || this.dowHash[unzonedDate.day()]) { + zonedDayStart = calendar.applyTimezone(unzonedDate); + instanceStart = zonedDayStart.clone(); + instanceEnd = null; + if (this.startTime) { + instanceStart.time(this.startTime); + } + else { + instanceStart.stripTime(); + } + if (this.endTime) { + instanceEnd = zonedDayStart.clone().time(this.endTime); + } + instances.push(new EventInstance_1.default(this, // definition + new EventDateProfile_1.default(instanceStart, instanceEnd, calendar))); + } + unzonedDate.add(1, 'days'); + } + return instances; + }; + RecurringEventDef.prototype.setDow = function (dowNumbers) { + if (!this.dowHash) { + this.dowHash = {}; + } + for (var i = 0; i < dowNumbers.length; i++) { + this.dowHash[dowNumbers[i]] = true; + } + }; + RecurringEventDef.prototype.clone = function () { + var def = _super.prototype.clone.call(this); + if (def.startTime) { + def.startTime = moment.duration(this.startTime); + } + if (def.endTime) { + def.endTime = moment.duration(this.endTime); + } + if (this.dowHash) { + def.dowHash = $.extend({}, this.dowHash); + } + return def; + }; + return RecurringEventDef; +}(EventDef_1.default)); +exports.default = RecurringEventDef; +/* +HACK to work with TypeScript mixins +NOTE: if super-method fails, should still attempt to apply +*/ +RecurringEventDef.prototype.applyProps = function (rawProps) { + var superSuccess = EventDef_1.default.prototype.applyProps.call(this, rawProps); + if (rawProps.start) { + this.startTime = moment.duration(rawProps.start); + } + if (rawProps.end) { + this.endTime = moment.duration(rawProps.end); + } + if (rawProps.dow) { + this.setDow(rawProps.dow); + } + return superSuccess; +}; +// Parsing +// --------------------------------------------------------------------------------------------------------------------- +RecurringEventDef.defineStandardProps({ + start: false, + end: false, + dow: false +}); + + +/***/ }), +/* 211 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var EventRange = /** @class */ (function () { + function EventRange(unzonedRange, eventDef, eventInstance) { + this.unzonedRange = unzonedRange; + this.eventDef = eventDef; + if (eventInstance) { + this.eventInstance = eventInstance; + } + } + return EventRange; +}()); +exports.default = EventRange; + + +/***/ }), +/* 212 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(35); +var EventInstanceGroup_1 = __webpack_require__(18); +var RecurringEventDef_1 = __webpack_require__(210); +var EventSource_1 = __webpack_require__(6); +var BUSINESS_HOUR_EVENT_DEFAULTS = { + start: '09:00', + end: '17:00', + dow: [1, 2, 3, 4, 5], + rendering: 'inverse-background' + // classNames are defined in businessHoursSegClasses +}; +var BusinessHourGenerator = /** @class */ (function () { + function BusinessHourGenerator(rawComplexDef, calendar) { + this.rawComplexDef = rawComplexDef; + this.calendar = calendar; + } + BusinessHourGenerator.prototype.buildEventInstanceGroup = function (isAllDay, unzonedRange) { + var eventDefs = this.buildEventDefs(isAllDay); + var eventInstanceGroup; + if (eventDefs.length) { + eventInstanceGroup = new EventInstanceGroup_1.default(util_1.eventDefsToEventInstances(eventDefs, unzonedRange)); + // so that inverse-background rendering can happen even when no eventRanges in view + eventInstanceGroup.explicitEventDef = eventDefs[0]; + return eventInstanceGroup; + } + }; + BusinessHourGenerator.prototype.buildEventDefs = function (isAllDay) { + var rawComplexDef = this.rawComplexDef; + var rawDefs = []; + var requireDow = false; + var i; + var defs = []; + if (rawComplexDef === true) { + rawDefs = [{}]; // will get BUSINESS_HOUR_EVENT_DEFAULTS verbatim + } + else if ($.isPlainObject(rawComplexDef)) { + rawDefs = [rawComplexDef]; + } + else if ($.isArray(rawComplexDef)) { + rawDefs = rawComplexDef; + requireDow = true; // every sub-definition NEEDS a day-of-week + } + for (i = 0; i < rawDefs.length; i++) { + if (!requireDow || rawDefs[i].dow) { + defs.push(this.buildEventDef(isAllDay, rawDefs[i])); + } + } + return defs; + }; + BusinessHourGenerator.prototype.buildEventDef = function (isAllDay, rawDef) { + var fullRawDef = $.extend({}, BUSINESS_HOUR_EVENT_DEFAULTS, rawDef); + if (isAllDay) { + fullRawDef.start = null; + fullRawDef.end = null; + } + return RecurringEventDef_1.default.parse(fullRawDef, new EventSource_1.default(this.calendar) // dummy source + ); + }; + return BusinessHourGenerator; +}()); +exports.default = BusinessHourGenerator; + + +/***/ }), +/* 213 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var Theme_1 = __webpack_require__(19); +var StandardTheme = /** @class */ (function (_super) { + tslib_1.__extends(StandardTheme, _super); + function StandardTheme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return StandardTheme; +}(Theme_1.default)); +exports.default = StandardTheme; +StandardTheme.prototype.classes = { + widget: 'fc-unthemed', + widgetHeader: 'fc-widget-header', + widgetContent: 'fc-widget-content', + buttonGroup: 'fc-button-group', + button: 'fc-button', + cornerLeft: 'fc-corner-left', + cornerRight: 'fc-corner-right', + stateDefault: 'fc-state-default', + stateActive: 'fc-state-active', + stateDisabled: 'fc-state-disabled', + stateHover: 'fc-state-hover', + stateDown: 'fc-state-down', + popoverHeader: 'fc-widget-header', + popoverContent: 'fc-widget-content', + // day grid + headerRow: 'fc-widget-header', + dayRow: 'fc-widget-content', + // list view + listView: 'fc-widget-content' +}; +StandardTheme.prototype.baseIconClass = 'fc-icon'; +StandardTheme.prototype.iconClasses = { + close: 'fc-icon-x', + prev: 'fc-icon-left-single-arrow', + next: 'fc-icon-right-single-arrow', + prevYear: 'fc-icon-left-double-arrow', + nextYear: 'fc-icon-right-double-arrow' +}; +StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; +StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon'; +StandardTheme.prototype.iconOverridePrefix = 'fc-icon-'; + + +/***/ }), +/* 214 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var Theme_1 = __webpack_require__(19); +var JqueryUiTheme = /** @class */ (function (_super) { + tslib_1.__extends(JqueryUiTheme, _super); + function JqueryUiTheme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return JqueryUiTheme; +}(Theme_1.default)); +exports.default = JqueryUiTheme; +JqueryUiTheme.prototype.classes = { + widget: 'ui-widget', + widgetHeader: 'ui-widget-header', + widgetContent: 'ui-widget-content', + buttonGroup: 'fc-button-group', + button: 'ui-button', + cornerLeft: 'ui-corner-left', + cornerRight: 'ui-corner-right', + stateDefault: 'ui-state-default', + stateActive: 'ui-state-active', + stateDisabled: 'ui-state-disabled', + stateHover: 'ui-state-hover', + stateDown: 'ui-state-down', + today: 'ui-state-highlight', + popoverHeader: 'ui-widget-header', + popoverContent: 'ui-widget-content', + // day grid + headerRow: 'ui-widget-header', + dayRow: 'ui-widget-content', + // list view + listView: 'ui-widget-content' +}; +JqueryUiTheme.prototype.baseIconClass = 'ui-icon'; +JqueryUiTheme.prototype.iconClasses = { + close: 'ui-icon-closethick', + prev: 'ui-icon-circle-triangle-w', + next: 'ui-icon-circle-triangle-e', + prevYear: 'ui-icon-seek-prev', + nextYear: 'ui-icon-seek-next' +}; +JqueryUiTheme.prototype.iconOverrideOption = 'themeButtonIcons'; +JqueryUiTheme.prototype.iconOverrideCustomButtonOption = 'themeIcon'; +JqueryUiTheme.prototype.iconOverridePrefix = 'ui-icon-'; + + +/***/ }), +/* 215 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var Promise_1 = __webpack_require__(20); +var EventSource_1 = __webpack_require__(6); +var FuncEventSource = /** @class */ (function (_super) { + tslib_1.__extends(FuncEventSource, _super); + function FuncEventSource() { + return _super !== null && _super.apply(this, arguments) || this; + } + FuncEventSource.parse = function (rawInput, calendar) { + var rawProps; + // normalize raw input + if ($.isFunction(rawInput.events)) { + rawProps = rawInput; + } + else if ($.isFunction(rawInput)) { + rawProps = { events: rawInput }; + } + if (rawProps) { + return EventSource_1.default.parse.call(this, rawProps, calendar); + } + return false; + }; + FuncEventSource.prototype.fetch = function (start, end, timezone) { + var _this = this; + this.calendar.pushLoading(); + return Promise_1.default.construct(function (onResolve) { + _this.func.call(_this.calendar, start.clone(), end.clone(), timezone, function (rawEventDefs) { + _this.calendar.popLoading(); + onResolve(_this.parseEventDefs(rawEventDefs)); + }); + }); + }; + FuncEventSource.prototype.getPrimitive = function () { + return this.func; + }; + FuncEventSource.prototype.applyManualStandardProps = function (rawProps) { + var superSuccess = _super.prototype.applyManualStandardProps.call(this, rawProps); + this.func = rawProps.events; + return superSuccess; + }; + return FuncEventSource; +}(EventSource_1.default)); +exports.default = FuncEventSource; +FuncEventSource.defineStandardProps({ + events: false // don't automatically transfer +}); + + +/***/ }), +/* 216 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var Promise_1 = __webpack_require__(20); +var EventSource_1 = __webpack_require__(6); +var JsonFeedEventSource = /** @class */ (function (_super) { + tslib_1.__extends(JsonFeedEventSource, _super); + function JsonFeedEventSource() { + return _super !== null && _super.apply(this, arguments) || this; + } + JsonFeedEventSource.parse = function (rawInput, calendar) { + var rawProps; + // normalize raw input + if (typeof rawInput.url === 'string') { + rawProps = rawInput; + } + else if (typeof rawInput === 'string') { + rawProps = { url: rawInput }; + } + if (rawProps) { + return EventSource_1.default.parse.call(this, rawProps, calendar); + } + return false; + }; + JsonFeedEventSource.prototype.fetch = function (start, end, timezone) { + var _this = this; + var ajaxSettings = this.ajaxSettings; + var onSuccess = ajaxSettings.success; + var onError = ajaxSettings.error; + var requestParams = this.buildRequestParams(start, end, timezone); + // todo: eventually handle the promise's then, + // don't intercept success/error + // tho will be a breaking API change + this.calendar.pushLoading(); + return Promise_1.default.construct(function (onResolve, onReject) { + $.ajax($.extend({}, // destination + JsonFeedEventSource.AJAX_DEFAULTS, ajaxSettings, { + url: _this.url, + data: requestParams, + success: function (rawEventDefs, status, xhr) { + var callbackRes; + _this.calendar.popLoading(); + if (rawEventDefs) { + callbackRes = util_1.applyAll(onSuccess, _this, [rawEventDefs, status, xhr]); // redirect `this` + if ($.isArray(callbackRes)) { + rawEventDefs = callbackRes; + } + onResolve(_this.parseEventDefs(rawEventDefs)); + } + else { + onReject(); + } + }, + error: function (xhr, statusText, errorThrown) { + _this.calendar.popLoading(); + util_1.applyAll(onError, _this, [xhr, statusText, errorThrown]); // redirect `this` + onReject(); + } + })); + }); + }; + JsonFeedEventSource.prototype.buildRequestParams = function (start, end, timezone) { + var calendar = this.calendar; + var ajaxSettings = this.ajaxSettings; + var startParam; + var endParam; + var timezoneParam; + var customRequestParams; + var params = {}; + startParam = this.startParam; + if (startParam == null) { + startParam = calendar.opt('startParam'); + } + endParam = this.endParam; + if (endParam == null) { + endParam = calendar.opt('endParam'); + } + timezoneParam = this.timezoneParam; + if (timezoneParam == null) { + timezoneParam = calendar.opt('timezoneParam'); + } + // retrieve any outbound GET/POST $.ajax data from the options + if ($.isFunction(ajaxSettings.data)) { + // supplied as a function that returns a key/value object + customRequestParams = ajaxSettings.data(); + } + else { + // probably supplied as a straight key/value object + customRequestParams = ajaxSettings.data || {}; + } + $.extend(params, customRequestParams); + params[startParam] = start.format(); + params[endParam] = end.format(); + if (timezone && timezone !== 'local') { + params[timezoneParam] = timezone; + } + return params; + }; + JsonFeedEventSource.prototype.getPrimitive = function () { + return this.url; + }; + JsonFeedEventSource.prototype.applyMiscProps = function (rawProps) { + this.ajaxSettings = rawProps; + }; + JsonFeedEventSource.AJAX_DEFAULTS = { + dataType: 'json', + cache: false + }; + return JsonFeedEventSource; +}(EventSource_1.default)); +exports.default = JsonFeedEventSource; +JsonFeedEventSource.defineStandardProps({ + // automatically transfer (true)... + url: true, + startParam: true, + endParam: true, + timezoneParam: true +}); + + +/***/ }), +/* 217 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var EmitterMixin_1 = __webpack_require__(11); +var TaskQueue = /** @class */ (function () { + function TaskQueue() { + this.q = []; + this.isPaused = false; + this.isRunning = false; + } + TaskQueue.prototype.queue = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + this.q.push.apply(this.q, args); // append + this.tryStart(); + }; + TaskQueue.prototype.pause = function () { + this.isPaused = true; + }; + TaskQueue.prototype.resume = function () { + this.isPaused = false; + this.tryStart(); + }; + TaskQueue.prototype.getIsIdle = function () { + return !this.isRunning && !this.isPaused; + }; + TaskQueue.prototype.tryStart = function () { + if (!this.isRunning && this.canRunNext()) { + this.isRunning = true; + this.trigger('start'); + this.runRemaining(); + } + }; + TaskQueue.prototype.canRunNext = function () { + return !this.isPaused && this.q.length; + }; + TaskQueue.prototype.runRemaining = function () { + var _this = this; + var task; + var res; + do { + task = this.q.shift(); // always freshly reference q. might have been reassigned. + res = this.runTask(task); + if (res && res.then) { + res.then(function () { + if (_this.canRunNext()) { + _this.runRemaining(); + } + }); + return; // prevent marking as stopped + } + } while (this.canRunNext()); + this.trigger('stop'); // not really a 'stop' ... more of a 'drained' + this.isRunning = false; + // if 'stop' handler added more tasks.... TODO: write test for this + this.tryStart(); + }; + TaskQueue.prototype.runTask = function (task) { + return task(); // task *is* the function, but subclasses can change the format of a task + }; + return TaskQueue; +}()); +exports.default = TaskQueue; +EmitterMixin_1.default.mixInto(TaskQueue); + + +/***/ }), +/* 218 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var TaskQueue_1 = __webpack_require__(217); +var RenderQueue = /** @class */ (function (_super) { + tslib_1.__extends(RenderQueue, _super); + function RenderQueue(waitsByNamespace) { + var _this = _super.call(this) || this; + _this.waitsByNamespace = waitsByNamespace || {}; + return _this; + } + RenderQueue.prototype.queue = function (taskFunc, namespace, type) { + var task = { + func: taskFunc, + namespace: namespace, + type: type + }; + var waitMs; + if (namespace) { + waitMs = this.waitsByNamespace[namespace]; + } + if (this.waitNamespace) { + if (namespace === this.waitNamespace && waitMs != null) { + this.delayWait(waitMs); + } + else { + this.clearWait(); + this.tryStart(); + } + } + if (this.compoundTask(task)) { + if (!this.waitNamespace && waitMs != null) { + this.startWait(namespace, waitMs); + } + else { + this.tryStart(); + } + } + }; + RenderQueue.prototype.startWait = function (namespace, waitMs) { + this.waitNamespace = namespace; + this.spawnWait(waitMs); + }; + RenderQueue.prototype.delayWait = function (waitMs) { + clearTimeout(this.waitId); + this.spawnWait(waitMs); + }; + RenderQueue.prototype.spawnWait = function (waitMs) { + var _this = this; + this.waitId = setTimeout(function () { + _this.waitNamespace = null; + _this.tryStart(); + }, waitMs); + }; + RenderQueue.prototype.clearWait = function () { + if (this.waitNamespace) { + clearTimeout(this.waitId); + this.waitId = null; + this.waitNamespace = null; + } + }; + RenderQueue.prototype.canRunNext = function () { + if (!_super.prototype.canRunNext.call(this)) { + return false; + } + // waiting for a certain namespace to stop receiving tasks? + if (this.waitNamespace) { + var q = this.q; + // if there was a different namespace task in the meantime, + // that forces all previously-waiting tasks to suddenly execute. + // TODO: find a way to do this in constant time. + for (var i = 0; i < q.length; i++) { + if (q[i].namespace !== this.waitNamespace) { + return true; // allow execution + } + } + return false; + } + return true; + }; + RenderQueue.prototype.runTask = function (task) { + task.func(); + }; + RenderQueue.prototype.compoundTask = function (newTask) { + var q = this.q; + var shouldAppend = true; + var i; + var task; + if (newTask.namespace && newTask.type === 'destroy') { + // remove all init/add/remove ops with same namespace, regardless of order + for (i = q.length - 1; i >= 0; i--) { + task = q[i]; + switch (task.type) { + case 'init': + shouldAppend = false; + // the latest destroy is cancelled out by not doing the init + /* falls through */ + case 'add': + /* falls through */ + case 'remove': + q.splice(i, 1); // remove task + } + } + } + if (shouldAppend) { + q.push(newTask); + } + return shouldAppend; + }; + return RenderQueue; +}(TaskQueue_1.default)); +exports.default = RenderQueue; + + +/***/ }), +/* 219 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var moment = __webpack_require__(0); +var util_1 = __webpack_require__(4); +var moment_ext_1 = __webpack_require__(10); +var date_formatting_1 = __webpack_require__(47); +var Component_1 = __webpack_require__(237); +var util_2 = __webpack_require__(35); +var DateComponent = /** @class */ (function (_super) { + tslib_1.__extends(DateComponent, _super); + function DateComponent(_view, _options) { + var _this = _super.call(this) || this; + _this.isRTL = false; // frequently accessed options + _this.hitsNeededDepth = 0; // necessary because multiple callers might need the same hits + _this.hasAllDayBusinessHours = false; // TODO: unify with largeUnit and isTimeScale? + _this.isDatesRendered = false; + // hack to set options prior to the this.opt calls + if (_view) { + _this['view'] = _view; + } + if (_options) { + _this['options'] = _options; + } + _this.uid = String(DateComponent.guid++); + _this.childrenByUid = {}; + _this.nextDayThreshold = moment.duration(_this.opt('nextDayThreshold')); + _this.isRTL = _this.opt('isRTL'); + if (_this.fillRendererClass) { + _this.fillRenderer = new _this.fillRendererClass(_this); + } + if (_this.eventRendererClass) { + _this.eventRenderer = new _this.eventRendererClass(_this, _this.fillRenderer); + } + if (_this.helperRendererClass && _this.eventRenderer) { + _this.helperRenderer = new _this.helperRendererClass(_this, _this.eventRenderer); + } + if (_this.businessHourRendererClass && _this.fillRenderer) { + _this.businessHourRenderer = new _this.businessHourRendererClass(_this, _this.fillRenderer); + } + return _this; + } + DateComponent.prototype.addChild = function (child) { + if (!this.childrenByUid[child.uid]) { + this.childrenByUid[child.uid] = child; + return true; + } + return false; + }; + DateComponent.prototype.removeChild = function (child) { + if (this.childrenByUid[child.uid]) { + delete this.childrenByUid[child.uid]; + return true; + } + return false; + }; + // TODO: only do if isInDom? + // TODO: make part of Component, along with children/batch-render system? + DateComponent.prototype.updateSize = function (totalHeight, isAuto, isResize) { + this.callChildren('updateSize', arguments); + }; + // Options + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.opt = function (name) { + return this._getView().opt(name); // default implementation + }; + DateComponent.prototype.publiclyTrigger = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var calendar = this._getCalendar(); + return calendar.publiclyTrigger.apply(calendar, args); + }; + DateComponent.prototype.hasPublicHandlers = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var calendar = this._getCalendar(); + return calendar.hasPublicHandlers.apply(calendar, args); + }; + // Date + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.executeDateRender = function (dateProfile) { + this.dateProfile = dateProfile; // for rendering + this.renderDates(dateProfile); + this.isDatesRendered = true; + this.callChildren('executeDateRender', arguments); + }; + DateComponent.prototype.executeDateUnrender = function () { + this.callChildren('executeDateUnrender', arguments); + this.dateProfile = null; + this.unrenderDates(); + this.isDatesRendered = false; + }; + // date-cell content only + DateComponent.prototype.renderDates = function (dateProfile) { + // subclasses should implement + }; + // date-cell content only + DateComponent.prototype.unrenderDates = function () { + // subclasses should override + }; + // Now-Indicator + // ----------------------------------------------------------------------------------------------------------------- + // Returns a string unit, like 'second' or 'minute' that defined how often the current time indicator + // should be refreshed. If something falsy is returned, no time indicator is rendered at all. + DateComponent.prototype.getNowIndicatorUnit = function () { + // subclasses should implement + }; + // Renders a current time indicator at the given datetime + DateComponent.prototype.renderNowIndicator = function (date) { + this.callChildren('renderNowIndicator', arguments); + }; + // Undoes the rendering actions from renderNowIndicator + DateComponent.prototype.unrenderNowIndicator = function () { + this.callChildren('unrenderNowIndicator', arguments); + }; + // Business Hours + // --------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.renderBusinessHours = function (businessHourGenerator) { + if (this.businessHourRenderer) { + this.businessHourRenderer.render(businessHourGenerator); + } + this.callChildren('renderBusinessHours', arguments); + }; + // Unrenders previously-rendered business-hours + DateComponent.prototype.unrenderBusinessHours = function () { + this.callChildren('unrenderBusinessHours', arguments); + if (this.businessHourRenderer) { + this.businessHourRenderer.unrender(); + } + }; + // Event Displaying + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.executeEventRender = function (eventsPayload) { + if (this.eventRenderer) { + this.eventRenderer.rangeUpdated(); // poorly named now + this.eventRenderer.render(eventsPayload); + } + else if (this['renderEvents']) { + this['renderEvents'](convertEventsPayloadToLegacyArray(eventsPayload)); + } + this.callChildren('executeEventRender', arguments); + }; + DateComponent.prototype.executeEventUnrender = function () { + this.callChildren('executeEventUnrender', arguments); + if (this.eventRenderer) { + this.eventRenderer.unrender(); + } + else if (this['destroyEvents']) { + this['destroyEvents'](); + } + }; + DateComponent.prototype.getBusinessHourSegs = function () { + var segs = this.getOwnBusinessHourSegs(); + this.iterChildren(function (child) { + segs.push.apply(segs, child.getBusinessHourSegs()); + }); + return segs; + }; + DateComponent.prototype.getOwnBusinessHourSegs = function () { + if (this.businessHourRenderer) { + return this.businessHourRenderer.getSegs(); + } + return []; + }; + DateComponent.prototype.getEventSegs = function () { + var segs = this.getOwnEventSegs(); + this.iterChildren(function (child) { + segs.push.apply(segs, child.getEventSegs()); + }); + return segs; + }; + DateComponent.prototype.getOwnEventSegs = function () { + if (this.eventRenderer) { + return this.eventRenderer.getSegs(); + } + return []; + }; + // Event Rendering Triggering + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.triggerAfterEventsRendered = function () { + this.triggerAfterEventSegsRendered(this.getEventSegs()); + this.publiclyTrigger('eventAfterAllRender', { + context: this, + args: [this] + }); + }; + DateComponent.prototype.triggerAfterEventSegsRendered = function (segs) { + var _this = this; + // an optimization, because getEventLegacy is expensive + if (this.hasPublicHandlers('eventAfterRender')) { + segs.forEach(function (seg) { + var legacy; + if (seg.el) { + legacy = seg.footprint.getEventLegacy(); + _this.publiclyTrigger('eventAfterRender', { + context: legacy, + args: [legacy, seg.el, _this] + }); + } + }); + } + }; + DateComponent.prototype.triggerBeforeEventsDestroyed = function () { + this.triggerBeforeEventSegsDestroyed(this.getEventSegs()); + }; + DateComponent.prototype.triggerBeforeEventSegsDestroyed = function (segs) { + var _this = this; + if (this.hasPublicHandlers('eventDestroy')) { + segs.forEach(function (seg) { + var legacy; + if (seg.el) { + legacy = seg.footprint.getEventLegacy(); + _this.publiclyTrigger('eventDestroy', { + context: legacy, + args: [legacy, seg.el, _this] + }); + } + }); + } + }; + // Event Rendering Utils + // ----------------------------------------------------------------------------------------------------------------- + // Hides all rendered event segments linked to the given event + // RECURSIVE with subcomponents + DateComponent.prototype.showEventsWithId = function (eventDefId) { + this.getEventSegs().forEach(function (seg) { + if (seg.footprint.eventDef.id === eventDefId && + seg.el // necessary? + ) { + seg.el.css('visibility', ''); + } + }); + this.callChildren('showEventsWithId', arguments); + }; + // Shows all rendered event segments linked to the given event + // RECURSIVE with subcomponents + DateComponent.prototype.hideEventsWithId = function (eventDefId) { + this.getEventSegs().forEach(function (seg) { + if (seg.footprint.eventDef.id === eventDefId && + seg.el // necessary? + ) { + seg.el.css('visibility', 'hidden'); + } + }); + this.callChildren('hideEventsWithId', arguments); + }; + // Drag-n-Drop Rendering (for both events and external elements) + // --------------------------------------------------------------------------------------------------------------- + // Renders a visual indication of a event or external-element drag over the given drop zone. + // If an external-element, seg will be `null`. + // Must return elements used for any mock events. + DateComponent.prototype.renderDrag = function (eventFootprints, seg, isTouch) { + var renderedHelper = false; + this.iterChildren(function (child) { + if (child.renderDrag(eventFootprints, seg, isTouch)) { + renderedHelper = true; + } + }); + return renderedHelper; + }; + // Unrenders a visual indication of an event or external-element being dragged. + DateComponent.prototype.unrenderDrag = function () { + this.callChildren('unrenderDrag', arguments); + }; + // Event Resizing + // --------------------------------------------------------------------------------------------------------------- + // Renders a visual indication of an event being resized. + DateComponent.prototype.renderEventResize = function (eventFootprints, seg, isTouch) { + this.callChildren('renderEventResize', arguments); + }; + // Unrenders a visual indication of an event being resized. + DateComponent.prototype.unrenderEventResize = function () { + this.callChildren('unrenderEventResize', arguments); + }; + // Selection + // --------------------------------------------------------------------------------------------------------------- + // Renders a visual indication of the selection + // TODO: rename to `renderSelection` after legacy is gone + DateComponent.prototype.renderSelectionFootprint = function (componentFootprint) { + this.renderHighlight(componentFootprint); + this.callChildren('renderSelectionFootprint', arguments); + }; + // Unrenders a visual indication of selection + DateComponent.prototype.unrenderSelection = function () { + this.unrenderHighlight(); + this.callChildren('unrenderSelection', arguments); + }; + // Highlight + // --------------------------------------------------------------------------------------------------------------- + // Renders an emphasis on the given date range. Given a span (unzoned start/end and other misc data) + DateComponent.prototype.renderHighlight = function (componentFootprint) { + if (this.fillRenderer) { + this.fillRenderer.renderFootprint('highlight', componentFootprint, { + getClasses: function () { + return ['fc-highlight']; + } + }); + } + this.callChildren('renderHighlight', arguments); + }; + // Unrenders the emphasis on a date range + DateComponent.prototype.unrenderHighlight = function () { + if (this.fillRenderer) { + this.fillRenderer.unrender('highlight'); + } + this.callChildren('unrenderHighlight', arguments); + }; + // Hit Areas + // --------------------------------------------------------------------------------------------------------------- + // just because all DateComponents support this interface + // doesn't mean they need to have their own internal coord system. they can defer to sub-components. + DateComponent.prototype.hitsNeeded = function () { + if (!(this.hitsNeededDepth++)) { + this.prepareHits(); + } + this.callChildren('hitsNeeded', arguments); + }; + DateComponent.prototype.hitsNotNeeded = function () { + if (this.hitsNeededDepth && !(--this.hitsNeededDepth)) { + this.releaseHits(); + } + this.callChildren('hitsNotNeeded', arguments); + }; + DateComponent.prototype.prepareHits = function () { + // subclasses can implement + }; + DateComponent.prototype.releaseHits = function () { + // subclasses can implement + }; + // Given coordinates from the topleft of the document, return data about the date-related area underneath. + // Can return an object with arbitrary properties (although top/right/left/bottom are encouraged). + // Must have a `grid` property, a reference to this current grid. TODO: avoid this + // The returned object will be processed by getHitFootprint and getHitEl. + DateComponent.prototype.queryHit = function (leftOffset, topOffset) { + var childrenByUid = this.childrenByUid; + var uid; + var hit; + for (uid in childrenByUid) { + hit = childrenByUid[uid].queryHit(leftOffset, topOffset); + if (hit) { + break; + } + } + return hit; + }; + DateComponent.prototype.getSafeHitFootprint = function (hit) { + var footprint = this.getHitFootprint(hit); + if (!this.dateProfile.activeUnzonedRange.containsRange(footprint.unzonedRange)) { + return null; + } + return footprint; + }; + DateComponent.prototype.getHitFootprint = function (hit) { + // what about being abstract!? + }; + // Given position-level information about a date-related area within the grid, + // should return a jQuery element that best represents it. passed to dayClick callback. + DateComponent.prototype.getHitEl = function (hit) { + // what about being abstract!? + }; + /* Converting eventRange -> eventFootprint + ------------------------------------------------------------------------------------------------------------------*/ + DateComponent.prototype.eventRangesToEventFootprints = function (eventRanges) { + var eventFootprints = []; + var i; + for (i = 0; i < eventRanges.length; i++) { + eventFootprints.push.apply(// append + eventFootprints, this.eventRangeToEventFootprints(eventRanges[i])); + } + return eventFootprints; + }; + DateComponent.prototype.eventRangeToEventFootprints = function (eventRange) { + return [util_2.eventRangeToEventFootprint(eventRange)]; + }; + /* Converting componentFootprint/eventFootprint -> segs + ------------------------------------------------------------------------------------------------------------------*/ + DateComponent.prototype.eventFootprintsToSegs = function (eventFootprints) { + var segs = []; + var i; + for (i = 0; i < eventFootprints.length; i++) { + segs.push.apply(segs, this.eventFootprintToSegs(eventFootprints[i])); + } + return segs; + }; + // Given an event's span (unzoned start/end and other misc data), and the event itself, + // slices into segments and attaches event-derived properties to them. + // eventSpan - { start, end, isStart, isEnd, otherthings... } + DateComponent.prototype.eventFootprintToSegs = function (eventFootprint) { + var unzonedRange = eventFootprint.componentFootprint.unzonedRange; + var segs; + var i; + var seg; + segs = this.componentFootprintToSegs(eventFootprint.componentFootprint); + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + if (!unzonedRange.isStart) { + seg.isStart = false; + } + if (!unzonedRange.isEnd) { + seg.isEnd = false; + } + seg.footprint = eventFootprint; + // TODO: rename to seg.eventFootprint + } + return segs; + }; + DateComponent.prototype.componentFootprintToSegs = function (componentFootprint) { + return []; + }; + // Utils + // --------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.callChildren = function (methodName, args) { + this.iterChildren(function (child) { + child[methodName].apply(child, args); + }); + }; + DateComponent.prototype.iterChildren = function (func) { + var childrenByUid = this.childrenByUid; + var uid; + for (uid in childrenByUid) { + func(childrenByUid[uid]); + } + }; + DateComponent.prototype._getCalendar = function () { + var t = this; + return t.calendar || t.view.calendar; + }; + DateComponent.prototype._getView = function () { + return this.view; + }; + DateComponent.prototype._getDateProfile = function () { + return this._getView().get('dateProfile'); + }; + // Generates HTML for an anchor to another view into the calendar. + // Will either generate an tag or a non-clickable tag, depending on enabled settings. + // `gotoOptions` can either be a moment input, or an object with the form: + // { date, type, forceOff } + // `type` is a view-type like "day" or "week". default value is "day". + // `attrs` and `innerHtml` are use to generate the rest of the HTML tag. + DateComponent.prototype.buildGotoAnchorHtml = function (gotoOptions, attrs, innerHtml) { + var date; + var type; + var forceOff; + var finalOptions; + if ($.isPlainObject(gotoOptions)) { + date = gotoOptions.date; + type = gotoOptions.type; + forceOff = gotoOptions.forceOff; + } + else { + date = gotoOptions; // a single moment input + } + date = moment_ext_1.default(date); // if a string, parse it + finalOptions = { + date: date.format('YYYY-MM-DD'), + type: type || 'day' + }; + if (typeof attrs === 'string') { + innerHtml = attrs; + attrs = null; + } + attrs = attrs ? ' ' + util_1.attrsToStr(attrs) : ''; // will have a leading space + innerHtml = innerHtml || ''; + if (!forceOff && this.opt('navLinks')) { + return '' + + innerHtml + + ''; + } + else { + return '' + + innerHtml + + ''; + } + }; + DateComponent.prototype.getAllDayHtml = function () { + return this.opt('allDayHtml') || util_1.htmlEscape(this.opt('allDayText')); + }; + // Computes HTML classNames for a single-day element + DateComponent.prototype.getDayClasses = function (date, noThemeHighlight) { + var view = this._getView(); + var classes = []; + var today; + if (!this.dateProfile.activeUnzonedRange.containsDate(date)) { + classes.push('fc-disabled-day'); // TODO: jQuery UI theme? + } + else { + classes.push('fc-' + util_1.dayIDs[date.day()]); + if (view.isDateInOtherMonth(date, this.dateProfile)) { + classes.push('fc-other-month'); + } + today = view.calendar.getNow(); + if (date.isSame(today, 'day')) { + classes.push('fc-today'); + if (noThemeHighlight !== true) { + classes.push(view.calendar.theme.getClass('today')); + } + } + else if (date < today) { + classes.push('fc-past'); + } + else { + classes.push('fc-future'); + } + } + return classes; + }; + // Utility for formatting a range. Accepts a range object, formatting string, and optional separator. + // Displays all-day ranges naturally, with an inclusive end. Takes the current isRTL into account. + // The timezones of the dates within `range` will be respected. + DateComponent.prototype.formatRange = function (range, isAllDay, formatStr, separator) { + var end = range.end; + if (isAllDay) { + end = end.clone().subtract(1); // convert to inclusive. last ms of previous day + } + return date_formatting_1.formatRange(range.start, end, formatStr, separator, this.isRTL); + }; + // Compute the number of the give units in the "current" range. + // Will return a floating-point number. Won't round. + DateComponent.prototype.currentRangeAs = function (unit) { + return this._getDateProfile().currentUnzonedRange.as(unit); + }; + // Returns the date range of the full days the given range visually appears to occupy. + // Returns a plain object with start/end, NOT an UnzonedRange! + DateComponent.prototype.computeDayRange = function (unzonedRange) { + var calendar = this._getCalendar(); + var startDay = calendar.msToUtcMoment(unzonedRange.startMs, true); // the beginning of the day the range starts + var end = calendar.msToUtcMoment(unzonedRange.endMs); + var endTimeMS = +end.time(); // # of milliseconds into `endDay` + var endDay = end.clone().stripTime(); // the beginning of the day the range exclusively ends + // If the end time is actually inclusively part of the next day and is equal to or + // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`. + // Otherwise, leaving it as inclusive will cause it to exclude `endDay`. + if (endTimeMS && endTimeMS >= this.nextDayThreshold) { + endDay.add(1, 'days'); + } + // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day. + if (endDay <= startDay) { + endDay = startDay.clone().add(1, 'days'); + } + return { start: startDay, end: endDay }; + }; + // Does the given range visually appear to occupy more than one day? + DateComponent.prototype.isMultiDayRange = function (unzonedRange) { + var dayRange = this.computeDayRange(unzonedRange); + return dayRange.end.diff(dayRange.start, 'days') > 1; + }; + DateComponent.guid = 0; // TODO: better system for this? + return DateComponent; +}(Component_1.default)); +exports.default = DateComponent; +// legacy +function convertEventsPayloadToLegacyArray(eventsPayload) { + var eventDefId; + var eventInstances; + var legacyEvents = []; + var i; + for (eventDefId in eventsPayload) { + eventInstances = eventsPayload[eventDefId].eventInstances; + for (i = 0; i < eventInstances.length; i++) { + legacyEvents.push(eventInstances[i].toLegacy()); + } + } + return legacyEvents; +} + + +/***/ }), +/* 220 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var moment = __webpack_require__(0); +var util_1 = __webpack_require__(4); +var options_1 = __webpack_require__(32); +var Iterator_1 = __webpack_require__(238); +var GlobalEmitter_1 = __webpack_require__(21); +var EmitterMixin_1 = __webpack_require__(11); +var ListenerMixin_1 = __webpack_require__(7); +var Toolbar_1 = __webpack_require__(239); +var OptionsManager_1 = __webpack_require__(240); +var ViewSpecManager_1 = __webpack_require__(241); +var Constraints_1 = __webpack_require__(207); +var locale_1 = __webpack_require__(31); +var moment_ext_1 = __webpack_require__(10); +var UnzonedRange_1 = __webpack_require__(5); +var ComponentFootprint_1 = __webpack_require__(12); +var EventDateProfile_1 = __webpack_require__(17); +var EventManager_1 = __webpack_require__(242); +var BusinessHourGenerator_1 = __webpack_require__(212); +var EventSourceParser_1 = __webpack_require__(38); +var EventDefParser_1 = __webpack_require__(49); +var SingleEventDef_1 = __webpack_require__(13); +var EventDefMutation_1 = __webpack_require__(37); +var EventSource_1 = __webpack_require__(6); +var ThemeRegistry_1 = __webpack_require__(51); +var Calendar = /** @class */ (function () { + function Calendar(el, overrides) { + this.loadingLevel = 0; // number of simultaneous loading tasks + this.ignoreUpdateViewSize = 0; + this.freezeContentHeightDepth = 0; + // declare the current calendar instance relies on GlobalEmitter. needed for garbage collection. + // unneeded() is called in destroy. + GlobalEmitter_1.default.needed(); + this.el = el; + this.viewsByType = {}; + this.optionsManager = new OptionsManager_1.default(this, overrides); + this.viewSpecManager = new ViewSpecManager_1.default(this.optionsManager, this); + this.initMomentInternals(); // needs to happen after options hash initialized + this.initCurrentDate(); + this.initEventManager(); + this.constraints = new Constraints_1.default(this.eventManager, this); + this.constructed(); + } + Calendar.prototype.constructed = function () { + // useful for monkeypatching. used? + }; + Calendar.prototype.getView = function () { + return this.view; + }; + Calendar.prototype.publiclyTrigger = function (name, triggerInfo) { + var optHandler = this.opt(name); + var context; + var args; + if ($.isPlainObject(triggerInfo)) { + context = triggerInfo.context; + args = triggerInfo.args; + } + else if ($.isArray(triggerInfo)) { + args = triggerInfo; + } + if (context == null) { + context = this.el[0]; // fallback context + } + if (!args) { + args = []; + } + this.triggerWith(name, context, args); // Emitter's method + if (optHandler) { + return optHandler.apply(context, args); + } + }; + Calendar.prototype.hasPublicHandlers = function (name) { + return this.hasHandlers(name) || + this.opt(name); // handler specified in options + }; + // Options Public API + // ----------------------------------------------------------------------------------------------------------------- + // public getter/setter + Calendar.prototype.option = function (name, value) { + var newOptionHash; + if (typeof name === 'string') { + if (value === undefined) { + return this.optionsManager.get(name); + } + else { + newOptionHash = {}; + newOptionHash[name] = value; + this.optionsManager.add(newOptionHash); + } + } + else if (typeof name === 'object') { + this.optionsManager.add(name); + } + }; + // private getter + Calendar.prototype.opt = function (name) { + return this.optionsManager.get(name); + }; + // View + // ----------------------------------------------------------------------------------------------------------------- + // Given a view name for a custom view or a standard view, creates a ready-to-go View object + Calendar.prototype.instantiateView = function (viewType) { + var spec = this.viewSpecManager.getViewSpec(viewType); + if (!spec) { + throw new Error("View type \"" + viewType + "\" is not valid"); + } + return new spec['class'](this, spec); + }; + // Returns a boolean about whether the view is okay to instantiate at some point + Calendar.prototype.isValidViewType = function (viewType) { + return Boolean(this.viewSpecManager.getViewSpec(viewType)); + }; + Calendar.prototype.changeView = function (viewName, dateOrRange) { + if (dateOrRange) { + if (dateOrRange.start && dateOrRange.end) { + this.optionsManager.recordOverrides({ + visibleRange: dateOrRange + }); + } + else { + this.currentDate = this.moment(dateOrRange).stripZone(); // just like gotoDate + } + } + this.renderView(viewName); + }; + // Forces navigation to a view for the given date. + // `viewType` can be a specific view name or a generic one like "week" or "day". + Calendar.prototype.zoomTo = function (newDate, viewType) { + var spec; + viewType = viewType || 'day'; // day is default zoom + spec = this.viewSpecManager.getViewSpec(viewType) || + this.viewSpecManager.getUnitViewSpec(viewType); + this.currentDate = newDate.clone(); + this.renderView(spec ? spec.type : null); + }; + // Current Date + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.initCurrentDate = function () { + var defaultDateInput = this.opt('defaultDate'); + // compute the initial ambig-timezone date + if (defaultDateInput != null) { + this.currentDate = this.moment(defaultDateInput).stripZone(); + } + else { + this.currentDate = this.getNow(); // getNow already returns unzoned + } + }; + Calendar.prototype.prev = function () { + var view = this.view; + var prevInfo = view.dateProfileGenerator.buildPrev(view.get('dateProfile')); + if (prevInfo.isValid) { + this.currentDate = prevInfo.date; + this.renderView(); + } + }; + Calendar.prototype.next = function () { + var view = this.view; + var nextInfo = view.dateProfileGenerator.buildNext(view.get('dateProfile')); + if (nextInfo.isValid) { + this.currentDate = nextInfo.date; + this.renderView(); + } + }; + Calendar.prototype.prevYear = function () { + this.currentDate.add(-1, 'years'); + this.renderView(); + }; + Calendar.prototype.nextYear = function () { + this.currentDate.add(1, 'years'); + this.renderView(); + }; + Calendar.prototype.today = function () { + this.currentDate = this.getNow(); // should deny like prev/next? + this.renderView(); + }; + Calendar.prototype.gotoDate = function (zonedDateInput) { + this.currentDate = this.moment(zonedDateInput).stripZone(); + this.renderView(); + }; + Calendar.prototype.incrementDate = function (delta) { + this.currentDate.add(moment.duration(delta)); + this.renderView(); + }; + // for external API + Calendar.prototype.getDate = function () { + return this.applyTimezone(this.currentDate); // infuse the calendar's timezone + }; + // Loading Triggering + // ----------------------------------------------------------------------------------------------------------------- + // Should be called when any type of async data fetching begins + Calendar.prototype.pushLoading = function () { + if (!(this.loadingLevel++)) { + this.publiclyTrigger('loading', [true, this.view]); + } + }; + // Should be called when any type of async data fetching completes + Calendar.prototype.popLoading = function () { + if (!(--this.loadingLevel)) { + this.publiclyTrigger('loading', [false, this.view]); + } + }; + // High-level Rendering + // ----------------------------------------------------------------------------------- + Calendar.prototype.render = function () { + if (!this.contentEl) { + this.initialRender(); + } + else if (this.elementVisible()) { + // mainly for the public API + this.calcSize(); + this.updateViewSize(); + } + }; + Calendar.prototype.initialRender = function () { + var _this = this; + var el = this.el; + el.addClass('fc'); + // event delegation for nav links + el.on('click.fc', 'a[data-goto]', function (ev) { + var anchorEl = $(ev.currentTarget); + var gotoOptions = anchorEl.data('goto'); // will automatically parse JSON + var date = _this.moment(gotoOptions.date); + var viewType = gotoOptions.type; + // property like "navLinkDayClick". might be a string or a function + var customAction = _this.view.opt('navLink' + util_1.capitaliseFirstLetter(viewType) + 'Click'); + if (typeof customAction === 'function') { + customAction(date, ev); + } + else { + if (typeof customAction === 'string') { + viewType = customAction; + } + _this.zoomTo(date, viewType); + } + }); + // called immediately, and upon option change + this.optionsManager.watch('settingTheme', ['?theme', '?themeSystem'], function (opts) { + var themeClass = ThemeRegistry_1.getThemeSystemClass(opts.themeSystem || opts.theme); + var theme = new themeClass(_this.optionsManager); + var widgetClass = theme.getClass('widget'); + _this.theme = theme; + if (widgetClass) { + el.addClass(widgetClass); + } + }, function () { + var widgetClass = _this.theme.getClass('widget'); + _this.theme = null; + if (widgetClass) { + el.removeClass(widgetClass); + } + }); + this.optionsManager.watch('settingBusinessHourGenerator', ['?businessHours'], function (deps) { + _this.businessHourGenerator = new BusinessHourGenerator_1.default(deps.businessHours, _this); + if (_this.view) { + _this.view.set('businessHourGenerator', _this.businessHourGenerator); + } + }, function () { + _this.businessHourGenerator = null; + }); + // called immediately, and upon option change. + // HACK: locale often affects isRTL, so we explicitly listen to that too. + this.optionsManager.watch('applyingDirClasses', ['?isRTL', '?locale'], function (opts) { + el.toggleClass('fc-ltr', !opts.isRTL); + el.toggleClass('fc-rtl', opts.isRTL); + }); + this.contentEl = $("").prependTo(el); + this.initToolbars(); + this.renderHeader(); + this.renderFooter(); + this.renderView(this.opt('defaultView')); + if (this.opt('handleWindowResize')) { + $(window).resize(this.windowResizeProxy = util_1.debounce(// prevents rapid calls + this.windowResize.bind(this), this.opt('windowResizeDelay'))); + } + }; + Calendar.prototype.destroy = function () { + if (this.view) { + this.clearView(); + } + this.toolbarsManager.proxyCall('removeElement'); + this.contentEl.remove(); + this.el.removeClass('fc fc-ltr fc-rtl'); + // removes theme-related root className + this.optionsManager.unwatch('settingTheme'); + this.optionsManager.unwatch('settingBusinessHourGenerator'); + this.el.off('.fc'); // unbind nav link handlers + if (this.windowResizeProxy) { + $(window).unbind('resize', this.windowResizeProxy); + this.windowResizeProxy = null; + } + GlobalEmitter_1.default.unneeded(); + }; + Calendar.prototype.elementVisible = function () { + return this.el.is(':visible'); + }; + // Render Queue + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.bindViewHandlers = function (view) { + var _this = this; + view.watch('titleForCalendar', ['title'], function (deps) { + if (view === _this.view) { + _this.setToolbarsTitle(deps.title); + } + }); + view.watch('dateProfileForCalendar', ['dateProfile'], function (deps) { + if (view === _this.view) { + _this.currentDate = deps.dateProfile.date; // might have been constrained by view dates + _this.updateToolbarButtons(deps.dateProfile); + } + }); + }; + Calendar.prototype.unbindViewHandlers = function (view) { + view.unwatch('titleForCalendar'); + view.unwatch('dateProfileForCalendar'); + }; + // View Rendering + // ----------------------------------------------------------------------------------- + // Renders a view because of a date change, view-type change, or for the first time. + // If not given a viewType, keep the current view but render different dates. + // Accepts an optional scroll state to restore to. + Calendar.prototype.renderView = function (viewType) { + var oldView = this.view; + var newView; + this.freezeContentHeight(); + if (oldView && viewType && oldView.type !== viewType) { + this.clearView(); + } + // if viewType changed, or the view was never created, create a fresh view + if (!this.view && viewType) { + newView = this.view = + this.viewsByType[viewType] || + (this.viewsByType[viewType] = this.instantiateView(viewType)); + this.bindViewHandlers(newView); + newView.startBatchRender(); // so that setElement+setDate rendering are joined + newView.setElement($("").appendTo(this.contentEl)); + this.toolbarsManager.proxyCall('activateButton', viewType); + } + if (this.view) { + // prevent unnecessary change firing + if (this.view.get('businessHourGenerator') !== this.businessHourGenerator) { + this.view.set('businessHourGenerator', this.businessHourGenerator); + } + this.view.setDate(this.currentDate); + if (newView) { + newView.stopBatchRender(); + } + } + this.thawContentHeight(); + }; + // Unrenders the current view and reflects this change in the Header. + // Unregsiters the `view`, but does not remove from viewByType hash. + Calendar.prototype.clearView = function () { + var currentView = this.view; + this.toolbarsManager.proxyCall('deactivateButton', currentView.type); + this.unbindViewHandlers(currentView); + currentView.removeElement(); + currentView.unsetDate(); // so bindViewHandlers doesn't fire with old values next time + this.view = null; + }; + // Destroys the view, including the view object. Then, re-instantiates it and renders it. + // Maintains the same scroll state. + // TODO: maintain any other user-manipulated state. + Calendar.prototype.reinitView = function () { + var oldView = this.view; + var scroll = oldView.queryScroll(); // wouldn't be so complicated if Calendar owned the scroll + this.freezeContentHeight(); + this.clearView(); + this.calcSize(); + this.renderView(oldView.type); // needs the type to freshly render + this.view.applyScroll(scroll); + this.thawContentHeight(); + }; + // Resizing + // ----------------------------------------------------------------------------------- + Calendar.prototype.getSuggestedViewHeight = function () { + if (this.suggestedViewHeight == null) { + this.calcSize(); + } + return this.suggestedViewHeight; + }; + Calendar.prototype.isHeightAuto = function () { + return this.opt('contentHeight') === 'auto' || this.opt('height') === 'auto'; + }; + Calendar.prototype.updateViewSize = function (isResize) { + if (isResize === void 0) { isResize = false; } + var view = this.view; + var scroll; + if (!this.ignoreUpdateViewSize && view) { + if (isResize) { + this.calcSize(); + scroll = view.queryScroll(); + } + this.ignoreUpdateViewSize++; + view.updateSize(this.getSuggestedViewHeight(), this.isHeightAuto(), isResize); + this.ignoreUpdateViewSize--; + if (isResize) { + view.applyScroll(scroll); + } + return true; // signal success + } + }; + Calendar.prototype.calcSize = function () { + if (this.elementVisible()) { + this._calcSize(); + } + }; + Calendar.prototype._calcSize = function () { + var contentHeightInput = this.opt('contentHeight'); + var heightInput = this.opt('height'); + if (typeof contentHeightInput === 'number') { + this.suggestedViewHeight = contentHeightInput; + } + else if (typeof contentHeightInput === 'function') { + this.suggestedViewHeight = contentHeightInput(); + } + else if (typeof heightInput === 'number') { + this.suggestedViewHeight = heightInput - this.queryToolbarsHeight(); + } + else if (typeof heightInput === 'function') { + this.suggestedViewHeight = heightInput() - this.queryToolbarsHeight(); + } + else if (heightInput === 'parent') { + this.suggestedViewHeight = this.el.parent().height() - this.queryToolbarsHeight(); + } + else { + this.suggestedViewHeight = Math.round(this.contentEl.width() / + Math.max(this.opt('aspectRatio'), .5)); + } + }; + Calendar.prototype.windowResize = function (ev) { + if ( + // the purpose: so we don't process jqui "resize" events that have bubbled up + // cast to any because .target, which is Element, can't be compared to window for some reason. + ev.target === window && + this.view && + this.view.isDatesRendered) { + if (this.updateViewSize(true)) { + this.publiclyTrigger('windowResize', [this.view]); + } + } + }; + /* Height "Freezing" + -----------------------------------------------------------------------------*/ + Calendar.prototype.freezeContentHeight = function () { + if (!(this.freezeContentHeightDepth++)) { + this.forceFreezeContentHeight(); + } + }; + Calendar.prototype.forceFreezeContentHeight = function () { + this.contentEl.css({ + width: '100%', + height: this.contentEl.height(), + overflow: 'hidden' + }); + }; + Calendar.prototype.thawContentHeight = function () { + this.freezeContentHeightDepth--; + // always bring back to natural height + this.contentEl.css({ + width: '', + height: '', + overflow: '' + }); + // but if there are future thaws, re-freeze + if (this.freezeContentHeightDepth) { + this.forceFreezeContentHeight(); + } + }; + // Toolbar + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.initToolbars = function () { + this.header = new Toolbar_1.default(this, this.computeHeaderOptions()); + this.footer = new Toolbar_1.default(this, this.computeFooterOptions()); + this.toolbarsManager = new Iterator_1.default([this.header, this.footer]); + }; + Calendar.prototype.computeHeaderOptions = function () { + return { + extraClasses: 'fc-header-toolbar', + layout: this.opt('header') + }; + }; + Calendar.prototype.computeFooterOptions = function () { + return { + extraClasses: 'fc-footer-toolbar', + layout: this.opt('footer') + }; + }; + // can be called repeatedly and Header will rerender + Calendar.prototype.renderHeader = function () { + var header = this.header; + header.setToolbarOptions(this.computeHeaderOptions()); + header.render(); + if (header.el) { + this.el.prepend(header.el); + } + }; + // can be called repeatedly and Footer will rerender + Calendar.prototype.renderFooter = function () { + var footer = this.footer; + footer.setToolbarOptions(this.computeFooterOptions()); + footer.render(); + if (footer.el) { + this.el.append(footer.el); + } + }; + Calendar.prototype.setToolbarsTitle = function (title) { + this.toolbarsManager.proxyCall('updateTitle', title); + }; + Calendar.prototype.updateToolbarButtons = function (dateProfile) { + var now = this.getNow(); + var view = this.view; + var todayInfo = view.dateProfileGenerator.build(now); + var prevInfo = view.dateProfileGenerator.buildPrev(view.get('dateProfile')); + var nextInfo = view.dateProfileGenerator.buildNext(view.get('dateProfile')); + this.toolbarsManager.proxyCall((todayInfo.isValid && !dateProfile.currentUnzonedRange.containsDate(now)) ? + 'enableButton' : + 'disableButton', 'today'); + this.toolbarsManager.proxyCall(prevInfo.isValid ? + 'enableButton' : + 'disableButton', 'prev'); + this.toolbarsManager.proxyCall(nextInfo.isValid ? + 'enableButton' : + 'disableButton', 'next'); + }; + Calendar.prototype.queryToolbarsHeight = function () { + return this.toolbarsManager.items.reduce(function (accumulator, toolbar) { + var toolbarHeight = toolbar.el ? toolbar.el.outerHeight(true) : 0; // includes margin + return accumulator + toolbarHeight; + }, 0); + }; + // Selection + // ----------------------------------------------------------------------------------------------------------------- + // this public method receives start/end dates in any format, with any timezone + Calendar.prototype.select = function (zonedStartInput, zonedEndInput) { + this.view.select(this.buildSelectFootprint.apply(this, arguments)); + }; + Calendar.prototype.unselect = function () { + if (this.view) { + this.view.unselect(); + } + }; + // Given arguments to the select method in the API, returns a span (unzoned start/end and other info) + Calendar.prototype.buildSelectFootprint = function (zonedStartInput, zonedEndInput) { + var start = this.moment(zonedStartInput).stripZone(); + var end; + if (zonedEndInput) { + end = this.moment(zonedEndInput).stripZone(); + } + else if (start.hasTime()) { + end = start.clone().add(this.defaultTimedEventDuration); + } + else { + end = start.clone().add(this.defaultAllDayEventDuration); + } + return new ComponentFootprint_1.default(new UnzonedRange_1.default(start, end), !start.hasTime()); + }; + // Date Utils + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.initMomentInternals = function () { + var _this = this; + this.defaultAllDayEventDuration = moment.duration(this.opt('defaultAllDayEventDuration')); + this.defaultTimedEventDuration = moment.duration(this.opt('defaultTimedEventDuration')); + // Called immediately, and when any of the options change. + // Happens before any internal objects rebuild or rerender, because this is very core. + this.optionsManager.watch('buildingMomentLocale', [ + '?locale', '?monthNames', '?monthNamesShort', '?dayNames', '?dayNamesShort', + '?firstDay', '?weekNumberCalculation' + ], function (opts) { + var weekNumberCalculation = opts.weekNumberCalculation; + var firstDay = opts.firstDay; + var _week; + // normalize + if (weekNumberCalculation === 'iso') { + weekNumberCalculation = 'ISO'; // normalize + } + var localeData = Object.create(// make a cheap copy + locale_1.getMomentLocaleData(opts.locale) // will fall back to en + ); + if (opts.monthNames) { + localeData._months = opts.monthNames; + } + if (opts.monthNamesShort) { + localeData._monthsShort = opts.monthNamesShort; + } + if (opts.dayNames) { + localeData._weekdays = opts.dayNames; + } + if (opts.dayNamesShort) { + localeData._weekdaysShort = opts.dayNamesShort; + } + if (firstDay == null && weekNumberCalculation === 'ISO') { + firstDay = 1; + } + if (firstDay != null) { + _week = Object.create(localeData._week); // _week: { dow: # } + _week.dow = firstDay; + localeData._week = _week; + } + if (weekNumberCalculation === 'ISO' || + weekNumberCalculation === 'local' || + typeof weekNumberCalculation === 'function') { + localeData._fullCalendar_weekCalc = weekNumberCalculation; // moment-ext will know what to do with it + } + _this.localeData = localeData; + // If the internal current date object already exists, move to new locale. + // We do NOT need to do this technique for event dates, because this happens when converting to "segments". + if (_this.currentDate) { + _this.localizeMoment(_this.currentDate); // sets to localeData + } + }); + }; + // Builds a moment using the settings of the current calendar: timezone and locale. + // Accepts anything the vanilla moment() constructor accepts. + Calendar.prototype.moment = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var mom; + if (this.opt('timezone') === 'local') { + mom = moment_ext_1.default.apply(null, args); + // Force the moment to be local, because momentExt doesn't guarantee it. + if (mom.hasTime()) { + mom.local(); + } + } + else if (this.opt('timezone') === 'UTC') { + mom = moment_ext_1.default.utc.apply(null, args); // process as UTC + } + else { + mom = moment_ext_1.default.parseZone.apply(null, args); // let the input decide the zone + } + this.localizeMoment(mom); // TODO + return mom; + }; + Calendar.prototype.msToMoment = function (ms, forceAllDay) { + var mom = moment_ext_1.default.utc(ms); // TODO: optimize by using Date.UTC + if (forceAllDay) { + mom.stripTime(); + } + else { + mom = this.applyTimezone(mom); // may or may not apply locale + } + this.localizeMoment(mom); + return mom; + }; + Calendar.prototype.msToUtcMoment = function (ms, forceAllDay) { + var mom = moment_ext_1.default.utc(ms); // TODO: optimize by using Date.UTC + if (forceAllDay) { + mom.stripTime(); + } + this.localizeMoment(mom); + return mom; + }; + // Updates the given moment's locale settings to the current calendar locale settings. + Calendar.prototype.localizeMoment = function (mom) { + mom._locale = this.localeData; + }; + // Returns a boolean about whether or not the calendar knows how to calculate + // the timezone offset of arbitrary dates in the current timezone. + Calendar.prototype.getIsAmbigTimezone = function () { + return this.opt('timezone') !== 'local' && this.opt('timezone') !== 'UTC'; + }; + // Returns a copy of the given date in the current timezone. Has no effect on dates without times. + Calendar.prototype.applyTimezone = function (date) { + if (!date.hasTime()) { + return date.clone(); + } + var zonedDate = this.moment(date.toArray()); + var timeAdjust = date.time().asMilliseconds() - zonedDate.time().asMilliseconds(); + var adjustedZonedDate; + // Safari sometimes has problems with this coersion when near DST. Adjust if necessary. (bug #2396) + if (timeAdjust) { + adjustedZonedDate = zonedDate.clone().add(timeAdjust); // add milliseconds + if (date.time().asMilliseconds() - adjustedZonedDate.time().asMilliseconds() === 0) { + zonedDate = adjustedZonedDate; + } + } + return zonedDate; + }; + /* + Assumes the footprint is non-open-ended. + */ + Calendar.prototype.footprintToDateProfile = function (componentFootprint, ignoreEnd) { + if (ignoreEnd === void 0) { ignoreEnd = false; } + var start = moment_ext_1.default.utc(componentFootprint.unzonedRange.startMs); + var end; + if (!ignoreEnd) { + end = moment_ext_1.default.utc(componentFootprint.unzonedRange.endMs); + } + if (componentFootprint.isAllDay) { + start.stripTime(); + if (end) { + end.stripTime(); + } + } + else { + start = this.applyTimezone(start); + if (end) { + end = this.applyTimezone(end); + } + } + return new EventDateProfile_1.default(start, end, this); + }; + // Returns a moment for the current date, as defined by the client's computer or from the `now` option. + // Will return an moment with an ambiguous timezone. + Calendar.prototype.getNow = function () { + var now = this.opt('now'); + if (typeof now === 'function') { + now = now(); + } + return this.moment(now).stripZone(); + }; + // Produces a human-readable string for the given duration. + // Side-effect: changes the locale of the given duration. + Calendar.prototype.humanizeDuration = function (duration) { + return duration.locale(this.opt('locale')).humanize(); + }; + // will return `null` if invalid range + Calendar.prototype.parseUnzonedRange = function (rangeInput) { + var start = null; + var end = null; + if (rangeInput.start) { + start = this.moment(rangeInput.start).stripZone(); + } + if (rangeInput.end) { + end = this.moment(rangeInput.end).stripZone(); + } + if (!start && !end) { + return null; + } + if (start && end && end.isBefore(start)) { + return null; + } + return new UnzonedRange_1.default(start, end); + }; + // Event-Date Utilities + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.initEventManager = function () { + var _this = this; + var eventManager = new EventManager_1.default(this); + var rawSources = this.opt('eventSources') || []; + var singleRawSource = this.opt('events'); + this.eventManager = eventManager; + if (singleRawSource) { + rawSources.unshift(singleRawSource); + } + eventManager.on('release', function (eventsPayload) { + _this.trigger('eventsReset', eventsPayload); + }); + eventManager.freeze(); + rawSources.forEach(function (rawSource) { + var source = EventSourceParser_1.default.parse(rawSource, _this); + if (source) { + eventManager.addSource(source); + } + }); + eventManager.thaw(); + }; + Calendar.prototype.requestEvents = function (start, end) { + return this.eventManager.requestEvents(start, end, this.opt('timezone'), !this.opt('lazyFetching')); + }; + // Get an event's normalized end date. If not present, calculate it from the defaults. + Calendar.prototype.getEventEnd = function (event) { + if (event.end) { + return event.end.clone(); + } + else { + return this.getDefaultEventEnd(event.allDay, event.start); + } + }; + // Given an event's allDay status and start date, return what its fallback end date should be. + // TODO: rename to computeDefaultEventEnd + Calendar.prototype.getDefaultEventEnd = function (allDay, zonedStart) { + var end = zonedStart.clone(); + if (allDay) { + end.stripTime().add(this.defaultAllDayEventDuration); + } + else { + end.add(this.defaultTimedEventDuration); + } + if (this.getIsAmbigTimezone()) { + end.stripZone(); // we don't know what the tzo should be + } + return end; + }; + // Public Events API + // ----------------------------------------------------------------------------------------------------------------- + Calendar.prototype.rerenderEvents = function () { + this.view.flash('displayingEvents'); + }; + Calendar.prototype.refetchEvents = function () { + this.eventManager.refetchAllSources(); + }; + Calendar.prototype.renderEvents = function (eventInputs, isSticky) { + this.eventManager.freeze(); + for (var i = 0; i < eventInputs.length; i++) { + this.renderEvent(eventInputs[i], isSticky); + } + this.eventManager.thaw(); + }; + Calendar.prototype.renderEvent = function (eventInput, isSticky) { + if (isSticky === void 0) { isSticky = false; } + var eventManager = this.eventManager; + var eventDef = EventDefParser_1.default.parse(eventInput, eventInput.source || eventManager.stickySource); + if (eventDef) { + eventManager.addEventDef(eventDef, isSticky); + } + }; + // legacyQuery operates on legacy event instance objects + Calendar.prototype.removeEvents = function (legacyQuery) { + var eventManager = this.eventManager; + var legacyInstances = []; + var idMap = {}; + var eventDef; + var i; + if (legacyQuery == null) { + eventManager.removeAllEventDefs(); // persist=true + } + else { + eventManager.getEventInstances().forEach(function (eventInstance) { + legacyInstances.push(eventInstance.toLegacy()); + }); + legacyInstances = filterLegacyEventInstances(legacyInstances, legacyQuery); + // compute unique IDs + for (i = 0; i < legacyInstances.length; i++) { + eventDef = this.eventManager.getEventDefByUid(legacyInstances[i]._id); + idMap[eventDef.id] = true; + } + eventManager.freeze(); + for (i in idMap) { + eventManager.removeEventDefsById(i); // persist=true + } + eventManager.thaw(); + } + }; + // legacyQuery operates on legacy event instance objects + Calendar.prototype.clientEvents = function (legacyQuery) { + var legacyEventInstances = []; + this.eventManager.getEventInstances().forEach(function (eventInstance) { + legacyEventInstances.push(eventInstance.toLegacy()); + }); + return filterLegacyEventInstances(legacyEventInstances, legacyQuery); + }; + Calendar.prototype.updateEvents = function (eventPropsArray) { + this.eventManager.freeze(); + for (var i = 0; i < eventPropsArray.length; i++) { + this.updateEvent(eventPropsArray[i]); + } + this.eventManager.thaw(); + }; + Calendar.prototype.updateEvent = function (eventProps) { + var eventDef = this.eventManager.getEventDefByUid(eventProps._id); + var eventInstance; + var eventDefMutation; + if (eventDef instanceof SingleEventDef_1.default) { + eventInstance = eventDef.buildInstance(); + eventDefMutation = EventDefMutation_1.default.createFromRawProps(eventInstance, eventProps, // raw props + null // largeUnit -- who uses it? + ); + this.eventManager.mutateEventsWithId(eventDef.id, eventDefMutation); // will release + } + }; + // Public Event Sources API + // ------------------------------------------------------------------------------------ + Calendar.prototype.getEventSources = function () { + return this.eventManager.otherSources.slice(); // clone + }; + Calendar.prototype.getEventSourceById = function (id) { + return this.eventManager.getSourceById(EventSource_1.default.normalizeId(id)); + }; + Calendar.prototype.addEventSource = function (sourceInput) { + var source = EventSourceParser_1.default.parse(sourceInput, this); + if (source) { + this.eventManager.addSource(source); + } + }; + Calendar.prototype.removeEventSources = function (sourceMultiQuery) { + var eventManager = this.eventManager; + var sources; + var i; + if (sourceMultiQuery == null) { + this.eventManager.removeAllSources(); + } + else { + sources = eventManager.multiQuerySources(sourceMultiQuery); + eventManager.freeze(); + for (i = 0; i < sources.length; i++) { + eventManager.removeSource(sources[i]); + } + eventManager.thaw(); + } + }; + Calendar.prototype.removeEventSource = function (sourceQuery) { + var eventManager = this.eventManager; + var sources = eventManager.querySources(sourceQuery); + var i; + eventManager.freeze(); + for (i = 0; i < sources.length; i++) { + eventManager.removeSource(sources[i]); + } + eventManager.thaw(); + }; + Calendar.prototype.refetchEventSources = function (sourceMultiQuery) { + var eventManager = this.eventManager; + var sources = eventManager.multiQuerySources(sourceMultiQuery); + var i; + eventManager.freeze(); + for (i = 0; i < sources.length; i++) { + eventManager.refetchSource(sources[i]); + } + eventManager.thaw(); + }; + // not for internal use. use options module directly instead. + Calendar.defaults = options_1.globalDefaults; + Calendar.englishDefaults = options_1.englishDefaults; + Calendar.rtlDefaults = options_1.rtlDefaults; + return Calendar; +}()); +exports.default = Calendar; +EmitterMixin_1.default.mixInto(Calendar); +ListenerMixin_1.default.mixInto(Calendar); +function filterLegacyEventInstances(legacyEventInstances, legacyQuery) { + if (legacyQuery == null) { + return legacyEventInstances; + } + else if ($.isFunction(legacyQuery)) { + return legacyEventInstances.filter(legacyQuery); + } + else { + legacyQuery += ''; // normalize to string + return legacyEventInstances.filter(function (legacyEventInstance) { + // soft comparison because id not be normalized to string + // tslint:disable-next-line + return legacyEventInstance.id == legacyQuery || + legacyEventInstance._id === legacyQuery; // can specify internal id, but must exactly match + }); + } +} + + +/***/ }), +/* 221 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var moment = __webpack_require__(0); +var util_1 = __webpack_require__(4); +var UnzonedRange_1 = __webpack_require__(5); +var DateProfileGenerator = /** @class */ (function () { + function DateProfileGenerator(_view) { + this._view = _view; + } + DateProfileGenerator.prototype.opt = function (name) { + return this._view.opt(name); + }; + DateProfileGenerator.prototype.trimHiddenDays = function (unzonedRange) { + return this._view.trimHiddenDays(unzonedRange); + }; + DateProfileGenerator.prototype.msToUtcMoment = function (ms, forceAllDay) { + return this._view.calendar.msToUtcMoment(ms, forceAllDay); + }; + /* Date Range Computation + ------------------------------------------------------------------------------------------------------------------*/ + // Builds a structure with info about what the dates/ranges will be for the "prev" view. + DateProfileGenerator.prototype.buildPrev = function (currentDateProfile) { + var prevDate = currentDateProfile.date.clone() + .startOf(currentDateProfile.currentRangeUnit) + .subtract(currentDateProfile.dateIncrement); + return this.build(prevDate, -1); + }; + // Builds a structure with info about what the dates/ranges will be for the "next" view. + DateProfileGenerator.prototype.buildNext = function (currentDateProfile) { + var nextDate = currentDateProfile.date.clone() + .startOf(currentDateProfile.currentRangeUnit) + .add(currentDateProfile.dateIncrement); + return this.build(nextDate, 1); + }; + // Builds a structure holding dates/ranges for rendering around the given date. + // Optional direction param indicates whether the date is being incremented/decremented + // from its previous value. decremented = -1, incremented = 1 (default). + DateProfileGenerator.prototype.build = function (date, direction, forceToValid) { + if (forceToValid === void 0) { forceToValid = false; } + var isDateAllDay = !date.hasTime(); + var validUnzonedRange; + var minTime = null; + var maxTime = null; + var currentInfo; + var isRangeAllDay; + var renderUnzonedRange; + var activeUnzonedRange; + var isValid; + validUnzonedRange = this.buildValidRange(); + validUnzonedRange = this.trimHiddenDays(validUnzonedRange); + if (forceToValid) { + date = this.msToUtcMoment(validUnzonedRange.constrainDate(date), // returns MS + isDateAllDay); + } + currentInfo = this.buildCurrentRangeInfo(date, direction); + isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit); + renderUnzonedRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.unzonedRange), currentInfo.unit, isRangeAllDay); + renderUnzonedRange = this.trimHiddenDays(renderUnzonedRange); + activeUnzonedRange = renderUnzonedRange.clone(); + if (!this.opt('showNonCurrentDates')) { + activeUnzonedRange = activeUnzonedRange.intersect(currentInfo.unzonedRange); + } + minTime = moment.duration(this.opt('minTime')); + maxTime = moment.duration(this.opt('maxTime')); + activeUnzonedRange = this.adjustActiveRange(activeUnzonedRange, minTime, maxTime); + activeUnzonedRange = activeUnzonedRange.intersect(validUnzonedRange); // might return null + if (activeUnzonedRange) { + date = this.msToUtcMoment(activeUnzonedRange.constrainDate(date), // returns MS + isDateAllDay); + } + // it's invalid if the originally requested date is not contained, + // or if the range is completely outside of the valid range. + isValid = currentInfo.unzonedRange.intersectsWith(validUnzonedRange); + return { + // constraint for where prev/next operations can go and where events can be dragged/resized to. + // an object with optional start and end properties. + validUnzonedRange: validUnzonedRange, + // range the view is formally responsible for. + // for example, a month view might have 1st-31st, excluding padded dates + currentUnzonedRange: currentInfo.unzonedRange, + // name of largest unit being displayed, like "month" or "week" + currentRangeUnit: currentInfo.unit, + isRangeAllDay: isRangeAllDay, + // dates that display events and accept drag-n-drop + // will be `null` if no dates accept events + activeUnzonedRange: activeUnzonedRange, + // date range with a rendered skeleton + // includes not-active days that need some sort of DOM + renderUnzonedRange: renderUnzonedRange, + // Duration object that denotes the first visible time of any given day + minTime: minTime, + // Duration object that denotes the exclusive visible end time of any given day + maxTime: maxTime, + isValid: isValid, + date: date, + // how far the current date will move for a prev/next operation + dateIncrement: this.buildDateIncrement(currentInfo.duration) + // pass a fallback (might be null) ^ + }; + }; + // Builds an object with optional start/end properties. + // Indicates the minimum/maximum dates to display. + // not responsible for trimming hidden days. + DateProfileGenerator.prototype.buildValidRange = function () { + return this._view.getUnzonedRangeOption('validRange', this._view.calendar.getNow()) || + new UnzonedRange_1.default(); // completely open-ended + }; + // Builds a structure with info about the "current" range, the range that is + // highlighted as being the current month for example. + // See build() for a description of `direction`. + // Guaranteed to have `range` and `unit` properties. `duration` is optional. + // TODO: accept a MS-time instead of a moment `date`? + DateProfileGenerator.prototype.buildCurrentRangeInfo = function (date, direction) { + var viewSpec = this._view.viewSpec; + var duration = null; + var unit = null; + var unzonedRange = null; + var dayCount; + if (viewSpec.duration) { + duration = viewSpec.duration; + unit = viewSpec.durationUnit; + unzonedRange = this.buildRangeFromDuration(date, direction, duration, unit); + } + else if ((dayCount = this.opt('dayCount'))) { + unit = 'day'; + unzonedRange = this.buildRangeFromDayCount(date, direction, dayCount); + } + else if ((unzonedRange = this.buildCustomVisibleRange(date))) { + unit = util_1.computeGreatestUnit(unzonedRange.getStart(), unzonedRange.getEnd()); + } + else { + duration = this.getFallbackDuration(); + unit = util_1.computeGreatestUnit(duration); + unzonedRange = this.buildRangeFromDuration(date, direction, duration, unit); + } + return { duration: duration, unit: unit, unzonedRange: unzonedRange }; + }; + DateProfileGenerator.prototype.getFallbackDuration = function () { + return moment.duration({ days: 1 }); + }; + // Returns a new activeUnzonedRange to have time values (un-ambiguate) + // minTime or maxTime causes the range to expand. + DateProfileGenerator.prototype.adjustActiveRange = function (unzonedRange, minTime, maxTime) { + var start = unzonedRange.getStart(); + var end = unzonedRange.getEnd(); + if (this._view.usesMinMaxTime) { + if (minTime < 0) { + start.time(0).add(minTime); + } + if (maxTime > 24 * 60 * 60 * 1000) { + end.time(maxTime - (24 * 60 * 60 * 1000)); + } + } + return new UnzonedRange_1.default(start, end); + }; + // Builds the "current" range when it is specified as an explicit duration. + // `unit` is the already-computed computeGreatestUnit value of duration. + // TODO: accept a MS-time instead of a moment `date`? + DateProfileGenerator.prototype.buildRangeFromDuration = function (date, direction, duration, unit) { + var alignment = this.opt('dateAlignment'); + var dateIncrementInput; + var dateIncrementDuration; + var start; + var end; + var res; + // compute what the alignment should be + if (!alignment) { + dateIncrementInput = this.opt('dateIncrement'); + if (dateIncrementInput) { + dateIncrementDuration = moment.duration(dateIncrementInput); + // use the smaller of the two units + if (dateIncrementDuration < duration) { + alignment = util_1.computeDurationGreatestUnit(dateIncrementDuration, dateIncrementInput); + } + else { + alignment = unit; + } + } + else { + alignment = unit; + } + } + // if the view displays a single day or smaller + if (duration.as('days') <= 1) { + if (this._view.isHiddenDay(start)) { + start = this._view.skipHiddenDays(start, direction); + start.startOf('day'); + } + } + function computeRes() { + start = date.clone().startOf(alignment); + end = start.clone().add(duration); + res = new UnzonedRange_1.default(start, end); + } + computeRes(); + // if range is completely enveloped by hidden days, go past the hidden days + if (!this.trimHiddenDays(res)) { + date = this._view.skipHiddenDays(date, direction); + computeRes(); + } + return res; + }; + // Builds the "current" range when a dayCount is specified. + // TODO: accept a MS-time instead of a moment `date`? + DateProfileGenerator.prototype.buildRangeFromDayCount = function (date, direction, dayCount) { + var customAlignment = this.opt('dateAlignment'); + var runningCount = 0; + var start = date.clone(); + var end; + if (customAlignment) { + start.startOf(customAlignment); + } + start.startOf('day'); + start = this._view.skipHiddenDays(start, direction); + end = start.clone(); + do { + end.add(1, 'day'); + if (!this._view.isHiddenDay(end)) { + runningCount++; + } + } while (runningCount < dayCount); + return new UnzonedRange_1.default(start, end); + }; + // Builds a normalized range object for the "visible" range, + // which is a way to define the currentUnzonedRange and activeUnzonedRange at the same time. + // TODO: accept a MS-time instead of a moment `date`? + DateProfileGenerator.prototype.buildCustomVisibleRange = function (date) { + var visibleUnzonedRange = this._view.getUnzonedRangeOption('visibleRange', this._view.calendar.applyTimezone(date) // correct zone. also generates new obj that avoids mutations + ); + if (visibleUnzonedRange && (visibleUnzonedRange.startMs == null || visibleUnzonedRange.endMs == null)) { + return null; + } + return visibleUnzonedRange; + }; + // Computes the range that will represent the element/cells for *rendering*, + // but which may have voided days/times. + // not responsible for trimming hidden days. + DateProfileGenerator.prototype.buildRenderRange = function (currentUnzonedRange, currentRangeUnit, isRangeAllDay) { + return currentUnzonedRange.clone(); + }; + // Compute the duration value that should be added/substracted to the current date + // when a prev/next operation happens. + DateProfileGenerator.prototype.buildDateIncrement = function (fallback) { + var dateIncrementInput = this.opt('dateIncrement'); + var customAlignment; + if (dateIncrementInput) { + return moment.duration(dateIncrementInput); + } + else if ((customAlignment = this.opt('dateAlignment'))) { + return moment.duration(1, customAlignment); + } + else if (fallback) { + return fallback; + } + else { + return moment.duration({ days: 1 }); + } + }; + return DateProfileGenerator; +}()); +exports.default = DateProfileGenerator; + + +/***/ }), +/* 222 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var moment = __webpack_require__(0); +var exportHooks = __webpack_require__(16); +var util_1 = __webpack_require__(4); +var moment_ext_1 = __webpack_require__(10); +var ListenerMixin_1 = __webpack_require__(7); +var HitDragListener_1 = __webpack_require__(23); +var SingleEventDef_1 = __webpack_require__(13); +var EventInstanceGroup_1 = __webpack_require__(18); +var EventSource_1 = __webpack_require__(6); +var Interaction_1 = __webpack_require__(15); +var ExternalDropping = /** @class */ (function (_super) { + tslib_1.__extends(ExternalDropping, _super); + function ExternalDropping() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.isDragging = false; // jqui-dragging an external element? boolean + return _this; + } + /* + component impements: + - eventRangesToEventFootprints + - isEventInstanceGroupAllowed + - isExternalInstanceGroupAllowed + - renderDrag + - unrenderDrag + */ + ExternalDropping.prototype.end = function () { + if (this.dragListener) { + this.dragListener.endInteraction(); + } + }; + ExternalDropping.prototype.bindToDocument = function () { + this.listenTo($(document), { + dragstart: this.handleDragStart, + sortstart: this.handleDragStart // jqui + }); + }; + ExternalDropping.prototype.unbindFromDocument = function () { + this.stopListeningTo($(document)); + }; + // Called when a jQuery UI drag is initiated anywhere in the DOM + ExternalDropping.prototype.handleDragStart = function (ev, ui) { + var el; + var accept; + if (this.opt('droppable')) { + el = $((ui ? ui.item : null) || ev.target); + // Test that the dragged element passes the dropAccept selector or filter function. + // FYI, the default is "*" (matches all) + accept = this.opt('dropAccept'); + if ($.isFunction(accept) ? accept.call(el[0], el) : el.is(accept)) { + if (!this.isDragging) { + this.listenToExternalDrag(el, ev, ui); + } + } + } + }; + // Called when a jQuery UI drag starts and it needs to be monitored for dropping + ExternalDropping.prototype.listenToExternalDrag = function (el, ev, ui) { + var _this = this; + var component = this.component; + var view = this.view; + var meta = getDraggedElMeta(el); // extra data about event drop, including possible event to create + var singleEventDef; // a null value signals an unsuccessful drag + // listener that tracks mouse movement over date-associated pixel regions + var dragListener = this.dragListener = new HitDragListener_1.default(component, { + interactionStart: function () { + _this.isDragging = true; + }, + hitOver: function (hit) { + var isAllowed = true; + var hitFootprint = hit.component.getSafeHitFootprint(hit); // hit might not belong to this grid + var mutatedEventInstanceGroup; + if (hitFootprint) { + singleEventDef = _this.computeExternalDrop(hitFootprint, meta); + if (singleEventDef) { + mutatedEventInstanceGroup = new EventInstanceGroup_1.default(singleEventDef.buildInstances()); + isAllowed = meta.eventProps ? // isEvent? + component.isEventInstanceGroupAllowed(mutatedEventInstanceGroup) : + component.isExternalInstanceGroupAllowed(mutatedEventInstanceGroup); + } + else { + isAllowed = false; + } + } + else { + isAllowed = false; + } + if (!isAllowed) { + singleEventDef = null; + util_1.disableCursor(); + } + if (singleEventDef) { + component.renderDrag(// called without a seg parameter + component.eventRangesToEventFootprints(mutatedEventInstanceGroup.sliceRenderRanges(component.dateProfile.renderUnzonedRange, view.calendar))); + } + }, + hitOut: function () { + singleEventDef = null; // signal unsuccessful + }, + hitDone: function () { + util_1.enableCursor(); + component.unrenderDrag(); + }, + interactionEnd: function (ev) { + if (singleEventDef) { + view.reportExternalDrop(singleEventDef, Boolean(meta.eventProps), // isEvent + Boolean(meta.stick), // isSticky + el, ev, ui); + } + _this.isDragging = false; + _this.dragListener = null; + } + }); + dragListener.startDrag(ev); // start listening immediately + }; + // Given a hit to be dropped upon, and misc data associated with the jqui drag (guaranteed to be a plain object), + // returns the zoned start/end dates for the event that would result from the hypothetical drop. end might be null. + // Returning a null value signals an invalid drop hit. + // DOES NOT consider overlap/constraint. + // Assumes both footprints are non-open-ended. + ExternalDropping.prototype.computeExternalDrop = function (componentFootprint, meta) { + var calendar = this.view.calendar; + var start = moment_ext_1.default.utc(componentFootprint.unzonedRange.startMs).stripZone(); + var end; + var eventDef; + if (componentFootprint.isAllDay) { + // if dropped on an all-day span, and element's metadata specified a time, set it + if (meta.startTime) { + start.time(meta.startTime); + } + else { + start.stripTime(); + } + } + if (meta.duration) { + end = start.clone().add(meta.duration); + } + start = calendar.applyTimezone(start); + if (end) { + end = calendar.applyTimezone(end); + } + eventDef = SingleEventDef_1.default.parse($.extend({}, meta.eventProps, { + start: start, + end: end + }), new EventSource_1.default(calendar)); + return eventDef; + }; + return ExternalDropping; +}(Interaction_1.default)); +exports.default = ExternalDropping; +ListenerMixin_1.default.mixInto(ExternalDropping); +/* External-Dragging-Element Data +----------------------------------------------------------------------------------------------------------------------*/ +// Require all HTML5 data-* attributes used by FullCalendar to have this prefix. +// A value of '' will query attributes like data-event. A value of 'fc' will query attributes like data-fc-event. +exportHooks.dataAttrPrefix = ''; +// Given a jQuery element that might represent a dragged FullCalendar event, returns an intermediate data structure +// to be used for Event Object creation. +// A defined `.eventProps`, even when empty, indicates that an event should be created. +function getDraggedElMeta(el) { + var prefix = exportHooks.dataAttrPrefix; + var eventProps; // properties for creating the event, not related to date/time + var startTime; // a Duration + var duration; + var stick; + if (prefix) { + prefix += '-'; + } + eventProps = el.data(prefix + 'event') || null; + if (eventProps) { + if (typeof eventProps === 'object') { + eventProps = $.extend({}, eventProps); // make a copy + } + else { + eventProps = {}; + } + // pluck special-cased date/time properties + startTime = eventProps.start; + if (startTime == null) { + startTime = eventProps.time; + } // accept 'time' as well + duration = eventProps.duration; + stick = eventProps.stick; + delete eventProps.start; + delete eventProps.time; + delete eventProps.duration; + delete eventProps.stick; + } + // fallback to standalone attribute values for each of the date/time properties + if (startTime == null) { + startTime = el.data(prefix + 'start'); + } + if (startTime == null) { + startTime = el.data(prefix + 'time'); + } // accept 'time' as well + if (duration == null) { + duration = el.data(prefix + 'duration'); + } + if (stick == null) { + stick = el.data(prefix + 'stick'); + } + // massage into correct data types + startTime = startTime != null ? moment.duration(startTime) : null; + duration = duration != null ? moment.duration(duration) : null; + stick = Boolean(stick); + return { eventProps: eventProps, startTime: startTime, duration: duration, stick: stick }; +} + + +/***/ }), +/* 223 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var EventDefMutation_1 = __webpack_require__(37); +var EventDefDateMutation_1 = __webpack_require__(50); +var HitDragListener_1 = __webpack_require__(23); +var Interaction_1 = __webpack_require__(15); +var EventResizing = /** @class */ (function (_super) { + tslib_1.__extends(EventResizing, _super); + /* + component impements: + - bindSegHandlerToEl + - publiclyTrigger + - diffDates + - eventRangesToEventFootprints + - isEventInstanceGroupAllowed + - getSafeHitFootprint + */ + function EventResizing(component, eventPointing) { + var _this = _super.call(this, component) || this; + _this.isResizing = false; + _this.eventPointing = eventPointing; + return _this; + } + EventResizing.prototype.end = function () { + if (this.dragListener) { + this.dragListener.endInteraction(); + } + }; + EventResizing.prototype.bindToEl = function (el) { + var component = this.component; + component.bindSegHandlerToEl(el, 'mousedown', this.handleMouseDown.bind(this)); + component.bindSegHandlerToEl(el, 'touchstart', this.handleTouchStart.bind(this)); + }; + EventResizing.prototype.handleMouseDown = function (seg, ev) { + if (this.component.canStartResize(seg, ev)) { + this.buildDragListener(seg, $(ev.target).is('.fc-start-resizer')) + .startInteraction(ev, { distance: 5 }); + } + }; + EventResizing.prototype.handleTouchStart = function (seg, ev) { + if (this.component.canStartResize(seg, ev)) { + this.buildDragListener(seg, $(ev.target).is('.fc-start-resizer')) + .startInteraction(ev); + } + }; + // Creates a listener that tracks the user as they resize an event segment. + // Generic enough to work with any type of Grid. + EventResizing.prototype.buildDragListener = function (seg, isStart) { + var _this = this; + var component = this.component; + var view = this.view; + var calendar = view.calendar; + var eventManager = calendar.eventManager; + var el = seg.el; + var eventDef = seg.footprint.eventDef; + var eventInstance = seg.footprint.eventInstance; + var isDragging; + var resizeMutation; // zoned event date properties. falsy if invalid resize + // Tracks mouse movement over the *grid's* coordinate map + var dragListener = this.dragListener = new HitDragListener_1.default(component, { + scroll: this.opt('dragScroll'), + subjectEl: el, + interactionStart: function () { + isDragging = false; + }, + dragStart: function (ev) { + isDragging = true; + // ensure a mouseout on the manipulated event has been reported + _this.eventPointing.handleMouseout(seg, ev); + _this.segResizeStart(seg, ev); + }, + hitOver: function (hit, isOrig, origHit) { + var isAllowed = true; + var origHitFootprint = component.getSafeHitFootprint(origHit); + var hitFootprint = component.getSafeHitFootprint(hit); + var mutatedEventInstanceGroup; + if (origHitFootprint && hitFootprint) { + resizeMutation = isStart ? + _this.computeEventStartResizeMutation(origHitFootprint, hitFootprint, seg.footprint) : + _this.computeEventEndResizeMutation(origHitFootprint, hitFootprint, seg.footprint); + if (resizeMutation) { + mutatedEventInstanceGroup = eventManager.buildMutatedEventInstanceGroup(eventDef.id, resizeMutation); + isAllowed = component.isEventInstanceGroupAllowed(mutatedEventInstanceGroup); + } + else { + isAllowed = false; + } + } + else { + isAllowed = false; + } + if (!isAllowed) { + resizeMutation = null; + util_1.disableCursor(); + } + else if (resizeMutation.isEmpty()) { + // no change. (FYI, event dates might have zones) + resizeMutation = null; + } + if (resizeMutation) { + view.hideEventsWithId(seg.footprint.eventDef.id); + view.renderEventResize(component.eventRangesToEventFootprints(mutatedEventInstanceGroup.sliceRenderRanges(component.dateProfile.renderUnzonedRange, calendar)), seg); + } + }, + hitOut: function () { + resizeMutation = null; + }, + hitDone: function () { + view.unrenderEventResize(seg); + view.showEventsWithId(seg.footprint.eventDef.id); + util_1.enableCursor(); + }, + interactionEnd: function (ev) { + if (isDragging) { + _this.segResizeStop(seg, ev); + } + if (resizeMutation) { + // no need to re-show original, will rerender all anyways. esp important if eventRenderWait + view.reportEventResize(eventInstance, resizeMutation, el, ev); + } + _this.dragListener = null; + } + }); + return dragListener; + }; + // Called before event segment resizing starts + EventResizing.prototype.segResizeStart = function (seg, ev) { + this.isResizing = true; + this.component.publiclyTrigger('eventResizeStart', { + context: seg.el[0], + args: [ + seg.footprint.getEventLegacy(), + ev, + {}, + this.view + ] + }); + }; + // Called after event segment resizing stops + EventResizing.prototype.segResizeStop = function (seg, ev) { + this.isResizing = false; + this.component.publiclyTrigger('eventResizeStop', { + context: seg.el[0], + args: [ + seg.footprint.getEventLegacy(), + ev, + {}, + this.view + ] + }); + }; + // Returns new date-information for an event segment being resized from its start + EventResizing.prototype.computeEventStartResizeMutation = function (startFootprint, endFootprint, origEventFootprint) { + var origRange = origEventFootprint.componentFootprint.unzonedRange; + var startDelta = this.component.diffDates(endFootprint.unzonedRange.getStart(), startFootprint.unzonedRange.getStart()); + var dateMutation; + var eventDefMutation; + if (origRange.getStart().add(startDelta) < origRange.getEnd()) { + dateMutation = new EventDefDateMutation_1.default(); + dateMutation.setStartDelta(startDelta); + eventDefMutation = new EventDefMutation_1.default(); + eventDefMutation.setDateMutation(dateMutation); + return eventDefMutation; + } + return false; + }; + // Returns new date-information for an event segment being resized from its end + EventResizing.prototype.computeEventEndResizeMutation = function (startFootprint, endFootprint, origEventFootprint) { + var origRange = origEventFootprint.componentFootprint.unzonedRange; + var endDelta = this.component.diffDates(endFootprint.unzonedRange.getEnd(), startFootprint.unzonedRange.getEnd()); + var dateMutation; + var eventDefMutation; + if (origRange.getEnd().add(endDelta) > origRange.getStart()) { + dateMutation = new EventDefDateMutation_1.default(); + dateMutation.setEndDelta(endDelta); + eventDefMutation = new EventDefMutation_1.default(); + eventDefMutation.setDateMutation(dateMutation); + return eventDefMutation; + } + return false; + }; + return EventResizing; +}(Interaction_1.default)); +exports.default = EventResizing; + + +/***/ }), +/* 224 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var util_1 = __webpack_require__(4); +var EventDefMutation_1 = __webpack_require__(37); +var EventDefDateMutation_1 = __webpack_require__(50); +var DragListener_1 = __webpack_require__(54); +var HitDragListener_1 = __webpack_require__(23); +var MouseFollower_1 = __webpack_require__(244); +var Interaction_1 = __webpack_require__(15); +var EventDragging = /** @class */ (function (_super) { + tslib_1.__extends(EventDragging, _super); + /* + component implements: + - bindSegHandlerToEl + - publiclyTrigger + - diffDates + - eventRangesToEventFootprints + - isEventInstanceGroupAllowed + */ + function EventDragging(component, eventPointing) { + var _this = _super.call(this, component) || this; + _this.isDragging = false; + _this.eventPointing = eventPointing; + return _this; + } + EventDragging.prototype.end = function () { + if (this.dragListener) { + this.dragListener.endInteraction(); + } + }; + EventDragging.prototype.getSelectionDelay = function () { + var delay = this.opt('eventLongPressDelay'); + if (delay == null) { + delay = this.opt('longPressDelay'); // fallback + } + return delay; + }; + EventDragging.prototype.bindToEl = function (el) { + var component = this.component; + component.bindSegHandlerToEl(el, 'mousedown', this.handleMousedown.bind(this)); + component.bindSegHandlerToEl(el, 'touchstart', this.handleTouchStart.bind(this)); + }; + EventDragging.prototype.handleMousedown = function (seg, ev) { + if (!this.component.shouldIgnoreMouse() && + this.component.canStartDrag(seg, ev)) { + this.buildDragListener(seg).startInteraction(ev, { distance: 5 }); + } + }; + EventDragging.prototype.handleTouchStart = function (seg, ev) { + var component = this.component; + var settings = { + delay: this.view.isEventDefSelected(seg.footprint.eventDef) ? // already selected? + 0 : this.getSelectionDelay() + }; + if (component.canStartDrag(seg, ev)) { + this.buildDragListener(seg).startInteraction(ev, settings); + } + else if (component.canStartSelection(seg, ev)) { + this.buildSelectListener(seg).startInteraction(ev, settings); + } + }; + // seg isn't draggable, but let's use a generic DragListener + // simply for the delay, so it can be selected. + // Has side effect of setting/unsetting `dragListener` + EventDragging.prototype.buildSelectListener = function (seg) { + var _this = this; + var view = this.view; + var eventDef = seg.footprint.eventDef; + var eventInstance = seg.footprint.eventInstance; // null for inverse-background events + if (this.dragListener) { + return this.dragListener; + } + var dragListener = this.dragListener = new DragListener_1.default({ + dragStart: function (ev) { + if (dragListener.isTouch && + !view.isEventDefSelected(eventDef) && + eventInstance) { + // if not previously selected, will fire after a delay. then, select the event + view.selectEventInstance(eventInstance); + } + }, + interactionEnd: function (ev) { + _this.dragListener = null; + } + }); + return dragListener; + }; + // Builds a listener that will track user-dragging on an event segment. + // Generic enough to work with any type of Grid. + // Has side effect of setting/unsetting `dragListener` + EventDragging.prototype.buildDragListener = function (seg) { + var _this = this; + var component = this.component; + var view = this.view; + var calendar = view.calendar; + var eventManager = calendar.eventManager; + var el = seg.el; + var eventDef = seg.footprint.eventDef; + var eventInstance = seg.footprint.eventInstance; // null for inverse-background events + var isDragging; + var mouseFollower; // A clone of the original element that will move with the mouse + var eventDefMutation; + if (this.dragListener) { + return this.dragListener; + } + // Tracks mouse movement over the *view's* coordinate map. Allows dragging and dropping between subcomponents + // of the view. + var dragListener = this.dragListener = new HitDragListener_1.default(view, { + scroll: this.opt('dragScroll'), + subjectEl: el, + subjectCenter: true, + interactionStart: function (ev) { + seg.component = component; // for renderDrag + isDragging = false; + mouseFollower = new MouseFollower_1.default(seg.el, { + additionalClass: 'fc-dragging', + parentEl: view.el, + opacity: dragListener.isTouch ? null : _this.opt('dragOpacity'), + revertDuration: _this.opt('dragRevertDuration'), + zIndex: 2 // one above the .fc-view + }); + mouseFollower.hide(); // don't show until we know this is a real drag + mouseFollower.start(ev); + }, + dragStart: function (ev) { + if (dragListener.isTouch && + !view.isEventDefSelected(eventDef) && + eventInstance) { + // if not previously selected, will fire after a delay. then, select the event + view.selectEventInstance(eventInstance); + } + isDragging = true; + // ensure a mouseout on the manipulated event has been reported + _this.eventPointing.handleMouseout(seg, ev); + _this.segDragStart(seg, ev); + view.hideEventsWithId(seg.footprint.eventDef.id); + }, + hitOver: function (hit, isOrig, origHit) { + var isAllowed = true; + var origFootprint; + var footprint; + var mutatedEventInstanceGroup; + // starting hit could be forced (DayGrid.limit) + if (seg.hit) { + origHit = seg.hit; + } + // hit might not belong to this grid, so query origin grid + origFootprint = origHit.component.getSafeHitFootprint(origHit); + footprint = hit.component.getSafeHitFootprint(hit); + if (origFootprint && footprint) { + eventDefMutation = _this.computeEventDropMutation(origFootprint, footprint, eventDef); + if (eventDefMutation) { + mutatedEventInstanceGroup = eventManager.buildMutatedEventInstanceGroup(eventDef.id, eventDefMutation); + isAllowed = component.isEventInstanceGroupAllowed(mutatedEventInstanceGroup); + } + else { + isAllowed = false; + } + } + else { + isAllowed = false; + } + if (!isAllowed) { + eventDefMutation = null; + util_1.disableCursor(); + } + // if a valid drop location, have the subclass render a visual indication + if (eventDefMutation && + view.renderDrag(// truthy if rendered something + component.eventRangesToEventFootprints(mutatedEventInstanceGroup.sliceRenderRanges(component.dateProfile.renderUnzonedRange, calendar)), seg, dragListener.isTouch)) { + mouseFollower.hide(); // if the subclass is already using a mock event "helper", hide our own + } + else { + mouseFollower.show(); // otherwise, have the helper follow the mouse (no snapping) + } + if (isOrig) { + // needs to have moved hits to be a valid drop + eventDefMutation = null; + } + }, + hitOut: function () { + view.unrenderDrag(seg); // unrender whatever was done in renderDrag + mouseFollower.show(); // show in case we are moving out of all hits + eventDefMutation = null; + }, + hitDone: function () { + util_1.enableCursor(); + }, + interactionEnd: function (ev) { + delete seg.component; // prevent side effects + // do revert animation if hasn't changed. calls a callback when finished (whether animation or not) + mouseFollower.stop(!eventDefMutation, function () { + if (isDragging) { + view.unrenderDrag(seg); + _this.segDragStop(seg, ev); + } + view.showEventsWithId(seg.footprint.eventDef.id); + if (eventDefMutation) { + // no need to re-show original, will rerender all anyways. esp important if eventRenderWait + view.reportEventDrop(eventInstance, eventDefMutation, el, ev); + } + }); + _this.dragListener = null; + } + }); + return dragListener; + }; + // Called before event segment dragging starts + EventDragging.prototype.segDragStart = function (seg, ev) { + this.isDragging = true; + this.component.publiclyTrigger('eventDragStart', { + context: seg.el[0], + args: [ + seg.footprint.getEventLegacy(), + ev, + {}, + this.view + ] + }); + }; + // Called after event segment dragging stops + EventDragging.prototype.segDragStop = function (seg, ev) { + this.isDragging = false; + this.component.publiclyTrigger('eventDragStop', { + context: seg.el[0], + args: [ + seg.footprint.getEventLegacy(), + ev, + {}, + this.view + ] + }); + }; + // DOES NOT consider overlap/constraint + EventDragging.prototype.computeEventDropMutation = function (startFootprint, endFootprint, eventDef) { + var eventDefMutation = new EventDefMutation_1.default(); + eventDefMutation.setDateMutation(this.computeEventDateMutation(startFootprint, endFootprint)); + return eventDefMutation; + }; + EventDragging.prototype.computeEventDateMutation = function (startFootprint, endFootprint) { + var date0 = startFootprint.unzonedRange.getStart(); + var date1 = endFootprint.unzonedRange.getStart(); + var clearEnd = false; + var forceTimed = false; + var forceAllDay = false; + var dateDelta; + var dateMutation; + if (startFootprint.isAllDay !== endFootprint.isAllDay) { + clearEnd = true; + if (endFootprint.isAllDay) { + forceAllDay = true; + date0.stripTime(); + } + else { + forceTimed = true; + } + } + dateDelta = this.component.diffDates(date1, date0); + dateMutation = new EventDefDateMutation_1.default(); + dateMutation.clearEnd = clearEnd; + dateMutation.forceTimed = forceTimed; + dateMutation.forceAllDay = forceAllDay; + dateMutation.setDateDelta(dateDelta); + return dateMutation; + }; + return EventDragging; +}(Interaction_1.default)); +exports.default = EventDragging; + + +/***/ }), +/* 225 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var util_1 = __webpack_require__(4); +var HitDragListener_1 = __webpack_require__(23); +var ComponentFootprint_1 = __webpack_require__(12); +var UnzonedRange_1 = __webpack_require__(5); +var Interaction_1 = __webpack_require__(15); +var DateSelecting = /** @class */ (function (_super) { + tslib_1.__extends(DateSelecting, _super); + /* + component must implement: + - bindDateHandlerToEl + - getSafeHitFootprint + - renderHighlight + - unrenderHighlight + */ + function DateSelecting(component) { + var _this = _super.call(this, component) || this; + _this.dragListener = _this.buildDragListener(); + return _this; + } + DateSelecting.prototype.end = function () { + this.dragListener.endInteraction(); + }; + DateSelecting.prototype.getDelay = function () { + var delay = this.opt('selectLongPressDelay'); + if (delay == null) { + delay = this.opt('longPressDelay'); // fallback + } + return delay; + }; + DateSelecting.prototype.bindToEl = function (el) { + var _this = this; + var component = this.component; + var dragListener = this.dragListener; + component.bindDateHandlerToEl(el, 'mousedown', function (ev) { + if (_this.opt('selectable') && !component.shouldIgnoreMouse()) { + dragListener.startInteraction(ev, { + distance: _this.opt('selectMinDistance') + }); + } + }); + component.bindDateHandlerToEl(el, 'touchstart', function (ev) { + if (_this.opt('selectable') && !component.shouldIgnoreTouch()) { + dragListener.startInteraction(ev, { + delay: _this.getDelay() + }); + } + }); + util_1.preventSelection(el); + }; + // Creates a listener that tracks the user's drag across day elements, for day selecting. + DateSelecting.prototype.buildDragListener = function () { + var _this = this; + var component = this.component; + var selectionFootprint; // null if invalid selection + var dragListener = new HitDragListener_1.default(component, { + scroll: this.opt('dragScroll'), + interactionStart: function () { + selectionFootprint = null; + }, + dragStart: function (ev) { + _this.view.unselect(ev); // since we could be rendering a new selection, we want to clear any old one + }, + hitOver: function (hit, isOrig, origHit) { + var origHitFootprint; + var hitFootprint; + if (origHit) { + origHitFootprint = component.getSafeHitFootprint(origHit); + hitFootprint = component.getSafeHitFootprint(hit); + if (origHitFootprint && hitFootprint) { + selectionFootprint = _this.computeSelection(origHitFootprint, hitFootprint); + } + else { + selectionFootprint = null; + } + if (selectionFootprint) { + component.renderSelectionFootprint(selectionFootprint); + } + else if (selectionFootprint === false) { + util_1.disableCursor(); + } + } + }, + hitOut: function () { + selectionFootprint = null; + component.unrenderSelection(); + }, + hitDone: function () { + util_1.enableCursor(); + }, + interactionEnd: function (ev, isCancelled) { + if (!isCancelled && selectionFootprint) { + // the selection will already have been rendered. just report it + _this.view.reportSelection(selectionFootprint, ev); + } + } + }); + return dragListener; + }; + // Given the first and last date-spans of a selection, returns another date-span object. + // Subclasses can override and provide additional data in the span object. Will be passed to renderSelectionFootprint(). + // Will return false if the selection is invalid and this should be indicated to the user. + // Will return null/undefined if a selection invalid but no error should be reported. + DateSelecting.prototype.computeSelection = function (footprint0, footprint1) { + var wholeFootprint = this.computeSelectionFootprint(footprint0, footprint1); + if (wholeFootprint && !this.isSelectionFootprintAllowed(wholeFootprint)) { + return false; + } + return wholeFootprint; + }; + // Given two spans, must return the combination of the two. + // TODO: do this separation of concerns (combining VS validation) for event dnd/resize too. + // Assumes both footprints are non-open-ended. + DateSelecting.prototype.computeSelectionFootprint = function (footprint0, footprint1) { + var ms = [ + footprint0.unzonedRange.startMs, + footprint0.unzonedRange.endMs, + footprint1.unzonedRange.startMs, + footprint1.unzonedRange.endMs + ]; + ms.sort(util_1.compareNumbers); + return new ComponentFootprint_1.default(new UnzonedRange_1.default(ms[0], ms[3]), footprint0.isAllDay); + }; + DateSelecting.prototype.isSelectionFootprintAllowed = function (componentFootprint) { + return this.component.dateProfile.validUnzonedRange.containsRange(componentFootprint.unzonedRange) && + this.view.calendar.constraints.isSelectionFootprintAllowed(componentFootprint); + }; + return DateSelecting; +}(Interaction_1.default)); +exports.default = DateSelecting; + + +/***/ }), +/* 226 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var moment = __webpack_require__(0); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var Scroller_1 = __webpack_require__(39); +var View_1 = __webpack_require__(41); +var TimeGrid_1 = __webpack_require__(227); +var DayGrid_1 = __webpack_require__(61); +var AGENDA_ALL_DAY_EVENT_LIMIT = 5; +var agendaTimeGridMethods; +var agendaDayGridMethods; +/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically. +----------------------------------------------------------------------------------------------------------------------*/ +// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on). +// Responsible for managing width/height. +var AgendaView = /** @class */ (function (_super) { + tslib_1.__extends(AgendaView, _super); + function AgendaView(calendar, viewSpec) { + var _this = _super.call(this, calendar, viewSpec) || this; + _this.usesMinMaxTime = true; // indicates that minTime/maxTime affects rendering + _this.timeGrid = _this.instantiateTimeGrid(); + _this.addChild(_this.timeGrid); + if (_this.opt('allDaySlot')) { + _this.dayGrid = _this.instantiateDayGrid(); // the all-day subcomponent of this view + _this.addChild(_this.dayGrid); + } + _this.scroller = new Scroller_1.default({ + overflowX: 'hidden', + overflowY: 'auto' + }); + return _this; + } + // Instantiates the TimeGrid object this view needs. Draws from this.timeGridClass + AgendaView.prototype.instantiateTimeGrid = function () { + var timeGrid = new this.timeGridClass(this); + util_1.copyOwnProps(agendaTimeGridMethods, timeGrid); + return timeGrid; + }; + // Instantiates the DayGrid object this view might need. Draws from this.dayGridClass + AgendaView.prototype.instantiateDayGrid = function () { + var dayGrid = new this.dayGridClass(this); + util_1.copyOwnProps(agendaDayGridMethods, dayGrid); + return dayGrid; + }; + /* Rendering + ------------------------------------------------------------------------------------------------------------------*/ + AgendaView.prototype.renderSkeleton = function () { + var timeGridWrapEl; + var timeGridEl; + this.el.addClass('fc-agenda-view').html(this.renderSkeletonHtml()); + this.scroller.render(); + timeGridWrapEl = this.scroller.el.addClass('fc-time-grid-container'); + timeGridEl = $('').appendTo(timeGridWrapEl); + this.el.find('.fc-body > tr > td').append(timeGridWrapEl); + this.timeGrid.headContainerEl = this.el.find('.fc-head-container'); + this.timeGrid.setElement(timeGridEl); + if (this.dayGrid) { + this.dayGrid.setElement(this.el.find('.fc-day-grid')); + // have the day-grid extend it's coordinate area over the dividing the two grids + this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight(); + } + }; + AgendaView.prototype.unrenderSkeleton = function () { + this.timeGrid.removeElement(); + if (this.dayGrid) { + this.dayGrid.removeElement(); + } + this.scroller.destroy(); + }; + // Builds the HTML skeleton for the view. + // The day-grid and time-grid components will render inside containers defined by this HTML. + AgendaView.prototype.renderSkeletonHtml = function () { + var theme = this.calendar.theme; + return '' + + '' + + (this.opt('columnHeader') ? + '' + + '' + + ' ' + + '' + + '' : + '') + + '' + + '' + + '' + + (this.dayGrid ? + '' + + '' : + '') + + '' + + '' + + '' + + ''; + }; + // Generates an HTML attribute string for setting the width of the axis, if it is known + AgendaView.prototype.axisStyleAttr = function () { + if (this.axisWidth != null) { + return 'style="width:' + this.axisWidth + 'px"'; + } + return ''; + }; + /* Now Indicator + ------------------------------------------------------------------------------------------------------------------*/ + AgendaView.prototype.getNowIndicatorUnit = function () { + return this.timeGrid.getNowIndicatorUnit(); + }; + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + // Adjusts the vertical dimensions of the view to the specified values + AgendaView.prototype.updateSize = function (totalHeight, isAuto, isResize) { + var eventLimit; + var scrollerHeight; + var scrollbarWidths; + _super.prototype.updateSize.call(this, totalHeight, isAuto, isResize); + // make all axis cells line up, and record the width so newly created axis cells will have it + this.axisWidth = util_1.matchCellWidths(this.el.find('.fc-axis')); + // hack to give the view some height prior to timeGrid's columns being rendered + // TODO: separate setting height from scroller VS timeGrid. + if (!this.timeGrid.colEls) { + if (!isAuto) { + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.scroller.setHeight(scrollerHeight); + } + return; + } + // set of fake row elements that must compensate when scroller has scrollbars + var noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); + // reset all dimensions back to the original state + this.timeGrid.bottomRuleEl.hide(); // .show() will be called later if this is necessary + this.scroller.clear(); // sets height to 'auto' and clears overflow + util_1.uncompensateScroll(noScrollRowEls); + // limit number of events in the all-day area + if (this.dayGrid) { + this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed + eventLimit = this.opt('eventLimit'); + if (eventLimit && typeof eventLimit !== 'number') { + eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number + } + if (eventLimit) { + this.dayGrid.limitRows(eventLimit); + } + } + if (!isAuto) { + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.scroller.setHeight(scrollerHeight); + scrollbarWidths = this.scroller.getScrollbarWidths(); + if (scrollbarWidths.left || scrollbarWidths.right) { + // make the all-day and header rows lines up + util_1.compensateScroll(noScrollRowEls, scrollbarWidths); + // the scrollbar compensation might have changed text flow, which might affect height, so recalculate + // and reapply the desired height to the scroller. + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.scroller.setHeight(scrollerHeight); + } + // guarantees the same scrollbar widths + this.scroller.lockOverflow(scrollbarWidths); + // if there's any space below the slats, show the horizontal rule. + // this won't cause any new overflow, because lockOverflow already called. + if (this.timeGrid.getTotalSlatHeight() < scrollerHeight) { + this.timeGrid.bottomRuleEl.show(); + } + } + }; + // given a desired total height of the view, returns what the height of the scroller should be + AgendaView.prototype.computeScrollerHeight = function (totalHeight) { + return totalHeight - + util_1.subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller + }; + /* Scroll + ------------------------------------------------------------------------------------------------------------------*/ + // Computes the initial pre-configured scroll state prior to allowing the user to change it + AgendaView.prototype.computeInitialDateScroll = function () { + var scrollTime = moment.duration(this.opt('scrollTime')); + var top = this.timeGrid.computeTimeTop(scrollTime); + // zoom can give weird floating-point values. rather scroll a little bit further + top = Math.ceil(top); + if (top) { + top++; // to overcome top border that slots beyond the first have. looks better + } + return { top: top }; + }; + AgendaView.prototype.queryDateScroll = function () { + return { top: this.scroller.getScrollTop() }; + }; + AgendaView.prototype.applyDateScroll = function (scroll) { + if (scroll.top !== undefined) { + this.scroller.setScrollTop(scroll.top); + } + }; + /* Hit Areas + ------------------------------------------------------------------------------------------------------------------*/ + // forward all hit-related method calls to the grids (dayGrid might not be defined) + AgendaView.prototype.getHitFootprint = function (hit) { + // TODO: hit.component is set as a hack to identify where the hit came from + return hit.component.getHitFootprint(hit); + }; + AgendaView.prototype.getHitEl = function (hit) { + // TODO: hit.component is set as a hack to identify where the hit came from + return hit.component.getHitEl(hit); + }; + /* Event Rendering + ------------------------------------------------------------------------------------------------------------------*/ + AgendaView.prototype.executeEventRender = function (eventsPayload) { + var dayEventsPayload = {}; + var timedEventsPayload = {}; + var id; + var eventInstanceGroup; + // separate the events into all-day and timed + for (id in eventsPayload) { + eventInstanceGroup = eventsPayload[id]; + if (eventInstanceGroup.getEventDef().isAllDay()) { + dayEventsPayload[id] = eventInstanceGroup; + } + else { + timedEventsPayload[id] = eventInstanceGroup; + } + } + this.timeGrid.executeEventRender(timedEventsPayload); + if (this.dayGrid) { + this.dayGrid.executeEventRender(dayEventsPayload); + } + }; + /* Dragging/Resizing Routing + ------------------------------------------------------------------------------------------------------------------*/ + // A returned value of `true` signals that a mock "helper" event has been rendered. + AgendaView.prototype.renderDrag = function (eventFootprints, seg, isTouch) { + var groups = groupEventFootprintsByAllDay(eventFootprints); + var renderedHelper = false; + renderedHelper = this.timeGrid.renderDrag(groups.timed, seg, isTouch); + if (this.dayGrid) { + renderedHelper = this.dayGrid.renderDrag(groups.allDay, seg, isTouch) || renderedHelper; + } + return renderedHelper; + }; + AgendaView.prototype.renderEventResize = function (eventFootprints, seg, isTouch) { + var groups = groupEventFootprintsByAllDay(eventFootprints); + this.timeGrid.renderEventResize(groups.timed, seg, isTouch); + if (this.dayGrid) { + this.dayGrid.renderEventResize(groups.allDay, seg, isTouch); + } + }; + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + // Renders a visual indication of a selection + AgendaView.prototype.renderSelectionFootprint = function (componentFootprint) { + if (!componentFootprint.isAllDay) { + this.timeGrid.renderSelectionFootprint(componentFootprint); + } + else if (this.dayGrid) { + this.dayGrid.renderSelectionFootprint(componentFootprint); + } + }; + return AgendaView; +}(View_1.default)); +exports.default = AgendaView; +AgendaView.prototype.timeGridClass = TimeGrid_1.default; +AgendaView.prototype.dayGridClass = DayGrid_1.default; +// Will customize the rendering behavior of the AgendaView's timeGrid +agendaTimeGridMethods = { + // Generates the HTML that will go before the day-of week header cells + renderHeadIntroHtml: function () { + var view = this.view; + var calendar = view.calendar; + var weekStart = calendar.msToUtcMoment(this.dateProfile.renderUnzonedRange.startMs, true); + var weekText; + if (this.opt('weekNumbers')) { + weekText = weekStart.format(this.opt('smallWeekFormat')); + return '' + + '' + + view.buildGotoAnchorHtml(// aside from link, important for matchCellWidths + { date: weekStart, type: 'week', forceOff: this.colCnt > 1 }, util_1.htmlEscape(weekText) // inner HTML + ) + + ''; + } + else { + return ''; + } + }, + // Generates the HTML that goes before the bg of the TimeGrid slot area. Long vertical column. + renderBgIntroHtml: function () { + var view = this.view; + return ''; + }, + // Generates the HTML that goes before all other types of cells. + // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid. + renderIntroHtml: function () { + var view = this.view; + return ''; + } +}; +// Will customize the rendering behavior of the AgendaView's dayGrid +agendaDayGridMethods = { + // Generates the HTML that goes before the all-day cells + renderBgIntroHtml: function () { + var view = this.view; + return '' + + '' + + '' + // needed for matchCellWidths + view.getAllDayHtml() + + '' + + ''; + }, + // Generates the HTML that goes before all other types of cells. + // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid. + renderIntroHtml: function () { + var view = this.view; + return ''; + } +}; +function groupEventFootprintsByAllDay(eventFootprints) { + var allDay = []; + var timed = []; + var i; + for (i = 0; i < eventFootprints.length; i++) { + if (eventFootprints[i].componentFootprint.isAllDay) { + allDay.push(eventFootprints[i]); + } + else { + timed.push(eventFootprints[i]); + } + } + return { allDay: allDay, timed: timed }; +} + + +/***/ }), +/* 227 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var moment = __webpack_require__(0); +var util_1 = __webpack_require__(4); +var InteractiveDateComponent_1 = __webpack_require__(40); +var BusinessHourRenderer_1 = __webpack_require__(56); +var StandardInteractionsMixin_1 = __webpack_require__(60); +var DayTableMixin_1 = __webpack_require__(55); +var CoordCache_1 = __webpack_require__(53); +var UnzonedRange_1 = __webpack_require__(5); +var ComponentFootprint_1 = __webpack_require__(12); +var TimeGridEventRenderer_1 = __webpack_require__(246); +var TimeGridHelperRenderer_1 = __webpack_require__(247); +var TimeGridFillRenderer_1 = __webpack_require__(248); +/* A component that renders one or more columns of vertical time slots +----------------------------------------------------------------------------------------------------------------------*/ +// We mixin DayTable, even though there is only a single row of days +// potential nice values for the slot-duration and interval-duration +// from largest to smallest +var AGENDA_STOCK_SUB_DURATIONS = [ + { hours: 1 }, + { minutes: 30 }, + { minutes: 15 }, + { seconds: 30 }, + { seconds: 15 } +]; +var TimeGrid = /** @class */ (function (_super) { + tslib_1.__extends(TimeGrid, _super); + function TimeGrid(view) { + var _this = _super.call(this, view) || this; + _this.processOptions(); + return _this; + } + // Slices up the given span (unzoned start/end with other misc data) into an array of segments + TimeGrid.prototype.componentFootprintToSegs = function (componentFootprint) { + var segs = this.sliceRangeByTimes(componentFootprint.unzonedRange); + var i; + for (i = 0; i < segs.length; i++) { + if (this.isRTL) { + segs[i].col = this.daysPerRow - 1 - segs[i].dayIndex; + } + else { + segs[i].col = segs[i].dayIndex; + } + } + return segs; + }; + /* Date Handling + ------------------------------------------------------------------------------------------------------------------*/ + TimeGrid.prototype.sliceRangeByTimes = function (unzonedRange) { + var segs = []; + var segRange; + var dayIndex; + for (dayIndex = 0; dayIndex < this.daysPerRow; dayIndex++) { + segRange = unzonedRange.intersect(this.dayRanges[dayIndex]); + if (segRange) { + segs.push({ + startMs: segRange.startMs, + endMs: segRange.endMs, + isStart: segRange.isStart, + isEnd: segRange.isEnd, + dayIndex: dayIndex + }); + } + } + return segs; + }; + /* Options + ------------------------------------------------------------------------------------------------------------------*/ + // Parses various options into properties of this object + TimeGrid.prototype.processOptions = function () { + var slotDuration = this.opt('slotDuration'); + var snapDuration = this.opt('snapDuration'); + var input; + slotDuration = moment.duration(slotDuration); + snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration; + this.slotDuration = slotDuration; + this.snapDuration = snapDuration; + this.snapsPerSlot = slotDuration / snapDuration; // TODO: ensure an integer multiple? + // might be an array value (for TimelineView). + // if so, getting the most granular entry (the last one probably). + input = this.opt('slotLabelFormat'); + if ($.isArray(input)) { + input = input[input.length - 1]; + } + this.labelFormat = input || + this.opt('smallTimeFormat'); // the computed default + input = this.opt('slotLabelInterval'); + this.labelInterval = input ? + moment.duration(input) : + this.computeLabelInterval(slotDuration); + }; + // Computes an automatic value for slotLabelInterval + TimeGrid.prototype.computeLabelInterval = function (slotDuration) { + var i; + var labelInterval; + var slotsPerLabel; + // find the smallest stock label interval that results in more than one slots-per-label + for (i = AGENDA_STOCK_SUB_DURATIONS.length - 1; i >= 0; i--) { + labelInterval = moment.duration(AGENDA_STOCK_SUB_DURATIONS[i]); + slotsPerLabel = util_1.divideDurationByDuration(labelInterval, slotDuration); + if (util_1.isInt(slotsPerLabel) && slotsPerLabel > 1) { + return labelInterval; + } + } + return moment.duration(slotDuration); // fall back. clone + }; + /* Date Rendering + ------------------------------------------------------------------------------------------------------------------*/ + TimeGrid.prototype.renderDates = function (dateProfile) { + this.dateProfile = dateProfile; + this.updateDayTable(); + this.renderSlats(); + this.renderColumns(); + }; + TimeGrid.prototype.unrenderDates = function () { + // this.unrenderSlats(); // don't need this because repeated .html() calls clear + this.unrenderColumns(); + }; + TimeGrid.prototype.renderSkeleton = function () { + var theme = this.view.calendar.theme; + this.el.html('' + + '' + + ''); + this.bottomRuleEl = this.el.find('hr'); + }; + TimeGrid.prototype.renderSlats = function () { + var theme = this.view.calendar.theme; + this.slatContainerEl = this.el.find('> .fc-slats') + .html(// avoids needing ::unrenderSlats() + '' + + this.renderSlatRowHtml() + + ''); + this.slatEls = this.slatContainerEl.find('tr'); + this.slatCoordCache = new CoordCache_1.default({ + els: this.slatEls, + isVertical: true + }); + }; + // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. + TimeGrid.prototype.renderSlatRowHtml = function () { + var view = this.view; + var calendar = view.calendar; + var theme = calendar.theme; + var isRTL = this.isRTL; + var dateProfile = this.dateProfile; + var html = ''; + var slotTime = moment.duration(+dateProfile.minTime); // wish there was .clone() for durations + var slotIterator = moment.duration(0); + var slotDate; // will be on the view's first day, but we only care about its time + var isLabeled; + var axisHtml; + // Calculate the time for each slot + while (slotTime < dateProfile.maxTime) { + slotDate = calendar.msToUtcMoment(dateProfile.renderUnzonedRange.startMs).time(slotTime); + isLabeled = util_1.isInt(util_1.divideDurationByDuration(slotIterator, this.labelInterval)); + axisHtml = + '' + + (isLabeled ? + '' + // for matchCellWidths + util_1.htmlEscape(slotDate.format(this.labelFormat)) + + '' : + '') + + ''; + html += + '' + + (!isRTL ? axisHtml : '') + + '' + + (isRTL ? axisHtml : '') + + ''; + slotTime.add(this.slotDuration); + slotIterator.add(this.slotDuration); + } + return html; + }; + TimeGrid.prototype.renderColumns = function () { + var dateProfile = this.dateProfile; + var theme = this.view.calendar.theme; + this.dayRanges = this.dayDates.map(function (dayDate) { + return new UnzonedRange_1.default(dayDate.clone().add(dateProfile.minTime), dayDate.clone().add(dateProfile.maxTime)); + }); + if (this.headContainerEl) { + this.headContainerEl.html(this.renderHeadHtml()); + } + this.el.find('> .fc-bg').html('' + + this.renderBgTrHtml(0) + // row=0 + ''); + this.colEls = this.el.find('.fc-day, .fc-disabled-day'); + this.colCoordCache = new CoordCache_1.default({ + els: this.colEls, + isHorizontal: true + }); + this.renderContentSkeleton(); + }; + TimeGrid.prototype.unrenderColumns = function () { + this.unrenderContentSkeleton(); + }; + /* Content Skeleton + ------------------------------------------------------------------------------------------------------------------*/ + // Renders the DOM that the view's content will live in + TimeGrid.prototype.renderContentSkeleton = function () { + var cellHtml = ''; + var i; + var skeletonEl; + for (i = 0; i < this.colCnt; i++) { + cellHtml += + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + } + skeletonEl = this.contentSkeletonEl = $('' + + '' + + '' + cellHtml + '' + + '' + + ''); + this.colContainerEls = skeletonEl.find('.fc-content-col'); + this.helperContainerEls = skeletonEl.find('.fc-helper-container'); + this.fgContainerEls = skeletonEl.find('.fc-event-container:not(.fc-helper-container)'); + this.bgContainerEls = skeletonEl.find('.fc-bgevent-container'); + this.highlightContainerEls = skeletonEl.find('.fc-highlight-container'); + this.businessContainerEls = skeletonEl.find('.fc-business-container'); + this.bookendCells(skeletonEl.find('tr')); // TODO: do this on string level + this.el.append(skeletonEl); + }; + TimeGrid.prototype.unrenderContentSkeleton = function () { + if (this.contentSkeletonEl) { + this.contentSkeletonEl.remove(); + this.contentSkeletonEl = null; + this.colContainerEls = null; + this.helperContainerEls = null; + this.fgContainerEls = null; + this.bgContainerEls = null; + this.highlightContainerEls = null; + this.businessContainerEls = null; + } + }; + // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col + TimeGrid.prototype.groupSegsByCol = function (segs) { + var segsByCol = []; + var i; + for (i = 0; i < this.colCnt; i++) { + segsByCol.push([]); + } + for (i = 0; i < segs.length; i++) { + segsByCol[segs[i].col].push(segs[i]); + } + return segsByCol; + }; + // Given segments grouped by column, insert the segments' elements into a parallel array of container + // elements, each living within a column. + TimeGrid.prototype.attachSegsByCol = function (segsByCol, containerEls) { + var col; + var segs; + var i; + for (col = 0; col < this.colCnt; col++) { + segs = segsByCol[col]; + for (i = 0; i < segs.length; i++) { + containerEls.eq(col).append(segs[i].el); + } + } + }; + /* Now Indicator + ------------------------------------------------------------------------------------------------------------------*/ + TimeGrid.prototype.getNowIndicatorUnit = function () { + return 'minute'; // will refresh on the minute + }; + TimeGrid.prototype.renderNowIndicator = function (date) { + // HACK: if date columns not ready for some reason (scheduler) + if (!this.colContainerEls) { + return; + } + // seg system might be overkill, but it handles scenario where line needs to be rendered + // more than once because of columns with the same date (resources columns for example) + var segs = this.componentFootprintToSegs(new ComponentFootprint_1.default(new UnzonedRange_1.default(date, date.valueOf() + 1), // protect against null range + false // all-day + )); + var top = this.computeDateTop(date, date); + var nodes = []; + var i; + // render lines within the columns + for (i = 0; i < segs.length; i++) { + nodes.push($('') + .css('top', top) + .appendTo(this.colContainerEls.eq(segs[i].col))[0]); + } + // render an arrow over the axis + if (segs.length > 0) { + nodes.push($('') + .css('top', top) + .appendTo(this.el.find('.fc-content-skeleton'))[0]); + } + this.nowIndicatorEls = $(nodes); + }; + TimeGrid.prototype.unrenderNowIndicator = function () { + if (this.nowIndicatorEls) { + this.nowIndicatorEls.remove(); + this.nowIndicatorEls = null; + } + }; + /* Coordinates + ------------------------------------------------------------------------------------------------------------------*/ + TimeGrid.prototype.updateSize = function (totalHeight, isAuto, isResize) { + _super.prototype.updateSize.call(this, totalHeight, isAuto, isResize); + this.slatCoordCache.build(); + if (isResize) { + this.updateSegVerticals([].concat(this.eventRenderer.getSegs(), this.businessSegs || [])); + } + }; + TimeGrid.prototype.getTotalSlatHeight = function () { + return this.slatContainerEl.outerHeight(); + }; + // Computes the top coordinate, relative to the bounds of the grid, of the given date. + // `ms` can be a millisecond UTC time OR a UTC moment. + // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. + TimeGrid.prototype.computeDateTop = function (ms, startOfDayDate) { + return this.computeTimeTop(moment.duration(ms - startOfDayDate.clone().stripTime())); + }; + // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). + TimeGrid.prototype.computeTimeTop = function (time) { + var len = this.slatEls.length; + var dateProfile = this.dateProfile; + var slatCoverage = (time - dateProfile.minTime) / this.slotDuration; // floating-point value of # of slots covered + var slatIndex; + var slatRemainder; + // compute a floating-point number for how many slats should be progressed through. + // from 0 to number of slats (inclusive) + // constrained because minTime/maxTime might be customized. + slatCoverage = Math.max(0, slatCoverage); + slatCoverage = Math.min(len, slatCoverage); + // an integer index of the furthest whole slat + // from 0 to number slats (*exclusive*, so len-1) + slatIndex = Math.floor(slatCoverage); + slatIndex = Math.min(slatIndex, len - 1); + // how much further through the slatIndex slat (from 0.0-1.0) must be covered in addition. + // could be 1.0 if slatCoverage is covering *all* the slots + slatRemainder = slatCoverage - slatIndex; + return this.slatCoordCache.getTopPosition(slatIndex) + + this.slatCoordCache.getHeight(slatIndex) * slatRemainder; + }; + // Refreshes the CSS top/bottom coordinates for each segment element. + // Works when called after initial render, after a window resize/zoom for example. + TimeGrid.prototype.updateSegVerticals = function (segs) { + this.computeSegVerticals(segs); + this.assignSegVerticals(segs); + }; + // For each segment in an array, computes and assigns its top and bottom properties + TimeGrid.prototype.computeSegVerticals = function (segs) { + var eventMinHeight = this.opt('agendaEventMinHeight'); + var i; + var seg; + var dayDate; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + dayDate = this.dayDates[seg.dayIndex]; + seg.top = this.computeDateTop(seg.startMs, dayDate); + seg.bottom = Math.max(seg.top + eventMinHeight, this.computeDateTop(seg.endMs, dayDate)); + } + }; + // Given segments that already have their top/bottom properties computed, applies those values to + // the segments' elements. + TimeGrid.prototype.assignSegVerticals = function (segs) { + var i; + var seg; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + seg.el.css(this.generateSegVerticalCss(seg)); + } + }; + // Generates an object with CSS properties for the top/bottom coordinates of a segment element + TimeGrid.prototype.generateSegVerticalCss = function (seg) { + return { + top: seg.top, + bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container + }; + }; + /* Hit System + ------------------------------------------------------------------------------------------------------------------*/ + TimeGrid.prototype.prepareHits = function () { + this.colCoordCache.build(); + this.slatCoordCache.build(); + }; + TimeGrid.prototype.releaseHits = function () { + this.colCoordCache.clear(); + // NOTE: don't clear slatCoordCache because we rely on it for computeTimeTop + }; + TimeGrid.prototype.queryHit = function (leftOffset, topOffset) { + var snapsPerSlot = this.snapsPerSlot; + var colCoordCache = this.colCoordCache; + var slatCoordCache = this.slatCoordCache; + if (colCoordCache.isLeftInBounds(leftOffset) && slatCoordCache.isTopInBounds(topOffset)) { + var colIndex = colCoordCache.getHorizontalIndex(leftOffset); + var slatIndex = slatCoordCache.getVerticalIndex(topOffset); + if (colIndex != null && slatIndex != null) { + var slatTop = slatCoordCache.getTopOffset(slatIndex); + var slatHeight = slatCoordCache.getHeight(slatIndex); + var partial = (topOffset - slatTop) / slatHeight; // floating point number between 0 and 1 + var localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat + var snapIndex = slatIndex * snapsPerSlot + localSnapIndex; + var snapTop = slatTop + (localSnapIndex / snapsPerSlot) * slatHeight; + var snapBottom = slatTop + ((localSnapIndex + 1) / snapsPerSlot) * slatHeight; + return { + col: colIndex, + snap: snapIndex, + component: this, + left: colCoordCache.getLeftOffset(colIndex), + right: colCoordCache.getRightOffset(colIndex), + top: snapTop, + bottom: snapBottom + }; + } + } + }; + TimeGrid.prototype.getHitFootprint = function (hit) { + var start = this.getCellDate(0, hit.col); // row=0 + var time = this.computeSnapTime(hit.snap); // pass in the snap-index + var end; + start.time(time); + end = start.clone().add(this.snapDuration); + return new ComponentFootprint_1.default(new UnzonedRange_1.default(start, end), false // all-day? + ); + }; + // Given a row number of the grid, representing a "snap", returns a time (Duration) from its start-of-day + TimeGrid.prototype.computeSnapTime = function (snapIndex) { + return moment.duration(this.dateProfile.minTime + this.snapDuration * snapIndex); + }; + TimeGrid.prototype.getHitEl = function (hit) { + return this.colEls.eq(hit.col); + }; + /* Event Drag Visualization + ------------------------------------------------------------------------------------------------------------------*/ + // Renders a visual indication of an event being dragged over the specified date(s). + // A returned value of `true` signals that a mock "helper" event has been rendered. + TimeGrid.prototype.renderDrag = function (eventFootprints, seg, isTouch) { + var i; + if (seg) { + if (eventFootprints.length) { + this.helperRenderer.renderEventDraggingFootprints(eventFootprints, seg, isTouch); + // signal that a helper has been rendered + return true; + } + } + else { + for (i = 0; i < eventFootprints.length; i++) { + this.renderHighlight(eventFootprints[i].componentFootprint); + } + } + }; + // Unrenders any visual indication of an event being dragged + TimeGrid.prototype.unrenderDrag = function () { + this.unrenderHighlight(); + this.helperRenderer.unrender(); + }; + /* Event Resize Visualization + ------------------------------------------------------------------------------------------------------------------*/ + // Renders a visual indication of an event being resized + TimeGrid.prototype.renderEventResize = function (eventFootprints, seg, isTouch) { + this.helperRenderer.renderEventResizingFootprints(eventFootprints, seg, isTouch); + }; + // Unrenders any visual indication of an event being resized + TimeGrid.prototype.unrenderEventResize = function () { + this.helperRenderer.unrender(); + }; + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight. + TimeGrid.prototype.renderSelectionFootprint = function (componentFootprint) { + if (this.opt('selectHelper')) { + this.helperRenderer.renderComponentFootprint(componentFootprint); + } + else { + this.renderHighlight(componentFootprint); + } + }; + // Unrenders any visual indication of a selection + TimeGrid.prototype.unrenderSelection = function () { + this.helperRenderer.unrender(); + this.unrenderHighlight(); + }; + return TimeGrid; +}(InteractiveDateComponent_1.default)); +exports.default = TimeGrid; +TimeGrid.prototype.eventRendererClass = TimeGridEventRenderer_1.default; +TimeGrid.prototype.businessHourRendererClass = BusinessHourRenderer_1.default; +TimeGrid.prototype.helperRendererClass = TimeGridHelperRenderer_1.default; +TimeGrid.prototype.fillRendererClass = TimeGridFillRenderer_1.default; +StandardInteractionsMixin_1.default.mixInto(TimeGrid); +DayTableMixin_1.default.mixInto(TimeGrid); + + +/***/ }), +/* 228 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var UnzonedRange_1 = __webpack_require__(5); +var DateProfileGenerator_1 = __webpack_require__(221); +var BasicViewDateProfileGenerator = /** @class */ (function (_super) { + tslib_1.__extends(BasicViewDateProfileGenerator, _super); + function BasicViewDateProfileGenerator() { + return _super !== null && _super.apply(this, arguments) || this; + } + // Computes the date range that will be rendered. + BasicViewDateProfileGenerator.prototype.buildRenderRange = function (currentUnzonedRange, currentRangeUnit, isRangeAllDay) { + var renderUnzonedRange = _super.prototype.buildRenderRange.call(this, currentUnzonedRange, currentRangeUnit, isRangeAllDay); // an UnzonedRange + var start = this.msToUtcMoment(renderUnzonedRange.startMs, isRangeAllDay); + var end = this.msToUtcMoment(renderUnzonedRange.endMs, isRangeAllDay); + // year and month views should be aligned with weeks. this is already done for week + if (/^(year|month)$/.test(currentRangeUnit)) { + start.startOf('week'); + // make end-of-week if not already + if (end.weekday()) { + end.add(1, 'week').startOf('week'); // exclusively move backwards + } + } + return new UnzonedRange_1.default(start, end); + }; + return BasicViewDateProfileGenerator; +}(DateProfileGenerator_1.default)); +exports.default = BasicViewDateProfileGenerator; + + +/***/ }), +/* 229 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var moment = __webpack_require__(0); +var util_1 = __webpack_require__(4); +var BasicView_1 = __webpack_require__(62); +var MonthViewDateProfileGenerator_1 = __webpack_require__(253); +/* A month view with day cells running in rows (one-per-week) and columns +----------------------------------------------------------------------------------------------------------------------*/ +var MonthView = /** @class */ (function (_super) { + tslib_1.__extends(MonthView, _super); + function MonthView() { + return _super !== null && _super.apply(this, arguments) || this; + } + // Overrides the default BasicView behavior to have special multi-week auto-height logic + MonthView.prototype.setGridHeight = function (height, isAuto) { + // if auto, make the height of each row the height that it would be if there were 6 weeks + if (isAuto) { + height *= this.dayGrid.rowCnt / 6; + } + util_1.distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows + }; + MonthView.prototype.isDateInOtherMonth = function (date, dateProfile) { + return date.month() !== moment.utc(dateProfile.currentUnzonedRange.startMs).month(); // TODO: optimize + }; + return MonthView; +}(BasicView_1.default)); +exports.default = MonthView; +MonthView.prototype.dateProfileGeneratorClass = MonthViewDateProfileGenerator_1.default; + + +/***/ }), +/* 230 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var UnzonedRange_1 = __webpack_require__(5); +var View_1 = __webpack_require__(41); +var Scroller_1 = __webpack_require__(39); +var ListEventRenderer_1 = __webpack_require__(254); +var ListEventPointing_1 = __webpack_require__(255); +/* +Responsible for the scroller, and forwarding event-related actions into the "grid". +*/ +var ListView = /** @class */ (function (_super) { + tslib_1.__extends(ListView, _super); + function ListView(calendar, viewSpec) { + var _this = _super.call(this, calendar, viewSpec) || this; + _this.segSelector = '.fc-list-item'; // which elements accept event actions + _this.scroller = new Scroller_1.default({ + overflowX: 'hidden', + overflowY: 'auto' + }); + return _this; + } + ListView.prototype.renderSkeleton = function () { + this.el.addClass('fc-list-view ' + + this.calendar.theme.getClass('listView')); + this.scroller.render(); + this.scroller.el.appendTo(this.el); + this.contentEl = this.scroller.scrollEl; // shortcut + }; + ListView.prototype.unrenderSkeleton = function () { + this.scroller.destroy(); // will remove the Grid too + }; + ListView.prototype.updateSize = function (totalHeight, isAuto, isResize) { + _super.prototype.updateSize.call(this, totalHeight, isAuto, isResize); + this.scroller.clear(); // sets height to 'auto' and clears overflow + if (!isAuto) { + this.scroller.setHeight(this.computeScrollerHeight(totalHeight)); + } + }; + ListView.prototype.computeScrollerHeight = function (totalHeight) { + return totalHeight - + util_1.subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller + }; + ListView.prototype.renderDates = function (dateProfile) { + var calendar = this.calendar; + var dayStart = calendar.msToUtcMoment(dateProfile.renderUnzonedRange.startMs, true); + var viewEnd = calendar.msToUtcMoment(dateProfile.renderUnzonedRange.endMs, true); + var dayDates = []; + var dayRanges = []; + while (dayStart < viewEnd) { + dayDates.push(dayStart.clone()); + dayRanges.push(new UnzonedRange_1.default(dayStart, dayStart.clone().add(1, 'day'))); + dayStart.add(1, 'day'); + } + this.dayDates = dayDates; + this.dayRanges = dayRanges; + // all real rendering happens in EventRenderer + }; + // slices by day + ListView.prototype.componentFootprintToSegs = function (footprint) { + var dayRanges = this.dayRanges; + var dayIndex; + var segRange; + var seg; + var segs = []; + for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex++) { + segRange = footprint.unzonedRange.intersect(dayRanges[dayIndex]); + if (segRange) { + seg = { + startMs: segRange.startMs, + endMs: segRange.endMs, + isStart: segRange.isStart, + isEnd: segRange.isEnd, + dayIndex: dayIndex + }; + segs.push(seg); + // detect when footprint won't go fully into the next day, + // and mutate the latest seg to the be the end. + if (!seg.isEnd && !footprint.isAllDay && + dayIndex + 1 < dayRanges.length && + footprint.unzonedRange.endMs < dayRanges[dayIndex + 1].startMs + this.nextDayThreshold) { + seg.endMs = footprint.unzonedRange.endMs; + seg.isEnd = true; + break; + } + } + } + return segs; + }; + ListView.prototype.renderEmptyMessage = function () { + this.contentEl.html('' + // TODO: try less wraps + '' + + '' + + util_1.htmlEscape(this.opt('noEventsMessage')) + + '' + + '' + + ''); + }; + // render the event segments in the view + ListView.prototype.renderSegList = function (allSegs) { + var segsByDay = this.groupSegsByDay(allSegs); // sparse array + var dayIndex; + var daySegs; + var i; + var tableEl = $(''); + var tbodyEl = tableEl.find('tbody'); + for (dayIndex = 0; dayIndex < segsByDay.length; dayIndex++) { + daySegs = segsByDay[dayIndex]; + if (daySegs) { + // append a day header + tbodyEl.append(this.dayHeaderHtml(this.dayDates[dayIndex])); + this.eventRenderer.sortEventSegs(daySegs); + for (i = 0; i < daySegs.length; i++) { + tbodyEl.append(daySegs[i].el); // append event row + } + } + } + this.contentEl.empty().append(tableEl); + }; + // Returns a sparse array of arrays, segs grouped by their dayIndex + ListView.prototype.groupSegsByDay = function (segs) { + var segsByDay = []; // sparse array + var i; + var seg; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + (segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = [])) + .push(seg); + } + return segsByDay; + }; + // generates the HTML for the day headers that live amongst the event rows + ListView.prototype.dayHeaderHtml = function (dayDate) { + var mainFormat = this.opt('listDayFormat'); + var altFormat = this.opt('listDayAltFormat'); + return '' + + '' + + (mainFormat ? + this.buildGotoAnchorHtml(dayDate, { 'class': 'fc-list-heading-main' }, util_1.htmlEscape(dayDate.format(mainFormat)) // inner HTML + ) : + '') + + (altFormat ? + this.buildGotoAnchorHtml(dayDate, { 'class': 'fc-list-heading-alt' }, util_1.htmlEscape(dayDate.format(altFormat)) // inner HTML + ) : + '') + + '' + + ''; + }; + return ListView; +}(View_1.default)); +exports.default = ListView; +ListView.prototype.eventRendererClass = ListEventRenderer_1.default; +ListView.prototype.eventPointingClass = ListEventPointing_1.default; + + +/***/ }), +/* 231 */, +/* 232 */, +/* 233 */, +/* 234 */, +/* 235 */, +/* 236 */ +/***/ (function(module, exports, __webpack_require__) { + +var $ = __webpack_require__(3); +var exportHooks = __webpack_require__(16); +var util_1 = __webpack_require__(4); +var Calendar_1 = __webpack_require__(220); +// for intentional side-effects +__webpack_require__(10); +__webpack_require__(47); +__webpack_require__(256); +__webpack_require__(257); +__webpack_require__(260); +__webpack_require__(261); +__webpack_require__(262); +__webpack_require__(263); +$.fullCalendar = exportHooks; +$.fn.fullCalendar = function (options) { + var args = Array.prototype.slice.call(arguments, 1); // for a possible method call + var res = this; // what this function will return (this jQuery object by default) + this.each(function (i, _element) { + var element = $(_element); + var calendar = element.data('fullCalendar'); // get the existing calendar object (if any) + var singleRes; // the returned value of this single method call + // a method call + if (typeof options === 'string') { + if (options === 'getCalendar') { + if (!i) { + res = calendar; + } + } + else if (options === 'destroy') { + if (calendar) { + calendar.destroy(); + element.removeData('fullCalendar'); + } + } + else if (!calendar) { + util_1.warn('Attempting to call a FullCalendar method on an element with no calendar.'); + } + else if ($.isFunction(calendar[options])) { + singleRes = calendar[options].apply(calendar, args); + if (!i) { + res = singleRes; // record the first method call result + } + if (options === 'destroy') { + element.removeData('fullCalendar'); + } + } + else { + util_1.warn("'" + options + "' is an unknown FullCalendar method."); + } + } + else if (!calendar) { + calendar = new Calendar_1.default(element, options); + element.data('fullCalendar', calendar); + calendar.render(); + } + }); + return res; +}; +module.exports = exportHooks; + + +/***/ }), +/* 237 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var Model_1 = __webpack_require__(48); +var Component = /** @class */ (function (_super) { + tslib_1.__extends(Component, _super); + function Component() { + return _super !== null && _super.apply(this, arguments) || this; + } + Component.prototype.setElement = function (el) { + this.el = el; + this.bindGlobalHandlers(); + this.renderSkeleton(); + this.set('isInDom', true); + }; + Component.prototype.removeElement = function () { + this.unset('isInDom'); + this.unrenderSkeleton(); + this.unbindGlobalHandlers(); + this.el.remove(); + // NOTE: don't null-out this.el in case the View was destroyed within an API callback. + // We don't null-out the View's other jQuery element references upon destroy, + // so we shouldn't kill this.el either. + }; + Component.prototype.bindGlobalHandlers = function () { + // subclasses can override + }; + Component.prototype.unbindGlobalHandlers = function () { + // subclasses can override + }; + /* + NOTE: Can't have a `render` method. Read the deprecation notice in View::executeDateRender + */ + // Renders the basic structure of the view before any content is rendered + Component.prototype.renderSkeleton = function () { + // subclasses should implement + }; + // Unrenders the basic structure of the view + Component.prototype.unrenderSkeleton = function () { + // subclasses should implement + }; + return Component; +}(Model_1.default)); +exports.default = Component; + + +/***/ }), +/* 238 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var Iterator = /** @class */ (function () { + function Iterator(items) { + this.items = items || []; + } + /* Calls a method on every item passing the arguments through */ + Iterator.prototype.proxyCall = function (methodName) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + var results = []; + this.items.forEach(function (item) { + results.push(item[methodName].apply(item, args)); + }); + return results; + }; + return Iterator; +}()); +exports.default = Iterator; + + +/***/ }), +/* 239 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +/* Toolbar with buttons and title +----------------------------------------------------------------------------------------------------------------------*/ +var Toolbar = /** @class */ (function () { + function Toolbar(calendar, toolbarOptions) { + this.el = null; // mirrors local `el` + this.viewsWithButtons = []; + this.calendar = calendar; + this.toolbarOptions = toolbarOptions; + } + // method to update toolbar-specific options, not calendar-wide options + Toolbar.prototype.setToolbarOptions = function (newToolbarOptions) { + this.toolbarOptions = newToolbarOptions; + }; + // can be called repeatedly and will rerender + Toolbar.prototype.render = function () { + var sections = this.toolbarOptions.layout; + var el = this.el; + if (sections) { + if (!el) { + el = this.el = $(""); + } + else { + el.empty(); + } + el.append(this.renderSection('left')) + .append(this.renderSection('right')) + .append(this.renderSection('center')) + .append(''); + } + else { + this.removeElement(); + } + }; + Toolbar.prototype.removeElement = function () { + if (this.el) { + this.el.remove(); + this.el = null; + } + }; + Toolbar.prototype.renderSection = function (position) { + var _this = this; + var calendar = this.calendar; + var theme = calendar.theme; + var optionsManager = calendar.optionsManager; + var viewSpecManager = calendar.viewSpecManager; + var sectionEl = $(''); + var buttonStr = this.toolbarOptions.layout[position]; + var calendarCustomButtons = optionsManager.get('customButtons') || {}; + var calendarButtonTextOverrides = optionsManager.overrides.buttonText || {}; + var calendarButtonText = optionsManager.get('buttonText') || {}; + if (buttonStr) { + $.each(buttonStr.split(' '), function (i, buttonGroupStr) { + var groupChildren = $(); + var isOnlyButtons = true; + var groupEl; + $.each(buttonGroupStr.split(','), function (j, buttonName) { + var customButtonProps; + var viewSpec; + var buttonClick; + var buttonIcon; // only one of these will be set + var buttonText; // " + var buttonInnerHtml; + var buttonClasses; + var buttonEl; + var buttonAriaAttr; + if (buttonName === 'title') { + groupChildren = groupChildren.add($(' ')); // we always want it to take up height + isOnlyButtons = false; + } + else { + if ((customButtonProps = calendarCustomButtons[buttonName])) { + buttonClick = function (ev) { + if (customButtonProps.click) { + customButtonProps.click.call(buttonEl[0], ev); + } + }; + (buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) || + (buttonIcon = theme.getIconClass(buttonName)) || + (buttonText = customButtonProps.text); + } + else if ((viewSpec = viewSpecManager.getViewSpec(buttonName))) { + _this.viewsWithButtons.push(buttonName); + buttonClick = function () { + calendar.changeView(buttonName); + }; + (buttonText = viewSpec.buttonTextOverride) || + (buttonIcon = theme.getIconClass(buttonName)) || + (buttonText = viewSpec.buttonTextDefault); + } + else if (calendar[buttonName]) { + buttonClick = function () { + calendar[buttonName](); + }; + (buttonText = calendarButtonTextOverrides[buttonName]) || + (buttonIcon = theme.getIconClass(buttonName)) || + (buttonText = calendarButtonText[buttonName]); + // ^ everything else is considered default + } + if (buttonClick) { + buttonClasses = [ + 'fc-' + buttonName + '-button', + theme.getClass('button'), + theme.getClass('stateDefault') + ]; + if (buttonText) { + buttonInnerHtml = util_1.htmlEscape(buttonText); + buttonAriaAttr = ''; + } + else if (buttonIcon) { + buttonInnerHtml = ""; + buttonAriaAttr = ' aria-label="' + buttonName + '"'; + } + buttonEl = $(// type="button" so that it doesn't submit a form + '' + buttonInnerHtml + '') + .click(function (ev) { + // don't process clicks for disabled buttons + if (!buttonEl.hasClass(theme.getClass('stateDisabled'))) { + buttonClick(ev); + // after the click action, if the button becomes the "active" tab, or disabled, + // it should never have a hover class, so remove it now. + if (buttonEl.hasClass(theme.getClass('stateActive')) || + buttonEl.hasClass(theme.getClass('stateDisabled'))) { + buttonEl.removeClass(theme.getClass('stateHover')); + } + } + }) + .mousedown(function () { + // the *down* effect (mouse pressed in). + // only on buttons that are not the "active" tab, or disabled + buttonEl + .not('.' + theme.getClass('stateActive')) + .not('.' + theme.getClass('stateDisabled')) + .addClass(theme.getClass('stateDown')); + }) + .mouseup(function () { + // undo the *down* effect + buttonEl.removeClass(theme.getClass('stateDown')); + }) + .hover(function () { + // the *hover* effect. + // only on buttons that are not the "active" tab, or disabled + buttonEl + .not('.' + theme.getClass('stateActive')) + .not('.' + theme.getClass('stateDisabled')) + .addClass(theme.getClass('stateHover')); + }, function () { + // undo the *hover* effect + buttonEl + .removeClass(theme.getClass('stateHover')) + .removeClass(theme.getClass('stateDown')); // if mouseleave happens before mouseup + }); + groupChildren = groupChildren.add(buttonEl); + } + } + }); + if (isOnlyButtons) { + groupChildren + .first().addClass(theme.getClass('cornerLeft')).end() + .last().addClass(theme.getClass('cornerRight')).end(); + } + if (groupChildren.length > 1) { + groupEl = $(''); + if (isOnlyButtons) { + groupEl.addClass(theme.getClass('buttonGroup')); + } + groupEl.append(groupChildren); + sectionEl.append(groupEl); + } + else { + sectionEl.append(groupChildren); // 1 or 0 children + } + }); + } + return sectionEl; + }; + Toolbar.prototype.updateTitle = function (text) { + if (this.el) { + this.el.find('h2').text(text); + } + }; + Toolbar.prototype.activateButton = function (buttonName) { + if (this.el) { + this.el.find('.fc-' + buttonName + '-button') + .addClass(this.calendar.theme.getClass('stateActive')); + } + }; + Toolbar.prototype.deactivateButton = function (buttonName) { + if (this.el) { + this.el.find('.fc-' + buttonName + '-button') + .removeClass(this.calendar.theme.getClass('stateActive')); + } + }; + Toolbar.prototype.disableButton = function (buttonName) { + if (this.el) { + this.el.find('.fc-' + buttonName + '-button') + .prop('disabled', true) + .addClass(this.calendar.theme.getClass('stateDisabled')); + } + }; + Toolbar.prototype.enableButton = function (buttonName) { + if (this.el) { + this.el.find('.fc-' + buttonName + '-button') + .prop('disabled', false) + .removeClass(this.calendar.theme.getClass('stateDisabled')); + } + }; + Toolbar.prototype.getViewsWithButtons = function () { + return this.viewsWithButtons; + }; + return Toolbar; +}()); +exports.default = Toolbar; + + +/***/ }), +/* 240 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var options_1 = __webpack_require__(32); +var locale_1 = __webpack_require__(31); +var Model_1 = __webpack_require__(48); +var OptionsManager = /** @class */ (function (_super) { + tslib_1.__extends(OptionsManager, _super); + function OptionsManager(_calendar, overrides) { + var _this = _super.call(this) || this; + _this._calendar = _calendar; + _this.overrides = $.extend({}, overrides); // make a copy + _this.dynamicOverrides = {}; + _this.compute(); + return _this; + } + OptionsManager.prototype.add = function (newOptionHash) { + var optionCnt = 0; + var optionName; + this.recordOverrides(newOptionHash); // will trigger this model's watchers + for (optionName in newOptionHash) { + optionCnt++; + } + // special-case handling of single option change. + // if only one option change, `optionName` will be its name. + if (optionCnt === 1) { + if (optionName === 'height' || optionName === 'contentHeight' || optionName === 'aspectRatio') { + this._calendar.updateViewSize(true); // isResize=true + return; + } + else if (optionName === 'defaultDate') { + return; // can't change date this way. use gotoDate instead + } + else if (optionName === 'businessHours') { + return; // this model already reacts to this + } + else if (/^(event|select)(Overlap|Constraint|Allow)$/.test(optionName)) { + return; // doesn't affect rendering. only interactions. + } + else if (optionName === 'timezone') { + this._calendar.view.flash('initialEvents'); + return; + } + } + // catch-all. rerender the header and footer and rebuild/rerender the current view + this._calendar.renderHeader(); + this._calendar.renderFooter(); + // even non-current views will be affected by this option change. do before rerender + // TODO: detangle + this._calendar.viewsByType = {}; + this._calendar.reinitView(); + }; + // Computes the flattened options hash for the calendar and assigns to `this.options`. + // Assumes this.overrides and this.dynamicOverrides have already been initialized. + OptionsManager.prototype.compute = function () { + var locale; + var localeDefaults; + var isRTL; + var dirDefaults; + var rawOptions; + locale = util_1.firstDefined(// explicit locale option given? + this.dynamicOverrides.locale, this.overrides.locale); + localeDefaults = locale_1.localeOptionHash[locale]; + if (!localeDefaults) { + locale = options_1.globalDefaults.locale; + localeDefaults = locale_1.localeOptionHash[locale] || {}; + } + isRTL = util_1.firstDefined(// based on options computed so far, is direction RTL? + this.dynamicOverrides.isRTL, this.overrides.isRTL, localeDefaults.isRTL, options_1.globalDefaults.isRTL); + dirDefaults = isRTL ? options_1.rtlDefaults : {}; + this.dirDefaults = dirDefaults; + this.localeDefaults = localeDefaults; + rawOptions = options_1.mergeOptions([ + options_1.globalDefaults, + dirDefaults, + localeDefaults, + this.overrides, + this.dynamicOverrides + ]); + locale_1.populateInstanceComputableOptions(rawOptions); // fill in gaps with computed options + this.reset(rawOptions); + }; + // stores the new options internally, but does not rerender anything. + OptionsManager.prototype.recordOverrides = function (newOptionHash) { + var optionName; + for (optionName in newOptionHash) { + this.dynamicOverrides[optionName] = newOptionHash[optionName]; + } + this._calendar.viewSpecManager.clearCache(); // the dynamic override invalidates the options in this cache, so just clear it + this.compute(); // this.options needs to be recomputed after the dynamic override + }; + return OptionsManager; +}(Model_1.default)); +exports.default = OptionsManager; + + +/***/ }), +/* 241 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var moment = __webpack_require__(0); +var $ = __webpack_require__(3); +var ViewRegistry_1 = __webpack_require__(22); +var util_1 = __webpack_require__(4); +var options_1 = __webpack_require__(32); +var locale_1 = __webpack_require__(31); +var ViewSpecManager = /** @class */ (function () { + function ViewSpecManager(optionsManager, _calendar) { + this.optionsManager = optionsManager; + this._calendar = _calendar; + this.clearCache(); + } + ViewSpecManager.prototype.clearCache = function () { + this.viewSpecCache = {}; + }; + // Gets information about how to create a view. Will use a cache. + ViewSpecManager.prototype.getViewSpec = function (viewType) { + var cache = this.viewSpecCache; + return cache[viewType] || (cache[viewType] = this.buildViewSpec(viewType)); + }; + // Given a duration singular unit, like "week" or "day", finds a matching view spec. + // Preference is given to views that have corresponding buttons. + ViewSpecManager.prototype.getUnitViewSpec = function (unit) { + var viewTypes; + var i; + var spec; + if ($.inArray(unit, util_1.unitsDesc) !== -1) { + // put views that have buttons first. there will be duplicates, but oh well + viewTypes = this._calendar.header.getViewsWithButtons(); // TODO: include footer as well? + $.each(ViewRegistry_1.viewHash, function (viewType) { + viewTypes.push(viewType); + }); + for (i = 0; i < viewTypes.length; i++) { + spec = this.getViewSpec(viewTypes[i]); + if (spec) { + if (spec.singleUnit === unit) { + return spec; + } + } + } + } + }; + // Builds an object with information on how to create a given view + ViewSpecManager.prototype.buildViewSpec = function (requestedViewType) { + var viewOverrides = this.optionsManager.overrides.views || {}; + var specChain = []; // for the view. lowest to highest priority + var defaultsChain = []; // for the view. lowest to highest priority + var overridesChain = []; // for the view. lowest to highest priority + var viewType = requestedViewType; + var spec; // for the view + var overrides; // for the view + var durationInput; + var duration; + var unit; + // iterate from the specific view definition to a more general one until we hit an actual View class + while (viewType) { + spec = ViewRegistry_1.viewHash[viewType]; + overrides = viewOverrides[viewType]; + viewType = null; // clear. might repopulate for another iteration + if (typeof spec === 'function') { + spec = { 'class': spec }; + } + if (spec) { + specChain.unshift(spec); + defaultsChain.unshift(spec.defaults || {}); + durationInput = durationInput || spec.duration; + viewType = viewType || spec.type; + } + if (overrides) { + overridesChain.unshift(overrides); // view-specific option hashes have options at zero-level + durationInput = durationInput || overrides.duration; + viewType = viewType || overrides.type; + } + } + spec = util_1.mergeProps(specChain); + spec.type = requestedViewType; + if (!spec['class']) { + return false; + } + // fall back to top-level `duration` option + durationInput = durationInput || + this.optionsManager.dynamicOverrides.duration || + this.optionsManager.overrides.duration; + if (durationInput) { + duration = moment.duration(durationInput); + if (duration.valueOf()) { + unit = util_1.computeDurationGreatestUnit(duration, durationInput); + spec.duration = duration; + spec.durationUnit = unit; + // view is a single-unit duration, like "week" or "day" + // incorporate options for this. lowest priority + if (duration.as(unit) === 1) { + spec.singleUnit = unit; + overridesChain.unshift(viewOverrides[unit] || {}); + } + } + } + spec.defaults = options_1.mergeOptions(defaultsChain); + spec.overrides = options_1.mergeOptions(overridesChain); + this.buildViewSpecOptions(spec); + this.buildViewSpecButtonText(spec, requestedViewType); + return spec; + }; + // Builds and assigns a view spec's options object from its already-assigned defaults and overrides + ViewSpecManager.prototype.buildViewSpecOptions = function (spec) { + var optionsManager = this.optionsManager; + spec.options = options_1.mergeOptions([ + options_1.globalDefaults, + spec.defaults, + optionsManager.dirDefaults, + optionsManager.localeDefaults, + optionsManager.overrides, + spec.overrides, + optionsManager.dynamicOverrides // dynamically set via setter. highest precedence + ]); + locale_1.populateInstanceComputableOptions(spec.options); + }; + // Computes and assigns a view spec's buttonText-related options + ViewSpecManager.prototype.buildViewSpecButtonText = function (spec, requestedViewType) { + var optionsManager = this.optionsManager; + // given an options object with a possible `buttonText` hash, lookup the buttonText for the + // requested view, falling back to a generic unit entry like "week" or "day" + function queryButtonText(options) { + var buttonText = options.buttonText || {}; + return buttonText[requestedViewType] || + // view can decide to look up a certain key + (spec.buttonTextKey ? buttonText[spec.buttonTextKey] : null) || + // a key like "month" + (spec.singleUnit ? buttonText[spec.singleUnit] : null); + } + // highest to lowest priority + spec.buttonTextOverride = + queryButtonText(optionsManager.dynamicOverrides) || + queryButtonText(optionsManager.overrides) || // constructor-specified buttonText lookup hash takes precedence + spec.overrides.buttonText; // `buttonText` for view-specific options is a string + // highest to lowest priority. mirrors buildViewSpecOptions + spec.buttonTextDefault = + queryButtonText(optionsManager.localeDefaults) || + queryButtonText(optionsManager.dirDefaults) || + spec.defaults.buttonText || // a single string. from ViewSubclass.defaults + queryButtonText(options_1.globalDefaults) || + (spec.duration ? this._calendar.humanizeDuration(spec.duration) : null) || // like "3 days" + requestedViewType; // fall back to given view name + }; + return ViewSpecManager; +}()); +exports.default = ViewSpecManager; + + +/***/ }), +/* 242 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var EventPeriod_1 = __webpack_require__(243); +var ArrayEventSource_1 = __webpack_require__(52); +var EventSource_1 = __webpack_require__(6); +var EventSourceParser_1 = __webpack_require__(38); +var SingleEventDef_1 = __webpack_require__(13); +var EventInstanceGroup_1 = __webpack_require__(18); +var EmitterMixin_1 = __webpack_require__(11); +var ListenerMixin_1 = __webpack_require__(7); +var EventManager = /** @class */ (function () { + function EventManager(calendar) { + this.calendar = calendar; + this.stickySource = new ArrayEventSource_1.default(calendar); + this.otherSources = []; + } + EventManager.prototype.requestEvents = function (start, end, timezone, force) { + if (force || + !this.currentPeriod || + !this.currentPeriod.isWithinRange(start, end) || + timezone !== this.currentPeriod.timezone) { + this.setPeriod(// will change this.currentPeriod + new EventPeriod_1.default(start, end, timezone)); + } + return this.currentPeriod.whenReleased(); + }; + // Source Adding/Removing + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.addSource = function (eventSource) { + this.otherSources.push(eventSource); + if (this.currentPeriod) { + this.currentPeriod.requestSource(eventSource); // might release + } + }; + EventManager.prototype.removeSource = function (doomedSource) { + util_1.removeExact(this.otherSources, doomedSource); + if (this.currentPeriod) { + this.currentPeriod.purgeSource(doomedSource); // might release + } + }; + EventManager.prototype.removeAllSources = function () { + this.otherSources = []; + if (this.currentPeriod) { + this.currentPeriod.purgeAllSources(); // might release + } + }; + // Source Refetching + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.refetchSource = function (eventSource) { + var currentPeriod = this.currentPeriod; + if (currentPeriod) { + currentPeriod.freeze(); + currentPeriod.purgeSource(eventSource); + currentPeriod.requestSource(eventSource); + currentPeriod.thaw(); + } + }; + EventManager.prototype.refetchAllSources = function () { + var currentPeriod = this.currentPeriod; + if (currentPeriod) { + currentPeriod.freeze(); + currentPeriod.purgeAllSources(); + currentPeriod.requestSources(this.getSources()); + currentPeriod.thaw(); + } + }; + // Source Querying + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.getSources = function () { + return [this.stickySource].concat(this.otherSources); + }; + // like querySources, but accepts multple match criteria (like multiple IDs) + EventManager.prototype.multiQuerySources = function (matchInputs) { + // coerce into an array + if (!matchInputs) { + matchInputs = []; + } + else if (!$.isArray(matchInputs)) { + matchInputs = [matchInputs]; + } + var matchingSources = []; + var i; + // resolve raw inputs to real event source objects + for (i = 0; i < matchInputs.length; i++) { + matchingSources.push.apply(// append + matchingSources, this.querySources(matchInputs[i])); + } + return matchingSources; + }; + // matchInput can either by a real event source object, an ID, or the function/URL for the source. + // returns an array of matching source objects. + EventManager.prototype.querySources = function (matchInput) { + var sources = this.otherSources; + var i; + var source; + // given a proper event source object + for (i = 0; i < sources.length; i++) { + source = sources[i]; + if (source === matchInput) { + return [source]; + } + } + // an ID match + source = this.getSourceById(EventSource_1.default.normalizeId(matchInput)); + if (source) { + return [source]; + } + // parse as an event source + matchInput = EventSourceParser_1.default.parse(matchInput, this.calendar); + if (matchInput) { + return $.grep(sources, function (source) { + return isSourcesEquivalent(matchInput, source); + }); + } + }; + /* + ID assumed to already be normalized + */ + EventManager.prototype.getSourceById = function (id) { + return $.grep(this.otherSources, function (source) { + return source.id && source.id === id; + })[0]; + }; + // Event-Period + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.setPeriod = function (eventPeriod) { + if (this.currentPeriod) { + this.unbindPeriod(this.currentPeriod); + this.currentPeriod = null; + } + this.currentPeriod = eventPeriod; + this.bindPeriod(eventPeriod); + eventPeriod.requestSources(this.getSources()); + }; + EventManager.prototype.bindPeriod = function (eventPeriod) { + this.listenTo(eventPeriod, 'release', function (eventsPayload) { + this.trigger('release', eventsPayload); + }); + }; + EventManager.prototype.unbindPeriod = function (eventPeriod) { + this.stopListeningTo(eventPeriod); + }; + // Event Getting/Adding/Removing + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.getEventDefByUid = function (uid) { + if (this.currentPeriod) { + return this.currentPeriod.getEventDefByUid(uid); + } + }; + EventManager.prototype.addEventDef = function (eventDef, isSticky) { + if (isSticky) { + this.stickySource.addEventDef(eventDef); + } + if (this.currentPeriod) { + this.currentPeriod.addEventDef(eventDef); // might release + } + }; + EventManager.prototype.removeEventDefsById = function (eventId) { + this.getSources().forEach(function (eventSource) { + eventSource.removeEventDefsById(eventId); + }); + if (this.currentPeriod) { + this.currentPeriod.removeEventDefsById(eventId); // might release + } + }; + EventManager.prototype.removeAllEventDefs = function () { + this.getSources().forEach(function (eventSource) { + eventSource.removeAllEventDefs(); + }); + if (this.currentPeriod) { + this.currentPeriod.removeAllEventDefs(); + } + }; + // Event Mutating + // ----------------------------------------------------------------------------------------------------------------- + /* + Returns an undo function. + */ + EventManager.prototype.mutateEventsWithId = function (eventDefId, eventDefMutation) { + var currentPeriod = this.currentPeriod; + var eventDefs; + var undoFuncs = []; + if (currentPeriod) { + currentPeriod.freeze(); + eventDefs = currentPeriod.getEventDefsById(eventDefId); + eventDefs.forEach(function (eventDef) { + // add/remove esp because id might change + currentPeriod.removeEventDef(eventDef); + undoFuncs.push(eventDefMutation.mutateSingle(eventDef)); + currentPeriod.addEventDef(eventDef); + }); + currentPeriod.thaw(); + return function () { + currentPeriod.freeze(); + for (var i = 0; i < eventDefs.length; i++) { + currentPeriod.removeEventDef(eventDefs[i]); + undoFuncs[i](); + currentPeriod.addEventDef(eventDefs[i]); + } + currentPeriod.thaw(); + }; + } + return function () { }; + }; + /* + copies and then mutates + */ + EventManager.prototype.buildMutatedEventInstanceGroup = function (eventDefId, eventDefMutation) { + var eventDefs = this.getEventDefsById(eventDefId); + var i; + var defCopy; + var allInstances = []; + for (i = 0; i < eventDefs.length; i++) { + defCopy = eventDefs[i].clone(); + if (defCopy instanceof SingleEventDef_1.default) { + eventDefMutation.mutateSingle(defCopy); + allInstances.push.apply(allInstances, // append + defCopy.buildInstances()); + } + } + return new EventInstanceGroup_1.default(allInstances); + }; + // Freezing + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.freeze = function () { + if (this.currentPeriod) { + this.currentPeriod.freeze(); + } + }; + EventManager.prototype.thaw = function () { + if (this.currentPeriod) { + this.currentPeriod.thaw(); + } + }; + // methods that simply forward to EventPeriod + EventManager.prototype.getEventDefsById = function (eventDefId) { + return this.currentPeriod.getEventDefsById(eventDefId); + }; + EventManager.prototype.getEventInstances = function () { + return this.currentPeriod.getEventInstances(); + }; + EventManager.prototype.getEventInstancesWithId = function (eventDefId) { + return this.currentPeriod.getEventInstancesWithId(eventDefId); + }; + EventManager.prototype.getEventInstancesWithoutId = function (eventDefId) { + return this.currentPeriod.getEventInstancesWithoutId(eventDefId); + }; + return EventManager; +}()); +exports.default = EventManager; +EmitterMixin_1.default.mixInto(EventManager); +ListenerMixin_1.default.mixInto(EventManager); +function isSourcesEquivalent(source0, source1) { + return source0.getPrimitive() === source1.getPrimitive(); +} + + +/***/ }), +/* 243 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var Promise_1 = __webpack_require__(20); +var EmitterMixin_1 = __webpack_require__(11); +var UnzonedRange_1 = __webpack_require__(5); +var EventInstanceGroup_1 = __webpack_require__(18); +var EventPeriod = /** @class */ (function () { + function EventPeriod(start, end, timezone) { + this.pendingCnt = 0; + this.freezeDepth = 0; + this.stuntedReleaseCnt = 0; + this.releaseCnt = 0; + this.start = start; + this.end = end; + this.timezone = timezone; + this.unzonedRange = new UnzonedRange_1.default(start.clone().stripZone(), end.clone().stripZone()); + this.requestsByUid = {}; + this.eventDefsByUid = {}; + this.eventDefsById = {}; + this.eventInstanceGroupsById = {}; + } + EventPeriod.prototype.isWithinRange = function (start, end) { + // TODO: use a range util function? + return !start.isBefore(this.start) && !end.isAfter(this.end); + }; + // Requesting and Purging + // ----------------------------------------------------------------------------------------------------------------- + EventPeriod.prototype.requestSources = function (sources) { + this.freeze(); + for (var i = 0; i < sources.length; i++) { + this.requestSource(sources[i]); + } + this.thaw(); + }; + EventPeriod.prototype.requestSource = function (source) { + var _this = this; + var request = { source: source, status: 'pending', eventDefs: null }; + this.requestsByUid[source.uid] = request; + this.pendingCnt += 1; + source.fetch(this.start, this.end, this.timezone).then(function (eventDefs) { + if (request.status !== 'cancelled') { + request.status = 'completed'; + request.eventDefs = eventDefs; + _this.addEventDefs(eventDefs); + _this.pendingCnt--; + _this.tryRelease(); + } + }, function () { + if (request.status !== 'cancelled') { + request.status = 'failed'; + _this.pendingCnt--; + _this.tryRelease(); + } + }); + }; + EventPeriod.prototype.purgeSource = function (source) { + var request = this.requestsByUid[source.uid]; + if (request) { + delete this.requestsByUid[source.uid]; + if (request.status === 'pending') { + request.status = 'cancelled'; + this.pendingCnt--; + this.tryRelease(); + } + else if (request.status === 'completed') { + request.eventDefs.forEach(this.removeEventDef.bind(this)); + } + } + }; + EventPeriod.prototype.purgeAllSources = function () { + var requestsByUid = this.requestsByUid; + var uid; + var request; + var completedCnt = 0; + for (uid in requestsByUid) { + request = requestsByUid[uid]; + if (request.status === 'pending') { + request.status = 'cancelled'; + } + else if (request.status === 'completed') { + completedCnt++; + } + } + this.requestsByUid = {}; + this.pendingCnt = 0; + if (completedCnt) { + this.removeAllEventDefs(); // might release + } + }; + // Event Definitions + // ----------------------------------------------------------------------------------------------------------------- + EventPeriod.prototype.getEventDefByUid = function (eventDefUid) { + return this.eventDefsByUid[eventDefUid]; + }; + EventPeriod.prototype.getEventDefsById = function (eventDefId) { + var a = this.eventDefsById[eventDefId]; + if (a) { + return a.slice(); // clone + } + return []; + }; + EventPeriod.prototype.addEventDefs = function (eventDefs) { + for (var i = 0; i < eventDefs.length; i++) { + this.addEventDef(eventDefs[i]); + } + }; + EventPeriod.prototype.addEventDef = function (eventDef) { + var eventDefsById = this.eventDefsById; + var eventDefId = eventDef.id; + var eventDefs = eventDefsById[eventDefId] || (eventDefsById[eventDefId] = []); + var eventInstances = eventDef.buildInstances(this.unzonedRange); + var i; + eventDefs.push(eventDef); + this.eventDefsByUid[eventDef.uid] = eventDef; + for (i = 0; i < eventInstances.length; i++) { + this.addEventInstance(eventInstances[i], eventDefId); + } + }; + EventPeriod.prototype.removeEventDefsById = function (eventDefId) { + var _this = this; + this.getEventDefsById(eventDefId).forEach(function (eventDef) { + _this.removeEventDef(eventDef); + }); + }; + EventPeriod.prototype.removeAllEventDefs = function () { + var isEmpty = $.isEmptyObject(this.eventDefsByUid); + this.eventDefsByUid = {}; + this.eventDefsById = {}; + this.eventInstanceGroupsById = {}; + if (!isEmpty) { + this.tryRelease(); + } + }; + EventPeriod.prototype.removeEventDef = function (eventDef) { + var eventDefsById = this.eventDefsById; + var eventDefs = eventDefsById[eventDef.id]; + delete this.eventDefsByUid[eventDef.uid]; + if (eventDefs) { + util_1.removeExact(eventDefs, eventDef); + if (!eventDefs.length) { + delete eventDefsById[eventDef.id]; + } + this.removeEventInstancesForDef(eventDef); + } + }; + // Event Instances + // ----------------------------------------------------------------------------------------------------------------- + EventPeriod.prototype.getEventInstances = function () { + var eventInstanceGroupsById = this.eventInstanceGroupsById; + var eventInstances = []; + var id; + for (id in eventInstanceGroupsById) { + eventInstances.push.apply(eventInstances, // append + eventInstanceGroupsById[id].eventInstances); + } + return eventInstances; + }; + EventPeriod.prototype.getEventInstancesWithId = function (eventDefId) { + var eventInstanceGroup = this.eventInstanceGroupsById[eventDefId]; + if (eventInstanceGroup) { + return eventInstanceGroup.eventInstances.slice(); // clone + } + return []; + }; + EventPeriod.prototype.getEventInstancesWithoutId = function (eventDefId) { + var eventInstanceGroupsById = this.eventInstanceGroupsById; + var matchingInstances = []; + var id; + for (id in eventInstanceGroupsById) { + if (id !== eventDefId) { + matchingInstances.push.apply(matchingInstances, // append + eventInstanceGroupsById[id].eventInstances); + } + } + return matchingInstances; + }; + EventPeriod.prototype.addEventInstance = function (eventInstance, eventDefId) { + var eventInstanceGroupsById = this.eventInstanceGroupsById; + var eventInstanceGroup = eventInstanceGroupsById[eventDefId] || + (eventInstanceGroupsById[eventDefId] = new EventInstanceGroup_1.default()); + eventInstanceGroup.eventInstances.push(eventInstance); + this.tryRelease(); + }; + EventPeriod.prototype.removeEventInstancesForDef = function (eventDef) { + var eventInstanceGroupsById = this.eventInstanceGroupsById; + var eventInstanceGroup = eventInstanceGroupsById[eventDef.id]; + var removeCnt; + if (eventInstanceGroup) { + removeCnt = util_1.removeMatching(eventInstanceGroup.eventInstances, function (currentEventInstance) { + return currentEventInstance.def === eventDef; + }); + if (!eventInstanceGroup.eventInstances.length) { + delete eventInstanceGroupsById[eventDef.id]; + } + if (removeCnt) { + this.tryRelease(); + } + } + }; + // Releasing and Freezing + // ----------------------------------------------------------------------------------------------------------------- + EventPeriod.prototype.tryRelease = function () { + if (!this.pendingCnt) { + if (!this.freezeDepth) { + this.release(); + } + else { + this.stuntedReleaseCnt++; + } + } + }; + EventPeriod.prototype.release = function () { + this.releaseCnt++; + this.trigger('release', this.eventInstanceGroupsById); + }; + EventPeriod.prototype.whenReleased = function () { + var _this = this; + if (this.releaseCnt) { + return Promise_1.default.resolve(this.eventInstanceGroupsById); + } + else { + return Promise_1.default.construct(function (onResolve) { + _this.one('release', onResolve); + }); + } + }; + EventPeriod.prototype.freeze = function () { + if (!(this.freezeDepth++)) { + this.stuntedReleaseCnt = 0; + } + }; + EventPeriod.prototype.thaw = function () { + if (!(--this.freezeDepth) && this.stuntedReleaseCnt && !this.pendingCnt) { + this.release(); + } + }; + return EventPeriod; +}()); +exports.default = EventPeriod; +EmitterMixin_1.default.mixInto(EventPeriod); + + +/***/ }), +/* 244 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var ListenerMixin_1 = __webpack_require__(7); +/* Creates a clone of an element and lets it track the mouse as it moves +----------------------------------------------------------------------------------------------------------------------*/ +var MouseFollower = /** @class */ (function () { + function MouseFollower(sourceEl, options) { + this.isFollowing = false; + this.isHidden = false; + this.isAnimating = false; // doing the revert animation? + this.options = options = options || {}; + this.sourceEl = sourceEl; + this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent + } + // Causes the element to start following the mouse + MouseFollower.prototype.start = function (ev) { + if (!this.isFollowing) { + this.isFollowing = true; + this.y0 = util_1.getEvY(ev); + this.x0 = util_1.getEvX(ev); + this.topDelta = 0; + this.leftDelta = 0; + if (!this.isHidden) { + this.updatePosition(); + } + if (util_1.getEvIsTouch(ev)) { + this.listenTo($(document), 'touchmove', this.handleMove); + } + else { + this.listenTo($(document), 'mousemove', this.handleMove); + } + } + }; + // Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position. + // `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately. + MouseFollower.prototype.stop = function (shouldRevert, callback) { + var _this = this; + var revertDuration = this.options.revertDuration; + var complete = function () { + _this.isAnimating = false; + _this.removeElement(); + _this.top0 = _this.left0 = null; // reset state for future updatePosition calls + if (callback) { + callback(); + } + }; + if (this.isFollowing && !this.isAnimating) { + this.isFollowing = false; + this.stopListeningTo($(document)); + if (shouldRevert && revertDuration && !this.isHidden) { + this.isAnimating = true; + this.el.animate({ + top: this.top0, + left: this.left0 + }, { + duration: revertDuration, + complete: complete + }); + } + else { + complete(); + } + } + }; + // Gets the tracking element. Create it if necessary + MouseFollower.prototype.getEl = function () { + var el = this.el; + if (!el) { + el = this.el = this.sourceEl.clone() + .addClass(this.options.additionalClass || '') + .css({ + position: 'absolute', + visibility: '', + display: this.isHidden ? 'none' : '', + margin: 0, + right: 'auto', + bottom: 'auto', + width: this.sourceEl.width(), + height: this.sourceEl.height(), + opacity: this.options.opacity || '', + zIndex: this.options.zIndex + }); + // we don't want long taps or any mouse interaction causing selection/menus. + // would use preventSelection(), but that prevents selectstart, causing problems. + el.addClass('fc-unselectable'); + el.appendTo(this.parentEl); + } + return el; + }; + // Removes the tracking element if it has already been created + MouseFollower.prototype.removeElement = function () { + if (this.el) { + this.el.remove(); + this.el = null; + } + }; + // Update the CSS position of the tracking element + MouseFollower.prototype.updatePosition = function () { + var sourceOffset; + var origin; + this.getEl(); // ensure this.el + // make sure origin info was computed + if (this.top0 == null) { + sourceOffset = this.sourceEl.offset(); + origin = this.el.offsetParent().offset(); + this.top0 = sourceOffset.top - origin.top; + this.left0 = sourceOffset.left - origin.left; + } + this.el.css({ + top: this.top0 + this.topDelta, + left: this.left0 + this.leftDelta + }); + }; + // Gets called when the user moves the mouse + MouseFollower.prototype.handleMove = function (ev) { + this.topDelta = util_1.getEvY(ev) - this.y0; + this.leftDelta = util_1.getEvX(ev) - this.x0; + if (!this.isHidden) { + this.updatePosition(); + } + }; + // Temporarily makes the tracking element invisible. Can be called before following starts + MouseFollower.prototype.hide = function () { + if (!this.isHidden) { + this.isHidden = true; + if (this.el) { + this.el.hide(); + } + } + }; + // Show the tracking element after it has been temporarily hidden + MouseFollower.prototype.show = function () { + if (this.isHidden) { + this.isHidden = false; + this.updatePosition(); + this.getEl().show(); + } + }; + return MouseFollower; +}()); +exports.default = MouseFollower; +ListenerMixin_1.default.mixInto(MouseFollower); + + +/***/ }), +/* 245 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var HitDragListener_1 = __webpack_require__(23); +var Interaction_1 = __webpack_require__(15); +var DateClicking = /** @class */ (function (_super) { + tslib_1.__extends(DateClicking, _super); + /* + component must implement: + - bindDateHandlerToEl + - getSafeHitFootprint + - getHitEl + */ + function DateClicking(component) { + var _this = _super.call(this, component) || this; + _this.dragListener = _this.buildDragListener(); + return _this; + } + DateClicking.prototype.end = function () { + this.dragListener.endInteraction(); + }; + DateClicking.prototype.bindToEl = function (el) { + var component = this.component; + var dragListener = this.dragListener; + component.bindDateHandlerToEl(el, 'mousedown', function (ev) { + if (!component.shouldIgnoreMouse()) { + dragListener.startInteraction(ev); + } + }); + component.bindDateHandlerToEl(el, 'touchstart', function (ev) { + if (!component.shouldIgnoreTouch()) { + dragListener.startInteraction(ev); + } + }); + }; + // Creates a listener that tracks the user's drag across day elements, for day clicking. + DateClicking.prototype.buildDragListener = function () { + var _this = this; + var component = this.component; + var dayClickHit; // null if invalid dayClick + var dragListener = new HitDragListener_1.default(component, { + scroll: this.opt('dragScroll'), + interactionStart: function () { + dayClickHit = dragListener.origHit; + }, + hitOver: function (hit, isOrig, origHit) { + // if user dragged to another cell at any point, it can no longer be a dayClick + if (!isOrig) { + dayClickHit = null; + } + }, + hitOut: function () { + dayClickHit = null; + }, + interactionEnd: function (ev, isCancelled) { + var componentFootprint; + if (!isCancelled && dayClickHit) { + componentFootprint = component.getSafeHitFootprint(dayClickHit); + if (componentFootprint) { + _this.view.triggerDayClick(componentFootprint, component.getHitEl(dayClickHit), ev); + } + } + } + }); + // because dragListener won't be called with any time delay, "dragging" will begin immediately, + // which will kill any touchmoving/scrolling. Prevent this. + dragListener.shouldCancelTouchScroll = false; + dragListener.scrollAlwaysKills = true; + return dragListener; + }; + return DateClicking; +}(Interaction_1.default)); +exports.default = DateClicking; + + +/***/ }), +/* 246 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var util_1 = __webpack_require__(4); +var EventRenderer_1 = __webpack_require__(42); +/* +Only handles foreground segs. +Does not own rendering. Use for low-level util methods by TimeGrid. +*/ +var TimeGridEventRenderer = /** @class */ (function (_super) { + tslib_1.__extends(TimeGridEventRenderer, _super); + function TimeGridEventRenderer(timeGrid, fillRenderer) { + var _this = _super.call(this, timeGrid, fillRenderer) || this; + _this.timeGrid = timeGrid; + return _this; + } + TimeGridEventRenderer.prototype.renderFgSegs = function (segs) { + this.renderFgSegsIntoContainers(segs, this.timeGrid.fgContainerEls); + }; + // Given an array of foreground segments, render a DOM element for each, computes position, + // and attaches to the column inner-container elements. + TimeGridEventRenderer.prototype.renderFgSegsIntoContainers = function (segs, containerEls) { + var segsByCol; + var col; + segsByCol = this.timeGrid.groupSegsByCol(segs); + for (col = 0; col < this.timeGrid.colCnt; col++) { + this.updateFgSegCoords(segsByCol[col]); + } + this.timeGrid.attachSegsByCol(segsByCol, containerEls); + }; + TimeGridEventRenderer.prototype.unrenderFgSegs = function () { + if (this.fgSegs) { + this.fgSegs.forEach(function (seg) { + seg.el.remove(); + }); + } + }; + // Computes a default event time formatting string if `timeFormat` is not explicitly defined + TimeGridEventRenderer.prototype.computeEventTimeFormat = function () { + return this.opt('noMeridiemTimeFormat'); // like "6:30" (no AM/PM) + }; + // Computes a default `displayEventEnd` value if one is not expliclty defined + TimeGridEventRenderer.prototype.computeDisplayEventEnd = function () { + return true; + }; + // Renders the HTML for a single event segment's default rendering + TimeGridEventRenderer.prototype.fgSegHtml = function (seg, disableResizing) { + var view = this.view; + var calendar = view.calendar; + var componentFootprint = seg.footprint.componentFootprint; + var isAllDay = componentFootprint.isAllDay; + var eventDef = seg.footprint.eventDef; + var isDraggable = view.isEventDefDraggable(eventDef); + var isResizableFromStart = !disableResizing && seg.isStart && view.isEventDefResizableFromStart(eventDef); + var isResizableFromEnd = !disableResizing && seg.isEnd && view.isEventDefResizableFromEnd(eventDef); + var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd); + var skinCss = util_1.cssToStr(this.getSkinCss(eventDef)); + var timeText; + var fullTimeText; // more verbose time text. for the print stylesheet + var startTimeText; // just the start time text + classes.unshift('fc-time-grid-event', 'fc-v-event'); + // if the event appears to span more than one day... + if (view.isMultiDayRange(componentFootprint.unzonedRange)) { + // Don't display time text on segments that run entirely through a day. + // That would appear as midnight-midnight and would look dumb. + // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am) + if (seg.isStart || seg.isEnd) { + var zonedStart = calendar.msToMoment(seg.startMs); + var zonedEnd = calendar.msToMoment(seg.endMs); + timeText = this._getTimeText(zonedStart, zonedEnd, isAllDay); + fullTimeText = this._getTimeText(zonedStart, zonedEnd, isAllDay, 'LT'); + startTimeText = this._getTimeText(zonedStart, zonedEnd, isAllDay, null, false); // displayEnd=false + } + } + else { + // Display the normal time text for the *event's* times + timeText = this.getTimeText(seg.footprint); + fullTimeText = this.getTimeText(seg.footprint, 'LT'); + startTimeText = this.getTimeText(seg.footprint, null, false); // displayEnd=false + } + return '' + + '' + + (timeText ? + '' + + '' + util_1.htmlEscape(timeText) + '' + + '' : + '') + + (eventDef.title ? + '' + + util_1.htmlEscape(eventDef.title) + + '' : + '') + + '' + + '' + + /* TODO: write CSS for this + (isResizableFromStart ? + '' : + '' + ) + + */ + (isResizableFromEnd ? + '' : + '') + + ''; + }; + // Given segments that are assumed to all live in the *same column*, + // compute their verical/horizontal coordinates and assign to their elements. + TimeGridEventRenderer.prototype.updateFgSegCoords = function (segs) { + this.timeGrid.computeSegVerticals(segs); // horizontals relies on this + this.computeFgSegHorizontals(segs); // compute horizontal coordinates, z-index's, and reorder the array + this.timeGrid.assignSegVerticals(segs); + this.assignFgSegHorizontals(segs); + }; + // Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each. + // NOTE: Also reorders the given array by date! + TimeGridEventRenderer.prototype.computeFgSegHorizontals = function (segs) { + var levels; + var level0; + var i; + this.sortEventSegs(segs); // order by certain criteria + levels = buildSlotSegLevels(segs); + computeForwardSlotSegs(levels); + if ((level0 = levels[0])) { + for (i = 0; i < level0.length; i++) { + computeSlotSegPressures(level0[i]); + } + for (i = 0; i < level0.length; i++) { + this.computeFgSegForwardBack(level0[i], 0, 0); + } + } + }; + // Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range + // from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and + // seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left. + // + // The segment might be part of a "series", which means consecutive segments with the same pressure + // who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of + // segments behind this one in the current series, and `seriesBackwardCoord` is the starting + // coordinate of the first segment in the series. + TimeGridEventRenderer.prototype.computeFgSegForwardBack = function (seg, seriesBackwardPressure, seriesBackwardCoord) { + var forwardSegs = seg.forwardSegs; + var i; + if (seg.forwardCoord === undefined) { + if (!forwardSegs.length) { + // if there are no forward segments, this segment should butt up against the edge + seg.forwardCoord = 1; + } + else { + // sort highest pressure first + this.sortForwardSegs(forwardSegs); + // this segment's forwardCoord will be calculated from the backwardCoord of the + // highest-pressure forward segment. + this.computeFgSegForwardBack(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord); + seg.forwardCoord = forwardSegs[0].backwardCoord; + } + // calculate the backwardCoord from the forwardCoord. consider the series + seg.backwardCoord = seg.forwardCoord - + (seg.forwardCoord - seriesBackwardCoord) / // available width for series + (seriesBackwardPressure + 1); // # of segments in the series + // use this segment's coordinates to computed the coordinates of the less-pressurized + // forward segments + for (i = 0; i < forwardSegs.length; i++) { + this.computeFgSegForwardBack(forwardSegs[i], 0, seg.forwardCoord); + } + } + }; + TimeGridEventRenderer.prototype.sortForwardSegs = function (forwardSegs) { + forwardSegs.sort(util_1.proxy(this, 'compareForwardSegs')); + }; + // A cmp function for determining which forward segment to rely on more when computing coordinates. + TimeGridEventRenderer.prototype.compareForwardSegs = function (seg1, seg2) { + // put higher-pressure first + return seg2.forwardPressure - seg1.forwardPressure || + // put segments that are closer to initial edge first (and favor ones with no coords yet) + (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) || + // do normal sorting... + this.compareEventSegs(seg1, seg2); + }; + // Given foreground event segments that have already had their position coordinates computed, + // assigns position-related CSS values to their elements. + TimeGridEventRenderer.prototype.assignFgSegHorizontals = function (segs) { + var i; + var seg; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + seg.el.css(this.generateFgSegHorizontalCss(seg)); + // if the height is short, add a className for alternate styling + if (seg.bottom - seg.top < 30) { + seg.el.addClass('fc-short'); + } + } + }; + // Generates an object with CSS properties/values that should be applied to an event segment element. + // Contains important positioning-related properties that should be applied to any event element, customized or not. + TimeGridEventRenderer.prototype.generateFgSegHorizontalCss = function (seg) { + var shouldOverlap = this.opt('slotEventOverlap'); + var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point + var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point + var props = this.timeGrid.generateSegVerticalCss(seg); // get top/bottom first + var isRTL = this.timeGrid.isRTL; + var left; // amount of space from left edge, a fraction of the total width + var right; // amount of space from right edge, a fraction of the total width + if (shouldOverlap) { + // double the width, but don't go beyond the maximum forward coordinate (1.0) + forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2); + } + if (isRTL) { + left = 1 - forwardCoord; + right = backwardCoord; + } + else { + left = backwardCoord; + right = 1 - forwardCoord; + } + props.zIndex = seg.level + 1; // convert from 0-base to 1-based + props.left = left * 100 + '%'; + props.right = right * 100 + '%'; + if (shouldOverlap && seg.forwardPressure) { + // add padding to the edge so that forward stacked events don't cover the resizer's icon + props[isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width + } + return props; + }; + return TimeGridEventRenderer; +}(EventRenderer_1.default)); +exports.default = TimeGridEventRenderer; +// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is +// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date. +function buildSlotSegLevels(segs) { + var levels = []; + var i; + var seg; + var j; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + // go through all the levels and stop on the first level where there are no collisions + for (j = 0; j < levels.length; j++) { + if (!computeSlotSegCollisions(seg, levels[j]).length) { + break; + } + } + seg.level = j; + (levels[j] || (levels[j] = [])).push(seg); + } + return levels; +} +// For every segment, figure out the other segments that are in subsequent +// levels that also occupy the same vertical space. Accumulate in seg.forwardSegs +function computeForwardSlotSegs(levels) { + var i; + var level; + var j; + var seg; + var k; + for (i = 0; i < levels.length; i++) { + level = levels[i]; + for (j = 0; j < level.length; j++) { + seg = level[j]; + seg.forwardSegs = []; + for (k = i + 1; k < levels.length; k++) { + computeSlotSegCollisions(seg, levels[k], seg.forwardSegs); + } + } + } +} +// Figure out which path forward (via seg.forwardSegs) results in the longest path until +// the furthest edge is reached. The number of segments in this path will be seg.forwardPressure +function computeSlotSegPressures(seg) { + var forwardSegs = seg.forwardSegs; + var forwardPressure = 0; + var i; + var forwardSeg; + if (seg.forwardPressure === undefined) { + for (i = 0; i < forwardSegs.length; i++) { + forwardSeg = forwardSegs[i]; + // figure out the child's maximum forward path + computeSlotSegPressures(forwardSeg); + // either use the existing maximum, or use the child's forward pressure + // plus one (for the forwardSeg itself) + forwardPressure = Math.max(forwardPressure, 1 + forwardSeg.forwardPressure); + } + seg.forwardPressure = forwardPressure; + } +} +// Find all the segments in `otherSegs` that vertically collide with `seg`. +// Append into an optionally-supplied `results` array and return. +function computeSlotSegCollisions(seg, otherSegs, results) { + if (results === void 0) { results = []; } + for (var i = 0; i < otherSegs.length; i++) { + if (isSlotSegCollision(seg, otherSegs[i])) { + results.push(otherSegs[i]); + } + } + return results; +} +// Do these segments occupy the same vertical space? +function isSlotSegCollision(seg1, seg2) { + return seg1.bottom > seg2.top && seg1.top < seg2.bottom; +} + + +/***/ }), +/* 247 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var HelperRenderer_1 = __webpack_require__(58); +var TimeGridHelperRenderer = /** @class */ (function (_super) { + tslib_1.__extends(TimeGridHelperRenderer, _super); + function TimeGridHelperRenderer() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeGridHelperRenderer.prototype.renderSegs = function (segs, sourceSeg) { + var helperNodes = []; + var i; + var seg; + var sourceEl; + // TODO: not good to call eventRenderer this way + this.eventRenderer.renderFgSegsIntoContainers(segs, this.component.helperContainerEls); + // Try to make the segment that is in the same row as sourceSeg look the same + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + if (sourceSeg && sourceSeg.col === seg.col) { + sourceEl = sourceSeg.el; + seg.el.css({ + left: sourceEl.css('left'), + right: sourceEl.css('right'), + 'margin-left': sourceEl.css('margin-left'), + 'margin-right': sourceEl.css('margin-right') + }); + } + helperNodes.push(seg.el[0]); + } + return $(helperNodes); // must return the elements rendered + }; + return TimeGridHelperRenderer; +}(HelperRenderer_1.default)); +exports.default = TimeGridHelperRenderer; + + +/***/ }), +/* 248 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var FillRenderer_1 = __webpack_require__(57); +var TimeGridFillRenderer = /** @class */ (function (_super) { + tslib_1.__extends(TimeGridFillRenderer, _super); + function TimeGridFillRenderer() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeGridFillRenderer.prototype.attachSegEls = function (type, segs) { + var timeGrid = this.component; + var containerEls; + // TODO: more efficient lookup + if (type === 'bgEvent') { + containerEls = timeGrid.bgContainerEls; + } + else if (type === 'businessHours') { + containerEls = timeGrid.businessContainerEls; + } + else if (type === 'highlight') { + containerEls = timeGrid.highlightContainerEls; + } + timeGrid.updateSegVerticals(segs); + timeGrid.attachSegsByCol(timeGrid.groupSegsByCol(segs), containerEls); + return segs.map(function (seg) { + return seg.el[0]; + }); + }; + return TimeGridFillRenderer; +}(FillRenderer_1.default)); +exports.default = TimeGridFillRenderer; + + +/***/ }), +/* 249 */ +/***/ (function(module, exports, __webpack_require__) { + +/* A rectangular panel that is absolutely positioned over other content +------------------------------------------------------------------------------------------------------------------------ +Options: + - className (string) + - content (HTML string or jQuery element set) + - parentEl + - top + - left + - right (the x coord of where the right edge should be. not a "CSS" right) + - autoHide (boolean) + - show (callback) + - hide (callback) +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var ListenerMixin_1 = __webpack_require__(7); +var Popover = /** @class */ (function () { + function Popover(options) { + this.isHidden = true; + this.margin = 10; // the space required between the popover and the edges of the scroll container + this.options = options || {}; + } + // Shows the popover on the specified position. Renders it if not already + Popover.prototype.show = function () { + if (this.isHidden) { + if (!this.el) { + this.render(); + } + this.el.show(); + this.position(); + this.isHidden = false; + this.trigger('show'); + } + }; + // Hides the popover, through CSS, but does not remove it from the DOM + Popover.prototype.hide = function () { + if (!this.isHidden) { + this.el.hide(); + this.isHidden = true; + this.trigger('hide'); + } + }; + // Creates `this.el` and renders content inside of it + Popover.prototype.render = function () { + var _this = this; + var options = this.options; + this.el = $('') + .addClass(options.className || '') + .css({ + // position initially to the top left to avoid creating scrollbars + top: 0, + left: 0 + }) + .append(options.content) + .appendTo(options.parentEl); + // when a click happens on anything inside with a 'fc-close' className, hide the popover + this.el.on('click', '.fc-close', function () { + _this.hide(); + }); + if (options.autoHide) { + this.listenTo($(document), 'mousedown', this.documentMousedown); + } + }; + // Triggered when the user clicks *anywhere* in the document, for the autoHide feature + Popover.prototype.documentMousedown = function (ev) { + // only hide the popover if the click happened outside the popover + if (this.el && !$(ev.target).closest(this.el).length) { + this.hide(); + } + }; + // Hides and unregisters any handlers + Popover.prototype.removeElement = function () { + this.hide(); + if (this.el) { + this.el.remove(); + this.el = null; + } + this.stopListeningTo($(document), 'mousedown'); + }; + // Positions the popover optimally, using the top/left/right options + Popover.prototype.position = function () { + var options = this.options; + var origin = this.el.offsetParent().offset(); + var width = this.el.outerWidth(); + var height = this.el.outerHeight(); + var windowEl = $(window); + var viewportEl = util_1.getScrollParent(this.el); + var viewportTop; + var viewportLeft; + var viewportOffset; + var top; // the "position" (not "offset") values for the popover + var left; // + // compute top and left + top = options.top || 0; + if (options.left !== undefined) { + left = options.left; + } + else if (options.right !== undefined) { + left = options.right - width; // derive the left value from the right value + } + else { + left = 0; + } + if (viewportEl.is(window) || viewportEl.is(document)) { + viewportEl = windowEl; + viewportTop = 0; // the window is always at the top left + viewportLeft = 0; // (and .offset() won't work if called here) + } + else { + viewportOffset = viewportEl.offset(); + viewportTop = viewportOffset.top; + viewportLeft = viewportOffset.left; + } + // if the window is scrolled, it causes the visible area to be further down + viewportTop += windowEl.scrollTop(); + viewportLeft += windowEl.scrollLeft(); + // constrain to the view port. if constrained by two edges, give precedence to top/left + if (options.viewportConstrain !== false) { + top = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin); + top = Math.max(top, viewportTop + this.margin); + left = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin); + left = Math.max(left, viewportLeft + this.margin); + } + this.el.css({ + top: top - origin.top, + left: left - origin.left + }); + }; + // Triggers a callback. Calls a function in the option hash of the same name. + // Arguments beyond the first `name` are forwarded on. + // TODO: better code reuse for this. Repeat code + Popover.prototype.trigger = function (name) { + if (this.options[name]) { + this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); + } + }; + return Popover; +}()); +exports.default = Popover; +ListenerMixin_1.default.mixInto(Popover); + + +/***/ }), +/* 250 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var EventRenderer_1 = __webpack_require__(42); +/* Event-rendering methods for the DayGrid class +----------------------------------------------------------------------------------------------------------------------*/ +var DayGridEventRenderer = /** @class */ (function (_super) { + tslib_1.__extends(DayGridEventRenderer, _super); + function DayGridEventRenderer(dayGrid, fillRenderer) { + var _this = _super.call(this, dayGrid, fillRenderer) || this; + _this.dayGrid = dayGrid; + return _this; + } + DayGridEventRenderer.prototype.renderBgRanges = function (eventRanges) { + // don't render timed background events + eventRanges = $.grep(eventRanges, function (eventRange) { + return eventRange.eventDef.isAllDay(); + }); + _super.prototype.renderBgRanges.call(this, eventRanges); + }; + // Renders the given foreground event segments onto the grid + DayGridEventRenderer.prototype.renderFgSegs = function (segs) { + var rowStructs = this.rowStructs = this.renderSegRows(segs); + // append to each row's content skeleton + this.dayGrid.rowEls.each(function (i, rowNode) { + $(rowNode).find('.fc-content-skeleton > table').append(rowStructs[i].tbodyEl); + }); + }; + // Unrenders all currently rendered foreground event segments + DayGridEventRenderer.prototype.unrenderFgSegs = function () { + var rowStructs = this.rowStructs || []; + var rowStruct; + while ((rowStruct = rowStructs.pop())) { + rowStruct.tbodyEl.remove(); + } + this.rowStructs = null; + }; + // Uses the given events array to generate elements that should be appended to each row's content skeleton. + // Returns an array of rowStruct objects (see the bottom of `renderSegRow`). + // PRECONDITION: each segment shoud already have a rendered and assigned `.el` + DayGridEventRenderer.prototype.renderSegRows = function (segs) { + var rowStructs = []; + var segRows; + var row; + segRows = this.groupSegRows(segs); // group into nested arrays + // iterate each row of segment groupings + for (row = 0; row < segRows.length; row++) { + rowStructs.push(this.renderSegRow(row, segRows[row])); + } + return rowStructs; + }; + // Given a row # and an array of segments all in the same row, render a element, a skeleton that contains + // the segments. Returns object with a bunch of internal data about how the render was calculated. + // NOTE: modifies rowSegs + DayGridEventRenderer.prototype.renderSegRow = function (row, rowSegs) { + var colCnt = this.dayGrid.colCnt; + var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels + var levelCnt = Math.max(1, segLevels.length); // ensure at least one level + var tbody = $(''); + var segMatrix = []; // lookup for which segments are rendered into which level+col cells + var cellMatrix = []; // lookup for all elements of the level+col matrix + var loneCellMatrix = []; // lookup for elements that only take up a single column + var i; + var levelSegs; + var col; + var tr; + var j; + var seg; + var td; + // populates empty cells from the current column (`col`) to `endCol` + function emptyCellsUntil(endCol) { + while (col < endCol) { + // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell + td = (loneCellMatrix[i - 1] || [])[col]; + if (td) { + td.attr('rowspan', parseInt(td.attr('rowspan') || 1, 10) + 1); + } + else { + td = $(''); + tr.append(td); + } + cellMatrix[i][col] = td; + loneCellMatrix[i][col] = td; + col++; + } + } + for (i = 0; i < levelCnt; i++) { + levelSegs = segLevels[i]; + col = 0; + tr = $(''); + segMatrix.push([]); + cellMatrix.push([]); + loneCellMatrix.push([]); + // levelCnt might be 1 even though there are no actual levels. protect against this. + // this single empty row is useful for styling. + if (levelSegs) { + for (j = 0; j < levelSegs.length; j++) { + seg = levelSegs[j]; + emptyCellsUntil(seg.leftCol); + // create a container that occupies or more columns. append the event element. + td = $('').append(seg.el); + if (seg.leftCol !== seg.rightCol) { + td.attr('colspan', seg.rightCol - seg.leftCol + 1); + } + else { + loneCellMatrix[i][col] = td; + } + while (col <= seg.rightCol) { + cellMatrix[i][col] = td; + segMatrix[i][col] = seg; + col++; + } + tr.append(td); + } + } + emptyCellsUntil(colCnt); // finish off the row + this.dayGrid.bookendCells(tr); + tbody.append(tr); + } + return { + row: row, + tbodyEl: tbody, + cellMatrix: cellMatrix, + segMatrix: segMatrix, + segLevels: segLevels, + segs: rowSegs + }; + }; + // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels. + // NOTE: modifies segs + DayGridEventRenderer.prototype.buildSegLevels = function (segs) { + var levels = []; + var i; + var seg; + var j; + // Give preference to elements with certain criteria, so they have + // a chance to be closer to the top. + this.sortEventSegs(segs); + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + // loop through levels, starting with the topmost, until the segment doesn't collide with other segments + for (j = 0; j < levels.length; j++) { + if (!isDaySegCollision(seg, levels[j])) { + break; + } + } + // `j` now holds the desired subrow index + seg.level = j; + // create new level array if needed and append segment + (levels[j] || (levels[j] = [])).push(seg); + } + // order segments left-to-right. very important if calendar is RTL + for (j = 0; j < levels.length; j++) { + levels[j].sort(compareDaySegCols); + } + return levels; + }; + // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row + DayGridEventRenderer.prototype.groupSegRows = function (segs) { + var segRows = []; + var i; + for (i = 0; i < this.dayGrid.rowCnt; i++) { + segRows.push([]); + } + for (i = 0; i < segs.length; i++) { + segRows[segs[i].row].push(segs[i]); + } + return segRows; + }; + // Computes a default event time formatting string if `timeFormat` is not explicitly defined + DayGridEventRenderer.prototype.computeEventTimeFormat = function () { + return this.opt('extraSmallTimeFormat'); // like "6p" or "6:30p" + }; + // Computes a default `displayEventEnd` value if one is not expliclty defined + DayGridEventRenderer.prototype.computeDisplayEventEnd = function () { + return this.dayGrid.colCnt === 1; // we'll likely have space if there's only one day + }; + // Builds the HTML to be used for the default element for an individual segment + DayGridEventRenderer.prototype.fgSegHtml = function (seg, disableResizing) { + var view = this.view; + var eventDef = seg.footprint.eventDef; + var isAllDay = seg.footprint.componentFootprint.isAllDay; + var isDraggable = view.isEventDefDraggable(eventDef); + var isResizableFromStart = !disableResizing && isAllDay && + seg.isStart && view.isEventDefResizableFromStart(eventDef); + var isResizableFromEnd = !disableResizing && isAllDay && + seg.isEnd && view.isEventDefResizableFromEnd(eventDef); + var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd); + var skinCss = util_1.cssToStr(this.getSkinCss(eventDef)); + var timeHtml = ''; + var timeText; + var titleHtml; + classes.unshift('fc-day-grid-event', 'fc-h-event'); + // Only display a timed events time if it is the starting segment + if (seg.isStart) { + timeText = this.getTimeText(seg.footprint); + if (timeText) { + timeHtml = '' + util_1.htmlEscape(timeText) + ''; + } + } + titleHtml = + '' + + (util_1.htmlEscape(eventDef.title || '') || ' ') + // we always want one line of height + ''; + return '' + + '' + + (this.dayGrid.isRTL ? + titleHtml + ' ' + timeHtml : // put a natural space in between + timeHtml + ' ' + titleHtml // + ) + + '' + + (isResizableFromStart ? + '' : + '') + + (isResizableFromEnd ? + '' : + '') + + ''; + }; + return DayGridEventRenderer; +}(EventRenderer_1.default)); +exports.default = DayGridEventRenderer; +// Computes whether two segments' columns collide. They are assumed to be in the same row. +function isDaySegCollision(seg, otherSegs) { + var i; + var otherSeg; + for (i = 0; i < otherSegs.length; i++) { + otherSeg = otherSegs[i]; + if (otherSeg.leftCol <= seg.rightCol && + otherSeg.rightCol >= seg.leftCol) { + return true; + } + } + return false; +} +// A cmp function for determining the leftmost event +function compareDaySegCols(a, b) { + return a.leftCol - b.leftCol; +} + + +/***/ }), +/* 251 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var HelperRenderer_1 = __webpack_require__(58); +var DayGridHelperRenderer = /** @class */ (function (_super) { + tslib_1.__extends(DayGridHelperRenderer, _super); + function DayGridHelperRenderer() { + return _super !== null && _super.apply(this, arguments) || this; + } + // Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null. + DayGridHelperRenderer.prototype.renderSegs = function (segs, sourceSeg) { + var helperNodes = []; + var rowStructs; + // TODO: not good to call eventRenderer this way + rowStructs = this.eventRenderer.renderSegRows(segs); + // inject each new event skeleton into each associated row + this.component.rowEls.each(function (row, rowNode) { + var rowEl = $(rowNode); // the .fc-row + var skeletonEl = $(''); // will be absolutely positioned + var skeletonTopEl; + var skeletonTop; + // If there is an original segment, match the top position. Otherwise, put it at the row's top level + if (sourceSeg && sourceSeg.row === row) { + skeletonTop = sourceSeg.el.position().top; + } + else { + skeletonTopEl = rowEl.find('.fc-content-skeleton tbody'); + if (!skeletonTopEl.length) { + skeletonTopEl = rowEl.find('.fc-content-skeleton table'); + } + skeletonTop = skeletonTopEl.position().top; + } + skeletonEl.css('top', skeletonTop) + .find('table') + .append(rowStructs[row].tbodyEl); + rowEl.append(skeletonEl); + helperNodes.push(skeletonEl[0]); + }); + return $(helperNodes); // must return the elements rendered + }; + return DayGridHelperRenderer; +}(HelperRenderer_1.default)); +exports.default = DayGridHelperRenderer; + + +/***/ }), +/* 252 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var FillRenderer_1 = __webpack_require__(57); +var DayGridFillRenderer = /** @class */ (function (_super) { + tslib_1.__extends(DayGridFillRenderer, _super); + function DayGridFillRenderer() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.fillSegTag = 'td'; // override the default tag name + return _this; + } + DayGridFillRenderer.prototype.attachSegEls = function (type, segs) { + var nodes = []; + var i; + var seg; + var skeletonEl; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + skeletonEl = this.renderFillRow(type, seg); + this.component.rowEls.eq(seg.row).append(skeletonEl); + nodes.push(skeletonEl[0]); + } + return nodes; + }; + // Generates the HTML needed for one row of a fill. Requires the seg's el to be rendered. + DayGridFillRenderer.prototype.renderFillRow = function (type, seg) { + var colCnt = this.component.colCnt; + var startCol = seg.leftCol; + var endCol = seg.rightCol + 1; + var className; + var skeletonEl; + var trEl; + if (type === 'businessHours') { + className = 'bgevent'; + } + else { + className = type.toLowerCase(); + } + skeletonEl = $('' + + '' + + ''); + trEl = skeletonEl.find('tr'); + if (startCol > 0) { + trEl.append(''); + } + trEl.append(seg.el.attr('colspan', endCol - startCol)); + if (endCol < colCnt) { + trEl.append(''); + } + this.component.bookendCells(trEl); + return skeletonEl; + }; + return DayGridFillRenderer; +}(FillRenderer_1.default)); +exports.default = DayGridFillRenderer; + + +/***/ }), +/* 253 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var BasicViewDateProfileGenerator_1 = __webpack_require__(228); +var UnzonedRange_1 = __webpack_require__(5); +var MonthViewDateProfileGenerator = /** @class */ (function (_super) { + tslib_1.__extends(MonthViewDateProfileGenerator, _super); + function MonthViewDateProfileGenerator() { + return _super !== null && _super.apply(this, arguments) || this; + } + // Computes the date range that will be rendered. + MonthViewDateProfileGenerator.prototype.buildRenderRange = function (currentUnzonedRange, currentRangeUnit, isRangeAllDay) { + var renderUnzonedRange = _super.prototype.buildRenderRange.call(this, currentUnzonedRange, currentRangeUnit, isRangeAllDay); + var start = this.msToUtcMoment(renderUnzonedRange.startMs, isRangeAllDay); + var end = this.msToUtcMoment(renderUnzonedRange.endMs, isRangeAllDay); + var rowCnt; + // ensure 6 weeks + if (this.opt('fixedWeekCount')) { + rowCnt = Math.ceil(// could be partial weeks due to hiddenDays + end.diff(start, 'weeks', true) // dontRound=true + ); + end.add(6 - rowCnt, 'weeks'); + } + return new UnzonedRange_1.default(start, end); + }; + return MonthViewDateProfileGenerator; +}(BasicViewDateProfileGenerator_1.default)); +exports.default = MonthViewDateProfileGenerator; + + +/***/ }), +/* 254 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var util_1 = __webpack_require__(4); +var EventRenderer_1 = __webpack_require__(42); +var ListEventRenderer = /** @class */ (function (_super) { + tslib_1.__extends(ListEventRenderer, _super); + function ListEventRenderer() { + return _super !== null && _super.apply(this, arguments) || this; + } + ListEventRenderer.prototype.renderFgSegs = function (segs) { + if (!segs.length) { + this.component.renderEmptyMessage(); + } + else { + this.component.renderSegList(segs); + } + }; + // generates the HTML for a single event row + ListEventRenderer.prototype.fgSegHtml = function (seg) { + var view = this.view; + var calendar = view.calendar; + var theme = calendar.theme; + var eventFootprint = seg.footprint; + var eventDef = eventFootprint.eventDef; + var componentFootprint = eventFootprint.componentFootprint; + var url = eventDef.url; + var classes = ['fc-list-item'].concat(this.getClasses(eventDef)); + var bgColor = this.getBgColor(eventDef); + var timeHtml; + if (componentFootprint.isAllDay) { + timeHtml = view.getAllDayHtml(); + } + else if (view.isMultiDayRange(componentFootprint.unzonedRange)) { + if (seg.isStart || seg.isEnd) { + timeHtml = util_1.htmlEscape(this._getTimeText(calendar.msToMoment(seg.startMs), calendar.msToMoment(seg.endMs), componentFootprint.isAllDay)); + } + else { + timeHtml = view.getAllDayHtml(); + } + } + else { + // Display the normal time text for the *event's* times + timeHtml = util_1.htmlEscape(this.getTimeText(eventFootprint)); + } + if (url) { + classes.push('fc-has-url'); + } + return '' + + (this.displayEventTime ? + '' + + (timeHtml || '') + + '' : + '') + + '' + + '' + + '' + + '' + + '' + + util_1.htmlEscape(eventDef.title || '') + + '' + + '' + + ''; + }; + // like "4:00am" + ListEventRenderer.prototype.computeEventTimeFormat = function () { + return this.opt('mediumTimeFormat'); + }; + return ListEventRenderer; +}(EventRenderer_1.default)); +exports.default = ListEventRenderer; + + +/***/ }), +/* 255 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var EventPointing_1 = __webpack_require__(59); +var ListEventPointing = /** @class */ (function (_super) { + tslib_1.__extends(ListEventPointing, _super); + function ListEventPointing() { + return _super !== null && _super.apply(this, arguments) || this; + } + // for events with a url, the whole should be clickable, + // but it's impossible to wrap with an tag. simulate this. + ListEventPointing.prototype.handleClick = function (seg, ev) { + var url; + _super.prototype.handleClick.call(this, seg, ev); // might prevent the default action + // not clicking on or within an with an href + if (!$(ev.target).closest('a[href]').length) { + url = seg.footprint.eventDef.url; + if (url && !ev.isDefaultPrevented()) { + window.location.href = url; // simulate link click + } + } + }; + return ListEventPointing; +}(EventPointing_1.default)); +exports.default = ListEventPointing; + + +/***/ }), +/* 256 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var EventSourceParser_1 = __webpack_require__(38); +var ArrayEventSource_1 = __webpack_require__(52); +var FuncEventSource_1 = __webpack_require__(215); +var JsonFeedEventSource_1 = __webpack_require__(216); +EventSourceParser_1.default.registerClass(ArrayEventSource_1.default); +EventSourceParser_1.default.registerClass(FuncEventSource_1.default); +EventSourceParser_1.default.registerClass(JsonFeedEventSource_1.default); + + +/***/ }), +/* 257 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var ThemeRegistry_1 = __webpack_require__(51); +var StandardTheme_1 = __webpack_require__(213); +var JqueryUiTheme_1 = __webpack_require__(214); +var Bootstrap3Theme_1 = __webpack_require__(258); +var Bootstrap4Theme_1 = __webpack_require__(259); +ThemeRegistry_1.defineThemeSystem('standard', StandardTheme_1.default); +ThemeRegistry_1.defineThemeSystem('jquery-ui', JqueryUiTheme_1.default); +ThemeRegistry_1.defineThemeSystem('bootstrap3', Bootstrap3Theme_1.default); +ThemeRegistry_1.defineThemeSystem('bootstrap4', Bootstrap4Theme_1.default); + + +/***/ }), +/* 258 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var Theme_1 = __webpack_require__(19); +var Bootstrap3Theme = /** @class */ (function (_super) { + tslib_1.__extends(Bootstrap3Theme, _super); + function Bootstrap3Theme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return Bootstrap3Theme; +}(Theme_1.default)); +exports.default = Bootstrap3Theme; +Bootstrap3Theme.prototype.classes = { + widget: 'fc-bootstrap3', + tableGrid: 'table-bordered', + tableList: 'table', + tableListHeading: 'active', + buttonGroup: 'btn-group', + button: 'btn btn-default', + stateActive: 'active', + stateDisabled: 'disabled', + today: 'alert alert-info', + popover: 'panel panel-default', + popoverHeader: 'panel-heading', + popoverContent: 'panel-body', + // day grid + // for left/right border color when border is inset from edges (all-day in agenda view) + // avoid `panel` class b/c don't want margins/radius. only border color. + headerRow: 'panel-default', + dayRow: 'panel-default', + // list view + listView: 'panel panel-default' +}; +Bootstrap3Theme.prototype.baseIconClass = 'glyphicon'; +Bootstrap3Theme.prototype.iconClasses = { + close: 'glyphicon-remove', + prev: 'glyphicon-chevron-left', + next: 'glyphicon-chevron-right', + prevYear: 'glyphicon-backward', + nextYear: 'glyphicon-forward' +}; +Bootstrap3Theme.prototype.iconOverrideOption = 'bootstrapGlyphicons'; +Bootstrap3Theme.prototype.iconOverrideCustomButtonOption = 'bootstrapGlyphicon'; +Bootstrap3Theme.prototype.iconOverridePrefix = 'glyphicon-'; + + +/***/ }), +/* 259 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var Theme_1 = __webpack_require__(19); +var Bootstrap4Theme = /** @class */ (function (_super) { + tslib_1.__extends(Bootstrap4Theme, _super); + function Bootstrap4Theme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return Bootstrap4Theme; +}(Theme_1.default)); +exports.default = Bootstrap4Theme; +Bootstrap4Theme.prototype.classes = { + widget: 'fc-bootstrap4', + tableGrid: 'table-bordered', + tableList: 'table', + tableListHeading: 'table-active', + buttonGroup: 'btn-group', + button: 'btn btn-primary', + stateActive: 'active', + stateDisabled: 'disabled', + today: 'alert alert-info', + popover: 'card card-primary', + popoverHeader: 'card-header', + popoverContent: 'card-body', + // day grid + // for left/right border color when border is inset from edges (all-day in agenda view) + // avoid `table` class b/c don't want margins/padding/structure. only border color. + headerRow: 'table-bordered', + dayRow: 'table-bordered', + // list view + listView: 'card card-primary' +}; +Bootstrap4Theme.prototype.baseIconClass = 'fa'; +Bootstrap4Theme.prototype.iconClasses = { + close: 'fa-times', + prev: 'fa-chevron-left', + next: 'fa-chevron-right', + prevYear: 'fa-angle-double-left', + nextYear: 'fa-angle-double-right' +}; +Bootstrap4Theme.prototype.iconOverrideOption = 'bootstrapFontAwesome'; +Bootstrap4Theme.prototype.iconOverrideCustomButtonOption = 'bootstrapFontAwesome'; +Bootstrap4Theme.prototype.iconOverridePrefix = 'fa-'; + + +/***/ }), +/* 260 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var ViewRegistry_1 = __webpack_require__(22); +var BasicView_1 = __webpack_require__(62); +var MonthView_1 = __webpack_require__(229); +ViewRegistry_1.defineView('basic', { + 'class': BasicView_1.default +}); +ViewRegistry_1.defineView('basicDay', { + type: 'basic', + duration: { days: 1 } +}); +ViewRegistry_1.defineView('basicWeek', { + type: 'basic', + duration: { weeks: 1 } +}); +ViewRegistry_1.defineView('month', { + 'class': MonthView_1.default, + duration: { months: 1 }, + defaults: { + fixedWeekCount: true + } +}); + + +/***/ }), +/* 261 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var ViewRegistry_1 = __webpack_require__(22); +var AgendaView_1 = __webpack_require__(226); +ViewRegistry_1.defineView('agenda', { + 'class': AgendaView_1.default, + defaults: { + allDaySlot: true, + slotDuration: '00:30:00', + slotEventOverlap: true // a bad name. confused with overlap/constraint system + } +}); +ViewRegistry_1.defineView('agendaDay', { + type: 'agenda', + duration: { days: 1 } +}); +ViewRegistry_1.defineView('agendaWeek', { + type: 'agenda', + duration: { weeks: 1 } +}); + + +/***/ }), +/* 262 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var ViewRegistry_1 = __webpack_require__(22); +var ListView_1 = __webpack_require__(230); +ViewRegistry_1.defineView('list', { + 'class': ListView_1.default, + buttonTextKey: 'list', + defaults: { + buttonText: 'list', + listDayFormat: 'LL', + noEventsMessage: 'No events to display' + } +}); +ViewRegistry_1.defineView('listDay', { + type: 'list', + duration: { days: 1 }, + defaults: { + listDayFormat: 'dddd' // day-of-week is all we need. full date is probably in header + } +}); +ViewRegistry_1.defineView('listWeek', { + type: 'list', + duration: { weeks: 1 }, + defaults: { + listDayFormat: 'dddd', + listDayAltFormat: 'LL' + } +}); +ViewRegistry_1.defineView('listMonth', { + type: 'list', + duration: { month: 1 }, + defaults: { + listDayAltFormat: 'dddd' // day-of-week is nice-to-have + } +}); +ViewRegistry_1.defineView('listYear', { + type: 'list', + duration: { year: 1 }, + defaults: { + listDayAltFormat: 'dddd' // day-of-week is nice-to-have + } +}); + + +/***/ }), +/* 263 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); + + +/***/ }) +/******/ ]); +}); \ No newline at end of file diff --git a/public/lib/fc/fullcalendar.min.css b/public/lib/fc/fullcalendar.min.css new file mode 100644 index 0000000000..cf86d29318 --- /dev/null +++ b/public/lib/fc/fullcalendar.min.css @@ -0,0 +1,5 @@ +/*! + * FullCalendar v3.9.0 + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */.fc button,.fc table,body .fc{font-size:1em}.fc-bg,.fc-row .fc-bgevent-skeleton,.fc-row .fc-highlight-skeleton{bottom:0}.fc-icon,.fc-unselectable{-webkit-touch-callout:none;-khtml-user-select:none}.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}.fc th,.fc-basic-view td.fc-week-number,.fc-icon,.fc-toolbar{text-align:center}.fc-highlight{background:#bce8f1;opacity:.3}.fc-bgevent{background:#8fdf82;opacity:.3}.fc-nonbusiness{background:#d7d7d7}.fc button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;height:2.1em;padding:0 .6em;white-space:nowrap;cursor:pointer}.fc button::-moz-focus-inner{margin:0;padding:0}.fc-state-default{border:1px solid;background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.fc-state-default.fc-corner-left{border-top-left-radius:4px;border-bottom-left-radius:4px}.fc-state-default.fc-corner-right{border-top-right-radius:4px;border-bottom-right-radius:4px}.fc button .fc-icon{position:relative;top:-.05em;margin:0 .2em;vertical-align:middle}.fc-state-active,.fc-state-disabled,.fc-state-down,.fc-state-hover{color:#333;background-color:#e6e6e6}.fc-state-hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.fc-state-active,.fc-state-down{background-color:#ccc;background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.fc-state-disabled{cursor:default;background-image:none;opacity:.65;box-shadow:none}.fc-event.fc-draggable,.fc-event[href],.fc-popover .fc-header .fc-close,a[data-goto]{cursor:pointer}.fc-button-group{display:inline-block}.fc .fc-button-group>*{float:left;margin:0 0 0 -1px}.fc .fc-button-group>:first-child{margin-left:0}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{padding:2px 4px}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-ltr .fc-popover .fc-header .fc-title,.fc-rtl .fc-popover .fc-header .fc-close{float:left}.fc-ltr .fc-popover .fc-header .fc-close,.fc-rtl .fc-popover .fc-header .fc-title{float:right}.fc-divider{border-style:solid;border-width:1px}hr.fc-divider{height:0;margin:0;padding:0 0 2px;border-width:1px 0}.fc-bg table,.fc-row .fc-bgevent-skeleton table,.fc-row .fc-highlight-skeleton table{height:100%}.fc-clear{clear:both}.fc-bg,.fc-bgevent-skeleton,.fc-helper-skeleton,.fc-highlight-skeleton{position:absolute;top:0;left:0;right:0}.fc table{width:100%;box-sizing:border-box;table-layout:fixed;border-collapse:collapse;border-spacing:0}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}a[data-goto]:hover{text-decoration:underline}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-bgevent-skeleton td,.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-bgevent-skeleton{z-index:2}.fc-row .fc-highlight-skeleton{z-index:3}.fc-row .fc-content-skeleton{position:relative;z-index:4;padding-bottom:2px}.fc-row .fc-helper-skeleton{z-index:5}.fc .fc-row .fc-content-skeleton table,.fc .fc-row .fc-content-skeleton td,.fc .fc-row .fc-helper-skeleton td{background:0 0;border-color:transparent}.fc-row .fc-content-skeleton td,.fc-row .fc-helper-skeleton td{border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-helper-skeleton tbody td{border-top:0}.fc-scroller{-webkit-overflow-scrolling:touch}.fc-icon,.fc-row.fc-rigid,.fc-time-grid-event{overflow:hidden}.fc-scroller>.fc-day-grid,.fc-scroller>.fc-time-grid{position:relative;width:100%}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.3;border-radius:3px;border:1px solid #3a87ad}.fc-event,.fc-event-dot{background-color:#3a87ad}.fc-event,.fc-event:hover{color:#fff;text-decoration:none}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc-event .fc-bg{z-index:1;background:#fff;opacity:.25}.fc-event .fc-content{position:relative;z-index:2}.fc-event .fc-resizer{position:absolute;z-index:4;display:none}.fc-event.fc-allow-mouse-resize .fc-resizer,.fc-event.fc-selected .fc-resizer{display:block}.fc-event.fc-selected .fc-resizer:before{content:"";position:absolute;z-index:9999;top:50%;left:50%;width:40px;height:40px;margin-left:-20px;margin-top:-20px}.fc-event.fc-selected{z-index:9999!important;box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event.fc-selected.fc-dragging{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-h-event.fc-selected:before{content:"";position:absolute;z-index:3;top:-10px;bottom:-10px;left:0;right:0}.fc-ltr .fc-h-event.fc-not-start,.fc-rtl .fc-h-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-h-event.fc-not-end,.fc-rtl .fc-h-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-ltr .fc-h-event .fc-start-resizer,.fc-rtl .fc-h-event .fc-end-resizer{cursor:w-resize;left:-1px}.fc-ltr .fc-h-event .fc-end-resizer,.fc-rtl .fc-h-event .fc-start-resizer{cursor:e-resize;right:-1px}.fc-h-event.fc-allow-mouse-resize .fc-resizer{width:7px;top:-1px;bottom:-1px}.fc-h-event.fc-selected .fc-resizer{border-radius:4px;border-width:1px;width:6px;height:6px;border-style:solid;border-color:inherit;background:#fff;top:50%;margin-top:-4px}.fc-ltr .fc-h-event.fc-selected .fc-start-resizer,.fc-rtl .fc-h-event.fc-selected .fc-end-resizer{margin-left:-4px}.fc-ltr .fc-h-event.fc-selected .fc-end-resizer,.fc-rtl .fc-h-event.fc-selected .fc-start-resizer{margin-right:-4px}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}tr:first-child>td>.fc-day-grid-event{margin-top:2px}.fc-day-grid-event.fc-selected:after{content:"";position:absolute;z-index:1;top:-1px;right:-1px;bottom:-1px;left:-1px;background:#000;opacity:.25}.fc-day-grid-event .fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer{margin-left:-2px}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer{margin-right:-2px}a.fc-more{margin:1px 3px;font-size:.85em;cursor:pointer;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc.fc-bootstrap3 a,.ui-widget .fc-event{text-decoration:none}.fc-limited{display:none}.fc-icon,.fc-toolbar .fc-center{display:inline-block}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-bootstrap3 .fc-popover .panel-body,.fc-bootstrap4 .fc-popover .card-body{padding:0}.fc-now-indicator{position:absolute;border:0 solid red}.fc-bootstrap3 .fc-today.alert,.fc-bootstrap4 .fc-today.alert{border-radius:0}.fc-unselectable{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.fc-unthemed .fc-content,.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-list-view,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff;border-width:1px;border-style:solid}.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-popover .fc-header{background:#eee}.fc-unthemed td.fc-today{background:#fcf8e3}.fc-unthemed .fc-disabled-day{background:#d7d7d7;opacity:.3}.fc-icon{height:1em;line-height:1em;font-size:1em;font-family:"Courier New",Courier,monospace;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fc-icon:after{position:relative}.fc-icon-left-single-arrow:after{content:"\2039";font-weight:700;font-size:200%;top:-7%}.fc-icon-right-single-arrow:after{content:"\203A";font-weight:700;font-size:200%;top:-7%}.fc-icon-left-double-arrow:after{content:"\AB";font-size:160%;top:-7%}.fc-icon-right-double-arrow:after{content:"\BB";font-size:160%;top:-7%}.fc-icon-left-triangle:after{content:"\25C4";font-size:125%;top:3%}.fc-icon-right-triangle:after{content:"\25BA";font-size:125%;top:3%}.fc-icon-down-triangle:after{content:"\25BC";font-size:125%;top:2%}.fc-icon-x:after{content:"\D7";font-size:200%;top:6%}.fc-unthemed .fc-popover .fc-header .fc-close{color:#666;font-size:.9em;margin-top:2px}.fc-unthemed .fc-list-item:hover td{background-color:#f5f5f5}.ui-widget .fc-disabled-day{background-image:none}.fc-bootstrap3 .fc-time-grid .fc-slats table,.fc-bootstrap4 .fc-time-grid .fc-slats table,.fc-time-grid .fc-slats .ui-widget-content{background:0 0}.fc-popover>.ui-widget-header+.ui-widget-content{border-top:0}.fc-bootstrap3 hr.fc-divider,.fc-bootstrap4 hr.fc-divider{border-color:inherit}.ui-widget .fc-event{color:#fff;font-weight:400}.ui-widget td.fc-axis{font-weight:400}.fc.fc-bootstrap3 a[data-goto]:hover{text-decoration:underline}.fc.fc-bootstrap4 a{text-decoration:none}.fc.fc-bootstrap4 a[data-goto]:hover{text-decoration:underline}.fc-bootstrap4 a.fc-event:not([href]):not([tabindex]){color:#fff}.fc-bootstrap4 .fc-popover.card{position:absolute}.fc-toolbar.fc-header-toolbar{margin-bottom:1em}.fc-toolbar.fc-footer-toolbar{margin-top:1em}.fc-toolbar .fc-left{float:left}.fc-toolbar .fc-right{float:right}.fc .fc-toolbar>*>*{float:left;margin-left:.75em}.fc .fc-toolbar>*>:first-child{margin-left:0}.fc-toolbar h2{margin:0}.fc-toolbar button{position:relative}.fc-toolbar .fc-state-hover,.fc-toolbar .ui-state-hover{z-index:2}.fc-toolbar .fc-state-down{z-index:3}.fc-toolbar .fc-state-active,.fc-toolbar .ui-state-active{z-index:4}.fc-toolbar button:focus{z-index:5}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}.fc-basicDay-view .fc-content-skeleton,.fc-basicWeek-view .fc-content-skeleton{padding-bottom:1em}.fc-basic-view .fc-body .fc-row{min-height:4em}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-day-top.fc-other-month{opacity:.3}.fc-basic-view .fc-day-number,.fc-basic-view .fc-week-number{padding:2px}.fc-basic-view th.fc-day-number,.fc-basic-view th.fc-week-number{padding:0 2px}.fc-ltr .fc-basic-view .fc-day-top .fc-day-number{float:right}.fc-rtl .fc-basic-view .fc-day-top .fc-day-number{float:left}.fc-ltr .fc-basic-view .fc-day-top .fc-week-number{float:left;border-radius:0 0 3px}.fc-rtl .fc-basic-view .fc-day-top .fc-week-number{float:right;border-radius:0 0 0 3px}.fc-basic-view .fc-day-top .fc-week-number{min-width:1.5em;text-align:center;background-color:#f2f2f2;color:grey}.fc-basic-view td.fc-week-number>*{display:inline-block;min-width:1.25em}.fc-agenda-view .fc-day-grid{position:relative;z-index:2}.fc-agenda-view .fc-day-grid .fc-row{min-height:3em}.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton{padding-bottom:1em}.fc .fc-axis{vertical-align:middle;padding:0 4px;white-space:nowrap}.fc-ltr .fc-axis{text-align:right}.fc-rtl .fc-axis{text-align:left}.fc-time-grid,.fc-time-grid-container{position:relative;z-index:1}.fc-time-grid{min-height:100%}.fc-time-grid table{border:0 hidden transparent}.fc-time-grid>.fc-bg{z-index:1}.fc-time-grid .fc-slats,.fc-time-grid>hr{position:relative;z-index:2}.fc-time-grid .fc-content-col{position:relative}.fc-time-grid .fc-content-skeleton{position:absolute;z-index:3;top:0;left:0;right:0}.fc-time-grid .fc-business-container{position:relative;z-index:1}.fc-time-grid .fc-bgevent-container{position:relative;z-index:2}.fc-time-grid .fc-highlight-container{z-index:3;position:relative}.fc-time-grid .fc-event-container{position:relative;z-index:4}.fc-time-grid .fc-now-indicator-line{z-index:5}.fc-time-grid .fc-helper-container{position:relative;z-index:6}.fc-time-grid .fc-slats td{height:1.5em;border-bottom:0}.fc-time-grid .fc-slats .fc-minor td{border-top-style:dotted}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-bgevent,.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid .fc-bgevent{left:0;right:0}.fc-v-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-v-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event.fc-selected{overflow:visible}.fc-time-grid-event.fc-selected .fc-bg{display:none}.fc-time-grid-event .fc-content{overflow:hidden}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\A0-\A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer{left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer:after{content:"="}.fc-time-grid-event.fc-selected .fc-resizer{border-radius:5px;border-width:1px;width:8px;height:8px;border-style:solid;border-color:inherit;background:#fff;left:50%;margin-left:-5px;bottom:-5px}.fc-time-grid .fc-now-indicator-line{border-top-width:1px;left:0;right:0}.fc-time-grid .fc-now-indicator-arrow{margin-top:-5px}.fc-ltr .fc-time-grid .fc-now-indicator-arrow{left:0;border-width:5px 0 5px 6px;border-top-color:transparent;border-bottom-color:transparent}.fc-rtl .fc-time-grid .fc-now-indicator-arrow{right:0;border-width:5px 6px 5px 0;border-top-color:transparent;border-bottom-color:transparent}.fc-event-dot{display:inline-block;width:10px;height:10px;border-radius:5px}.fc-rtl .fc-list-view{direction:rtl}.fc-list-view{border-width:1px;border-style:solid}.fc .fc-list-table{table-layout:auto}.fc-list-table td{border-width:1px 0 0;padding:8px 14px}.fc-list-table tr:first-child td{border-top-width:0}.fc-list-heading{border-bottom-width:1px}.fc-list-heading td{font-weight:700}.fc-ltr .fc-list-heading-main{float:left}.fc-ltr .fc-list-heading-alt,.fc-rtl .fc-list-heading-main{float:right}.fc-rtl .fc-list-heading-alt{float:left}.fc-list-item.fc-has-url{cursor:pointer}.fc-list-item-marker,.fc-list-item-time{white-space:nowrap;width:1px}.fc-ltr .fc-list-item-marker{padding-right:0}.fc-rtl .fc-list-item-marker{padding-left:0}.fc-list-item-title a{text-decoration:none;color:inherit}.fc-list-item-title a[href]:hover{text-decoration:underline}.fc-list-empty-wrap2{position:absolute;top:0;left:0;right:0;bottom:0}.fc-list-empty-wrap1{width:100%;height:100%;display:table}.fc-list-empty{display:table-cell;vertical-align:middle;text-align:center}.fc-unthemed .fc-list-empty{background-color:#eee} \ No newline at end of file diff --git a/public/lib/fc/fullcalendar.min.js b/public/lib/fc/fullcalendar.min.js new file mode 100644 index 0000000000..88045457d9 --- /dev/null +++ b/public/lib/fc/fullcalendar.min.js @@ -0,0 +1,12 @@ +/*! + * FullCalendar v3.9.0 + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("moment"),require("jquery")):"function"==typeof define&&define.amd?define(["moment","jquery"],e):"object"==typeof exports?exports.FullCalendar=e(require("moment"),require("jquery")):t.FullCalendar=e(t.moment,t.jQuery)}("undefined"!=typeof self?self:this,function(t,e){return function(t){function e(i){if(n[i])return n[i].exports;var r=n[i]={i:i,l:!1,exports:{}};return t[i].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var n={};return e.m=t,e.c=n,e.d=function(t,n,i){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:i})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=236)}([function(e,n){e.exports=t},,function(t,e){var n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])};e.__extends=function(t,e){function i(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(i.prototype=e.prototype,new i)}},function(t,n){t.exports=e},function(t,e,n){function i(t,e){e.left&&t.css({"border-left-width":1,"margin-left":e.left-1}),e.right&&t.css({"border-right-width":1,"margin-right":e.right-1})}function r(t){t.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})}function o(){ht("body").addClass("fc-not-allowed")}function s(){ht("body").removeClass("fc-not-allowed")}function a(t,e,n){var i=Math.floor(e/t.length),r=Math.floor(e-i*(t.length-1)),o=[],s=[],a=[],u=0;l(t),t.each(function(e,n){var l=e===t.length-1?r:i,d=ht(n).outerHeight(!0);d *").each(function(t,n){var i=ht(n).outerWidth();i>e&&(e=i)}),e++,t.width(e),e}function d(t,e){var n,i=t.add(e);return i.css({position:"relative",left:-1}),n=t.outerHeight()-e.outerHeight(),i.css({position:"",left:""}),n}function c(t){var e=t.css("position"),n=t.parents().filter(function(){var t=ht(this);return/(auto|scroll)/.test(t.css("overflow")+t.css("overflow-y")+t.css("overflow-x"))}).eq(0);return"fixed"!==e&&n.length?n:ht(t[0].ownerDocument||document)}function p(t,e){var n=t.offset(),i=n.left-(e?e.left:0),r=n.top-(e?e.top:0);return{left:i,right:i+t.outerWidth(),top:r,bottom:r+t.outerHeight()}}function h(t,e){var n=t.offset(),i=g(t),r=n.left+b(t,"border-left-width")+i.left-(e?e.left:0),o=n.top+b(t,"border-top-width")+i.top-(e?e.top:0);return{left:r,right:r+t[0].clientWidth,top:o,bottom:o+t[0].clientHeight}}function f(t,e){var n=t.offset(),i=n.left+b(t,"border-left-width")+b(t,"padding-left")-(e?e.left:0),r=n.top+b(t,"border-top-width")+b(t,"padding-top")-(e?e.top:0);return{left:i,right:i+t.width(),top:r,bottom:r+t.height()}}function g(t){var e,n=t[0].offsetWidth-t[0].clientWidth,i=t[0].offsetHeight-t[0].clientHeight;return n=v(n),i=v(i),e={left:0,right:0,top:0,bottom:i},y()&&"rtl"===t.css("direction")?e.left=n:e.right=n,e}function v(t){return t=Math.max(0,t),t=Math.round(t)}function y(){return null===ft&&(ft=m()),ft}function m(){var t=ht("").css({position:"absolute",top:-1e3,left:0,border:0,padding:0,overflow:"scroll",direction:"rtl"}).appendTo("body"),e=t.children(),n=e.offset().left>t.offset().left;return t.remove(),n}function b(t,e){return parseFloat(t.css(e))||0}function w(t){return 1===t.which&&!t.ctrlKey}function D(t){var e=t.originalEvent.touches;return e&&e.length?e[0].pageX:t.pageX}function E(t){var e=t.originalEvent.touches;return e&&e.length?e[0].pageY:t.pageY}function S(t){return/^touch/.test(t.type)}function C(t){t.addClass("fc-unselectable").on("selectstart",T)}function R(t){t.removeClass("fc-unselectable").off("selectstart",T)}function T(t){t.preventDefault()}function M(t,e){var n={left:Math.max(t.left,e.left),right:Math.min(t.right,e.right),top:Math.max(t.top,e.top),bottom:Math.min(t.bottom,e.bottom)};return n.left=1&&ut(o)));i++);return r}function L(t,e){var n=k(t);return"week"===n&&"object"==typeof e&&e.days&&(n="day"),n}function V(t,e,n){return null!=n?n.diff(e,t,!0):pt.isDuration(e)?e.as(t):e.end.diff(e.start,t,!0)}function G(t,e,n){var i;return U(n)?(e-t)/n:(i=n.asMonths(),Math.abs(i)>=1&&ut(i)?e.diff(t,"months",!0)/i:e.diff(t,"days",!0)/n.asDays())}function N(t,e){var n,i;return U(t)||U(e)?t/e:(n=t.asMonths(),i=e.asMonths(),Math.abs(n)>=1&&ut(n)&&Math.abs(i)>=1&&ut(i)?n/i:t.asDays()/e.asDays())}function j(t,e){var n;return U(t)?pt.duration(t*e):(n=t.asMonths(),Math.abs(n)>=1&&ut(n)?pt.duration({months:n*e}):pt.duration({days:t.asDays()*e}))}function U(t){return Boolean(t.hours()||t.minutes()||t.seconds()||t.milliseconds())}function W(t){return"[object Date]"===Object.prototype.toString.call(t)||t instanceof Date}function q(t){return"string"==typeof t&&/^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(t)}function Y(){for(var t=[],e=0;e=0;o--)if("object"==typeof(s=t[o][i]))r.unshift(s);else if(void 0!==s){l[i]=s;break}r.length&&(l[i]=Q(r))}for(n=t.length-1;n>=0;n--){a=t[n];for(i in a)i in l||(l[i]=a[i])}return l}function X(t,e){for(var n in t)$(t,n)&&(e[n]=t[n])}function $(t,e){return gt.call(t,e)}function K(t,e,n){if(ht.isFunction(t)&&(t=[t]),t){var i=void 0,r=void 0;for(i=0;i/g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\n/g,"")}function rt(t){return t.replace(/&.*?;/g,"")}function ot(t){var e=[];return ht.each(t,function(t,n){null!=n&&e.push(t+":"+n)}),e.join(";")}function st(t){var e=[];return ht.each(t,function(t,n){null!=n&&e.push(t+'="'+it(n)+'"')}),e.join(" ")}function at(t){return t.charAt(0).toUpperCase()+t.slice(1)}function lt(t,e){return t-e}function ut(t){return t%1==0}function dt(t,e){var n=t[e];return function(){return n.apply(t,arguments)}}function ct(t,e,n){void 0===n&&(n=!1);var i,r,o,s,a,l=function(){var u=+new Date-s;ua&&s.push(new t(a,o.startMs)),o.endMs>a&&(a=o.endMs);return at.startMs)&&(null==this.startMs||null==t.endMs||this.startMs=this.startMs)&&(null==this.endMs||null!=t.endMs&&t.endMs<=this.endMs)},t.prototype.containsDate=function(t){var e=t.valueOf();return(null==this.startMs||e>=this.startMs)&&(null==this.endMs||e=this.endMs&&(e=this.endMs-1),e},t.prototype.equals=function(t){return this.startMs===t.startMs&&this.endMs===t.endMs},t.prototype.clone=function(){var e=new t(this.startMs,this.endMs);return e.isStart=this.isStart,e.isEnd=this.isEnd,e},t.prototype.getStart=function(){return null!=this.startMs?o.default.utc(this.startMs).stripZone():null},t.prototype.getEnd=function(){return null!=this.endMs?o.default.utc(this.endMs).stripZone():null},t.prototype.as=function(t){return r.utc(this.endMs).diff(r.utc(this.startMs),t,!0)},t}();e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(208),s=n(33),a=n(49),l=function(t){function e(n){var i=t.call(this)||this;return i.calendar=n,i.className=[],i.uid=String(e.uuid++),i}return i.__extends(e,t),e.parse=function(t,e){var n=new this(e);return!("object"!=typeof t||!n.applyProps(t))&&n},e.normalizeId=function(t){return t?String(t):null},e.prototype.fetch=function(t,e,n){},e.prototype.removeEventDefsById=function(t){},e.prototype.removeAllEventDefs=function(){},e.prototype.getPrimitive=function(t){},e.prototype.parseEventDefs=function(t){var e,n,i=[];for(e=0;e0},e}(o.default);e.default=s},function(t,e){Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(t,e){this.isAllDay=!1,this.unzonedRange=t,this.isAllDay=e}return t.prototype.toLegacy=function(t){return{start:t.msToMoment(this.unzonedRange.startMs,this.isAllDay),end:t.msToMoment(this.unzonedRange.endMs,this.isAllDay)}},t}();e.default=n},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(34),o=n(209),s=n(17),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.buildInstances=function(){return[this.buildInstance()]},e.prototype.buildInstance=function(){return new o.default(this,this.dateProfile)},e.prototype.isAllDay=function(){return this.dateProfile.isAllDay()},e.prototype.clone=function(){var e=t.prototype.clone.call(this);return e.dateProfile=this.dateProfile,e},e.prototype.rezone=function(){var t=this.source.calendar,e=this.dateProfile;this.dateProfile=new s.default(t.moment(e.start),e.end?t.moment(e.end):null,t)},e.prototype.applyManualStandardProps=function(e){var n=t.prototype.applyManualStandardProps.call(this,e),i=s.default.parse(e,this.source);return!!i&&(this.dateProfile=i,null!=e.date&&(this.miscProps.date=e.date),n)},e}(r.default);e.default=a,a.defineStandardProps({start:!1,date:!1,end:!1,allDay:!1})},function(t,e){Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(){}return t.mixInto=function(t){var e=this;Object.getOwnPropertyNames(this.prototype).forEach(function(n){t.prototype[n]||(t.prototype[n]=e.prototype[n])})},t.mixOver=function(t){var e=this;Object.getOwnPropertyNames(this.prototype).forEach(function(n){t.prototype[n]=e.prototype[n]})},t}();e.default=n},function(t,e){Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(t){this.view=t._getView(),this.component=t}return t.prototype.opt=function(t){return this.view.opt(t)},t.prototype.end=function(){},t}();e.default=n},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0}),e.version="3.9.0",e.internalApiVersion=12;var i=n(4);e.applyAll=i.applyAll,e.debounce=i.debounce,e.isInt=i.isInt,e.htmlEscape=i.htmlEscape,e.cssToStr=i.cssToStr,e.proxy=i.proxy,e.capitaliseFirstLetter=i.capitaliseFirstLetter,e.getOuterRect=i.getOuterRect,e.getClientRect=i.getClientRect,e.getContentRect=i.getContentRect,e.getScrollbarWidths=i.getScrollbarWidths,e.preventDefault=i.preventDefault,e.parseFieldSpecs=i.parseFieldSpecs,e.compareByFieldSpecs=i.compareByFieldSpecs,e.compareByFieldSpec=i.compareByFieldSpec,e.flexibleCompare=i.flexibleCompare,e.computeGreatestUnit=i.computeGreatestUnit,e.divideRangeByDuration=i.divideRangeByDuration,e.divideDurationByDuration=i.divideDurationByDuration,e.multiplyDuration=i.multiplyDuration,e.durationHasTime=i.durationHasTime,e.log=i.log,e.warn=i.warn,e.removeExact=i.removeExact,e.intersectRects=i.intersectRects;var r=n(47);e.formatDate=r.formatDate,e.formatRange=r.formatRange,e.queryMostGranularFormatUnit=r.queryMostGranularFormatUnit;var o=n(31);e.datepickerLocale=o.datepickerLocale,e.locale=o.locale;var s=n(10);e.moment=s.default;var a=n(11);e.EmitterMixin=a.default;var l=n(7);e.ListenerMixin=l.default;var u=n(48);e.Model=u.default;var d=n(207);e.Constraints=d.default;var c=n(5);e.UnzonedRange=c.default;var p=n(12);e.ComponentFootprint=p.default;var h=n(212);e.BusinessHourGenerator=h.default;var f=n(34);e.EventDef=f.default;var g=n(37);e.EventDefMutation=g.default;var v=n(38);e.EventSourceParser=v.default;var y=n(6);e.EventSource=y.default;var m=n(51);e.defineThemeSystem=m.defineThemeSystem;var b=n(18);e.EventInstanceGroup=b.default;var w=n(52);e.ArrayEventSource=w.default;var D=n(215);e.FuncEventSource=D.default;var E=n(216);e.JsonFeedEventSource=E.default;var S=n(36);e.EventFootprint=S.default;var C=n(33);e.Class=C.default;var R=n(14);e.Mixin=R.default;var T=n(53);e.CoordCache=T.default;var M=n(54);e.DragListener=M.default;var I=n(20);e.Promise=I.default;var H=n(217);e.TaskQueue=H.default;var P=n(218);e.RenderQueue=P.default;var _=n(39);e.Scroller=_.default;var x=n(19);e.Theme=x.default;var O=n(219);e.DateComponent=O.default;var F=n(40);e.InteractiveDateComponent=F.default;var z=n(220);e.Calendar=z.default;var B=n(41);e.View=B.default;var A=n(22);e.defineView=A.defineView,e.getViewConfig=A.getViewConfig;var k=n(55);e.DayTableMixin=k.default;var L=n(56);e.BusinessHourRenderer=L.default;var V=n(42);e.EventRenderer=V.default;var G=n(57);e.FillRenderer=G.default;var N=n(58);e.HelperRenderer=N.default;var j=n(222);e.ExternalDropping=j.default;var U=n(223);e.EventResizing=U.default;var W=n(59);e.EventPointing=W.default;var q=n(224);e.EventDragging=q.default;var Y=n(225);e.DateSelecting=Y.default;var Z=n(60);e.StandardInteractionsMixin=Z.default;var Q=n(226);e.AgendaView=Q.default;var X=n(227);e.TimeGrid=X.default;var $=n(61);e.DayGrid=$.default;var K=n(62);e.BasicView=K.default;var J=n(229);e.MonthView=J.default;var tt=n(230);e.ListView=tt.default},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),r=function(){function t(t,e,n){this.start=t,this.end=e||null,this.unzonedRange=this.buildUnzonedRange(n)}return t.parse=function(e,n){var i=e.start||e.date,r=e.end;if(!i)return!1;var o=n.calendar,s=o.moment(i),a=r?o.moment(r):null,l=e.allDay,u=o.opt("forceEventDuration");return!!s.isValid()&&(!a||a.isValid()&&a.isAfter(s)||(a=null),null==l&&null==(l=n.allDayDefault)&&(l=o.opt("allDayDefault")),!0===l?(s.stripTime(),a&&a.stripTime()):!1===l&&(s.hasTime()||s.time(0),a&&!a.hasTime()&&a.time(0)),!a&&u&&(a=o.getDefaultEventEnd(!s.hasTime(),s)),new t(s,a,o))},t.isStandardProp=function(t){return"start"===t||"date"===t||"end"===t||"allDay"===t},t.prototype.isAllDay=function(){return!(this.start.hasTime()||this.end&&this.end.hasTime())},t.prototype.buildUnzonedRange=function(t){var e=this.start.clone().stripZone().valueOf(),n=this.getEnd(t).stripZone().valueOf();return new i.default(e,n)},t.prototype.getEnd=function(t){return this.end?this.end.clone():t.getDefaultEventEnd(this.isAllDay(),this.start)},t}();e.default=r},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),r=n(35),o=n(211),s=function(){function t(t){this.eventInstances=t||[]}return t.prototype.getAllEventRanges=function(t){return t?this.sliceNormalRenderRanges(t):this.eventInstances.map(r.eventInstanceToEventRange)},t.prototype.sliceRenderRanges=function(t){return this.isInverse()?this.sliceInverseRenderRanges(t):this.sliceNormalRenderRanges(t)},t.prototype.sliceNormalRenderRanges=function(t){var e,n,i,r=this.eventInstances,s=[];for(e=0;e')},e.prototype.clear=function(){this.setHeight("auto"),this.applyOverflow()},e.prototype.destroy=function(){this.el.remove()},e.prototype.applyOverflow=function(){this.scrollEl.css({"overflow-x":this.overflowX,"overflow-y":this.overflowY})},e.prototype.lockOverflow=function(t){var e=this.overflowX,n=this.overflowY;t=t||this.getScrollbarWidths(),"auto"===e&&(e=t.top||t.bottom||this.scrollEl[0].scrollWidth-1>this.scrollEl[0].clientWidth?"scroll":"hidden"),"auto"===n&&(n=t.left||t.right||this.scrollEl[0].scrollHeight-1>this.scrollEl[0].clientHeight?"scroll":"hidden"),this.scrollEl.css({"overflow-x":e,"overflow-y":n})},e.prototype.setHeight=function(t){this.scrollEl.height(t)},e.prototype.getScrollTop=function(){return this.scrollEl.scrollTop()},e.prototype.setScrollTop=function(t){this.scrollEl.scrollTop(t)},e.prototype.getClientWidth=function(){return this.scrollEl[0].clientWidth},e.prototype.getClientHeight=function(){return this.scrollEl[0].clientHeight},e.prototype.getScrollbarWidths=function(){return o.getScrollbarWidths(this.scrollEl)},e}(s.default);e.default=a},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(4),s=n(219),a=n(21),l=function(t){function e(e,n){var i=t.call(this,e,n)||this;return i.segSelector=".fc-event-container > *",i.dateSelectingClass&&(i.dateClicking=new i.dateClickingClass(i)),i.dateSelectingClass&&(i.dateSelecting=new i.dateSelectingClass(i)),i.eventPointingClass&&(i.eventPointing=new i.eventPointingClass(i)),i.eventDraggingClass&&i.eventPointing&&(i.eventDragging=new i.eventDraggingClass(i,i.eventPointing)),i.eventResizingClass&&i.eventPointing&&(i.eventResizing=new i.eventResizingClass(i,i.eventPointing)),i.externalDroppingClass&&(i.externalDropping=new i.externalDroppingClass(i)),i}return i.__extends(e,t),e.prototype.setElement=function(e){t.prototype.setElement.call(this,e),this.dateClicking&&this.dateClicking.bindToEl(e),this.dateSelecting&&this.dateSelecting.bindToEl(e),this.bindAllSegHandlersToEl(e)},e.prototype.removeElement=function(){this.endInteractions(),t.prototype.removeElement.call(this)},e.prototype.executeEventUnrender=function(){this.endInteractions(),t.prototype.executeEventUnrender.call(this)},e.prototype.bindGlobalHandlers=function(){t.prototype.bindGlobalHandlers.call(this),this.externalDropping&&this.externalDropping.bindToDocument()},e.prototype.unbindGlobalHandlers=function(){t.prototype.unbindGlobalHandlers.call(this),this.externalDropping&&this.externalDropping.unbindFromDocument()},e.prototype.bindDateHandlerToEl=function(t,e,n){var i=this;this.el.on(e,function(t){if(!r(t.target).is(i.segSelector+":not(.fc-helper),"+i.segSelector+":not(.fc-helper) *,.fc-more,a[data-goto]"))return n.call(i,t)})},e.prototype.bindAllSegHandlersToEl=function(t){[this.eventPointing,this.eventDragging,this.eventResizing].forEach(function(e){e&&e.bindToEl(t)})},e.prototype.bindSegHandlerToEl=function(t,e,n){var i=this;t.on(e,this.segSelector,function(t){var e=r(t.currentTarget);if(!e.is(".fc-helper")){var o=e.data("fc-seg");if(o&&!i.shouldIgnoreEventPointing())return n.call(i,o,t)}})},e.prototype.shouldIgnoreMouse=function(){return a.default.get().shouldIgnoreMouse()},e.prototype.shouldIgnoreTouch=function(){var t=this._getView();return t.isSelected||t.selectedEvent},e.prototype.shouldIgnoreEventPointing=function(){return this.eventDragging&&this.eventDragging.isDragging||this.eventResizing&&this.eventResizing.isResizing},e.prototype.canStartSelection=function(t,e){return o.getEvIsTouch(e)&&!this.canStartResize(t,e)&&(this.isEventDefDraggable(t.footprint.eventDef)||this.isEventDefResizable(t.footprint.eventDef))},e.prototype.canStartDrag=function(t,e){return!this.canStartResize(t,e)&&this.isEventDefDraggable(t.footprint.eventDef)},e.prototype.canStartResize=function(t,e){var n=this._getView(),i=t.footprint.eventDef;return(!o.getEvIsTouch(e)||n.isEventDefSelected(i))&&this.isEventDefResizable(i)&&r(e.target).is(".fc-resizer")},e.prototype.endInteractions=function(){[this.dateClicking,this.dateSelecting,this.eventPointing,this.eventDragging,this.eventResizing].forEach(function(t){t&&t.end()})},e.prototype.isEventDefDraggable=function(t){return this.isEventDefStartEditable(t)},e.prototype.isEventDefStartEditable=function(t){var e=t.isStartExplicitlyEditable();return null==e&&null==(e=this.opt("eventStartEditable"))&&(e=this.isEventDefGenerallyEditable(t)),e},e.prototype.isEventDefGenerallyEditable=function(t){var e=t.isExplicitlyEditable();return null==e&&(e=this.opt("editable")),e},e.prototype.isEventDefResizableFromStart=function(t){return this.opt("eventResizableFromStart")&&this.isEventDefResizable(t)},e.prototype.isEventDefResizableFromEnd=function(t){return this.isEventDefResizable(t)},e.prototype.isEventDefResizable=function(t){var e=t.isDurationExplicitlyEditable();return null==e&&null==(e=this.opt("eventDurationEditable"))&&(e=this.isEventDefGenerallyEditable(t)),e},e.prototype.diffDates=function(t,e){return this.largeUnit?o.diffByUnit(t,e,this.largeUnit):o.diffDayTime(t,e)},e.prototype.isEventInstanceGroupAllowed=function(t){var e,n=this._getView(),i=this.dateProfile,r=this.eventRangesToEventFootprints(t.getAllEventRanges());for(e=0;e1?"ll":"LL"},e.prototype.setDate=function(t){var e=this.get("dateProfile"),n=this.dateProfileGenerator.build(t,void 0,!0);e&&e.activeUnzonedRange.equals(n.activeUnzonedRange)||this.set("dateProfile",n)},e.prototype.unsetDate=function(){this.unset("dateProfile")},e.prototype.fetchInitialEvents=function(t){var e=this.calendar,n=t.isRangeAllDay&&!this.usesMinMaxTime;return e.requestEvents(e.msToMoment(t.activeUnzonedRange.startMs,n),e.msToMoment(t.activeUnzonedRange.endMs,n))},e.prototype.bindEventChanges=function(){this.listenTo(this.calendar,"eventsReset",this.resetEvents)},e.prototype.unbindEventChanges=function(){this.stopListeningTo(this.calendar,"eventsReset")},e.prototype.setEvents=function(t){this.set("currentEvents",t),this.set("hasEvents",!0)},e.prototype.unsetEvents=function(){this.unset("currentEvents"),this.unset("hasEvents")},e.prototype.resetEvents=function(t){this.startBatchRender(),this.unsetEvents(),this.setEvents(t),this.stopBatchRender()},e.prototype.requestDateRender=function(t){var e=this;this.requestRender(function(){e.executeDateRender(t)},"date","init")},e.prototype.requestDateUnrender=function(){var t=this;this.requestRender(function(){t.executeDateUnrender()},"date","destroy")},e.prototype.executeDateRender=function(e){t.prototype.executeDateRender.call(this,e),this.render&&this.render(),this.trigger("datesRendered"),this.addScroll({isDateInit:!0}),this.startNowIndicator()},e.prototype.executeDateUnrender=function(){this.unselect(),this.stopNowIndicator(),this.trigger("before:datesUnrendered"),this.destroy&&this.destroy(),t.prototype.executeDateUnrender.call(this)},e.prototype.bindBaseRenderHandlers=function(){var t=this;this.on("datesRendered",function(){t.whenSizeUpdated(t.triggerViewRender)}),this.on("before:datesUnrendered",function(){t.triggerViewDestroy()})},e.prototype.triggerViewRender=function(){this.publiclyTrigger("viewRender",{context:this,args:[this,this.el]})},e.prototype.triggerViewDestroy=function(){this.publiclyTrigger("viewDestroy",{context:this,args:[this,this.el]})},e.prototype.requestEventsRender=function(t){var e=this;this.requestRender(function(){e.executeEventRender(t),e.whenSizeUpdated(e.triggerAfterEventsRendered)},"event","init")},e.prototype.requestEventsUnrender=function(){var t=this;this.requestRender(function(){t.triggerBeforeEventsDestroyed(),t.executeEventUnrender()},"event","destroy")},e.prototype.requestBusinessHoursRender=function(t){var e=this;this.requestRender(function(){e.renderBusinessHours(t)},"businessHours","init")},e.prototype.requestBusinessHoursUnrender=function(){var t=this;this.requestRender(function(){t.unrenderBusinessHours()},"businessHours","destroy")},e.prototype.bindGlobalHandlers=function(){t.prototype.bindGlobalHandlers.call(this),this.listenTo(d.default.get(),{touchstart:this.processUnselect,mousedown:this.handleDocumentMousedown})},e.prototype.unbindGlobalHandlers=function(){t.prototype.unbindGlobalHandlers.call(this),this.stopListeningTo(d.default.get())},e.prototype.startNowIndicator=function(){var t,e,n,i=this;this.opt("nowIndicator")&&(t=this.getNowIndicatorUnit())&&(e=s.proxy(this,"updateNowIndicator"),this.initialNowDate=this.calendar.getNow(),this.initialNowQueriedMs=(new Date).valueOf(),n=this.initialNowDate.clone().startOf(t).add(1,t).valueOf()-this.initialNowDate.valueOf(),this.nowIndicatorTimeoutID=setTimeout(function(){i.nowIndicatorTimeoutID=null,e(),n=+o.duration(1,t),n=Math.max(100,n),i.nowIndicatorIntervalID=setInterval(e,n)},n))},e.prototype.updateNowIndicator=function(){this.isDatesRendered&&this.initialNowDate&&(this.unrenderNowIndicator(),this.renderNowIndicator(this.initialNowDate.clone().add((new Date).valueOf()-this.initialNowQueriedMs)),this.isNowIndicatorRendered=!0)},e.prototype.stopNowIndicator=function(){this.isNowIndicatorRendered&&(this.nowIndicatorTimeoutID&&(clearTimeout(this.nowIndicatorTimeoutID),this.nowIndicatorTimeoutID=null),this.nowIndicatorIntervalID&&(clearInterval(this.nowIndicatorIntervalID),this.nowIndicatorIntervalID=null),this.unrenderNowIndicator(),this.isNowIndicatorRendered=!1)},e.prototype.updateSize=function(e,n,i){this.setHeight?this.setHeight(e,n):t.prototype.updateSize.call(this,e,n,i),this.updateNowIndicator()},e.prototype.addScroll=function(t){var e=this.queuedScroll||(this.queuedScroll={});r.extend(e,t)},e.prototype.popScroll=function(){this.applyQueuedScroll(),this.queuedScroll=null},e.prototype.applyQueuedScroll=function(){this.queuedScroll&&this.applyScroll(this.queuedScroll)},e.prototype.queryScroll=function(){var t={};return this.isDatesRendered&&r.extend(t,this.queryDateScroll()),t},e.prototype.applyScroll=function(t){t.isDateInit&&this.isDatesRendered&&r.extend(t,this.computeInitialDateScroll()),this.isDatesRendered&&this.applyDateScroll(t)},e.prototype.computeInitialDateScroll=function(){return{}},e.prototype.queryDateScroll=function(){return{}},e.prototype.applyDateScroll=function(t){},e.prototype.reportEventDrop=function(t,e,n,i){var r=this.calendar.eventManager,s=r.mutateEventsWithId(t.def.id,e),a=e.dateMutation;a&&(t.dateProfile=a.buildNewDateProfile(t.dateProfile,this.calendar)),this.triggerEventDrop(t,a&&a.dateDelta||o.duration(),s,n,i)},e.prototype.triggerEventDrop=function(t,e,n,i,r){this.publiclyTrigger("eventDrop",{context:i[0],args:[t.toLegacy(),e,n,r,{},this]})},e.prototype.reportExternalDrop=function(t,e,n,i,r,o){e&&this.calendar.eventManager.addEventDef(t,n),this.triggerExternalDrop(t,e,i,r,o)},e.prototype.triggerExternalDrop=function(t,e,n,i,r){this.publiclyTrigger("drop",{context:n[0],args:[t.dateProfile.start.clone(),i,r,this]}),e&&this.publiclyTrigger("eventReceive",{context:this,args:[t.buildInstance().toLegacy(),this]})},e.prototype.reportEventResize=function(t,e,n,i){var r=this.calendar.eventManager,o=r.mutateEventsWithId(t.def.id,e);t.dateProfile=e.dateMutation.buildNewDateProfile(t.dateProfile,this.calendar),this.triggerEventResize(t,e.dateMutation.endDelta,o,n,i)},e.prototype.triggerEventResize=function(t,e,n,i,r){this.publiclyTrigger("eventResize",{context:i[0],args:[t.toLegacy(),e,n,r,{},this]})},e.prototype.select=function(t,e){this.unselect(e),this.renderSelectionFootprint(t),this.reportSelection(t,e)},e.prototype.renderSelectionFootprint=function(e){this.renderSelection?this.renderSelection(e.toLegacy(this.calendar)):t.prototype.renderSelectionFootprint.call(this,e)},e.prototype.reportSelection=function(t,e){this.isSelected=!0,this.triggerSelect(t,e)},e.prototype.triggerSelect=function(t,e){var n=this.calendar.footprintToDateProfile(t);this.publiclyTrigger("select",{context:this,args:[n.start,n.end,e,this]})},e.prototype.unselect=function(t){this.isSelected&&(this.isSelected=!1,this.destroySelection&&this.destroySelection(),this.unrenderSelection(),this.publiclyTrigger("unselect",{context:this,args:[t,this]}))},e.prototype.selectEventInstance=function(t){this.selectedEventInstance&&this.selectedEventInstance===t||(this.unselectEventInstance(),this.getEventSegs().forEach(function(e){e.footprint.eventInstance===t&&e.el&&e.el.addClass("fc-selected")}),this.selectedEventInstance=t)},e.prototype.unselectEventInstance=function(){this.selectedEventInstance&&(this.getEventSegs().forEach(function(t){t.el&&t.el.removeClass("fc-selected")}),this.selectedEventInstance=null)},e.prototype.isEventDefSelected=function(t){return this.selectedEventInstance&&this.selectedEventInstance.def.id===t.id},e.prototype.handleDocumentMousedown=function(t){s.isPrimaryMouseButton(t)&&this.processUnselect(t)},e.prototype.processUnselect=function(t){this.processRangeUnselect(t),this.processEventUnselect(t)},e.prototype.processRangeUnselect=function(t){var e;this.isSelected&&this.opt("unselectAuto")&&((e=this.opt("unselectCancel"))&&r(t.target).closest(e).length||this.unselect(t))},e.prototype.processEventUnselect=function(t){this.selectedEventInstance&&(r(t.target).closest(".fc-selected").length||this.unselectEventInstance())},e.prototype.triggerBaseRendered=function(){this.publiclyTrigger("viewRender",{context:this,args:[this,this.el]})},e.prototype.triggerBaseUnrendered=function(){this.publiclyTrigger("viewDestroy",{context:this,args:[this,this.el]})},e.prototype.triggerDayClick=function(t,e,n){var i=this.calendar.footprintToDateProfile(t);this.publiclyTrigger("dayClick",{context:e,args:[i.start,n,this]})},e.prototype.isDateInOtherMonth=function(t,e){return!1},e.prototype.getUnzonedRangeOption=function(t){var e=this.opt(t);if("function"==typeof e&&(e=e.apply(null,Array.prototype.slice.call(arguments,1))),e)return this.calendar.parseUnzonedRange(e)},e.prototype.initHiddenDays=function(){var t,e=this.opt("hiddenDays")||[],n=[],i=0;for(!1===this.opt("weekends")&&e.push(0,6),t=0;t<7;t++)(n[t]=-1!==r.inArray(t,e))||i++;if(!i)throw new Error("invalid hiddenDays");this.isHiddenDayHash=n},e.prototype.trimHiddenDays=function(t){var e=t.getStart(),n=t.getEnd();return e&&(e=this.skipHiddenDays(e)),n&&(n=this.skipHiddenDays(n,-1,!0)),null===e||null===n||eo&&(!l[s]||u.isSame(d,l[s]))&&(s-1!==o||"."!==c[s]);s--)v=c[s]+v;for(a=o;a<=s;a++)y+=c[a],m+=p[a];return(y||m)&&(b=r?m+i+y:y+i+m),g(h+b+v)}function a(t){return C[t]||(C[t]=l(t))}function l(t){var e=u(t);return{fakeFormatString:c(e),sameUnits:p(e)}}function u(t){for(var e,n=[],i=/\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g;e=i.exec(t);)e[1]?n.push.apply(n,d(e[1])):e[2]?n.push({maybe:u(e[2])}):e[3]?n.push({token:e[3]}):e[5]&&n.push.apply(n,d(e[5]));return n}function d(t){return". "===t?["."," "]:[t]}function c(t){var e,n,i=[];for(e=0;er.value)&&(r=i);return r?r.unit:null}Object.defineProperty(e,"__esModule",{value:!0});var y=n(10);y.newMomentProto.format=function(){return this._fullCalendar&&arguments[0]?r(this,arguments[0]):this._ambigTime?y.oldMomentFormat(i(this),"YYYY-MM-DD"):this._ambigZone?y.oldMomentFormat(i(this),"YYYY-MM-DD[T]HH:mm:ss"):this._fullCalendar?y.oldMomentFormat(i(this)):y.oldMomentProto.format.apply(this,arguments)},y.newMomentProto.toISOString=function(){return this._ambigTime?y.oldMomentFormat(i(this),"YYYY-MM-DD"):this._ambigZone?y.oldMomentFormat(i(this),"YYYY-MM-DD[T]HH:mm:ss"):this._fullCalendar?y.oldMomentProto.toISOString.apply(i(this),arguments):y.oldMomentProto.toISOString.apply(this,arguments)};var m="\v",b="",w="",D=new RegExp(w+"([^"+w+"]*)"+w,"g"),E={t:function(t){return y.oldMomentFormat(t,"a").charAt(0)},T:function(t){return y.oldMomentFormat(t,"A").charAt(0)}},S={Y:{value:1,unit:"year"},M:{value:2,unit:"month"},W:{value:3,unit:"week"},w:{value:3,unit:"week"},D:{value:4,unit:"day"},d:{value:4,unit:"day"}};e.formatDate=r,e.formatRange=o;var C={};e.queryMostGranularFormatUnit=v},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(33),o=n(11),s=n(7),a=function(t){function e(){var e=t.call(this)||this;return e._watchers={},e._props={},e.applyGlobalWatchers(),e.constructed(),e}return i.__extends(e,t),e.watch=function(t){for(var e=[],n=1;n0&&(t=this.els.eq(0).offsetParent()),this.origin=t?t.offset():null,this.boundingRect=this.queryBoundingRect(),this.isHorizontal&&this.buildElHorizontals(),this.isVertical&&this.buildElVerticals()},t.prototype.clear=function(){this.origin=null,this.boundingRect=null,this.lefts=null,this.rights=null,this.tops=null,this.bottoms=null},t.prototype.ensureBuilt=function(){this.origin||this.build()},t.prototype.buildElHorizontals=function(){var t=[],e=[];this.els.each(function(n,r){var o=i(r),s=o.offset().left,a=o.outerWidth();t.push(s),e.push(s+a)}),this.lefts=t,this.rights=e},t.prototype.buildElVerticals=function(){var t=[],e=[];this.els.each(function(n,r){var o=i(r),s=o.offset().top,a=o.outerHeight();t.push(s),e.push(s+a)}),this.tops=t,this.bottoms=e},t.prototype.getHorizontalIndex=function(t){this.ensureBuilt();var e,n=this.lefts,i=this.rights,r=n.length;for(e=0;e=n[e]&&t=n[e]&&t0&&(t=r.getScrollParent(this.els.eq(0)),!t.is(document))?r.getClientRect(t):null},t.prototype.isPointInBounds=function(t,e){return this.isLeftInBounds(t)&&this.isTopInBounds(e)},t.prototype.isLeftInBounds=function(t){return!this.boundingRect||t>=this.boundingRect.left&&t=this.boundingRect.top&&t=i*i&&this.handleDistanceSurpassed(t),this.isDragging&&this.handleDrag(e,n,t)},t.prototype.handleDrag=function(t,e,n){this.trigger("drag",t,e,n),this.updateAutoScroll(n)},t.prototype.endDrag=function(t){this.isDragging&&(this.isDragging=!1,this.handleDragEnd(t))},t.prototype.handleDragEnd=function(t){this.trigger("dragEnd",t)},t.prototype.startDelay=function(t){var e=this;this.delay?this.delayTimeoutId=setTimeout(function(){e.handleDelayEnd(t)},this.delay):this.handleDelayEnd(t)},t.prototype.handleDelayEnd=function(t){this.isDelayEnded=!0,this.isDistanceSurpassed&&this.startDrag(t)},t.prototype.handleDistanceSurpassed=function(t){this.isDistanceSurpassed=!0,this.isDelayEnded&&this.startDrag(t)},t.prototype.handleTouchMove=function(t){this.isDragging&&this.shouldCancelTouchScroll&&t.preventDefault(),this.handleMove(t)},t.prototype.handleMouseMove=function(t){this.handleMove(t)},t.prototype.handleTouchScroll=function(t){this.isDragging&&!this.scrollAlwaysKills||this.endInteraction(t,!0)},t.prototype.trigger=function(t){for(var e=[],n=1;n=0&&e<=1?l=e*this.scrollSpeed*-1:n>=0&&n<=1&&(l=n*this.scrollSpeed),i>=0&&i<=1?u=i*this.scrollSpeed*-1:o>=0&&o<=1&&(u=o*this.scrollSpeed)),this.setScrollVel(l,u)},t.prototype.setScrollVel=function(t,e){this.scrollTopVel=t,this.scrollLeftVel=e,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(r.proxy(this,"scrollIntervalFunc"),this.scrollIntervalMs))},t.prototype.constrainScrollVel=function(){var t=this.scrollEl;this.scrollTopVel<0?t.scrollTop()<=0&&(this.scrollTopVel=0):this.scrollTopVel>0&&t.scrollTop()+t[0].clientHeight>=t[0].scrollHeight&&(this.scrollTopVel=0),this.scrollLeftVel<0?t.scrollLeft()<=0&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&t.scrollLeft()+t[0].clientWidth>=t[0].scrollWidth&&(this.scrollLeftVel=0)},t.prototype.scrollIntervalFunc=function(){var t=this.scrollEl,e=this.scrollIntervalMs/1e3;this.scrollTopVel&&t.scrollTop(t.scrollTop()+this.scrollTopVel*e),this.scrollLeftVel&&t.scrollLeft(t.scrollLeft()+this.scrollLeftVel*e),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.endAutoScroll()},t.prototype.endAutoScroll=function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.handleScrollEnd())},t.prototype.handleDebouncedScroll=function(){this.scrollIntervalId||this.handleScrollEnd()},t.prototype.handleScrollEnd=function(){},t}();e.default=a,o.default.mixInto(a)},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(4),o=n(14),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.updateDayTable=function(){for(var t,e,n,i=this,r=i.view,o=r.calendar,s=o.msToUtcMoment(i.dateProfile.renderUnzonedRange.startMs,!0),a=o.msToUtcMoment(i.dateProfile.renderUnzonedRange.endMs,!0),l=-1,u=[],d=[];s.isBefore(a);)r.isHiddenDay(s)?u.push(l+.5):(l++,u.push(l),d.push(s.clone())),s.add(1,"days");if(this.breakOnWeeks){for(e=d[0].day(),t=1;t=e.length?e[e.length-1]+1:e[n]},e.prototype.computeColHeadFormat=function(){return this.rowCnt>1||this.colCnt>10?"ddd":this.colCnt>1?this.opt("dayOfMonthFormat"):"dddd"},e.prototype.sliceRangeByRow=function(t){var e,n,i,r,o,s=this.daysPerRow,a=this.view.computeDayRange(t),l=this.getDateDayIndex(a.start),u=this.getDateDayIndex(a.end.clone().subtract(1,"days")),d=[];for(e=0;e'+this.renderHeadTrHtml()+""},e.prototype.renderHeadIntroHtml=function(){return this.renderIntroHtml()},e.prototype.renderHeadTrHtml=function(){return""+(this.isRTL?"":this.renderHeadIntroHtml())+this.renderHeadDateCellsHtml()+(this.isRTL?this.renderHeadIntroHtml():"")+""},e.prototype.renderHeadDateCellsHtml=function(){var t,e,n=[];for(t=0;t1?' colspan="'+e+'"':"")+(n?" "+n:"")+">"+(a?s.buildGotoAnchorHtml({date:t,forceOff:o.rowCnt>1||1===o.colCnt},i):i)+""},e.prototype.renderBgTrHtml=function(t){return""+(this.isRTL?"":this.renderBgIntroHtml(t))+this.renderBgCellsHtml(t)+(this.isRTL?this.renderBgIntroHtml(t):"")+""},e.prototype.renderBgIntroHtml=function(t){return this.renderIntroHtml()},e.prototype.renderBgCellsHtml=function(t){var e,n,i=[];for(e=0;e"},e.prototype.renderIntroHtml=function(){},e.prototype.bookendCells=function(t){var e=this.renderIntroHtml();e&&(this.isRTL?t.append(e):t.prepend(e))},e}(o.default);e.default=s},function(t,e){Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(t,e){this.component=t,this.fillRenderer=e}return t.prototype.render=function(t){var e=this.component,n=e._getDateProfile().activeUnzonedRange,i=t.buildEventInstanceGroup(e.hasAllDayBusinessHours,n),r=i?e.eventRangesToEventFootprints(i.sliceRenderRanges(n)):[];this.renderEventFootprints(r)},t.prototype.renderEventFootprints=function(t){var e=this.component.eventFootprintsToSegs(t);this.renderSegs(e),this.segs=e},t.prototype.renderSegs=function(t){this.fillRenderer&&this.fillRenderer.renderSegs("businessHours",t,{getClasses:function(t){return["fc-nonbusiness","fc-bgevent"]}})},t.prototype.unrender=function(){this.fillRenderer&&this.fillRenderer.unrender("businessHours"),this.segs=null},t.prototype.getSegs=function(){return this.segs||[]},t}();e.default=n},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(3),r=n(4),o=function(){function t(t){this.fillSegTag="div",this.component=t,this.elsByFill={}}return t.prototype.renderFootprint=function(t,e,n){this.renderSegs(t,this.component.componentFootprintToSegs(e),n)},t.prototype.renderSegs=function(t,e,n){var i;return e=this.buildSegEls(t,e,n),i=this.attachSegEls(t,e),i&&this.reportEls(t,i),e},t.prototype.unrender=function(t){var e=this.elsByFill[t];e&&(e.remove(),delete this.elsByFill[t])},t.prototype.buildSegEls=function(t,e,n){var r,o=this,s="",a=[];if(e.length){for(r=0;r"},t.prototype.attachSegEls=function(t,e){},t.prototype.reportEls=function(t,e){this.elsByFill[t]?this.elsByFill[t]=this.elsByFill[t].add(e):this.elsByFill[t]=i(e)},t}();e.default=o},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(13),r=n(36),o=n(6),s=function(){function t(t,e){this.view=t._getView(),this.component=t,this.eventRenderer=e}return t.prototype.renderComponentFootprint=function(t){this.renderEventFootprints([this.fabricateEventFootprint(t)])},t.prototype.renderEventDraggingFootprints=function(t,e,n){this.renderEventFootprints(t,e,"fc-dragging",n?null:this.view.opt("dragOpacity"))},t.prototype.renderEventResizingFootprints=function(t,e,n){this.renderEventFootprints(t,e,"fc-resizing")},t.prototype.renderEventFootprints=function(t,e,n,i){var r,o=this.component.eventFootprintsToSegs(t),s="fc-helper "+(n||"");for(o=this.eventRenderer.renderFgSegEls(o),r=0;r'+this.renderBgTrHtml(t)+''+(this.getIsNumbersVisible()?""+this.renderNumberTrHtml(t)+"":"")+""},e.prototype.getIsNumbersVisible=function(){return this.getIsDayNumbersVisible()||this.cellWeekNumbersVisible},e.prototype.getIsDayNumbersVisible=function(){return this.rowCnt>1},e.prototype.renderNumberTrHtml=function(t){return""+(this.isRTL?"":this.renderNumberIntroHtml(t))+this.renderNumberCellsHtml(t)+(this.isRTL?this.renderNumberIntroHtml(t):"")+""},e.prototype.renderNumberIntroHtml=function(t){return this.renderIntroHtml()},e.prototype.renderNumberCellsHtml=function(t){var e,n,i=[];for(e=0;e",this.cellWeekNumbersVisible&&t.day()===n&&(r+=i.buildGotoAnchorHtml({date:t,type:"week"},{class:"fc-week-number"},t.format("w"))),s&&(r+=i.buildGotoAnchorHtml(t,{class:"fc-day-number"},t.format("D"))),r+=""):""},e.prototype.prepareHits=function(){this.colCoordCache.build(),this.rowCoordCache.build(),this.rowCoordCache.bottoms[this.rowCnt-1]+=this.bottomCoordPadding},e.prototype.releaseHits=function(){this.colCoordCache.clear(),this.rowCoordCache.clear()},e.prototype.queryHit=function(t,e){if(this.colCoordCache.isLeftInBounds(t)&&this.rowCoordCache.isTopInBounds(e)){var n=this.colCoordCache.getHorizontalIndex(t),i=this.rowCoordCache.getVerticalIndex(e);if(null!=i&&null!=n)return this.getCellHit(i,n)}},e.prototype.getHitFootprint=function(t){var e=this.getCellRange(t.row,t.col);return new u.default(new l.default(e.start,e.end),!0)},e.prototype.getHitEl=function(t){return this.getCellEl(t.row,t.col)},e.prototype.getCellHit=function(t,e){return{row:t,col:e,component:this,left:this.colCoordCache.getLeftOffset(e),right:this.colCoordCache.getRightOffset(e),top:this.rowCoordCache.getTopOffset(t),bottom:this.rowCoordCache.getBottomOffset(t)}},e.prototype.getCellEl=function(t,e){return this.cellEls.eq(t*this.colCnt+e)},e.prototype.executeEventUnrender=function(){this.removeSegPopover(),t.prototype.executeEventUnrender.call(this)},e.prototype.getOwnEventSegs=function(){ +return t.prototype.getOwnEventSegs.call(this).concat(this.popoverSegs||[])},e.prototype.renderDrag=function(t,e,n){var i;for(i=0;i td > :first-child").each(e),i.position().top+o>a)return n;return!1},e.prototype.limitRow=function(t,e){var n,i,o,s,a,l,u,d,c,p,h,f,g,v,y,m=this,b=this.eventRenderer.rowStructs[t],w=[],D=0,E=function(n){for(;D").append(y),c.append(v),w.push(v[0])),D++};if(e&&e').attr("rowspan",p),l=d[f],y=this.renderMoreLink(t,a.leftCol+f,[a].concat(l)),v=r("").append(y),g.append(v),h.push(g[0]),w.push(g[0]);c.addClass("fc-limited").after(r(h)),o.push(c[0])}}E(this.colCnt),b.moreEls=r(w),b.limitedEls=r(o)}},e.prototype.unlimitRow=function(t){var e=this.eventRenderer.rowStructs[t];e.moreEls&&(e.moreEls.remove(),e.moreEls=null),e.limitedEls&&(e.limitedEls.removeClass("fc-limited"),e.limitedEls=null)},e.prototype.renderMoreLink=function(t,e,n){var i=this,o=this.view;return r('').text(this.getMoreLinkText(n.length)).on("click",function(s){var a=i.opt("eventLimitClick"),l=i.getCellDate(t,e),u=r(s.currentTarget),d=i.getCellEl(t,e),c=i.getCellSegs(t,e),p=i.resliceDaySegs(c,l),h=i.resliceDaySegs(n,l);"function"==typeof a&&(a=i.publiclyTrigger("eventLimitClick",{context:o,args:[{date:l.clone(),dayEl:d,moreEl:u,segs:p,hiddenSegs:h},s,o]})),"popover"===a?i.showSegPopover(t,e,u,p):"string"==typeof a&&o.calendar.zoomTo(l,a)})},e.prototype.showSegPopover=function(t,e,n,i){var r,o,s=this,l=this.view,u=n.parent();r=1===this.rowCnt?l.el:this.rowEls.eq(t),o={className:"fc-more-popover "+l.calendar.theme.getClass("popover"),content:this.renderSegPopoverContent(t,e,i),parentEl:l.el,top:r.offset().top,autoHide:!0,viewportConstrain:this.opt("popoverViewportConstrain"),hide:function(){s.popoverSegs&&s.triggerBeforeEventSegsDestroyed(s.popoverSegs),s.segPopover.removeElement(),s.segPopover=null,s.popoverSegs=null}},this.isRTL?o.right=u.offset().left+u.outerWidth()+1:o.left=u.offset().left-1,this.segPopover=new a.default(o),this.segPopover.show(),this.bindAllSegHandlersToEl(this.segPopover.el),this.triggerAfterEventSegsRendered(i)},e.prototype.renderSegPopoverContent=function(t,e,n){var i,s=this.view,a=s.calendar.theme,l=this.getCellDate(t,e).format(this.opt("dayPopoverFormat")),u=r(''+o.htmlEscape(l)+''),d=u.find(".fc-event-container");for(n=this.eventRenderer.renderFgSegEls(n,!0),this.popoverSegs=n,i=0;i"+s.htmlEscape(this.opt("weekNumberTitle"))+"":""},e.prototype.renderNumberIntroHtml=function(t){var e=this.view,n=this.getCellDate(t,0);return this.colWeekNumbersVisible?'"+e.buildGotoAnchorHtml({date:n,type:"week",forceOff:1===this.colCnt},n.format("w"))+"":""},e.prototype.renderBgIntroHtml=function(){var t=this.view;return this.colWeekNumbersVisible?'":""},e.prototype.renderIntroHtml=function(){var t=this.view;return this.colWeekNumbersVisible?'":""},e.prototype.getIsNumbersVisible=function(){return d.default.prototype.getIsNumbersVisible.apply(this,arguments)||this.colWeekNumbersVisible},e}(t)}Object.defineProperty(e,"__esModule",{value:!0});var r=n(2),o=n(3),s=n(4),a=n(39),l=n(41),u=n(228),d=n(61),c=function(t){function e(e,n){var i=t.call(this,e,n)||this;return i.dayGrid=i.instantiateDayGrid(),i.dayGrid.isRigid=i.hasRigidRows(),i.opt("weekNumbers")&&(i.opt("weekNumbersWithinDays")?(i.dayGrid.cellWeekNumbersVisible=!0,i.dayGrid.colWeekNumbersVisible=!1):(i.dayGrid.cellWeekNumbersVisible=!1,i.dayGrid.colWeekNumbersVisible=!0)),i.addChild(i.dayGrid),i.scroller=new a.default({overflowX:"hidden",overflowY:"auto"}),i}return r.__extends(e,t),e.prototype.instantiateDayGrid=function(){return new(i(this.dayGridClass))(this)},e.prototype.executeDateRender=function(e){this.dayGrid.breakOnWeeks=/year|month|week/.test(e.currentRangeUnit),t.prototype.executeDateRender.call(this,e)},e.prototype.renderSkeleton=function(){var t,e;this.el.addClass("fc-basic-view").html(this.renderSkeletonHtml()),this.scroller.render(),t=this.scroller.el.addClass("fc-day-grid-container"),e=o('').appendTo(t),this.el.find(".fc-body > tr > td").append(t),this.dayGrid.headContainerEl=this.el.find(".fc-head-container"),this.dayGrid.setElement(e)},e.prototype.unrenderSkeleton=function(){this.dayGrid.removeElement(),this.scroller.destroy()},e.prototype.renderSkeletonHtml=function(){var t=this.calendar.theme;return''+(this.opt("columnHeader")?' ':"")+''},e.prototype.weekNumberStyleAttr=function(){return null!=this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},e.prototype.hasRigidRows=function(){var t=this.opt("eventLimit");return t&&"number"!=typeof t},e.prototype.updateSize=function(e,n,i){var r,o,a=this.opt("eventLimit"),l=this.dayGrid.headContainerEl.find(".fc-row");if(!this.dayGrid.rowEls)return void(n||(r=this.computeScrollerHeight(e),this.scroller.setHeight(r)));t.prototype.updateSize.call(this,e,n,i),this.dayGrid.colWeekNumbersVisible&&(this.weekNumberWidth=s.matchCellWidths(this.el.find(".fc-week-number"))),this.scroller.clear(),s.uncompensateScroll(l),this.dayGrid.removeSegPopover(),a&&"number"==typeof a&&this.dayGrid.limitRows(a),r=this.computeScrollerHeight(e),this.setGridHeight(r,n),a&&"number"!=typeof a&&this.dayGrid.limitRows(a),n||(this.scroller.setHeight(r),o=this.scroller.getScrollbarWidths(),(o.left||o.right)&&(s.compensateScroll(l,o),r=this.computeScrollerHeight(e),this.scroller.setHeight(r)),this.scroller.lockOverflow(o))},e.prototype.computeScrollerHeight=function(t){return t-s.subtractInnerElHeight(this.el,this.scroller.el)},e.prototype.setGridHeight=function(t,e){e?s.undistributeHeight(this.dayGrid.rowEls):s.distributeHeight(this.dayGrid.rowEls,t,!0)},e.prototype.computeInitialDateScroll=function(){return{top:0}},e.prototype.queryDateScroll=function(){return{top:this.scroller.getScrollTop()}},e.prototype.applyDateScroll=function(t){void 0!==t.top&&this.scroller.setScrollTop(t.top)},e}(l.default);e.default=c,c.prototype.dateProfileGeneratorClass=u.default,c.prototype.dayGridClass=d.default},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(t,e,n){function i(t,e,n){var i;for(i=0;i=0;e--)switch(n=i[e],n.type){case"init":r=!1;case"add":case"remove":i.splice(e,1)}return r&&i.push(t),r},e}(r.default);e.default=o},function(t,e,n){function i(t){var e,n,i,r=[];for(e in t)for(n=t[e].eventInstances,i=0;i'+n+"":""+n+""},e.prototype.getAllDayHtml=function(){return this.opt("allDayHtml")||a.htmlEscape(this.opt("allDayText"))},e.prototype.getDayClasses=function(t,e){var n,i=this._getView(),r=[] +;return this.dateProfile.activeUnzonedRange.containsDate(t)?(r.push("fc-"+a.dayIDs[t.day()]),i.isDateInOtherMonth(t,this.dateProfile)&&r.push("fc-other-month"),n=i.calendar.getNow(),t.isSame(n,"day")?(r.push("fc-today"),!0!==e&&r.push(i.calendar.theme.getClass("today"))):t=this.nextDayThreshold&&o.add(1,"days"),o<=n&&(o=n.clone().add(1,"days")),{start:n,end:o}},e.prototype.isMultiDayRange=function(t){var e=this.computeDayRange(t);return e.end.diff(e.start,"days")>1},e.guid=0,e}(d.default);e.default=p},function(t,e,n){function i(t,e){return null==e?t:r.isFunction(e)?t.filter(e):(e+="",t.filter(function(t){return t.id==e||t._id===e}))}Object.defineProperty(e,"__esModule",{value:!0});var r=n(3),o=n(0),s=n(4),a=n(32),l=n(238),u=n(21),d=n(11),c=n(7),p=n(239),h=n(240),f=n(241),g=n(207),v=n(31),y=n(10),m=n(5),b=n(12),w=n(17),D=n(242),E=n(212),S=n(38),C=n(49),R=n(13),T=n(37),M=n(6),I=n(51),H=function(){function t(t,e){this.loadingLevel=0,this.ignoreUpdateViewSize=0,this.freezeContentHeightDepth=0,u.default.needed(),this.el=t,this.viewsByType={},this.optionsManager=new h.default(this,e),this.viewSpecManager=new f.default(this.optionsManager,this),this.initMomentInternals(),this.initCurrentDate(),this.initEventManager(),this.constraints=new g.default(this.eventManager,this),this.constructed()}return t.prototype.constructed=function(){},t.prototype.getView=function(){return this.view},t.prototype.publiclyTrigger=function(t,e){var n,i,o=this.opt(t);if(r.isPlainObject(e)?(n=e.context,i=e.args):r.isArray(e)&&(i=e),null==n&&(n=this.el[0]),i||(i=[]),this.triggerWith(t,n,i),o)return o.apply(n,i)},t.prototype.hasPublicHandlers=function(t){return this.hasHandlers(t)||this.opt(t)},t.prototype.option=function(t,e){var n;if("string"==typeof t){if(void 0===e)return this.optionsManager.get(t);n={},n[t]=e,this.optionsManager.add(n)}else"object"==typeof t&&this.optionsManager.add(t)},t.prototype.opt=function(t){return this.optionsManager.get(t)},t.prototype.instantiateView=function(t){var e=this.viewSpecManager.getViewSpec(t);if(!e)throw new Error('View type "'+t+'" is not valid');return new e.class(this,e)},t.prototype.isValidViewType=function(t){return Boolean(this.viewSpecManager.getViewSpec(t))},t.prototype.changeView=function(t,e){e&&(e.start&&e.end?this.optionsManager.recordOverrides({visibleRange:e}):this.currentDate=this.moment(e).stripZone()),this.renderView(t)},t.prototype.zoomTo=function(t,e){var n;e=e||"day",n=this.viewSpecManager.getViewSpec(e)||this.viewSpecManager.getUnitViewSpec(e),this.currentDate=t.clone(),this.renderView(n?n.type:null)},t.prototype.initCurrentDate=function(){var t=this.opt("defaultDate");this.currentDate=null!=t?this.moment(t).stripZone():this.getNow()},t.prototype.prev=function(){var t=this.view,e=t.dateProfileGenerator.buildPrev(t.get("dateProfile"));e.isValid&&(this.currentDate=e.date,this.renderView())},t.prototype.next=function(){var t=this.view,e=t.dateProfileGenerator.buildNext(t.get("dateProfile"));e.isValid&&(this.currentDate=e.date,this.renderView())},t.prototype.prevYear=function(){this.currentDate.add(-1,"years"),this.renderView()},t.prototype.nextYear=function(){this.currentDate.add(1,"years"),this.renderView()},t.prototype.today=function(){this.currentDate=this.getNow(),this.renderView()},t.prototype.gotoDate=function(t){this.currentDate=this.moment(t).stripZone(),this.renderView()},t.prototype.incrementDate=function(t){this.currentDate.add(o.duration(t)),this.renderView()},t.prototype.getDate=function(){return this.applyTimezone(this.currentDate)},t.prototype.pushLoading=function(){this.loadingLevel++||this.publiclyTrigger("loading",[!0,this.view])},t.prototype.popLoading=function(){--this.loadingLevel||this.publiclyTrigger("loading",[!1,this.view])},t.prototype.render=function(){this.contentEl?this.elementVisible()&&(this.calcSize(),this.updateViewSize()):this.initialRender()},t.prototype.initialRender=function(){var t=this,e=this.el;e.addClass("fc"),e.on("click.fc","a[data-goto]",function(e){var n=r(e.currentTarget),i=n.data("goto"),o=t.moment(i.date),a=i.type,l=t.view.opt("navLink"+s.capitaliseFirstLetter(a)+"Click");"function"==typeof l?l(o,e):("string"==typeof l&&(a=l),t.zoomTo(o,a))}),this.optionsManager.watch("settingTheme",["?theme","?themeSystem"],function(n){var i=I.getThemeSystemClass(n.themeSystem||n.theme),r=new i(t.optionsManager),o=r.getClass("widget");t.theme=r,o&&e.addClass(o)},function(){var n=t.theme.getClass("widget");t.theme=null,n&&e.removeClass(n)}),this.optionsManager.watch("settingBusinessHourGenerator",["?businessHours"],function(e){t.businessHourGenerator=new E.default(e.businessHours,t),t.view&&t.view.set("businessHourGenerator",t.businessHourGenerator)},function(){t.businessHourGenerator=null}),this.optionsManager.watch("applyingDirClasses",["?isRTL","?locale"],function(t){e.toggleClass("fc-ltr",!t.isRTL),e.toggleClass("fc-rtl",t.isRTL)}),this.contentEl=r("").prependTo(e),this.initToolbars(),this.renderHeader(),this.renderFooter(),this.renderView(this.opt("defaultView")),this.opt("handleWindowResize")&&r(window).resize(this.windowResizeProxy=s.debounce(this.windowResize.bind(this),this.opt("windowResizeDelay")))},t.prototype.destroy=function(){this.view&&this.clearView(),this.toolbarsManager.proxyCall("removeElement"),this.contentEl.remove(),this.el.removeClass("fc fc-ltr fc-rtl"),this.optionsManager.unwatch("settingTheme"),this.optionsManager.unwatch("settingBusinessHourGenerator"),this.el.off(".fc"),this.windowResizeProxy&&(r(window).unbind("resize",this.windowResizeProxy),this.windowResizeProxy=null),u.default.unneeded()},t.prototype.elementVisible=function(){return this.el.is(":visible")},t.prototype.bindViewHandlers=function(t){var e=this;t.watch("titleForCalendar",["title"],function(n){t===e.view&&e.setToolbarsTitle(n.title)}),t.watch("dateProfileForCalendar",["dateProfile"],function(n){t===e.view&&(e.currentDate=n.dateProfile.date,e.updateToolbarButtons(n.dateProfile))})},t.prototype.unbindViewHandlers=function(t){t.unwatch("titleForCalendar"),t.unwatch("dateProfileForCalendar")},t.prototype.renderView=function(t){var e,n=this.view;this.freezeContentHeight(),n&&t&&n.type!==t&&this.clearView(),!this.view&&t&&(e=this.view=this.viewsByType[t]||(this.viewsByType[t]=this.instantiateView(t)),this.bindViewHandlers(e),e.startBatchRender(),e.setElement(r("").appendTo(this.contentEl)),this.toolbarsManager.proxyCall("activateButton",t)),this.view&&(this.view.get("businessHourGenerator")!==this.businessHourGenerator&&this.view.set("businessHourGenerator",this.businessHourGenerator),this.view.setDate(this.currentDate),e&&e.stopBatchRender()),this.thawContentHeight()},t.prototype.clearView=function(){var t=this.view;this.toolbarsManager.proxyCall("deactivateButton",t.type),this.unbindViewHandlers(t),t.removeElement(),t.unsetDate(),this.view=null},t.prototype.reinitView=function(){var t=this.view,e=t.queryScroll();this.freezeContentHeight(),this.clearView(),this.calcSize(),this.renderView(t.type),this.view.applyScroll(e),this.thawContentHeight()},t.prototype.getSuggestedViewHeight=function(){return null==this.suggestedViewHeight&&this.calcSize(),this.suggestedViewHeight},t.prototype.isHeightAuto=function(){return"auto"===this.opt("contentHeight")||"auto"===this.opt("height")},t.prototype.updateViewSize=function(t){void 0===t&&(t=!1);var e,n=this.view;if(!this.ignoreUpdateViewSize&&n)return t&&(this.calcSize(),e=n.queryScroll()),this.ignoreUpdateViewSize++,n.updateSize(this.getSuggestedViewHeight(),this.isHeightAuto(),t),this.ignoreUpdateViewSize--,t&&n.applyScroll(e),!0},t.prototype.calcSize=function(){this.elementVisible()&&this._calcSize()},t.prototype._calcSize=function(){var t=this.opt("contentHeight"),e=this.opt("height");this.suggestedViewHeight="number"==typeof t?t:"function"==typeof t?t():"number"==typeof e?e-this.queryToolbarsHeight():"function"==typeof e?e()-this.queryToolbarsHeight():"parent"===e?this.el.parent().height()-this.queryToolbarsHeight():Math.round(this.contentEl.width()/Math.max(this.opt("aspectRatio"),.5))},t.prototype.windowResize=function(t){t.target===window&&this.view&&this.view.isDatesRendered&&this.updateViewSize(!0)&&this.publiclyTrigger("windowResize",[this.view])},t.prototype.freezeContentHeight=function(){this.freezeContentHeightDepth++||this.forceFreezeContentHeight()},t.prototype.forceFreezeContentHeight=function(){this.contentEl.css({width:"100%",height:this.contentEl.height(),overflow:"hidden"})},t.prototype.thawContentHeight=function(){this.freezeContentHeightDepth--,this.contentEl.css({width:"",height:"",overflow:""}),this.freezeContentHeightDepth&&this.forceFreezeContentHeight()},t.prototype.initToolbars=function(){this.header=new p.default(this,this.computeHeaderOptions()),this.footer=new p.default(this,this.computeFooterOptions()),this.toolbarsManager=new l.default([this.header,this.footer])},t.prototype.computeHeaderOptions=function(){return{extraClasses:"fc-header-toolbar",layout:this.opt("header")}},t.prototype.computeFooterOptions=function(){return{extraClasses:"fc-footer-toolbar",layout:this.opt("footer")}},t.prototype.renderHeader=function(){var t=this.header;t.setToolbarOptions(this.computeHeaderOptions()),t.render(),t.el&&this.el.prepend(t.el)},t.prototype.renderFooter=function(){var t=this.footer;t.setToolbarOptions(this.computeFooterOptions()),t.render(),t.el&&this.el.append(t.el)},t.prototype.setToolbarsTitle=function(t){this.toolbarsManager.proxyCall("updateTitle",t)},t.prototype.updateToolbarButtons=function(t){var e=this.getNow(),n=this.view,i=n.dateProfileGenerator.build(e),r=n.dateProfileGenerator.buildPrev(n.get("dateProfile")),o=n.dateProfileGenerator.buildNext(n.get("dateProfile"));this.toolbarsManager.proxyCall(i.isValid&&!t.currentUnzonedRange.containsDate(e)?"enableButton":"disableButton","today"),this.toolbarsManager.proxyCall(r.isValid?"enableButton":"disableButton","prev"),this.toolbarsManager.proxyCall(o.isValid?"enableButton":"disableButton","next")},t.prototype.queryToolbarsHeight=function(){return this.toolbarsManager.items.reduce(function(t,e){return t+(e.el?e.el.outerHeight(!0):0)},0)},t.prototype.select=function(t,e){this.view.select(this.buildSelectFootprint.apply(this,arguments))},t.prototype.unselect=function(){this.view&&this.view.unselect()},t.prototype.buildSelectFootprint=function(t,e){var n,i=this.moment(t).stripZone();return n=e?this.moment(e).stripZone():i.hasTime()?i.clone().add(this.defaultTimedEventDuration):i.clone().add(this.defaultAllDayEventDuration),new b.default(new m.default(i,n),!i.hasTime())},t.prototype.initMomentInternals=function(){var t=this;this.defaultAllDayEventDuration=o.duration(this.opt("defaultAllDayEventDuration")),this.defaultTimedEventDuration=o.duration(this.opt("defaultTimedEventDuration")),this.optionsManager.watch("buildingMomentLocale",["?locale","?monthNames","?monthNamesShort","?dayNames","?dayNamesShort","?firstDay","?weekNumberCalculation"],function(e){var n,i=e.weekNumberCalculation,r=e.firstDay;"iso"===i&&(i="ISO");var o=Object.create(v.getMomentLocaleData(e.locale));e.monthNames&&(o._months=e.monthNames),e.monthNamesShort&&(o._monthsShort=e.monthNamesShort),e.dayNames&&(o._weekdays=e.dayNames),e.dayNamesShort&&(o._weekdaysShort=e.dayNamesShort),null==r&&"ISO"===i&&(r=1),null!=r&&(n=Object.create(o._week),n.dow=r,o._week=n),"ISO"!==i&&"local"!==i&&"function"!=typeof i||(o._fullCalendar_weekCalc=i),t.localeData=o,t.currentDate&&t.localizeMoment(t.currentDate)})},t.prototype.moment=function(){for(var t=[],e=0;e864e5&&r.time(n-864e5)),new o.default(i,r)},t.prototype.buildRangeFromDuration=function(t,e,n,s){function a(){d=t.clone().startOf(h),c=d.clone().add(n),p=new o.default(d,c)}var l,u,d,c,p,h=this.opt("dateAlignment");return h||(l=this.opt("dateIncrement"),l?(u=i.duration(l),h=uo.getStart()&&(i=new a.default,i.setEndDelta(l),r=new s.default,r.setDateMutation(i),r)},e}(u.default);e.default=d},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(4),o=n(37),s=n(50),a=n(54),l=n(23),u=n(244),d=n(15),c=function(t){function e(e,n){var i=t.call(this,e)||this;return i.isDragging=!1,i.eventPointing=n,i}return i.__extends(e,t),e.prototype.end=function(){this.dragListener&&this.dragListener.endInteraction()},e.prototype.getSelectionDelay=function(){var t=this.opt("eventLongPressDelay");return null==t&&(t=this.opt("longPressDelay")),t},e.prototype.bindToEl=function(t){var e=this.component;e.bindSegHandlerToEl(t,"mousedown",this.handleMousedown.bind(this)),e.bindSegHandlerToEl(t,"touchstart",this.handleTouchStart.bind(this))},e.prototype.handleMousedown=function(t,e){!this.component.shouldIgnoreMouse()&&this.component.canStartDrag(t,e)&&this.buildDragListener(t).startInteraction(e,{distance:5})},e.prototype.handleTouchStart=function(t,e){var n=this.component,i={delay:this.view.isEventDefSelected(t.footprint.eventDef)?0:this.getSelectionDelay()};n.canStartDrag(t,e)?this.buildDragListener(t).startInteraction(e,i):n.canStartSelection(t,e)&&this.buildSelectListener(t).startInteraction(e,i)},e.prototype.buildSelectListener=function(t){var e=this,n=this.view,i=t.footprint.eventDef,r=t.footprint.eventInstance;if(this.dragListener)return this.dragListener;var o=this.dragListener=new a.default({dragStart:function(t){o.isTouch&&!n.isEventDefSelected(i)&&r&&n.selectEventInstance(r)},interactionEnd:function(t){e.dragListener=null}});return o},e.prototype.buildDragListener=function(t){var e,n,i,o=this,s=this.component,a=this.view,d=a.calendar,c=d.eventManager,p=t.el,h=t.footprint.eventDef,f=t.footprint.eventInstance;if(this.dragListener)return this.dragListener;var g=this.dragListener=new l.default(a,{scroll:this.opt("dragScroll"),subjectEl:p,subjectCenter:!0,interactionStart:function(i){t.component=s,e=!1,n=new u.default(t.el,{additionalClass:"fc-dragging",parentEl:a.el,opacity:g.isTouch?null:o.opt("dragOpacity"),revertDuration:o.opt("dragRevertDuration"),zIndex:2}),n.hide(),n.start(i)},dragStart:function(n){g.isTouch&&!a.isEventDefSelected(h)&&f&&a.selectEventInstance(f),e=!0,o.eventPointing.handleMouseout(t,n),o.segDragStart(t,n),a.hideEventsWithId(t.footprint.eventDef.id)},hitOver:function(e,l,u){var p,f,v,y=!0;t.hit&&(u=t.hit),p=u.component.getSafeHitFootprint(u),f=e.component.getSafeHitFootprint(e),p&&f?(i=o.computeEventDropMutation(p,f,h),i?(v=c.buildMutatedEventInstanceGroup(h.id,i),y=s.isEventInstanceGroupAllowed(v)):y=!1):y=!1,y||(i=null,r.disableCursor()),i&&a.renderDrag(s.eventRangesToEventFootprints(v.sliceRenderRanges(s.dateProfile.renderUnzonedRange,d)),t,g.isTouch)?n.hide():n.show(),l&&(i=null)},hitOut:function(){a.unrenderDrag(t),n.show(),i=null},hitDone:function(){r.enableCursor()},interactionEnd:function(r){delete t.component,n.stop(!i,function(){e&&(a.unrenderDrag(t),o.segDragStop(t,r)),a.showEventsWithId(t.footprint.eventDef.id),i&&a.reportEventDrop(f,i,p,r)}),o.dragListener=null}});return g},e.prototype.segDragStart=function(t,e){this.isDragging=!0,this.component.publiclyTrigger("eventDragStart",{context:t.el[0],args:[t.footprint.getEventLegacy(),e,{},this.view]})},e.prototype.segDragStop=function(t,e){this.isDragging=!1,this.component.publiclyTrigger("eventDragStop",{context:t.el[0],args:[t.footprint.getEventLegacy(),e,{},this.view]})},e.prototype.computeEventDropMutation=function(t,e,n){var i=new o.default;return i.setDateMutation(this.computeEventDateMutation(t,e)),i},e.prototype.computeEventDateMutation=function(t,e){var n,i,r=t.unzonedRange.getStart(),o=e.unzonedRange.getStart(),a=!1,l=!1,u=!1;return t.isAllDay!==e.isAllDay&&(a=!0,e.isAllDay?(u=!0,r.stripTime()):l=!0),n=this.component.diffDates(o,r),i=new s.default,i.clearEnd=a,i.forceTimed=l,i.forceAllDay=u,i.setDateDelta(n),i},e}(d.default);e.default=c},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(4),o=n(23),s=n(12),a=n(5),l=n(15),u=function(t){function e(e){var n=t.call(this,e)||this;return n.dragListener=n.buildDragListener(),n}return i.__extends(e,t),e.prototype.end=function(){this.dragListener.endInteraction()},e.prototype.getDelay=function(){var t=this.opt("selectLongPressDelay");return null==t&&(t=this.opt("longPressDelay")),t},e.prototype.bindToEl=function(t){var e=this,n=this.component,i=this.dragListener;n.bindDateHandlerToEl(t,"mousedown",function(t){e.opt("selectable")&&!n.shouldIgnoreMouse()&&i.startInteraction(t,{distance:e.opt("selectMinDistance")})}),n.bindDateHandlerToEl(t,"touchstart",function(t){e.opt("selectable")&&!n.shouldIgnoreTouch()&&i.startInteraction(t,{delay:e.getDelay()})}),r.preventSelection(t)},e.prototype.buildDragListener=function(){var t,e=this,n=this.component;return new o.default(n,{scroll:this.opt("dragScroll"),interactionStart:function(){t=null},dragStart:function(t){e.view.unselect(t)},hitOver:function(i,o,s){var a,l;s&&(a=n.getSafeHitFootprint(s),l=n.getSafeHitFootprint(i),t=a&&l?e.computeSelection(a,l):null,t?n.renderSelectionFootprint(t):!1===t&&r.disableCursor())},hitOut:function(){t=null,n.unrenderSelection()},hitDone:function(){r.enableCursor()},interactionEnd:function(n,i){!i&&t&&e.view.reportSelection(t,n)}})},e.prototype.computeSelection=function(t,e){var n=this.computeSelectionFootprint(t,e);return!(n&&!this.isSelectionFootprintAllowed(n))&&n},e.prototype.computeSelectionFootprint=function(t,e){var n=[t.unzonedRange.startMs,t.unzonedRange.endMs,e.unzonedRange.startMs,e.unzonedRange.endMs];return n.sort(r.compareNumbers),new s.default(new a.default(n[0],n[3]),t.isAllDay)},e.prototype.isSelectionFootprintAllowed=function(t){return this.component.dateProfile.validUnzonedRange.containsRange(t.unzonedRange)&&this.view.calendar.constraints.isSelectionFootprintAllowed(t)},e}(l.default);e.default=u},function(t,e,n){function i(t){var e,n=[],i=[];for(e=0;e').appendTo(t),this.el.find(".fc-body > tr > td").append(t),this.timeGrid.headContainerEl=this.el.find(".fc-head-container"),this.timeGrid.setElement(e),this.dayGrid&&(this.dayGrid.setElement(this.el.find(".fc-day-grid")),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight())},e.prototype.unrenderSkeleton=function(){this.timeGrid.removeElement(),this.dayGrid&&this.dayGrid.removeElement(),this.scroller.destroy()},e.prototype.renderSkeletonHtml=function(){var t=this.calendar.theme;return''+(this.opt("columnHeader")?' ':"")+''+(this.dayGrid?'':"")+""},e.prototype.axisStyleAttr=function(){return null!=this.axisWidth?'style="width:'+this.axisWidth+'px"':""},e.prototype.getNowIndicatorUnit=function(){return this.timeGrid.getNowIndicatorUnit()},e.prototype.updateSize=function(e,n,i){var r,o,s;if(t.prototype.updateSize.call(this,e,n,i),this.axisWidth=u.matchCellWidths(this.el.find(".fc-axis")),!this.timeGrid.colEls)return void(n||(o=this.computeScrollerHeight(e),this.scroller.setHeight(o)));var a=this.el.find(".fc-row:not(.fc-scroller *)");this.timeGrid.bottomRuleEl.hide(),this.scroller.clear(),u.uncompensateScroll(a),this.dayGrid&&(this.dayGrid.removeSegPopover(),r=this.opt("eventLimit"),r&&"number"!=typeof r&&(r=5),r&&this.dayGrid.limitRows(r)),n||(o=this.computeScrollerHeight(e),this.scroller.setHeight(o),s=this.scroller.getScrollbarWidths(),(s.left||s.right)&&(u.compensateScroll(a,s),o=this.computeScrollerHeight(e),this.scroller.setHeight(o)),this.scroller.lockOverflow(s),this.timeGrid.getTotalSlatHeight()"+e.buildGotoAnchorHtml({date:i,type:"week",forceOff:this.colCnt>1},u.htmlEscape(t))+""):'"},renderBgIntroHtml:function(){var t=this.view;return'"},renderIntroHtml:function(){return'"}},o={renderBgIntroHtml:function(){var t=this.view;return'"+t.getAllDayHtml()+""},renderIntroHtml:function(){return'"}}},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(0),s=n(4),a=n(40),l=n(56),u=n(60),d=n(55),c=n(53),p=n(5),h=n(12),f=n(246),g=n(247),v=n(248),y=[{hours:1},{minutes:30},{minutes:15},{seconds:30},{seconds:15}],m=function(t){function e(e){var n=t.call(this,e)||this;return n.processOptions(),n}return i.__extends(e,t),e.prototype.componentFootprintToSegs=function(t){var e,n=this.sliceRangeByTimes(t.unzonedRange);for(e=0;e=0;e--)if(n=o.duration(y[e]),i=s.divideDurationByDuration(n,t),s.isInt(i)&&i>1)return n;return o.duration(t)},e.prototype.renderDates=function(t){this.dateProfile=t,this.updateDayTable(),this.renderSlats(),this.renderColumns()},e.prototype.unrenderDates=function(){this.unrenderColumns()},e.prototype.renderSkeleton=function(){var t=this.view.calendar.theme;this.el.html(''),this.bottomRuleEl=this.el.find("hr")},e.prototype.renderSlats=function(){var t=this.view.calendar.theme;this.slatContainerEl=this.el.find("> .fc-slats").html(''+this.renderSlatRowHtml()+""),this.slatEls=this.slatContainerEl.find("tr"),this.slatCoordCache=new c.default({els:this.slatEls,isVertical:!0})},e.prototype.renderSlatRowHtml=function(){for(var t,e,n,i=this.view,r=i.calendar,a=r.theme,l=this.isRTL,u=this.dateProfile,d="",c=o.duration(+u.minTime),p=o.duration(0);c"+(e?""+s.htmlEscape(t.format(this.labelFormat))+"":"")+"",d+='"+(l?"":n)+''+(l?n:"")+"",c.add(this.slotDuration),p.add(this.slotDuration);return d},e.prototype.renderColumns=function(){var t=this.dateProfile,e=this.view.calendar.theme;this.dayRanges=this.dayDates.map(function(e){return new p.default(e.clone().add(t.minTime),e.clone().add(t.maxTime))}),this.headContainerEl&&this.headContainerEl.html(this.renderHeadHtml()),this.el.find("> .fc-bg").html(''+this.renderBgTrHtml(0)+""),this.colEls=this.el.find(".fc-day, .fc-disabled-day"),this.colCoordCache=new c.default({els:this.colEls,isHorizontal:!0}),this.renderContentSkeleton()},e.prototype.unrenderColumns=function(){this.unrenderContentSkeleton()},e.prototype.renderContentSkeleton=function(){var t,e,n="";for(t=0;t';e=this.contentSkeletonEl=r(''+n+""),this.colContainerEls=e.find(".fc-content-col"),this.helperContainerEls=e.find(".fc-helper-container"),this.fgContainerEls=e.find(".fc-event-container:not(.fc-helper-container)"),this.bgContainerEls=e.find(".fc-bgevent-container"),this.highlightContainerEls=e.find(".fc-highlight-container"),this.businessContainerEls=e.find(".fc-business-container"),this.bookendCells(e.find("tr")),this.el.append(e)},e.prototype.unrenderContentSkeleton=function(){this.contentSkeletonEl&&(this.contentSkeletonEl.remove(),this.contentSkeletonEl=null,this.colContainerEls=null,this.helperContainerEls=null,this.fgContainerEls=null,this.bgContainerEls=null,this.highlightContainerEls=null,this.businessContainerEls=null)},e.prototype.groupSegsByCol=function(t){var e,n=[];for(e=0;e').css("top",i).appendTo(this.colContainerEls.eq(n[e].col))[0]);n.length>0&&o.push(r('').css("top",i).appendTo(this.el.find(".fc-content-skeleton"))[0]),this.nowIndicatorEls=r(o)}},e.prototype.unrenderNowIndicator=function(){this.nowIndicatorEls&&(this.nowIndicatorEls.remove(),this.nowIndicatorEls=null)},e.prototype.updateSize=function(e,n,i){t.prototype.updateSize.call(this,e,n,i),this.slatCoordCache.build(),i&&this.updateSegVerticals([].concat(this.eventRenderer.getSegs(),this.businessSegs||[]))},e.prototype.getTotalSlatHeight=function(){return this.slatContainerEl.outerHeight()},e.prototype.computeDateTop=function(t,e){return this.computeTimeTop(o.duration(t-e.clone().stripTime()))},e.prototype.computeTimeTop=function(t){var e,n,i=this.slatEls.length,r=this.dateProfile,o=(t-r.minTime)/this.slotDuration;return o=Math.max(0,o),o=Math.min(i,o),e=Math.floor(o),e=Math.min(e,i-1),n=o-e,this.slatCoordCache.getTopPosition(e)+this.slatCoordCache.getHeight(e)*n},e.prototype.updateSegVerticals=function(t){this.computeSegVerticals(t),this.assignSegVerticals(t)},e.prototype.computeSegVerticals=function(t){var e,n,i,r=this.opt("agendaEventMinHeight");for(e=0;e'+o.htmlEscape(this.opt("noEventsMessage"))+"")},e.prototype.renderSegList=function(t){var e,n,i,o=this.groupSegsByDay(t),s=r(''),a=s.find("tbody");for(e=0;e'+(e?this.buildGotoAnchorHtml(t,{class:"fc-list-heading-main"},o.htmlEscape(t.format(e))):"")+(n?this.buildGotoAnchorHtml(t,{class:"fc-list-heading-alt"},o.htmlEscape(t.format(n))):"")+""},e}(a.default);e.default=c,c.prototype.eventRendererClass=u.default,c.prototype.eventPointingClass=d.default},,,,,,function(t,e,n){var i=n(3),r=n(16),o=n(4),s=n(220);n(10),n(47),n(256),n(257),n(260),n(261),n(262),n(263),i.fullCalendar=r,i.fn.fullCalendar=function(t){var e=Array.prototype.slice.call(arguments,1),n=this;return this.each(function(r,a){var l,u=i(a),d=u.data("fullCalendar");"string"==typeof t?"getCalendar"===t?r||(n=d):"destroy"===t?d&&(d.destroy(),u.removeData("fullCalendar")):d?i.isFunction(d[t])?(l=d[t].apply(d,e),r||(n=l),"destroy"===t&&u.removeData("fullCalendar")):o.warn("'"+t+"' is an unknown FullCalendar method."):o.warn("Attempting to call a FullCalendar method on an element with no calendar."):d||(d=new s.default(u,t),u.data("fullCalendar",d),d.render())}),n},t.exports=r},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(48),o=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.setElement=function(t){this.el=t,this.bindGlobalHandlers(),this.renderSkeleton(),this.set("isInDom",!0)},e.prototype.removeElement=function(){this.unset("isInDom"),this.unrenderSkeleton(),this.unbindGlobalHandlers(),this.el.remove()},e.prototype.bindGlobalHandlers=function(){},e.prototype.unbindGlobalHandlers=function(){},e.prototype.renderSkeleton=function(){},e.prototype.unrenderSkeleton=function(){},e}(r.default);e.default=o},function(t,e){Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(t){this.items=t||[]}return t.prototype.proxyCall=function(t){for(var e=[],n=1;n"),e.append(this.renderSection("left")).append(this.renderSection("right")).append(this.renderSection("center")).append('')):this.removeElement()},t.prototype.removeElement=function(){this.el&&(this.el.remove(),this.el=null)},t.prototype.renderSection=function(t){var e=this,n=this.calendar,o=n.theme,s=n.optionsManager,a=n.viewSpecManager,l=i(''),u=this.toolbarOptions.layout[t],d=s.get("customButtons")||{},c=s.overrides.buttonText||{},p=s.get("buttonText")||{};return u&&i.each(u.split(" "),function(t,s){var u,h=i(),f=!0;i.each(s.split(","),function(t,s){var l,u,g,v,y,m,b,w,D;"title"===s?(h=h.add(i(" ")),f=!1):((l=d[s])?(g=function(t){l.click&&l.click.call(w[0],t)},(v=o.getCustomButtonIconClass(l))||(v=o.getIconClass(s))||(y=l.text)):(u=a.getViewSpec(s))?(e.viewsWithButtons.push(s),g=function(){n.changeView(s)},(y=u.buttonTextOverride)||(v=o.getIconClass(s))||(y=u.buttonTextDefault)):n[s]&&(g=function(){n[s]()},(y=c[s])||(v=o.getIconClass(s))||(y=p[s])),g&&(b=["fc-"+s+"-button",o.getClass("button"),o.getClass("stateDefault")],y?(m=r.htmlEscape(y),D=""):v&&(m="",D=' aria-label="'+s+'"'),w=i('"+m+"").click(function(t){w.hasClass(o.getClass("stateDisabled"))||(g(t),(w.hasClass(o.getClass("stateActive"))||w.hasClass(o.getClass("stateDisabled")))&&w.removeClass(o.getClass("stateHover")))}).mousedown(function(){w.not("."+o.getClass("stateActive")).not("."+o.getClass("stateDisabled")).addClass(o.getClass("stateDown"))}).mouseup(function(){w.removeClass(o.getClass("stateDown"))}).hover(function(){w.not("."+o.getClass("stateActive")).not("."+o.getClass("stateDisabled")).addClass(o.getClass("stateHover"))},function(){w.removeClass(o.getClass("stateHover")).removeClass(o.getClass("stateDown"))}),h=h.add(w)))}),f&&h.first().addClass(o.getClass("cornerLeft")).end().last().addClass(o.getClass("cornerRight")).end(),h.length>1?(u=i(""),f&&u.addClass(o.getClass("buttonGroup")),u.append(h),l.append(u)):l.append(h)}),l},t.prototype.updateTitle=function(t){this.el&&this.el.find("h2").text(t)},t.prototype.activateButton=function(t){this.el&&this.el.find(".fc-"+t+"-button").addClass(this.calendar.theme.getClass("stateActive"))},t.prototype.deactivateButton=function(t){this.el&&this.el.find(".fc-"+t+"-button").removeClass(this.calendar.theme.getClass("stateActive"))},t.prototype.disableButton=function(t){this.el&&this.el.find(".fc-"+t+"-button").prop("disabled",!0).addClass(this.calendar.theme.getClass("stateDisabled"))},t.prototype.enableButton=function(t){this.el&&this.el.find(".fc-"+t+"-button").prop("disabled",!1).removeClass(this.calendar.theme.getClass("stateDisabled"))},t.prototype.getViewsWithButtons=function(){return this.viewsWithButtons},t}();e.default=o},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(4),s=n(32),a=n(31),l=n(48),u=function(t){function e(e,n){var i=t.call(this)||this;return i._calendar=e,i.overrides=r.extend({},n),i.dynamicOverrides={},i.compute(),i}return i.__extends(e,t),e.prototype.add=function(t){var e,n=0;this.recordOverrides(t);for(e in t)n++;if(1===n){if("height"===e||"contentHeight"===e||"aspectRatio"===e)return void this._calendar.updateViewSize(!0);if("defaultDate"===e)return;if("businessHours"===e)return;if(/^(event|select)(Overlap|Constraint|Allow)$/.test(e))return;if("timezone"===e)return void this._calendar.view.flash("initialEvents")}this._calendar.renderHeader(),this._calendar.renderFooter(),this._calendar.viewsByType={},this._calendar.reinitView()},e.prototype.compute=function(){var t,e,n,i,r;t=o.firstDefined(this.dynamicOverrides.locale,this.overrides.locale),e=a.localeOptionHash[t],e||(t=s.globalDefaults.locale,e=a.localeOptionHash[t]||{}),n=o.firstDefined(this.dynamicOverrides.isRTL,this.overrides.isRTL,e.isRTL,s.globalDefaults.isRTL),i=n?s.rtlDefaults:{},this.dirDefaults=i,this.localeDefaults=e,r=s.mergeOptions([s.globalDefaults,i,e,this.overrides,this.dynamicOverrides]),a.populateInstanceComputableOptions(r),this.reset(r)},e.prototype.recordOverrides=function(t){var e;for(e in t)this.dynamicOverrides[e]=t[e];this._calendar.viewSpecManager.clearCache(),this.compute()},e}(l.default);e.default=u},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(0),r=n(3),o=n(22),s=n(4),a=n(32),l=n(31),u=function(){function t(t,e){this.optionsManager=t,this._calendar=e,this.clearCache()}return t.prototype.clearCache=function(){this.viewSpecCache={}},t.prototype.getViewSpec=function(t){var e=this.viewSpecCache;return e[t]||(e[t]=this.buildViewSpec(t))},t.prototype.getUnitViewSpec=function(t){var e,n,i;if(-1!==r.inArray(t,s.unitsDesc))for(e=this._calendar.header.getViewsWithButtons(),r.each(o.viewHash,function(t){e.push(t)}),n=0;ne.top&&t.top'+(n?''+u.htmlEscape(n)+"":"")+(d.title?''+u.htmlEscape(d.title)+"":"")+''+(h?'':"")+""},e.prototype.updateFgSegCoords=function(t){this.timeGrid.computeSegVerticals(t),this.computeFgSegHorizontals(t),this.timeGrid.assignSegVerticals(t),this.assignFgSegHorizontals(t)},e.prototype.computeFgSegHorizontals=function(t){var e,n,s;if(this.sortEventSegs(t),e=i(t),r(e),n=e[0]){for(s=0;s').addClass(e.className||"").css({top:0,left:0}).append(e.content).appendTo(e.parentEl),this.el.on("click",".fc-close",function(){t.hide()}),e.autoHide&&this.listenTo(i(document),"mousedown",this.documentMousedown)},t.prototype.documentMousedown=function(t){this.el&&!i(t.target).closest(this.el).length&&this.hide()},t.prototype.removeElement=function(){this.hide(),this.el&&(this.el.remove(),this.el=null),this.stopListeningTo(i(document),"mousedown")},t.prototype.position=function(){var t,e,n,o,s,a=this.options,l=this.el.offsetParent().offset(),u=this.el.outerWidth(),d=this.el.outerHeight(),c=i(window),p=r.getScrollParent(this.el);o=a.top||0,s=void 0!==a.left?a.left:void 0!==a.right?a.right-u:0,p.is(window)||p.is(document)?(p=c,t=0,e=0):(n=p.offset(),t=n.top,e=n.left),t+=c.scrollTop(),e+=c.scrollLeft(),!1!==a.viewportConstrain&&(o=Math.min(o,t+p.outerHeight()-d-this.margin),o=Math.max(o,t+this.margin),s=Math.min(s,e+p.outerWidth()-u-this.margin),s=Math.max(s,e+this.margin)),this.el.css({top:o-l.top,left:s-l.left})},t.prototype.trigger=function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1))},t}();e.default=s,o.default.mixInto(s)},function(t,e,n){function i(t,e){var n,i;for(n=0;n=t.leftCol)return!0;return!1}function r(t,e){return t.leftCol-e.leftCol}Object.defineProperty(e,"__esModule",{value:!0});var o=n(2),s=n(3),a=n(4),l=n(42),u=function(t){function e(e,n){var i=t.call(this,e,n)||this;return i.dayGrid=e,i}return o.__extends(e,t),e.prototype.renderBgRanges=function(e){e=s.grep(e,function(t){return t.eventDef.isAllDay()}),t.prototype.renderBgRanges.call(this,e)},e.prototype.renderFgSegs=function(t){var e=this.rowStructs=this.renderSegRows(t);this.dayGrid.rowEls.each(function(t,n){s(n).find(".fc-content-skeleton > table").append(e[t].tbodyEl)})},e.prototype.unrenderFgSegs=function(){for(var t,e=this.rowStructs||[];t=e.pop();)t.tbodyEl.remove();this.rowStructs=null},e.prototype.renderSegRows=function(t){var e,n,i=[];for(e=this.groupSegRows(t),n=0;n"),a.append(d)),v[i][o]=d,y[i][o]=d,o++}var i,r,o,a,l,u,d,c=this.dayGrid.colCnt,p=this.buildSegLevels(e),h=Math.max(1,p.length),f=s(""),g=[],v=[],y=[];for(i=0;i"),g.push([]),v.push([]),y.push([]),r)for(l=0;l').append(u.el),u.leftCol!==u.rightCol?d.attr("colspan",u.rightCol-u.leftCol+1):y[i][o]=d;o<=u.rightCol;)v[i][o]=d,g[i][o]=u,o++;a.append(d)}n(c),this.dayGrid.bookendCells(a),f.append(a)}return{row:t,tbodyEl:f,cellMatrix:v,segMatrix:g,segLevels:p,segs:e}},e.prototype.buildSegLevels=function(t){var e,n,o,s=[];for(this.sortEventSegs(t),e=0;e'+a.htmlEscape(n)+""),i=''+(a.htmlEscape(o.title||"")||" ")+"",''+(this.dayGrid.isRTL?i+" "+h:h+" "+i)+""+(u?'':"")+(d?'':"")+""},e}(l.default);e.default=u},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(58),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.renderSegs=function(t,e){var n,i=[];return n=this.eventRenderer.renderSegRows(t),this.component.rowEls.each(function(t,o){var s,a,l=r(o),u=r('');e&&e.row===t?a=e.el.position().top:(s=l.find(".fc-content-skeleton tbody"),s.length||(s=l.find(".fc-content-skeleton table")),a=s.position().top),u.css("top",a).find("table").append(n[t].tbodyEl),l.append(u),i.push(u[0])}),r(i)},e}(o.default);e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(57),s=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.fillSegTag="td",e}return i.__extends(e,t),e.prototype.attachSegEls=function(t,e){var n,i,r,o=[];for(n=0;n'),o=i.find("tr"),a>0&&o.append(''),o.append(e.el.attr("colspan",l-a)),l'),this.component.bookendCells(o),i},e}(o.default);e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(228),o=n(5),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.buildRenderRange=function(e,n,i){var r,s=t.prototype.buildRenderRange.call(this,e,n,i),a=this.msToUtcMoment(s.startMs,i),l=this.msToUtcMoment(s.endMs,i);return this.opt("fixedWeekCount")&&(r=Math.ceil(l.diff(a,"weeks",!0)),l.add(6-r,"weeks")),new o.default(a,l)},e}(r.default);e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(4),o=n(42),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.renderFgSegs=function(t){t.length?this.component.renderSegList(t):this.component.renderEmptyMessage()},e.prototype.fgSegHtml=function(t){var e,n=this.view,i=n.calendar,o=i.theme,s=t.footprint,a=s.eventDef,l=s.componentFootprint,u=a.url,d=["fc-list-item"].concat(this.getClasses(a)),c=this.getBgColor(a);return e=l.isAllDay?n.getAllDayHtml():n.isMultiDayRange(l.unzonedRange)?t.isStart||t.isEnd?r.htmlEscape(this._getTimeText(i.msToMoment(t.startMs),i.msToMoment(t.endMs),l.isAllDay)):n.getAllDayHtml():r.htmlEscape(this.getTimeText(s)),u&&d.push("fc-has-url"),''+(this.displayEventTime?''+(e||"")+"":"")+'"+r.htmlEscape(a.title||"")+""},e.prototype.computeEventTimeFormat=function(){return this.opt("mediumTimeFormat")},e}(o.default);e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(59),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.handleClick=function(e,n){var i;t.prototype.handleClick.call(this,e,n),r(n.target).closest("a[href]").length||(i=e.footprint.eventDef.url)&&!n.isDefaultPrevented()&&(window.location.href=i)},e}(o.default);e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(38),r=n(52),o=n(215),s=n(216);i.default.registerClass(r.default),i.default.registerClass(o.default),i.default.registerClass(s.default)},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(51),r=n(213),o=n(214),s=n(258),a=n(259);i.defineThemeSystem("standard",r.default),i.defineThemeSystem("jquery-ui",o.default),i.defineThemeSystem("bootstrap3",s.default),i.defineThemeSystem("bootstrap4",a.default)},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(19),o=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e}(r.default);e.default=o,o.prototype.classes={widget:"fc-bootstrap3",tableGrid:"table-bordered",tableList:"table",tableListHeading:"active",buttonGroup:"btn-group",button:"btn btn-default",stateActive:"active",stateDisabled:"disabled",today:"alert alert-info",popover:"panel panel-default",popoverHeader:"panel-heading",popoverContent:"panel-body",headerRow:"panel-default",dayRow:"panel-default",listView:"panel panel-default"},o.prototype.baseIconClass="glyphicon",o.prototype.iconClasses={close:"glyphicon-remove",prev:"glyphicon-chevron-left",next:"glyphicon-chevron-right",prevYear:"glyphicon-backward",nextYear:"glyphicon-forward"},o.prototype.iconOverrideOption="bootstrapGlyphicons",o.prototype.iconOverrideCustomButtonOption="bootstrapGlyphicon",o.prototype.iconOverridePrefix="glyphicon-"},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(19),o=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e}(r.default);e.default=o,o.prototype.classes={widget:"fc-bootstrap4",tableGrid:"table-bordered",tableList:"table",tableListHeading:"table-active",buttonGroup:"btn-group",button:"btn btn-primary",stateActive:"active",stateDisabled:"disabled",today:"alert alert-info",popover:"card card-primary",popoverHeader:"card-header",popoverContent:"card-body",headerRow:"table-bordered",dayRow:"table-bordered",listView:"card card-primary"},o.prototype.baseIconClass="fa",o.prototype.iconClasses={close:"fa-times",prev:"fa-chevron-left",next:"fa-chevron-right",prevYear:"fa-angle-double-left",nextYear:"fa-angle-double-right"},o.prototype.iconOverrideOption="bootstrapFontAwesome",o.prototype.iconOverrideCustomButtonOption="bootstrapFontAwesome",o.prototype.iconOverridePrefix="fa-"},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(22),r=n(62),o=n(229);i.defineView("basic",{class:r.default}),i.defineView("basicDay",{type:"basic",duration:{days:1}}),i.defineView("basicWeek",{type:"basic",duration:{weeks:1}}),i.defineView("month",{class:o.default,duration:{months:1},defaults:{fixedWeekCount:!0}})},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(22),r=n(226);i.defineView("agenda",{class:r.default,defaults:{allDaySlot:!0,slotDuration:"00:30:00",slotEventOverlap:!0}}),i.defineView("agendaDay",{type:"agenda",duration:{days:1}}),i.defineView("agendaWeek",{type:"agenda",duration:{weeks:1}})},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(22),r=n(230);i.defineView("list",{class:r.default,buttonTextKey:"list",defaults:{buttonText:"list",listDayFormat:"LL",noEventsMessage:"No events to display"}}),i.defineView("listDay",{type:"list",duration:{days:1},defaults:{listDayFormat:"dddd"}}),i.defineView("listWeek",{type:"list",duration:{weeks:1},defaults:{listDayFormat:"dddd",listDayAltFormat:"LL"}}),i.defineView("listMonth",{type:"list",duration:{month:1},defaults:{listDayAltFormat:"dddd"}}),i.defineView("listYear",{type:"list",duration:{year:1},defaults:{listDayAltFormat:"dddd"}})},function(t,e){Object.defineProperty(e,"__esModule",{value:!0})}])}); \ No newline at end of file diff --git a/public/lib/fc/fullcalendar.print.css b/public/lib/fc/fullcalendar.print.css new file mode 100644 index 0000000000..fb858cd790 --- /dev/null +++ b/public/lib/fc/fullcalendar.print.css @@ -0,0 +1,176 @@ +/*! + * FullCalendar v3.9.0 + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */ +/*! + * FullCalendar v3.9.0 Print Stylesheet + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */ +/* + * Include this stylesheet on your page to get a more printer-friendly calendar. + * When including this stylesheet, use the media='print' attribute of the tag. + * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css. + */ +.fc { + max-width: 100% !important; } + +/* Global Event Restyling +--------------------------------------------------------------------------------------------------*/ +.fc-event { + background: #fff !important; + color: #000 !important; + page-break-inside: avoid; } + +.fc-event .fc-resizer { + display: none; } + +/* Table & Day-Row Restyling +--------------------------------------------------------------------------------------------------*/ +.fc th, +.fc td, +.fc hr, +.fc thead, +.fc tbody, +.fc-row { + border-color: #ccc !important; + background: #fff !important; } + +/* kill the overlaid, absolutely-positioned components */ +/* common... */ +.fc-bg, +.fc-bgevent-skeleton, +.fc-highlight-skeleton, +.fc-helper-skeleton, +.fc-bgevent-container, +.fc-business-container, +.fc-highlight-container, +.fc-helper-container { + display: none; } + +/* don't force a min-height on rows (for DayGrid) */ +.fc tbody .fc-row { + height: auto !important; + /* undo height that JS set in distributeHeight */ + min-height: 0 !important; + /* undo the min-height from each view's specific stylesheet */ } + +.fc tbody .fc-row .fc-content-skeleton { + position: static; + /* undo .fc-rigid */ + padding-bottom: 0 !important; + /* use a more border-friendly method for this... */ } + +.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { + /* only works in newer browsers */ + padding-bottom: 1em; + /* ...gives space within the skeleton. also ensures min height in a way */ } + +.fc tbody .fc-row .fc-content-skeleton table { + /* provides a min-height for the row, but only effective for IE, which exaggerates this value, + making it look more like 3em. for other browers, it will already be this tall */ + height: 1em; } + +/* Undo month-view event limiting. Display all events and hide the "more" links +--------------------------------------------------------------------------------------------------*/ +.fc-more-cell, +.fc-more { + display: none !important; } + +.fc tr.fc-limited { + display: table-row !important; } + +.fc td.fc-limited { + display: table-cell !important; } + +.fc-popover { + display: none; + /* never display the "more.." popover in print mode */ } + +/* TimeGrid Restyling +--------------------------------------------------------------------------------------------------*/ +/* undo the min-height 100% trick used to fill the container's height */ +.fc-time-grid { + min-height: 0 !important; } + +/* don't display the side axis at all ("all-day" and time cells) */ +.fc-agenda-view .fc-axis { + display: none; } + +/* don't display the horizontal lines */ +.fc-slats, +.fc-time-grid hr { + /* this hr is used when height is underused and needs to be filled */ + display: none !important; + /* important overrides inline declaration */ } + +/* let the container that holds the events be naturally positioned and create real height */ +.fc-time-grid .fc-content-skeleton { + position: static; } + +/* in case there are no events, we still want some height */ +.fc-time-grid .fc-content-skeleton table { + height: 4em; } + +/* kill the horizontal spacing made by the event container. event margins will be done below */ +.fc-time-grid .fc-event-container { + margin: 0 !important; } + +/* TimeGrid *Event* Restyling +--------------------------------------------------------------------------------------------------*/ +/* naturally position events, vertically stacking them */ +.fc-time-grid .fc-event { + position: static !important; + margin: 3px 2px !important; } + +/* for events that continue to a future day, give the bottom border back */ +.fc-time-grid .fc-event.fc-not-end { + border-bottom-width: 1px !important; } + +/* indicate the event continues via "..." text */ +.fc-time-grid .fc-event.fc-not-end:after { + content: "..."; } + +/* for events that are continuations from previous days, give the top border back */ +.fc-time-grid .fc-event.fc-not-start { + border-top-width: 1px !important; } + +/* indicate the event is a continuation via "..." text */ +.fc-time-grid .fc-event.fc-not-start:before { + content: "..."; } + +/* time */ +/* undo a previous declaration and let the time text span to a second line */ +.fc-time-grid .fc-event .fc-time { + white-space: normal !important; } + +/* hide the the time that is normally displayed... */ +.fc-time-grid .fc-event .fc-time span { + display: none; } + +/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */ +.fc-time-grid .fc-event .fc-time:after { + content: attr(data-full); } + +/* Vertical Scroller & Containers +--------------------------------------------------------------------------------------------------*/ +/* kill the scrollbars and allow natural height */ +.fc-scroller, +.fc-day-grid-container, +.fc-time-grid-container { + /* */ + overflow: visible !important; + height: auto !important; } + +/* kill the horizontal border/padding used to compensate for scrollbars */ +.fc-row { + border: 0 !important; + margin: 0 !important; } + +/* Button Controls +--------------------------------------------------------------------------------------------------*/ +.fc-button-group, +.fc button { + display: none; + /* don't display any button-related controls */ } diff --git a/public/lib/fc/fullcalendar.print.min.css b/public/lib/fc/fullcalendar.print.min.css new file mode 100644 index 0000000000..59a405c0a4 --- /dev/null +++ b/public/lib/fc/fullcalendar.print.min.css @@ -0,0 +1,9 @@ +/*! + * FullCalendar v3.9.0 + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + *//*! + * FullCalendar v3.9.0 Print Stylesheet + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */.fc-bg,.fc-bgevent-container,.fc-bgevent-skeleton,.fc-business-container,.fc-event .fc-resizer,.fc-helper-container,.fc-helper-skeleton,.fc-highlight-container,.fc-highlight-skeleton{display:none}.fc tbody .fc-row,.fc-time-grid{min-height:0!important}.fc-time-grid .fc-event.fc-not-end:after,.fc-time-grid .fc-event.fc-not-start:before{content:"..."}.fc{max-width:100%!important}.fc-event{background:#fff!important;color:#000!important;page-break-inside:avoid}.fc hr,.fc tbody,.fc td,.fc th,.fc thead,.fc-row{border-color:#ccc!important;background:#fff!important}.fc tbody .fc-row{height:auto!important}.fc tbody .fc-row .fc-content-skeleton{position:static;padding-bottom:0!important}.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td{padding-bottom:1em}.fc tbody .fc-row .fc-content-skeleton table{height:1em}.fc-more,.fc-more-cell{display:none!important}.fc tr.fc-limited{display:table-row!important}.fc td.fc-limited{display:table-cell!important}.fc-agenda-view .fc-axis,.fc-popover{display:none}.fc-slats,.fc-time-grid hr{display:none!important}.fc button,.fc-button-group,.fc-time-grid .fc-event .fc-time span{display:none}.fc-time-grid .fc-content-skeleton{position:static}.fc-time-grid .fc-content-skeleton table{height:4em}.fc-time-grid .fc-event-container{margin:0!important}.fc-time-grid .fc-event{position:static!important;margin:3px 2px!important}.fc-time-grid .fc-event.fc-not-end{border-bottom-width:1px!important}.fc-time-grid .fc-event.fc-not-start{border-top-width:1px!important}.fc-time-grid .fc-event .fc-time{white-space:normal!important}.fc-time-grid .fc-event .fc-time:after{content:attr(data-full)}.fc-day-grid-container,.fc-scroller,.fc-time-grid-container{overflow:visible!important;height:auto!important}.fc-row{border:0!important;margin:0!important} \ No newline at end of file diff --git a/readme.md b/readme.md index ca966ebf2b..f6cb936fa2 100644 --- a/readme.md +++ b/readme.md @@ -2,14 +2,12 @@ Firefly III - - - - + + @@ -44,38 +42,38 @@ Personal financial management is pretty difficult, and everybody has their own a By keeping track of your expenses and your income you can budget accordingly and save money. Stop living from paycheck to paycheck but give yourself the financial wiggle room you need. -You can read more about this in the [official documentation](http://firefly-iii.readthedocs.io/en/latest/index.html). +You can read more about this in the [official documentation](https://firefly-iii.readthedocs.io/en/latest/index.html). ### Features Most importantly... * Firefly III runs on your own server, so you are fully in control of your data. It will not contact other sites or servers. -* You can import from over 2500 financial providers, in 55 countries when you enable the [Spectre API](http://firefly-iii.readthedocs.io/en/latest/import/spectre.html). +* You can import from over 2500 financial providers, in 55 countries when you enable the [Spectre API](https://firefly-iii.readthedocs.io/en/latest/import/spectre.html). * You can import from [bunq](https://www.bunq.com/). * You can import CSV files from practically any bank. -* Firefly III features an JSON REST API. +* Firefly III features an [JSON REST API](https://firefly-iii.readthedocs.io/en/latest/api/start.html). * If you feel you’re missing something you can just ask me and I’ll add it! But actually, it features: -* [A double-entry bookkeeping system](http://firefly-iii.readthedocs.io/en/latest/concepts/transactions.html) -* You can store, edit and remove [withdrawals, deposits and transfers](http://firefly-iii.readthedocs.io/en/latest/concepts/transactions.html). This allows you full financial management +* [A double-entry bookkeeping system](https://firefly-iii.readthedocs.io/en/latest/concepts/transactions.html) +* You can store, edit and remove [withdrawals, deposits and transfers](https://firefly-iii.readthedocs.io/en/latest/concepts/transactions.html). This allows you full financial management * You can manage different types of accounts - * [Asset](http://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html) accounts - * Shared [asset accounts](http://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html) ([household accounts](http://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html)) + * [Asset](https://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html) accounts + * Shared [asset accounts](https://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html) ([household accounts](https://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html)) * Saving accounts * Credit cards -* It's possible to create, change and manage money using [budgets](http://firefly-iii.readthedocs.io/en/latest/concepts/budgets.html) -* Organize transactions using [categories](http://firefly-iii.readthedocs.io/en/latest/concepts/categories.html) -* Save towards a goal using [piggy banks](http://firefly-iii.readthedocs.io/en/latest/advanced/piggies.html) -* Predict and anticipate [bills](http://firefly-iii.readthedocs.io/en/latest/advanced/bills.html) -* View income / expense [reports](http://firefly-iii.readthedocs.io/en/latest/advanced/reports.html) -* [Rule based](http://firefly-iii.readthedocs.io/en/latest/advanced/rules.html) transaction handling with the ability to create your own rules. -* The ability to [export data](http://firefly-iii.readthedocs.io/en/latest/import/export.html) so you can move to another system. -* The ability to [import data](http://firefly-iii.readthedocs.io/en/latest/import/csv.html) so you can move _from_ another system. -* Organize expenses using [tags](http://firefly-iii.readthedocs.io/en/latest/concepts/tags.html) +* It's possible to create, change and manage money using [budgets](https://firefly-iii.readthedocs.io/en/latest/concepts/budgets.html) +* Organize transactions using [categories](https://firefly-iii.readthedocs.io/en/latest/concepts/categories.html) +* Save towards a goal using [piggy banks](https://firefly-iii.readthedocs.io/en/latest/advanced/piggies.html) +* Predict and anticipate [bills](https://firefly-iii.readthedocs.io/en/latest/advanced/bills.html) +* View income / expense [reports](https://firefly-iii.readthedocs.io/en/latest/advanced/reports.html) +* [Rule based](https://firefly-iii.readthedocs.io/en/latest/advanced/rules.html) transaction handling with the ability to create your own rules. +* The ability to [export data](https://firefly-iii.readthedocs.io/en/latest/import/export.html) so you can move to another system. +* The ability to [import data](https://firefly-iii.readthedocs.io/en/latest/import/csv.html) so you can move _from_ another system. +* Organize expenses using [tags](https://firefly-iii.readthedocs.io/en/latest/concepts/tags.html) * 2 factor authentication for extra security 🔒 -* Supports any currency you want, including [crypto currencies](http://firefly-iii.readthedocs.io/en/latest/concepts/currencies.html) such as ₿itcoin and Ξthereum +* Supports any currency you want, including [crypto currencies](https://firefly-iii.readthedocs.io/en/latest/concepts/currencies.html) such as ₿itcoin and Ξthereum * Lots of help text in case you don’t get it * Translations into 10(!) languages, proudly powered by Crowdin @@ -94,13 +92,14 @@ This application is for people who want to track their finances, keep an eye on ## Get started There are many ways to run Firefly III 1. There is a [demo site](https://demo.firefly-iii.org) with an example financial administration already present. -2. You can [install it on your server](http://firefly-iii.readthedocs.io/en/latest/installation/server.html). -3. You can [run it using Docker](http://firefly-iii.readthedocs.io/en/latest/installation/docker.html). +2. You can [install it on your server](https://firefly-iii.readthedocs.io/en/latest/installation/server.html). +3. You can [run it using Docker](https://firefly-iii.readthedocs.io/en/latest/installation/docker.html). 4. You can [deploy to Heroku](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master) 5. You can [deploy to Sandstorm.io](https://apps.sandstorm.io/app/uws252ya9mep4t77tevn85333xzsgrpgth8q4y1rhknn1hammw70) -6. You can [install it using Softaculous](https://softaculous.com/). These guys even have made [another demo site](http://www.softaculous.com/softaculous/apps/others/Firefly_III)! +6. You can [install it using Softaculous](https://softaculous.com/). These guys even have made [another demo site](https://www.softaculous.com/softaculous/apps/others/Firefly_III)! 7. You can [install it using AMPPS](https://www.ampps.com/) -5. *Even more options are on the way!* +8. You can [install it with YunoHost](https://install-app.yunohost.org/?app=firefly-iii). +9. *Even more options are on the way!* ### Update your instance Make sure you check for updates regularly. Your Firefly III instance will ask you to do this. Upgrade instructions can be found with the installation instructions. @@ -110,7 +109,7 @@ Your help is always welcome! Feel free to open issues, ask questions, talk about Of course there are some [contributing guidelines](https://github.com/firefly-iii/firefly-iii/blob/master/.github/contributing.md) and a [code of conduct](https://github.com/firefly-iii/firefly-iii/blob/master/.github/code_of_conduct.md), which I invite you to check out. -I can always use your help [squashing bugs](http://firefly-iii.readthedocs.io/en/latest/support/contribute.html#bugs), thinking about [new features](http://firefly-iii.readthedocs.io/en/latest/support/contribute.html#feature-requests) or [translating Firefly III](http://firefly-iii.readthedocs.io/en/latest/support/contribute.html#translations) into other languages. +I can always use your help [squashing bugs](https://firefly-iii.readthedocs.io/en/latest/support/contribute.html#bugs), thinking about [new features](https://firefly-iii.readthedocs.io/en/latest/support/contribute.html#feature-requests) or [translating Firefly III](https://firefly-iii.readthedocs.io/en/latest/support/contribute.html#translations) into other languages. For all other contributions, see below. @@ -126,7 +125,7 @@ Over time, [many people have contributed to Firefly III](https://github.com/fire ## Other stuff ### Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see [the tags](https://github.com/firefly-iii/firefly-iii/tags) on this repository. +We use [SemVer](https://semver.org/) for versioning. For the versions available, see [the tags](https://github.com/firefly-iii/firefly-iii/tags) on this repository. ### License This work [is licensed](https://github.com/firefly-iii/firefly-iii/blob/master/LICENSE) under the [GPL v3](https://www.gnu.org/licenses/gpl.html). @@ -142,4 +141,4 @@ If you are looking for alternatives, check out [Kickball's Awesome-Selfhosted li ### Badges I like badges! -[](https://travis-ci.org/firefly-iii/firefly-iii/branches) [](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/) [](https://coveralls.io/github/firefly-iii/firefly-iii) [](https://secure.php.net/downloads.php) [](https://www.gnu.org/licenses/gpl-3.0.en.html) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) +[](https://travis-ci.org/firefly-iii/firefly-iii/branches) [](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/) [](https://coveralls.io/github/firefly-iii/firefly-iii) [](https://secure.php.net/downloads.php) [](https://www.gnu.org/licenses/gpl-3.0.en.html) [](https://patreon.com/JC5) diff --git a/resources/lang/de_DE/config.php b/resources/lang/de_DE/config.php index a42da571da..7ddcf49b6e 100644 --- a/resources/lang/de_DE/config.php +++ b/resources/lang/de_DE/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'de', - 'locale' => 'de, Deutsch, de_DE, de_DE.utf8, de_DE.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e. %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e. %B %Y', - 'week_in_year' => 'KW %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'Do MMMM YYYY', - 'date_time_js' => 'Do MMMM YYYY um HH:mm:ss', - 'specific_day_js' => 'D. MMMM YYYY', - 'week_in_year_js' => '[Week]. KW, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q. Quartal YYYY', + 'html_language' => 'de', + 'locale' => 'de, Deutsch, de_DE, de_DE.utf8, de_DE.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e. %B %Y', + 'month_and_date_day' => '%A, %B %e. %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e. %B %Y', + 'week_in_year' => 'KW %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'Do MMMM YYYY', + 'date_time_js' => 'Do MMMM YYYY um HH:mm:ss', + 'specific_day_js' => 'D. MMMM YYYY', + 'week_in_year_js' => '[Week]. KW, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q. Quartal YYYY', + 'dow_1' => 'Montag', + 'dow_2' => 'Dienstag', + 'dow_3' => 'Mittwoch', + 'dow_4' => 'Donnerstag', + 'dow_5' => 'Freitag', + 'dow_6' => 'Samstag', + 'dow_7' => 'Sonntag', ]; diff --git a/resources/lang/de_DE/demo.php b/resources/lang/de_DE/demo.php index 479be2661f..8a65f1adcf 100644 --- a/resources/lang/de_DE/demo.php +++ b/resources/lang/de_DE/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Diese Ausgaben, Einnahmen und Umbuchungen sind nicht besonders einfallsreich. Sie wurden automatisch generiert.', 'piggy-banks-index' => 'Hier wurden bereits drei Sparschweine angelegt. Der Betrag in den Sparschweinen kann über die Plus-/Minus-Buttons angepasst werden. Klicken Sie auf den Namen des Sparschweins um weitere Informationen einzusehen.', 'import-index' => 'Jede CSV-Datei kann in Firefly III importiert werden. Es wird auch der Import von Daten aus Bunq und Spectre unterstützt. Weitere Banken und Finanzaggregatoren werden in Zukunft implementiert. Als Demo-Anwender können Sie jedoch nur einen „Schein”-Anbieter in Aktion erleben. Es werden einige zufällige Transaktionen generiert, um Ihnen zu zeigen, wie der Prozess funktioniert.', + 'recurring-index' => 'Bitte beachten Sie, dass sich diese Funktion in der aktiven Entwicklung befindet und möglicherweise nicht wie erwartet funktioniert.', + 'recurring-create' => 'Bitte beachten Sie, dass sich diese Funktion in der aktiven Entwicklung befindet und möglicherweise nicht wie erwartet funktioniert.', ]; diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index 6bbbcaf6de..89858113db 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scannen Sie den QR-Code mit einer Anwendung wie Authy oder Google Authenticator auf ihrem Handy und geben Sie den generierten Code ein.', 'pref_two_factor_auth_reset_code' => 'Verifizierungscode zurücksetzen', 'pref_two_factor_auth_disable_2fa' => '2FA deaktivieren', + '2fa_use_secret_instead' => 'Wenn Sie den QR-Code nicht scannen können, verwenden Sie stattdessen das Geheimnis: :secret.', 'pref_save_settings' => 'Einstellungen speichern', 'saved_preferences' => 'Einstellungen gespeichert!', 'preferences_general' => 'Allgemein', @@ -821,7 +822,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'language' => 'Sprache', 'new_savings_account' => ':bank_name-Sparkonto', 'cash_wallet' => 'Geldbörse', - 'currency_not_present' => 'Wenn die Währung, die Sie normalerweise verwenden, nicht aufgeführt ist, machen Sie sich keine Sorgen. Unter Optionen ➜ Währungen können Sie eigene Währungen anlegen.', + 'currency_not_present' => 'Wenn die Währung, die Sie normalerweise verwenden, nicht aufgeführt ist, machen Sie sich keine Sorgen. Unter Optionen ➜ Währungen können Sie eigene Währungen anlegen.', // home page: 'yourAccounts' => 'Deine Konten', @@ -901,7 +902,6 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'balanceEnd' => 'Bilanz zum Ende der Periode', 'splitByAccount' => 'Nach Konto aufteilen', 'coveredWithTags' => 'Mit Schlagwörtern versehen', - 'leftUnbalanced' => 'Unausgeglichen belassen', 'leftInBudget' => 'Verblieben im Kostenrahmen', 'sumOfSums' => 'Summe der Summen', 'noCategory' => '(keine Kategorie)', @@ -1063,7 +1063,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'instance_configuration' => 'Konfiguration', 'firefly_instance_configuration' => 'Konfigurationsoptionen für Firefly III', 'setting_single_user_mode' => 'Einzelnutzermodus', - 'setting_single_user_mode_explain' => 'Standardmäßig akzeptiert Firefly III nur eine Registrierung: Sie. Diese Sicherheitsmaßnahme verhindert das Benutzen Ihrer Instanz durch andere, außer Sie erlauben es. Zukünftige Registrierungen sind gesperrt. Wenn Sie dieses Kontrollkästchen deaktivieren können andere ihre Instanz ebenfalls benutzen, vorausgesetzt sie ist erreichbar (mit dem Internet verbunden).', + 'setting_single_user_mode_explain' => 'Dies ist eine sehr fortschrittliche Funktion, welche aber sehr nützlich sein kann. Stellen Sie sicher, dass Sie die Dokumentation (❓-Symbol in der oberen rechten Ecke) lesen, bevor Sie fortfahren.', 'store_configuration' => 'Konfiguration speichern', 'single_user_administration' => 'Benutzerverwaltung für :email', 'edit_user' => 'Benutzer :email bearbeiten', @@ -1135,6 +1135,8 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'is (partially) refunded by_inward' => 'wird (teilweise) erstattet durch', 'is (partially) paid for by_inward' => 'wird (teilweise) bezahlt von', 'is (partially) reimbursed by_inward' => 'wird (teilweise) erstattet durch', + 'inward_transaction' => 'Eingehende Zahlung', + 'outward_transaction' => 'Ausgehende Zahlung', 'relates to_outward' => 'bezieht sich auf', '(partially) refunds_outward' => '(Teil-)Erstattungen', '(partially) pays for_outward' => '(teilweise) bezahlt für', @@ -1156,8 +1158,9 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'cannot_convert_split_journal' => 'Eine Splitbuchung konnte nicht umgesetzt werden', // Import page (general strings only) - 'import_index_title' => 'Daten in Firefly III importieren', + 'import_index_title' => 'Buchungen in Firefly III importieren', 'import_data' => 'Daten importieren', + 'import_transactions' => 'Buchungen importieren', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Diese Funktion ist nicht verfügbar, wenn Sie Firefly III in einer Sandstorm.io-Umgebung verwenden.', @@ -1207,4 +1210,68 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'no_bills_intro_default' => 'Du hast noch keine Rechnungen. Sie können Rechnungen erstellen, um die laufenden Ausgaben, wie zum Beispiel Ihre Versicherung oder Miete, nachzuverfolgen.', 'no_bills_imperative_default' => 'Haben Sie regelmäßige Rechnungen? Erstellen Sie eine Rechnung und verfolgen Sie Ihre Zahlungen:', 'no_bills_create_default' => 'Eine Rechnung erstellen', + + // recurring transactions + 'recurrences' => 'Regelmäßige Buchungen', + 'no_recurring_title_default' => 'Lassen Sie uns eine regelmäßige Buchung erstellen!', + 'no_recurring_intro_default' => 'Sie verfügen noch über keine regelmäßigen Buchungen. Mit diesen können Sie Firefly III dazu einsetzen, automatisch Buchungen für Sie zu erstellen.', + 'no_recurring_imperative_default' => 'Dies ist eine sehr fortschrittliche Funktion, welche aber sehr nützlich sein kann. Stellen Sie sicher, dass Sie die Dokumentation (❓-Symbol in der oberen rechten Ecke) lesen, bevor Sie fortfahren.', + 'no_recurring_create_default' => 'Regelmäßige Buchung erstellen', + 'make_new_recurring' => 'Regelmäßige Buchung erstellen', + 'recurring_daily' => 'Täglich', + 'recurring_weekly' => 'Wöchentlich am :weekday', + 'recurring_monthly' => 'An jedem :dayOfMonth. Tag des Monats', + 'recurring_ndom' => 'An jedem :dayOfMonth. :weekday', + 'recurring_yearly' => 'Jährlich am :date', + 'overview_for_recurrence' => 'Übersicht der regelmäßigen Buchungen „:title”', + 'warning_duplicates_repetitions' => 'In seltenen Fällen werden die Daten zweimal in dieser Liste angezeigt. Dies kann passieren, wenn mehrere Wiederholungen aufeinandertreffen. Firefly III erzeugt immer eine Transaktion pro Tag.', + 'created_transactions' => 'Ähnliche Buchungen', + 'expected_Withdrawals' => 'Erwartete Abzüge', + 'expected_Deposits' => 'Erwartete Einzahlungen', + 'expected_Transfers' => 'Erwartete Überweisungen', + 'created_Withdrawals' => 'Erstellte Abzüge', + 'created_Deposits' => 'Erstellte Einzahlungen', + 'created_Transfers' => 'Erstellte Überweisungen', + 'created_from_recurrence' => 'Erstellt aus Dauerauftrag „:title” (#:id)', + + 'recurring_meta_field_tags' => 'Schlagwörter', + 'recurring_meta_field_notes' => 'Anmerkungen', + 'recurring_meta_field_bill_id' => 'Rechnung', + 'recurring_meta_field_piggy_bank_id' => 'Sparschwein', + 'create_new_recurrence' => 'Neuen Dauerauftrag erstellen', + 'help_first_date' => 'Geben Sie die erste erwartete Wiederholung an. Zeitpunkt muss in der Zukunft liegen.', + 'help_first_date_no_past' => 'Geben Sie die erste erwartete Wiederholung an. Firefly III erzeugt keine Buchungen die in der Vergangenheit liegen.', + 'no_currency' => '(ohne Währung)', + 'mandatory_for_recurring' => 'Erforderliche Wiederholungsinformationen', + 'mandatory_for_transaction' => 'Erforderliche Buchungsinformationen', + 'optional_for_recurring' => 'Optionale Wiederholungsinformationen', + 'optional_for_transaction' => 'Optionale Buchungsinformationen', + 'change_date_other_options' => 'Ändern Sie das „erste Datum”, um weitere Optionen anzuzeigen.', + 'mandatory_fields_for_tranaction' => 'Diese Werte enden in der/den zu erstellenden Buchung(en)', + 'click_for_calendar' => 'Klicken Sie hier für einen Kalender, der Ihnen anzeigt, wann sich die Buchung wiederholen würde.', + 'repeat_forever' => 'Wiederholt sich für immer', + 'repeat_until_date' => 'Wiederholen bis Datum', + 'repeat_times' => 'Wiederholen Sie mehrmals', + 'recurring_skips_one' => 'Alle anderen', + 'recurring_skips_more' => 'Überspringt :count Vorgänge', + 'store_new_recurrence' => 'Dauerauftrag speichern', + 'stored_new_recurrence' => 'Dauerauftrag „:title” erfolgreich gespeichert.', + 'edit_recurrence' => 'Dauerauftrag „:title” bearbeiten', + 'recurring_repeats_until' => 'Wiederholt sich bis :date', + 'recurring_repeats_forever' => 'Wiederholt sich für immer', + 'recurring_repeats_x_times' => 'Wiederholt sich :count mal', + 'update_recurrence' => 'Dauerauftrag aktualisieren', + 'updated_recurrence' => 'Dauerauftrag ":title" aktualisiert', + 'recurrence_is_inactive' => 'Dieser Dauerauftrag ist nicht aktiv und erzeugt keine neuen Buchungen.', + 'delete_recurring' => 'Dauerauftrag „:title” löschen', + 'new_recurring_transaction' => 'Neue Dauerauftrag', + 'help_weekend' => 'Was sollte Firefly III tun, wenn der Dauerauftrag auf einen Samstag oder Sonntag fällt?', + 'do_nothing' => 'Einfach die Buchung anlegen', + 'skip_transaction' => 'Vorkommen überspringen', + 'jump_to_friday' => 'Die Buchung stattdessen am vorhergehenden Freitag ausführen', + 'jump_to_monday' => 'Die Buchung stattdessen am darauffolgenden Montag ausführen', + 'will_jump_friday' => 'Wird am Freitag statt am Wochenende ausgeführt.', + 'will_jump_monday' => 'Wird am Montag statt am Wochenende ausgeführt.', + 'except_weekends' => 'Außer an Wochenenden', + 'recurrence_deleted' => 'Dauerauftrag „:title” gelöscht', ]; diff --git a/resources/lang/de_DE/form.php b/resources/lang/de_DE/form.php index 3195ec20e9..cc1d5e1783 100644 --- a/resources/lang/de_DE/form.php +++ b/resources/lang/de_DE/form.php @@ -24,66 +24,66 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Name der Bank', - 'bank_balance' => 'Kontostand', - 'savings_balance' => 'Sparguthaben', - 'credit_card_limit' => 'Kreditkartenlimit', - 'automatch' => 'Automatisch reagieren', - 'skip' => 'Überspringen', - 'name' => 'Name', - 'active' => 'Aktiv', - 'amount_min' => 'Mindestbetrag', - 'amount_max' => 'Höchstbetrag', - 'match' => 'Reagiert auf', - 'strict' => 'Strenger Modus', - 'repeat_freq' => 'Wiederholungen', - 'journal_currency_id' => 'Währung', - 'currency_id' => 'Währung', - 'transaction_currency_id' => 'Währung', - 'external_ip' => 'Die externe IP-Adresse Ihres Servers', - 'attachments' => 'Anhänge', - 'journal_amount' => 'Betrag', - 'journal_source_account_name' => 'Kreditor (Quelle)', - 'journal_source_account_id' => 'Bestandskonto (Quelle)', - 'BIC' => 'BIC', - 'verify_password' => 'Passwortsicherheit überprüfen', - 'source_account' => 'Quellkonto', - 'destination_account' => 'Zielkonto', - 'journal_destination_account_id' => 'Bestandskonto (Ziel)', - 'asset_destination_account' => 'Bestandskonto (Ziel)', - 'asset_source_account' => 'Bestandskonto (Quelle)', - 'journal_description' => 'Beschreibung', - 'note' => 'Notizen', - 'split_journal' => 'Diese Überweisung aufteilen', - 'split_journal_explanation' => 'Diese Überweisung in mehrere Teile aufteilen', - 'currency' => 'Währung', - 'account_id' => 'Bestandskonto', - 'budget_id' => 'Kostenrahmen', - 'openingBalance' => 'Eröffnungsbilanz', - 'tagMode' => 'Schlagwort-Modus', - 'tag_position' => 'Schlagwort-Speicherort', - 'virtualBalance' => 'Virtueller Kontostand', - 'targetamount' => 'Zielbetrag', - 'accountRole' => 'Rolle des Kontos', - 'openingBalanceDate' => 'Eröffnungsbilanzdatum', - 'ccType' => 'Zahlungsplan der Kreditkarte', - 'ccMonthlyPaymentDate' => 'Monatliches Zahlungsdatum der Kreditkarte', - 'piggy_bank_id' => 'Sparschwein', - 'returnHere' => 'Hierhin zurückkehren', - 'returnHereExplanation' => 'Nach dem Speichern hierher zurückkehren, um ein weiteres Element zu erstellen.', - 'returnHereUpdateExplanation' => 'Nach dem Update, hierher zurückkehren.', - 'description' => 'Beschreibung', - 'expense_account' => 'Debitor (Ausgabe)', - 'revenue_account' => 'Kreditor (Einnahme)', - 'decimal_places' => 'Nachkommastellen', - 'exchange_rate_instruction' => 'Fremdwährungen', - 'source_amount' => 'Betrag (Quelle)', - 'destination_amount' => 'Betrag (Ziel)', - 'native_amount' => 'Nativer Betrag', - 'new_email_address' => 'Neue E-Mail-Adresse', - 'verification' => 'Bestätigung', - 'api_key' => 'API-Schlüssel', - 'remember_me' => 'Angemeldet bleiben', + 'bank_name' => 'Name der Bank', + 'bank_balance' => 'Kontostand', + 'savings_balance' => 'Sparguthaben', + 'credit_card_limit' => 'Kreditkartenlimit', + 'automatch' => 'Automatisch reagieren', + 'skip' => 'Überspringen', + 'name' => 'Name', + 'active' => 'Aktiv', + 'amount_min' => 'Mindestbetrag', + 'amount_max' => 'Höchstbetrag', + 'match' => 'Reagiert auf', + 'strict' => 'Strenger Modus', + 'repeat_freq' => 'Wiederholungen', + 'journal_currency_id' => 'Währung', + 'currency_id' => 'Währung', + 'transaction_currency_id' => 'Währung', + 'external_ip' => 'Die externe IP-Adresse Ihres Servers', + 'attachments' => 'Anhänge', + 'journal_amount' => 'Betrag', + 'journal_source_name' => 'Erlöskonto (Herkunft)', + 'journal_source_id' => 'Anlagenkonto (Herkunft)', + 'BIC' => 'BIC', + 'verify_password' => 'Passwortsicherheit überprüfen', + 'source_account' => 'Quellkonto', + 'destination_account' => 'Zielkonto', + 'journal_destination_id' => 'Anlagenkonto (Ziel)', + 'asset_destination_account' => 'Bestandskonto (Ziel)', + 'asset_source_account' => 'Bestandskonto (Quelle)', + 'journal_description' => 'Beschreibung', + 'note' => 'Notizen', + 'split_journal' => 'Diese Überweisung aufteilen', + 'split_journal_explanation' => 'Diese Überweisung in mehrere Teile aufteilen', + 'currency' => 'Währung', + 'account_id' => 'Bestandskonto', + 'budget_id' => 'Kostenrahmen', + 'openingBalance' => 'Eröffnungsbilanz', + 'tagMode' => 'Schlagwort-Modus', + 'tag_position' => 'Schlagwort-Speicherort', + 'virtualBalance' => 'Virtueller Kontostand', + 'targetamount' => 'Zielbetrag', + 'accountRole' => 'Rolle des Kontos', + 'openingBalanceDate' => 'Eröffnungsbilanzdatum', + 'ccType' => 'Zahlungsplan der Kreditkarte', + 'ccMonthlyPaymentDate' => 'Monatliches Zahlungsdatum der Kreditkarte', + 'piggy_bank_id' => 'Sparschwein', + 'returnHere' => 'Hierhin zurückkehren', + 'returnHereExplanation' => 'Nach dem Speichern hierher zurückkehren, um ein weiteres Element zu erstellen.', + 'returnHereUpdateExplanation' => 'Nach dem Update, hierher zurückkehren.', + 'description' => 'Beschreibung', + 'expense_account' => 'Debitor (Ausgabe)', + 'revenue_account' => 'Kreditor (Einnahme)', + 'decimal_places' => 'Nachkommastellen', + 'exchange_rate_instruction' => 'Fremdwährungen', + 'source_amount' => 'Betrag (Quelle)', + 'destination_amount' => 'Betrag (Ziel)', + 'native_amount' => 'Nativer Betrag', + 'new_email_address' => 'Neue E-Mail-Adresse', + 'verification' => 'Bestätigung', + 'api_key' => 'API-Schlüssel', + 'remember_me' => 'Angemeldet bleiben', 'source_account_asset' => 'Quellkonto (Bestandskonto)', 'destination_account_expense' => 'Zielkonto (Unkostenkonto)', @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Ändere zu Einzahlung', 'convert_Transfer' => 'In Umbuchung umwandeln', - 'amount' => 'Betrag', - 'foreign_amount' => 'Ausländischer Betrag', - 'existing_attachments' => 'Bestehende Anhänge', - 'date' => 'Datum', - 'interest_date' => 'Zinstermin', - 'book_date' => 'Buchungsdatum', - 'process_date' => 'Bearbeitungsdatum', - 'category' => 'Kategorie', - 'tags' => 'Schlagwörter', - 'deletePermanently' => 'Dauerhaft löschen', - 'cancel' => 'Abbrechen', - 'targetdate' => 'Zieldatum', - 'startdate' => 'Startdatum', - 'tag' => 'Schlagwort', - 'under' => 'Unter', - 'symbol' => 'Zeichen', - 'code' => 'Schlüssel', - 'iban' => 'IBAN', - 'accountNumber' => 'Kontonummer', - 'creditCardNumber' => 'Kreditkartennummer', - 'has_headers' => 'Kopfzeilen', - 'date_format' => 'Datumsformat', - 'specifix' => 'Bank- oder Dateispezifischer Korrekturen', - 'attachments[]' => 'Anhänge', - 'store_new_withdrawal' => 'Speichere neue Ausgabe', - 'store_new_deposit' => 'Speichere neue Einnahme', - 'store_new_transfer' => 'Neue Umbuchung speichern', - 'add_new_withdrawal' => 'Fügen Sie eine neue Ausgabe hinzu', - 'add_new_deposit' => 'Fügen Sie eine neue Einnahme hinzu', - 'add_new_transfer' => 'Neue Umbuchung anlegen', - 'title' => 'Titel', - 'notes' => 'Notizen', - 'filename' => 'Dateiname', - 'mime' => 'MIME-Typ', - 'size' => 'Größe', - 'trigger' => 'Auslöser', - 'stop_processing' => 'Verarbeitung beenden', - 'start_date' => 'Anfang des Bereichs', - 'end_date' => 'Ende des Bereichs', - 'export_start_range' => 'Beginn des Exportbereichs', - 'export_end_range' => 'Ende des Exportbereichs', - 'export_format' => 'Dateiformat', - 'include_attachments' => 'Hochgeladene Anhänge hinzufügen', - 'include_old_uploads' => 'Importierte Daten hinzufügen', - 'accounts' => 'Exportiere die Überweisungen von diesem Konto', - 'delete_account' => 'Konto „:name” löschen', - 'delete_bill' => 'Rechnung „:name” löschen', - 'delete_budget' => 'Kostenrahmen „:name” löschen', - 'delete_category' => 'Kategorie „:name” löschen', - 'delete_currency' => 'Währung „:name” löschen', - 'delete_journal' => 'Lösche Überweisung mit Beschreibung ":description"', - 'delete_attachment' => 'Anhang „:name” löschen', - 'delete_rule' => 'Lösche Regel ":title"', - 'delete_rule_group' => 'Lösche Regelgruppe ":title"', - 'delete_link_type' => 'Verknüpfungstyp „:name” löschen', - 'delete_user' => 'Benutzer ":email" löschen', - 'user_areYouSure' => 'Wenn Sie den Benutzer ":email" löschen, ist alles weg. Es gibt keine Sicherung, Wiederherstellung oder ähnliches. Wenn Sie sich selbst löschen, verlieren Sie den Zugriff auf diese Instanz von Firefly III.', - 'attachment_areYouSure' => 'Möchten Sie den Anhang „:name” wirklich löschen?', - 'account_areYouSure' => 'Möchten Sie das Konto „:name” wirklich löschen?', - 'bill_areYouSure' => 'Möchten Sie die Rechnung „:name” wirklich löschen?', - 'rule_areYouSure' => 'Sind Sie sicher, dass Sie die Regel mit dem Titel ":title" löschen möchten?', - 'ruleGroup_areYouSure' => 'Sind Sie sicher, dass sie die Regelgruppe ":title" löschen möchten?', - 'budget_areYouSure' => 'Möchten Sie den Kostenrahmen „:name” wirklich löschen?', - 'category_areYouSure' => 'Möchten Sie die Kategorie „:name” wirklich löschen?', - 'currency_areYouSure' => 'Möchten Sie die Währung „:name” wirklich löschen?', - 'piggyBank_areYouSure' => 'Möchten Sie das Sparschwein „:name” wirklich löschen?', - 'journal_areYouSure' => 'Sind Sie sicher, dass Sie die Überweisung mit dem Namen ":description" löschen möchten?', - 'mass_journal_are_you_sure' => 'Sind Sie sicher, dass Sie diese Überweisung löschen möchten?', - 'tag_areYouSure' => 'Möchten Sie das Schlagwort „:tag” wirklich löschen?', - 'journal_link_areYouSure' => 'Sind Sie sicher, dass Sie die Verknüpfung zwischen :source und :destination löschen möchten?', - 'linkType_areYouSure' => 'Möchten Sie den Verknüpfungstyp „:name” („:inward”/„:outward”) wirklich löschen?', - 'permDeleteWarning' => 'Das Löschen von Dingen in Firefly III ist dauerhaft und kann nicht rückgängig gemacht werden.', - 'mass_make_selection' => 'Sie können das Löschen von Elementen verhindern, indem Sie die Checkbox entfernen.', - 'delete_all_permanently' => 'Ausgewähltes dauerhaft löschen', - 'update_all_journals' => 'Diese Transaktionen aktualisieren', - 'also_delete_transactions' => 'Die einzige Überweisung, die mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Überweisungen, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', - 'also_delete_connections' => 'Die einzige Transaktion, die mit diesem Verknüpfungstyp verknüpft ist, verliert diese Verbindung. • Alle :count Buchungen, die mit diesem Verknüpfungstyp verknüpft sind, verlieren ihre Verbindung.', - 'also_delete_rules' => 'Die einzige Regel, die mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Regeln, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', - 'also_delete_piggyBanks' => 'Das einzige Sparschwein, das mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Sparschweine, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', - 'bill_keep_transactions' => 'Die einzige Überweisung, die mit dieser Rechnung verknüpft ist, wird nicht gelöscht. | Keine der :count Überweisungen, die mit dieser Rechnung verknüpft sind, werden gelöscht.', - 'budget_keep_transactions' => 'Die einzige Buchung, die mit dieser Rechnung verknüpft ist, wird nicht gelöscht. | Keine der :count Buchungen, die mit dieser Rechnung verknüpft sind, werden gelöscht.', - 'category_keep_transactions' => 'Die eine Überweisungen, die mit dieser Kategorie verknüpft ist, wird nicht gelöscht. | Keine der :count Kategorien, die mit dieser Rechnung verknüpft sind, werden gelöscht.', - 'tag_keep_transactions' => 'Die einzige Buchung, die mit diesem Schlagwort verbunden ist, wird nicht gelöscht. • Alle :count Vorgänge, die mit diesem Schlagwort verbunden sind, werden nicht gelöscht.', - 'check_for_updates' => 'Nach Updates suchen', + 'amount' => 'Betrag', + 'foreign_amount' => 'Ausländischer Betrag', + 'existing_attachments' => 'Bestehende Anhänge', + 'date' => 'Datum', + 'interest_date' => 'Zinstermin', + 'book_date' => 'Buchungsdatum', + 'process_date' => 'Bearbeitungsdatum', + 'category' => 'Kategorie', + 'tags' => 'Schlagwörter', + 'deletePermanently' => 'Dauerhaft löschen', + 'cancel' => 'Abbrechen', + 'targetdate' => 'Zieldatum', + 'startdate' => 'Startdatum', + 'tag' => 'Schlagwort', + 'under' => 'Unter', + 'symbol' => 'Zeichen', + 'code' => 'Schlüssel', + 'iban' => 'IBAN', + 'accountNumber' => 'Kontonummer', + 'creditCardNumber' => 'Kreditkartennummer', + 'has_headers' => 'Kopfzeilen', + 'date_format' => 'Datumsformat', + 'specifix' => 'Bank- oder Dateispezifischer Korrekturen', + 'attachments[]' => 'Anhänge', + 'store_new_withdrawal' => 'Speichere neue Ausgabe', + 'store_new_deposit' => 'Speichere neue Einnahme', + 'store_new_transfer' => 'Neue Umbuchung speichern', + 'add_new_withdrawal' => 'Fügen Sie eine neue Ausgabe hinzu', + 'add_new_deposit' => 'Fügen Sie eine neue Einnahme hinzu', + 'add_new_transfer' => 'Neue Umbuchung anlegen', + 'title' => 'Titel', + 'notes' => 'Notizen', + 'filename' => 'Dateiname', + 'mime' => 'MIME-Typ', + 'size' => 'Größe', + 'trigger' => 'Auslöser', + 'stop_processing' => 'Verarbeitung beenden', + 'start_date' => 'Anfang des Bereichs', + 'end_date' => 'Ende des Bereichs', + 'export_start_range' => 'Beginn des Exportbereichs', + 'export_end_range' => 'Ende des Exportbereichs', + 'export_format' => 'Dateiformat', + 'include_attachments' => 'Hochgeladene Anhänge hinzufügen', + 'include_old_uploads' => 'Importierte Daten hinzufügen', + 'accounts' => 'Exportiere die Überweisungen von diesem Konto', + 'delete_account' => 'Konto „:name” löschen', + 'delete_bill' => 'Rechnung „:name” löschen', + 'delete_budget' => 'Kostenrahmen „:name” löschen', + 'delete_category' => 'Kategorie „:name” löschen', + 'delete_currency' => 'Währung „:name” löschen', + 'delete_journal' => 'Lösche Überweisung mit Beschreibung ":description"', + 'delete_attachment' => 'Anhang „:name” löschen', + 'delete_rule' => 'Lösche Regel ":title"', + 'delete_rule_group' => 'Lösche Regelgruppe ":title"', + 'delete_link_type' => 'Verknüpfungstyp „:name” löschen', + 'delete_user' => 'Benutzer ":email" löschen', + 'delete_recurring' => 'Dauerauftrag „:title” löschen', + 'user_areYouSure' => 'Wenn Sie den Benutzer ":email" löschen, ist alles weg. Es gibt keine Sicherung, Wiederherstellung oder ähnliches. Wenn Sie sich selbst löschen, verlieren Sie den Zugriff auf diese Instanz von Firefly III.', + 'attachment_areYouSure' => 'Möchten Sie den Anhang „:name” wirklich löschen?', + 'account_areYouSure' => 'Möchten Sie das Konto „:name” wirklich löschen?', + 'bill_areYouSure' => 'Möchten Sie die Rechnung „:name” wirklich löschen?', + 'rule_areYouSure' => 'Sind Sie sicher, dass Sie die Regel mit dem Titel ":title" löschen möchten?', + 'ruleGroup_areYouSure' => 'Sind Sie sicher, dass sie die Regelgruppe ":title" löschen möchten?', + 'budget_areYouSure' => 'Möchten Sie den Kostenrahmen „:name” wirklich löschen?', + 'category_areYouSure' => 'Möchten Sie die Kategorie „:name” wirklich löschen?', + 'recurring_areYouSure' => 'Möchten Sie den Dauerauftrag „:title” wirklich löschen?', + 'currency_areYouSure' => 'Möchten Sie die Währung „:name” wirklich löschen?', + 'piggyBank_areYouSure' => 'Möchten Sie das Sparschwein „:name” wirklich löschen?', + 'journal_areYouSure' => 'Sind Sie sicher, dass Sie die Überweisung mit dem Namen ":description" löschen möchten?', + 'mass_journal_are_you_sure' => 'Sind Sie sicher, dass Sie diese Überweisung löschen möchten?', + 'tag_areYouSure' => 'Möchten Sie das Schlagwort „:tag” wirklich löschen?', + 'journal_link_areYouSure' => 'Sind Sie sicher, dass Sie die Verknüpfung zwischen :source und :destination löschen möchten?', + 'linkType_areYouSure' => 'Möchten Sie den Verknüpfungstyp „:name” („:inward”/„:outward”) wirklich löschen?', + 'permDeleteWarning' => 'Das Löschen von Dingen in Firefly III ist dauerhaft und kann nicht rückgängig gemacht werden.', + 'mass_make_selection' => 'Sie können das Löschen von Elementen verhindern, indem Sie die Checkbox entfernen.', + 'delete_all_permanently' => 'Ausgewähltes dauerhaft löschen', + 'update_all_journals' => 'Diese Transaktionen aktualisieren', + 'also_delete_transactions' => 'Die einzige Überweisung, die mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Überweisungen, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', + 'also_delete_connections' => 'Die einzige Transaktion, die mit diesem Verknüpfungstyp verknüpft ist, verliert diese Verbindung. • Alle :count Buchungen, die mit diesem Verknüpfungstyp verknüpft sind, verlieren ihre Verbindung.', + 'also_delete_rules' => 'Die einzige Regel, die mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Regeln, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', + 'also_delete_piggyBanks' => 'Das einzige Sparschwein, das mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Sparschweine, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', + 'bill_keep_transactions' => 'Die einzige mit dieser Rechnung verbundene Buchung wird nicht gelöscht. | Alle :count Buchungen, die mit dieser Rechnung verbunden sind, werden nicht gelöscht.', + 'budget_keep_transactions' => 'Die einzige mit diesem Kostenrahmen verbundene Buchung wird nicht gelöscht. | Alle :count Buchungen, die mit diesem Kostenrahmen verbunden sind, werden nicht gelöscht.', + 'category_keep_transactions' => 'Die einzige Buchung, die mit dieser Kategorie verbunden ist, wird nicht gelöscht. | Alle :count Buchungen, die mit dieser Kategorie verbunden sind, werden nicht gelöscht.', + 'recurring_keep_transactions' => 'Die einzige Buchung, die durch diesen Dauerauftrag erstellt wurde, wird nicht gelöscht. | Alle :count Buchungen, die durch diesen Dauerauftrag erstellt wurden, werden nicht gelöscht.', + 'tag_keep_transactions' => 'Das einzige mit dieser Rechnung verbundene Schlagwort wird nicht gelöscht. | Alle :count Schlagwörter, die mit dieser Rechnung verbunden sind, werden nicht gelöscht.', + 'check_for_updates' => 'Nach Updates suchen', 'email' => 'E-Mail Adresse', 'password' => 'Passwort', @@ -216,11 +219,23 @@ return [ 'country_code' => 'Ländercode', 'provider_code' => 'Bank oder Datenanbieter', - 'due_date' => 'Fälligkeitstermin', - 'payment_date' => 'Zahlungsdatum', - 'invoice_date' => 'Rechnungsdatum', - 'internal_reference' => 'Interner Verweis', - 'inward' => 'Beschreibung der Eingänge', - 'outward' => 'Beschreibung der Ausgänge', - 'rule_group_id' => 'Regelgruppe', + 'due_date' => 'Fälligkeitstermin', + 'payment_date' => 'Zahlungsdatum', + 'invoice_date' => 'Rechnungsdatum', + 'internal_reference' => 'Interner Verweis', + 'inward' => 'Beschreibung der Eingänge', + 'outward' => 'Beschreibung der Ausgänge', + 'rule_group_id' => 'Regelgruppe', + 'transaction_description' => 'Beschreibung der Buchung', + 'first_date' => 'Erstes Datum', + 'transaction_type' => 'Art der Buchung', + 'repeat_until' => 'Wiederholen bis', + 'recurring_description' => 'Beschreibung des Dauerauftrags', + 'repetition_type' => 'Art der Wiederholung', + 'foreign_currency_id' => 'Fremdwährung', + 'repetition_end' => 'Wiederholung endet', + 'repetitions' => 'Wiederholungen', + 'calendar' => 'Kalender', + 'weekend' => 'Wochenende', + ]; diff --git a/resources/lang/de_DE/import.php b/resources/lang/de_DE/import.php index 2e2c30e8e5..e19c46222c 100644 --- a/resources/lang/de_DE/import.php +++ b/resources/lang/de_DE/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'Es scheint, dass Sie keine Konten zum Importieren ausgewählt haben.', 'imported_from_account' => 'Von „:account” importiert', 'spectre_account_with_number' => 'Konto :number', + 'job_config_spectre_apply_rules' => 'Regeln übernehmen', + 'job_config_spectre_apply_rules_text' => 'Standardmäßig werden Ihre Regeln auf die Buchungen angewendet, die während dieser Importfunktion erstellt wurden. Wenn Sie dies nicht wünschen, entfernen Sie die Markierung dieses Kontrollkästchens.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq-Konten', 'job_config_bunq_accounts_text' => 'Dies sind jene Konten, die mit Ihrem „bunq”-Konto verknüpft sind. Bitte wählen Sie die Konten aus, von denen Sie importieren möchten, und in welches Konto die Buchungen importiert werden sollen.', 'bunq_no_mapping' => 'Es scheint, dass Sie keine Konten ausgewählt haben.', 'should_download_config' => 'Sie sollten die Konfigurationsdatei für diesen Aufgabe herunterladen. Dies wird zukünftige Importe erheblich erleichtern.', 'share_config_file' => 'Wenn Sie Daten von einer öffentlichen Bank importiert haben, sollten Sie Ihre Konfigurationsdatei freigeben, damit es für andere Benutzer einfach ist, ihre Daten zu importieren. Wenn Sie Ihre Konfigurationsdatei freigeben, werden Ihre finanziellen Daten nicht preisgegeben.', - + 'job_config_bunq_apply_rules' => 'Regeln übernehmen', + 'job_config_bunq_apply_rules_text' => 'Standardmäßig werden Ihre Regeln auf die Buchungen angewendet, die während dieser Importfunktion erstellt wurden. Wenn Sie dies nicht wünschen, entfernen Sie die Markierung dieses Kontrollkästchens.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT Code', diff --git a/resources/lang/de_DE/list.php b/resources/lang/de_DE/list.php index f2d030e3f7..fe4b250ed6 100644 --- a/resources/lang/de_DE/list.php +++ b/resources/lang/de_DE/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Letzte Anmeldung', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq-Zahlungskennung', + 'repetitions' => 'Wiederholungen', + 'title' => 'Titel', + 'transaction_s' => 'Buchung(en)', + 'field' => 'Feld', + 'value' => 'Wert', ]; diff --git a/resources/lang/de_DE/validation.php b/resources/lang/de_DE/validation.php index 7cc5d3de94..d30156cb75 100644 --- a/resources/lang/de_DE/validation.php +++ b/resources/lang/de_DE/validation.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'iban' => 'Dies ist keine gültige IBAN.', - 'source_equals_destination' => 'Das Quellkonto entspricht dem Zielkonto', + 'source_equals_destination' => 'Das Quellkonto entspricht dem Zielkonto.', 'unique_account_number_for_user' => 'Diese Kontonummer scheint bereits verwendet zu sein.', 'unique_iban_for_user' => 'Dieser IBAN scheint bereits verwendet zu werden.', 'deleted_user' => 'Aufgrund von Sicherheitsbeschränkungen ist eine Registrierung mit dieser E-Mail-Adresse nicht zugelassen.', @@ -34,16 +34,22 @@ return [ 'file_attached' => 'Datei „:name” erfolgreich hochgeladen.', 'must_exist' => 'Die ID in Feld :attribute existiert nicht in der Datenbank.', 'all_accounts_equal' => 'Alle Konten in diesem Feld müssen identisch sein.', - 'invalid_selection' => 'Die Auswahl ist ungültig', + 'invalid_selection' => 'Ihre Auswahl ist ungültig.', 'belongs_user' => 'Dieser Wert ist für dieses Feld ungültig.', 'at_least_one_transaction' => 'Sie brauchen mindestens eine Transaktion.', + 'at_least_one_repetition' => 'Mindestens eine Wiederholung erforderlich.', + 'require_repeat_until' => 'Erfordert entweder eine Anzahl von Wiederholungen oder ein Enddatum (repeat_until). Nicht beides.', 'require_currency_info' => 'Der Inhalt dieses Feldes ist ohne Währungsinformationen ungültig.', 'equal_description' => 'Die Transaktionsbeschreibung darf nicht der globalen Beschreibung entsprechen.', 'file_invalid_mime' => 'Die Datei „:name” ist vom Typ „:mime”, welcher nicht zum Hochladen zugelassen ist.', 'file_too_large' => 'Die Datei „:name” ist zu groß.', - 'belongs_to_user' => 'Der Wert von :attribute ist nicht bekannt', + 'belongs_to_user' => 'Der Wert von :attribute ist unbekannt.', 'accepted' => ':attribute muss akzeptiert werden.', 'bic' => 'Dies ist kein gültiger BIC.', + 'at_least_one_trigger' => 'Regel muss mindestens einen Auslöser enthalten', + 'at_least_one_action' => 'Regel muss mindestens eine Aktion enthalten', + 'base64' => 'Dies sind keine gültigen base64-kodierten Daten.', + 'model_id_invalid' => 'Die angegebene ID scheint für dieses Modell ungültig zu sein.', 'more' => ':attribute muss größer als Null sein.', 'active_url' => ':attribute ist keine gültige URL.', 'after' => ':attribute muss ein Datum nach :date sein.', @@ -53,8 +59,8 @@ return [ 'array' => ':attribute muss eine Liste sein.', 'unique_for_user' => 'Es gibt bereits einen Eintrag mit diesem :attribute.', 'before' => ':attribute muss ein Datum vor dem :date sein.', - 'unique_object_for_user' => 'Der Name wird bereits verwendet', - 'unique_account_for_user' => 'Dieser Kontoname wird bereits verwendet', + 'unique_object_for_user' => 'Dieser Name wird bereits verwendet.', + 'unique_account_for_user' => 'Dieser Kontoname wird bereits verwendet.', 'between.numeric' => ':attribute muss zwischen :min und :max liegen.', 'between.file' => ':attribute muss zwischen :min und :max Kilobytes groß sein.', 'between.string' => ':attribute muss zwischen :min und :max Zeichen lang sein.', @@ -85,6 +91,9 @@ return [ 'min.array' => ':attribute muss mindestens :min Elemente enthalten.', 'not_in' => ':attribute ist ungültig.', 'numeric' => ':attribute muss eine Zahl sein.', + 'numeric_native' => 'Die native Betrag muss eine Zahl sein.', + 'numeric_destination' => 'Der Zielbeitrag muss eine Zahl sein.', + 'numeric_source' => 'Der Quellbetrag muss eine Zahl sein.', 'regex' => 'Das Format von :attribute ist ungültig.', 'required' => ':attribute Feld muss ausgefüllt sein.', 'required_if' => ':attribute Feld ist notwendig, wenn :other :value entspricht.', @@ -109,9 +118,12 @@ return [ 'file' => 'Das :attribute muss eine Datei sein.', 'in_array' => ':attribute existiert nicht in :other.', 'present' => 'Das :attribute Feld muss vorhanden sein.', - 'amount_zero' => 'Der Gesamtbetrag darf nicht Null sein', + 'amount_zero' => 'Der Gesamtbetrag darf nicht Null sein.', 'unique_piggy_bank_for_user' => 'Der Name des Sparschweins muss eindeutig sein.', - 'secure_password' => 'Das ist kein sicheres Passwort. Bitte versuchen Sie es erneut. Weitere Informationen finden Sie unter https://github.com/firefly-iii/help/wiki/Secure-password', + 'secure_password' => 'Dies ist kein sicheres Passwort. Bitte versuchen Sie es erneut. Weitere Informationen finden Sie unter https://github.com/firefly-iii/help/wiki/Secure-password (engl.).', + 'valid_recurrence_rep_type' => 'Ungültige Wiederholungsart für Daueraufträge.', + 'valid_recurrence_rep_moment' => 'Ungültiges Wiederholungsmoment für diese Art der Wiederholung.', + 'invalid_account_info' => 'Ungültige Kontodaten.', 'attributes' => [ 'email' => 'E-Mail Adresse', 'description' => 'Beschreibung', diff --git a/resources/lang/en_US/config.php b/resources/lang/en_US/config.php index f7a905591f..eb6b1f2322 100644 --- a/resources/lang/en_US/config.php +++ b/resources/lang/en_US/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'en', - 'locale' => 'en, English, en_US, en_US.utf8, en_US.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%B %e, %Y', - 'date_time' => '%B %e, %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Week %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'MMMM Do, YYYY', - 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'en', + 'locale' => 'en, English, en_US, en_US.utf8, en_US.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%B %e, %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%B %e, %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Week %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'MMMM Do, YYYY', + 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/en_US/demo.php b/resources/lang/en_US/demo.php index 640ab10298..e56f6f24c6 100644 --- a/resources/lang/en_US/demo.php +++ b/resources/lang/en_US/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'These expenses, deposits and transfers are not particularly imaginative. They have been generated automatically.', 'piggy-banks-index' => 'As you can see, there are three piggy banks. Use the plus and minus buttons to influence the amount of money in each piggy bank. Click the name of the piggy bank to see the administration for each piggy bank.', 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index bd4b5d4bd1..ad47b99796 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', 'pref_two_factor_auth_reset_code' => 'Reset verification code', 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Save settings', 'saved_preferences' => 'Preferences saved!', 'preferences_general' => 'General', @@ -820,7 +821,7 @@ return [ 'language' => 'Language', 'new_savings_account' => ':bank_name savings account', 'cash_wallet' => 'Cash wallet', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Your accounts', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Balance at end of period', 'splitByAccount' => 'Split by account', 'coveredWithTags' => 'Covered with tags', - 'leftUnbalanced' => 'Left unbalanced', 'leftInBudget' => 'Left in budget', 'sumOfSums' => 'Sum of sums', 'noCategory' => '(no category)', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Configuration', 'firefly_instance_configuration' => 'Configuration options for Firefly III', 'setting_single_user_mode' => 'Single user mode', - 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Store configuration', 'single_user_administration' => 'User administration for :email', 'edit_user' => 'Edit user :email', @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => 'is (partially) refunded by', 'is (partially) paid for by_inward' => 'is (partially) paid for by', 'is (partially) reimbursed by_inward' => 'is (partially) reimbursed by', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'relates to', '(partially) refunds_outward' => '(partially) refunds', '(partially) pays for_outward' => '(partially) pays for', @@ -1155,8 +1157,9 @@ return [ 'cannot_convert_split_journal' => 'Cannot convert a split transaction', // Import page (general strings only) - 'import_index_title' => 'Import data into Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Import data', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', @@ -1206,4 +1209,68 @@ return [ 'no_bills_intro_default' => 'You have no bills yet. You can create bills to keep track of regular expenses, like your rent or insurance.', 'no_bills_imperative_default' => 'Do you have such regular bills? Create a bill and keep track of your payments:', 'no_bills_create_default' => 'Create a bill', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 39955eb731..fe18364b8e 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -24,66 +24,66 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Bank name', - 'bank_balance' => 'Balance', - 'savings_balance' => 'Savings balance', - 'credit_card_limit' => 'Credit card limit', - 'automatch' => 'Match automatically', - 'skip' => 'Skip', - 'name' => 'Name', - 'active' => 'Active', - 'amount_min' => 'Minimum amount', - 'amount_max' => 'Maximum amount', - 'match' => 'Matches on', - 'strict' => 'Strict mode', - 'repeat_freq' => 'Repeats', - 'journal_currency_id' => 'Currency', - 'currency_id' => 'Currency', - 'transaction_currency_id' => 'Currency', - 'external_ip' => 'Your server\'s external IP', - 'attachments' => 'Attachments', - 'journal_amount' => 'Amount', - 'journal_source_account_name' => 'Revenue account (source)', - 'journal_source_account_id' => 'Asset account (source)', - 'BIC' => 'BIC', - 'verify_password' => 'Verify password security', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', - 'journal_destination_account_id' => 'Asset account (destination)', - 'asset_destination_account' => 'Asset account (destination)', - 'asset_source_account' => 'Asset account (source)', - 'journal_description' => 'Description', - 'note' => 'Notes', - 'split_journal' => 'Split this transaction', - 'split_journal_explanation' => 'Split this transaction in multiple parts', - 'currency' => 'Currency', - 'account_id' => 'Asset account', - 'budget_id' => 'Budget', - 'openingBalance' => 'Opening balance', - 'tagMode' => 'Tag mode', - 'tag_position' => 'Tag location', - 'virtualBalance' => 'Virtual balance', - 'targetamount' => 'Target amount', - 'accountRole' => 'Account role', - 'openingBalanceDate' => 'Opening balance date', - 'ccType' => 'Credit card payment plan', - 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', - 'piggy_bank_id' => 'Piggy bank', - 'returnHere' => 'Return here', - 'returnHereExplanation' => 'After storing, return here to create another one.', - 'returnHereUpdateExplanation' => 'After updating, return here.', - 'description' => 'Description', - 'expense_account' => 'Expense account', - 'revenue_account' => 'Revenue account', - 'decimal_places' => 'Decimal places', - 'exchange_rate_instruction' => 'Foreign currencies', - 'source_amount' => 'Amount (source)', - 'destination_amount' => 'Amount (destination)', - 'native_amount' => 'Native amount', - 'new_email_address' => 'New email address', - 'verification' => 'Verification', - 'api_key' => 'API key', - 'remember_me' => 'Remember me', + 'bank_name' => 'Bank name', + 'bank_balance' => 'Balance', + 'savings_balance' => 'Savings balance', + 'credit_card_limit' => 'Credit card limit', + 'automatch' => 'Match automatically', + 'skip' => 'Skip', + 'name' => 'Name', + 'active' => 'Active', + 'amount_min' => 'Minimum amount', + 'amount_max' => 'Maximum amount', + 'match' => 'Matches on', + 'strict' => 'Strict mode', + 'repeat_freq' => 'Repeats', + 'journal_currency_id' => 'Currency', + 'currency_id' => 'Currency', + 'transaction_currency_id' => 'Currency', + 'external_ip' => 'Your server\'s external IP', + 'attachments' => 'Attachments', + 'journal_amount' => 'Amount', + 'journal_source_name' => 'Revenue account (source)', + 'journal_source_id' => 'Asset account (source)', + 'BIC' => 'BIC', + 'verify_password' => 'Verify password security', + 'source_account' => 'Source account', + 'destination_account' => 'Destination account', + 'journal_destination_id' => 'Asset account (destination)', + 'asset_destination_account' => 'Asset account (destination)', + 'asset_source_account' => 'Asset account (source)', + 'journal_description' => 'Description', + 'note' => 'Notes', + 'split_journal' => 'Split this transaction', + 'split_journal_explanation' => 'Split this transaction in multiple parts', + 'currency' => 'Currency', + 'account_id' => 'Asset account', + 'budget_id' => 'Budget', + 'openingBalance' => 'Opening balance', + 'tagMode' => 'Tag mode', + 'tag_position' => 'Tag location', + 'virtualBalance' => 'Virtual balance', + 'targetamount' => 'Target amount', + 'accountRole' => 'Account role', + 'openingBalanceDate' => 'Opening balance date', + 'ccType' => 'Credit card payment plan', + 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', + 'piggy_bank_id' => 'Piggy bank', + 'returnHere' => 'Return here', + 'returnHereExplanation' => 'After storing, return here to create another one.', + 'returnHereUpdateExplanation' => 'After updating, return here.', + 'description' => 'Description', + 'expense_account' => 'Expense account', + 'revenue_account' => 'Revenue account', + 'decimal_places' => 'Decimal places', + 'exchange_rate_instruction' => 'Foreign currencies', + 'source_amount' => 'Amount (source)', + 'destination_amount' => 'Amount (destination)', + 'native_amount' => 'Native amount', + 'new_email_address' => 'New email address', + 'verification' => 'Verification', + 'api_key' => 'API key', + 'remember_me' => 'Remember me', 'source_account_asset' => 'Source account (asset account)', 'destination_account_expense' => 'Destination account (expense account)', @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Convert deposit', 'convert_Transfer' => 'Convert transfer', - 'amount' => 'Amount', - 'foreign_amount' => 'Foreign amount', - 'existing_attachments' => 'Existing attachments', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'category' => 'Category', - 'tags' => 'Tags', - 'deletePermanently' => 'Delete permanently', - 'cancel' => 'Cancel', - 'targetdate' => 'Target date', - 'startdate' => 'Start date', - 'tag' => 'Tag', - 'under' => 'Under', - 'symbol' => 'Symbol', - 'code' => 'Code', - 'iban' => 'IBAN', - 'accountNumber' => 'Account number', - 'creditCardNumber' => 'Credit card number', - 'has_headers' => 'Headers', - 'date_format' => 'Date format', - 'specifix' => 'Bank- or file specific fixes', - 'attachments[]' => 'Attachments', - 'store_new_withdrawal' => 'Store new withdrawal', - 'store_new_deposit' => 'Store new deposit', - 'store_new_transfer' => 'Store new transfer', - 'add_new_withdrawal' => 'Add a new withdrawal', - 'add_new_deposit' => 'Add a new deposit', - 'add_new_transfer' => 'Add a new transfer', - 'title' => 'Title', - 'notes' => 'Notes', - 'filename' => 'File name', - 'mime' => 'Mime type', - 'size' => 'Size', - 'trigger' => 'Trigger', - 'stop_processing' => 'Stop processing', - 'start_date' => 'Start of range', - 'end_date' => 'End of range', - 'export_start_range' => 'Start of export range', - 'export_end_range' => 'End of export range', - 'export_format' => 'File format', - 'include_attachments' => 'Include uploaded attachments', - 'include_old_uploads' => 'Include imported data', - 'accounts' => 'Export transactions from these accounts', - 'delete_account' => 'Delete account ":name"', - 'delete_bill' => 'Delete bill ":name"', - 'delete_budget' => 'Delete budget ":name"', - 'delete_category' => 'Delete category ":name"', - 'delete_currency' => 'Delete currency ":name"', - 'delete_journal' => 'Delete transaction with description ":description"', - 'delete_attachment' => 'Delete attachment ":name"', - 'delete_rule' => 'Delete rule ":title"', - 'delete_rule_group' => 'Delete rule group ":title"', - 'delete_link_type' => 'Delete link type ":name"', - 'delete_user' => 'Delete user ":email"', - 'user_areYouSure' => 'If you delete user ":email", everything will be gone. There is no undo, undelete or anything. If you delete yourself, you will lose access to this instance of Firefly III.', - 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', - 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', - 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', - 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', - 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', - 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', - 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', - 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', - 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', - 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', - 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', - 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', - 'journal_link_areYouSure' => 'Are you sure you want to delete the link between :source and :destination?', - 'linkType_areYouSure' => 'Are you sure you want to delete the link type ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', - 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', - 'delete_all_permanently' => 'Delete selected permanently', - 'update_all_journals' => 'Update these transactions', - 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', - 'also_delete_connections' => 'The only transaction linked with this link type will lose this connection.|All :count transactions linked with this link type will lose their connection.', - 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', - 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', - 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.', - 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.', - 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.', - 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.', - 'check_for_updates' => 'Check for updates', + 'amount' => 'Amount', + 'foreign_amount' => 'Foreign amount', + 'existing_attachments' => 'Existing attachments', + 'date' => 'Date', + 'interest_date' => 'Interest date', + 'book_date' => 'Book date', + 'process_date' => 'Processing date', + 'category' => 'Category', + 'tags' => 'Tags', + 'deletePermanently' => 'Delete permanently', + 'cancel' => 'Cancel', + 'targetdate' => 'Target date', + 'startdate' => 'Start date', + 'tag' => 'Tag', + 'under' => 'Under', + 'symbol' => 'Symbol', + 'code' => 'Code', + 'iban' => 'IBAN', + 'accountNumber' => 'Account number', + 'creditCardNumber' => 'Credit card number', + 'has_headers' => 'Headers', + 'date_format' => 'Date format', + 'specifix' => 'Bank- or file specific fixes', + 'attachments[]' => 'Attachments', + 'store_new_withdrawal' => 'Store new withdrawal', + 'store_new_deposit' => 'Store new deposit', + 'store_new_transfer' => 'Store new transfer', + 'add_new_withdrawal' => 'Add a new withdrawal', + 'add_new_deposit' => 'Add a new deposit', + 'add_new_transfer' => 'Add a new transfer', + 'title' => 'Title', + 'notes' => 'Notes', + 'filename' => 'File name', + 'mime' => 'Mime type', + 'size' => 'Size', + 'trigger' => 'Trigger', + 'stop_processing' => 'Stop processing', + 'start_date' => 'Start of range', + 'end_date' => 'End of range', + 'export_start_range' => 'Start of export range', + 'export_end_range' => 'End of export range', + 'export_format' => 'File format', + 'include_attachments' => 'Include uploaded attachments', + 'include_old_uploads' => 'Include imported data', + 'accounts' => 'Export transactions from these accounts', + 'delete_account' => 'Delete account ":name"', + 'delete_bill' => 'Delete bill ":name"', + 'delete_budget' => 'Delete budget ":name"', + 'delete_category' => 'Delete category ":name"', + 'delete_currency' => 'Delete currency ":name"', + 'delete_journal' => 'Delete transaction with description ":description"', + 'delete_attachment' => 'Delete attachment ":name"', + 'delete_rule' => 'Delete rule ":title"', + 'delete_rule_group' => 'Delete rule group ":title"', + 'delete_link_type' => 'Delete link type ":name"', + 'delete_user' => 'Delete user ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'If you delete user ":email", everything will be gone. There is no undo, undelete or anything. If you delete yourself, you will lose access to this instance of Firefly III.', + 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', + 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', + 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', + 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', + 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', + 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', + 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', + 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', + 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', + 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', + 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', + 'journal_link_areYouSure' => 'Are you sure you want to delete the link between :source and :destination?', + 'linkType_areYouSure' => 'Are you sure you want to delete the link type ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', + 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', + 'delete_all_permanently' => 'Delete selected permanently', + 'update_all_journals' => 'Update these transactions', + 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', + 'also_delete_connections' => 'The only transaction linked with this link type will lose this connection.|All :count transactions linked with this link type will lose their connection.', + 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', + 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Check for updates', 'email' => 'Email address', 'password' => 'Password', @@ -216,11 +219,23 @@ return [ 'country_code' => 'Country code', 'provider_code' => 'Bank or data-provider', - 'due_date' => 'Due date', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'internal_reference' => 'Internal reference', - 'inward' => 'Inward description', - 'outward' => 'Outward description', - 'rule_group_id' => 'Rule group', + 'due_date' => 'Due date', + 'payment_date' => 'Payment date', + 'invoice_date' => 'Invoice date', + 'internal_reference' => 'Internal reference', + 'inward' => 'Inward description', + 'outward' => 'Outward description', + 'rule_group_id' => 'Rule group', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + 'weekend' => 'Weekend', + ]; diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index feb4f58645..824704689e 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php index bd11777b11..171a7acf23 100644 --- a/resources/lang/en_US/list.php +++ b/resources/lang/en_US/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 9ca9ec0f61..b573da5844 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'iban' => 'This is not a valid IBAN.', - 'source_equals_destination' => 'The source account equals the destination account', + 'source_equals_destination' => 'The source account equals the destination account.', 'unique_account_number_for_user' => 'It looks like this account number is already in use.', 'unique_iban_for_user' => 'It looks like this IBAN is already in use.', 'deleted_user' => 'Due to security constraints, you cannot register using this email address.', @@ -34,16 +34,22 @@ return [ 'file_attached' => 'Succesfully uploaded file ":name".', 'must_exist' => 'The ID in field :attribute does not exist in the database.', 'all_accounts_equal' => 'All accounts in this field must be equal.', - 'invalid_selection' => 'Your selection is invalid', + 'invalid_selection' => 'Your selection is invalid.', 'belongs_user' => 'This value is invalid for this field.', 'at_least_one_transaction' => 'Need at least one transaction.', + 'at_least_one_repetition' => 'Need at least one repetition.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'The content of this field is invalid without currency information.', 'equal_description' => 'Transaction description should not equal global description.', 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', 'file_too_large' => 'File ":name" is too large.', - 'belongs_to_user' => 'The value of :attribute is unknown', + 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => 'The :attribute must be accepted.', 'bic' => 'This is not a valid BIC.', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute must be larger than zero.', 'active_url' => 'The :attribute is not a valid URL.', 'after' => 'The :attribute must be a date after :date.', @@ -53,8 +59,8 @@ return [ 'array' => 'The :attribute must be an array.', 'unique_for_user' => 'There already is an entry with this :attribute.', 'before' => 'The :attribute must be a date before :date.', - 'unique_object_for_user' => 'This name is already in use', - 'unique_account_for_user' => 'This account name is already in use', + 'unique_object_for_user' => 'This name is already in use.', + 'unique_account_for_user' => 'This account name is already in use.', 'between.numeric' => 'The :attribute must be between :min and :max.', 'between.file' => 'The :attribute must be between :min and :max kilobytes.', 'between.string' => 'The :attribute must be between :min and :max characters.', @@ -85,6 +91,9 @@ return [ 'min.array' => 'The :attribute must have at least :min items.', 'not_in' => 'The selected :attribute is invalid.', 'numeric' => 'The :attribute must be a number.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'The :attribute format is invalid.', 'required' => 'The :attribute field is required.', 'required_if' => 'The :attribute field is required when :other is :value.', @@ -109,9 +118,12 @@ return [ 'file' => 'The :attribute must be a file.', 'in_array' => 'The :attribute field does not exist in :other.', 'present' => 'The :attribute field must be present.', - 'amount_zero' => 'The total amount cannot be zero', + 'amount_zero' => 'The total amount cannot be zero.', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security.', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions.', + 'valid_recurrence_rep_moment' => 'Invalid repetition moment for this type of repetition.', + 'invalid_account_info' => 'Invalid account information.', 'attributes' => [ 'email' => 'email address', 'description' => 'description', diff --git a/resources/lang/es_ES/config.php b/resources/lang/es_ES/config.php index d5bebf299c..f4981713fe 100644 --- a/resources/lang/es_ES/config.php +++ b/resources/lang/es_ES/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'es', - 'locale' => 'es, Spanish, es_ES, es_ES.utf8, es_ES.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%B %e, %Y', - 'date_time' => '%B %e, %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Semana %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'MMMM Do, YYYY', - 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'es', + 'locale' => 'es, Spanish, es_ES, es_ES.utf8, es_ES.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%B %e, %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%B %e, %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Semana %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'MMMM Do, YYYY', + 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/es_ES/demo.php b/resources/lang/es_ES/demo.php index 67a5f2fe85..4588444cea 100644 --- a/resources/lang/es_ES/demo.php +++ b/resources/lang/es_ES/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Estos gastos, depósitos y transferencias no son particularmente imaginativos. Se han generado automáticamente.', 'piggy-banks-index' => 'Como puede ver, hay tres alcancías. Utilice los botones más y menos para influir en la cantidad de dinero en cada alcancía. Haga clic en el nombre de la alcancía para ver la administración de cada una.', 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index 70fe05fe79..4d83113a79 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Escanee el código QR con una aplicación en su teléfono como Authy o Google autenticator y ingrese el código generado.', 'pref_two_factor_auth_reset_code' => 'Reiniciar código de verificación', 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Guardar la configuración', 'saved_preferences' => '¡Preferencias guardadas!', 'preferences_general' => 'General', @@ -821,7 +822,7 @@ return [ 'language' => 'Language', 'new_savings_account' => ':bank_name savings account', 'cash_wallet' => 'Cash wallet', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Tus cuentas', @@ -901,7 +902,6 @@ return [ 'balanceEnd' => 'Balance al final de un periodo', 'splitByAccount' => 'Separada por cuenta', 'coveredWithTags' => 'Cubierta con etiquetas', - 'leftUnbalanced' => 'Izquierda desbalanceada', 'leftInBudget' => 'Dejado en el presupuesto', 'sumOfSums' => 'Suma de sumas', 'noCategory' => '(sin categoría)', @@ -1063,7 +1063,7 @@ return [ 'instance_configuration' => 'Configuracion', 'firefly_instance_configuration' => 'Opciones de configuración de Firefly III', 'setting_single_user_mode' => 'Modo de usuario único', - 'setting_single_user_mode_explain' => 'Por defecto, Firefly III solo acepta un (1) registro: Usted. Esta es una medida de seguridad que impide que otros utilicen su instancia a menos que usted lo permita. Los registros futuros están bloqueados. Cuando usted desbloquee esta casilla, otros pueden usar su instancia también, suponiendo que puedan alcanzarla ( cuando este conectada a Internet).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Configuración de tienda', 'single_user_administration' => 'Administración de usuarios para :email', 'edit_user' => 'Editar usuario :email', @@ -1135,6 +1135,8 @@ return [ 'is (partially) refunded by_inward' => 'es (parcialmente) es devuelto por', 'is (partially) paid for by_inward' => 'es(parcialmente) pagado por', 'is (partially) reimbursed by_inward' => 'es(parcialmente) reembolsado por', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'relacionado con', '(partially) refunds_outward' => '(parcialmente) reembolso', '(partially) pays for_outward' => '(parcialmente) paga por', @@ -1156,8 +1158,9 @@ return [ 'cannot_convert_split_journal' => 'No se puede convertir una transacción dividida', // Import page (general strings only) - 'import_index_title' => 'Importar datos a Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Importar datos', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Esta función no esta disponible cuando usted esta utilizando Firefly III dentro de un ambiente Sandstorm.io.', @@ -1207,4 +1210,68 @@ return [ 'no_bills_intro_default' => 'Usted no tiene facturas aun. Usted puede crear facturas para hacer un seguimiento de los gastos regulares, como su alquiler o el seguro.', 'no_bills_imperative_default' => '¿Tienes facturas periódicas? Crea una factura y haz un seguimiento de tus pagos:', 'no_bills_create_default' => 'Crear una factura', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php index 11d94d8b7f..b2162ec655 100644 --- a/resources/lang/es_ES/form.php +++ b/resources/lang/es_ES/form.php @@ -24,66 +24,66 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Banco', - 'bank_balance' => 'Saldo', - 'savings_balance' => 'Salgo de ahorro', - 'credit_card_limit' => 'Límite de la tarjeta de crédito', - 'automatch' => 'Coinciden automáticamente', - 'skip' => 'Saltar', - 'name' => 'Nombre', - 'active' => 'Activo', - 'amount_min' => 'Importe mínimo', - 'amount_max' => 'Importe máximo', - 'match' => 'Encuentros en', - 'strict' => 'Strict mode', - 'repeat_freq' => 'Repetición', - 'journal_currency_id' => 'Divisa', - 'currency_id' => 'Divisa', - 'transaction_currency_id' => 'Currency', - 'external_ip' => 'Your server\'s external IP', - 'attachments' => 'Adjuntos', - 'journal_amount' => 'Importe', - 'journal_source_account_name' => 'Cuenta de ingresos (origen)', - 'journal_source_account_id' => 'Cuenta de activos (origen)', - 'BIC' => 'BIC', - 'verify_password' => 'Verificar la seguridad de contraseña', - 'source_account' => 'Cuenta origen', - 'destination_account' => 'Cuenta destino', - 'journal_destination_account_id' => 'Cuenta de activos (destino)', - 'asset_destination_account' => 'Cuenta de activos (destino)', - 'asset_source_account' => 'Cuenta de activos (origen)', - 'journal_description' => 'Descripción', - 'note' => 'Notas', - 'split_journal' => 'Dividir esta transacción', - 'split_journal_explanation' => 'Dividir esta transacción en múltiples partes', - 'currency' => 'Divisa', - 'account_id' => 'Cuenta', - 'budget_id' => 'Presupuesto', - 'openingBalance' => 'Saldo inicial', - 'tagMode' => 'Modo de etiqueta', - 'tag_position' => 'Etiquetar ubicación', - 'virtualBalance' => 'Saldo virtual', - 'targetamount' => 'Cantidad objetivo', - 'accountRole' => 'Tipo de cuenta', - 'openingBalanceDate' => 'Fecha del saldo inicial', - 'ccType' => 'Plan de pagos con tarjeta de crédito', - 'ccMonthlyPaymentDate' => 'Fecha de pago mensual de la tarjeta de crédito', - 'piggy_bank_id' => 'Hucha', - 'returnHere' => 'Volver aquí', - 'returnHereExplanation' => 'Después de guardar, vuelve aquí para crear otro.', - 'returnHereUpdateExplanation' => 'Después de actualizar, vuelve aquí.', - 'description' => 'Descripción', - 'expense_account' => 'Cuenta de gastos', - 'revenue_account' => 'Cuenta de ingresos', - 'decimal_places' => 'Lugares decimales', - 'exchange_rate_instruction' => 'Monedas extranjeras', - 'source_amount' => 'Importe (origen)', - 'destination_amount' => 'Importe (destino)', - 'native_amount' => 'Cantidad nativa', - 'new_email_address' => 'Nueva dirección de email', - 'verification' => 'Verificación', - 'api_key' => 'Clave de API', - 'remember_me' => 'Recordarme', + 'bank_name' => 'Banco', + 'bank_balance' => 'Saldo', + 'savings_balance' => 'Salgo de ahorro', + 'credit_card_limit' => 'Límite de la tarjeta de crédito', + 'automatch' => 'Coinciden automáticamente', + 'skip' => 'Saltar', + 'name' => 'Nombre', + 'active' => 'Activo', + 'amount_min' => 'Importe mínimo', + 'amount_max' => 'Importe máximo', + 'match' => 'Encuentros en', + 'strict' => 'Strict mode', + 'repeat_freq' => 'Repetición', + 'journal_currency_id' => 'Divisa', + 'currency_id' => 'Divisa', + 'transaction_currency_id' => 'Currency', + 'external_ip' => 'Your server\'s external IP', + 'attachments' => 'Adjuntos', + 'journal_amount' => 'Importe', + 'journal_source_name' => 'Revenue account (source)', + 'journal_source_id' => 'Asset account (source)', + 'BIC' => 'BIC', + 'verify_password' => 'Verificar la seguridad de contraseña', + 'source_account' => 'Cuenta origen', + 'destination_account' => 'Cuenta destino', + 'journal_destination_id' => 'Asset account (destination)', + 'asset_destination_account' => 'Cuenta de activos (destino)', + 'asset_source_account' => 'Cuenta de activos (origen)', + 'journal_description' => 'Descripción', + 'note' => 'Notas', + 'split_journal' => 'Dividir esta transacción', + 'split_journal_explanation' => 'Dividir esta transacción en múltiples partes', + 'currency' => 'Divisa', + 'account_id' => 'Cuenta', + 'budget_id' => 'Presupuesto', + 'openingBalance' => 'Saldo inicial', + 'tagMode' => 'Modo de etiqueta', + 'tag_position' => 'Etiquetar ubicación', + 'virtualBalance' => 'Saldo virtual', + 'targetamount' => 'Cantidad objetivo', + 'accountRole' => 'Tipo de cuenta', + 'openingBalanceDate' => 'Fecha del saldo inicial', + 'ccType' => 'Plan de pagos con tarjeta de crédito', + 'ccMonthlyPaymentDate' => 'Fecha de pago mensual de la tarjeta de crédito', + 'piggy_bank_id' => 'Hucha', + 'returnHere' => 'Volver aquí', + 'returnHereExplanation' => 'Después de guardar, vuelve aquí para crear otro.', + 'returnHereUpdateExplanation' => 'Después de actualizar, vuelve aquí.', + 'description' => 'Descripción', + 'expense_account' => 'Cuenta de gastos', + 'revenue_account' => 'Cuenta de ingresos', + 'decimal_places' => 'Lugares decimales', + 'exchange_rate_instruction' => 'Monedas extranjeras', + 'source_amount' => 'Importe (origen)', + 'destination_amount' => 'Importe (destino)', + 'native_amount' => 'Cantidad nativa', + 'new_email_address' => 'Nueva dirección de email', + 'verification' => 'Verificación', + 'api_key' => 'Clave de API', + 'remember_me' => 'Recordarme', 'source_account_asset' => 'Cuenta de origen (cuenta de activos)', 'destination_account_expense' => 'Cuenta de destino (cuenta de gastos)', @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Convertir depósito', 'convert_Transfer' => 'Convertir transferencia', - 'amount' => 'Importe', - 'foreign_amount' => 'Foreign amount', - 'existing_attachments' => 'Existing attachments', - 'date' => 'Fecha', - 'interest_date' => 'Fecha de interés', - 'book_date' => 'Fecha de registro', - 'process_date' => 'Fecha de procesamiento', - 'category' => 'Categoría', - 'tags' => 'Etiquetas', - 'deletePermanently' => 'Borrar permanentemente', - 'cancel' => 'Cancelar', - 'targetdate' => 'Fecha tope', - 'startdate' => 'Fecha de inicio', - 'tag' => 'Etiqueta', - 'under' => 'Debajo', - 'symbol' => 'Símbolo', - 'code' => 'Código', - 'iban' => 'IBAN', - 'accountNumber' => 'Número de cuenta', - 'creditCardNumber' => 'Número de la tarjeta de crédito', - 'has_headers' => 'Encabezados', - 'date_format' => 'Formato de fecha', - 'specifix' => 'Banco- o archivo de soluciones especificas', - 'attachments[]' => 'Adjuntos', - 'store_new_withdrawal' => 'Guardar rueva retirada de efectivo', - 'store_new_deposit' => 'Guardar nuevo depósito', - 'store_new_transfer' => 'Guardar nueva transferencia', - 'add_new_withdrawal' => 'Añadir rueva retirada de efectivo', - 'add_new_deposit' => 'Añadir nuevo depósito', - 'add_new_transfer' => 'Añadir nueva transferencia', - 'title' => 'Título', - 'notes' => 'Notas', - 'filename' => 'Nombre de fichero', - 'mime' => 'Tipo Mime', - 'size' => 'Tamaño', - 'trigger' => 'Disparador', - 'stop_processing' => 'Detener el procesamiento', - 'start_date' => 'Inicio del rango', - 'end_date' => 'Final del rango', - 'export_start_range' => 'Inicio del rango de exportación', - 'export_end_range' => 'Fin del rango de exportación', - 'export_format' => 'Formato del archivo', - 'include_attachments' => 'Incluir archivos adjuntos subidos', - 'include_old_uploads' => 'Incluir datos importados', - 'accounts' => 'Exportar transacciones de estas cuentas', - 'delete_account' => 'Borrar cuenta ":name"', - 'delete_bill' => 'Eliminar factura ":name"', - 'delete_budget' => 'Eliminar presupuesto ":name"', - 'delete_category' => 'Eliminar categoría ":name"', - 'delete_currency' => 'Eliminar divisa ":name"', - 'delete_journal' => 'Eliminar la transacción con descripción ":description"', - 'delete_attachment' => 'Eliminar adjunto ":name"', - 'delete_rule' => 'Eliminar regla ":title"', - 'delete_rule_group' => 'Eliminar grupo de reglas ":title"', - 'delete_link_type' => 'Eliminar tipo de enlace ":name"', - 'delete_user' => 'Eliminar usuario ":email"', - 'user_areYouSure' => 'Si elimina usuario ":email", todo desaparecerá. No hay deshacer, recuperar ni nada. Si te eliminas, perderás el acceso a esta instancia de Firefly III.', - 'attachment_areYouSure' => '¿Seguro que quieres eliminar el archivo adjunto llamado "name"?', - 'account_areYouSure' => '¿Seguro que quieres eliminar la cuenta llamada ":name"?', - 'bill_areYouSure' => '¿Seguro que quieres eliminar la factura llamada ":name"?', - 'rule_areYouSure' => '¿Seguro que quieres eliminar la regla titulada ":title"?', - 'ruleGroup_areYouSure' => '¿Seguro que quieres eliminar el grupo de reglas titulado ":title"?', - 'budget_areYouSure' => '¿Seguro que quieres eliminar el presupuesto llamado ":name"?', - 'category_areYouSure' => '¿Seguro que quieres eliminar la categoría llamada ":name"?', - 'currency_areYouSure' => '¿Está seguro que desea eliminar la moneda denominada ":name"?', - 'piggyBank_areYouSure' => '¿Está seguro que desea eliminar la hucha llamada ":name"?', - 'journal_areYouSure' => '¿Estás seguro de que deseas eliminar la transacción descrita ":description"?', - 'mass_journal_are_you_sure' => '¿Usted esta seguro de querer eliminar estas transacciones?', - 'tag_areYouSure' => '¿Seguro que quieres eliminar la etiqueta ":tag"?', - 'journal_link_areYouSure' => '¿Seguro que quieres eliminar el vínculo entre :source y :destination?', - 'linkType_areYouSure' => '¿Estás seguro de que deseas eliminar el tipo de vínculo ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', - 'mass_make_selection' => 'Aún puede evitar que se eliminen elementos quitando la casilla de verificación.', - 'delete_all_permanently' => 'Eliminar selección permanentemente', - 'update_all_journals' => 'Actualiza estas transacciones', - 'also_delete_transactions' => 'La única transacción conectada a esta cuenta también se eliminará. | Todas las :count transacciones conectadas a esta cuenta también se eliminarán.', - 'also_delete_connections' => 'La única transacción vinculada con este tipo de enlace perderá esta conexión. | Todas las :count transacciones vinculadas con este tipo de enlace perderán su conexión.', - 'also_delete_rules' => 'La única regla conectada a este grupo de reglas también se eliminará. | Todas las :count reglas conectadas a este grupo de reglas también se eliminarán.', - 'also_delete_piggyBanks' => 'La única alcancía conectada a esta cuenta también se eliminará. | Todas las :count alcancías conectadas a esta cuenta también se eliminará.', - 'bill_keep_transactions' => 'La única transacción conectada a esta factura no se eliminará. | Todas las :count transacciones conectadas a esta factura evitarán la eliminación.', - 'budget_keep_transactions' => 'La única transacción conectada a este presupuesto no se eliminará. | Todas las :count transacciones conectadas a este presupuesto evitarán la eliminación.', - 'category_keep_transactions' => 'La única transacción conectada a esta categoría no se eliminará. | Todas las :count transacciones conectadas a esta categoría evitarán la eliminación.', - 'tag_keep_transactions' => 'La única transacción conectada a esta etiqueta no se eliminará. | Todas las :count transacciones conectadas a esta etiqueta evitarán la eliminación.', - 'check_for_updates' => 'Ver actualizaciones', + 'amount' => 'Importe', + 'foreign_amount' => 'Foreign amount', + 'existing_attachments' => 'Existing attachments', + 'date' => 'Fecha', + 'interest_date' => 'Fecha de interés', + 'book_date' => 'Fecha de registro', + 'process_date' => 'Fecha de procesamiento', + 'category' => 'Categoría', + 'tags' => 'Etiquetas', + 'deletePermanently' => 'Borrar permanentemente', + 'cancel' => 'Cancelar', + 'targetdate' => 'Fecha tope', + 'startdate' => 'Fecha de inicio', + 'tag' => 'Etiqueta', + 'under' => 'Debajo', + 'symbol' => 'Símbolo', + 'code' => 'Código', + 'iban' => 'IBAN', + 'accountNumber' => 'Número de cuenta', + 'creditCardNumber' => 'Número de la tarjeta de crédito', + 'has_headers' => 'Encabezados', + 'date_format' => 'Formato de fecha', + 'specifix' => 'Banco- o archivo de soluciones especificas', + 'attachments[]' => 'Adjuntos', + 'store_new_withdrawal' => 'Guardar rueva retirada de efectivo', + 'store_new_deposit' => 'Guardar nuevo depósito', + 'store_new_transfer' => 'Guardar nueva transferencia', + 'add_new_withdrawal' => 'Añadir rueva retirada de efectivo', + 'add_new_deposit' => 'Añadir nuevo depósito', + 'add_new_transfer' => 'Añadir nueva transferencia', + 'title' => 'Título', + 'notes' => 'Notas', + 'filename' => 'Nombre de fichero', + 'mime' => 'Tipo Mime', + 'size' => 'Tamaño', + 'trigger' => 'Disparador', + 'stop_processing' => 'Detener el procesamiento', + 'start_date' => 'Inicio del rango', + 'end_date' => 'Final del rango', + 'export_start_range' => 'Inicio del rango de exportación', + 'export_end_range' => 'Fin del rango de exportación', + 'export_format' => 'Formato del archivo', + 'include_attachments' => 'Incluir archivos adjuntos subidos', + 'include_old_uploads' => 'Incluir datos importados', + 'accounts' => 'Exportar transacciones de estas cuentas', + 'delete_account' => 'Borrar cuenta ":name"', + 'delete_bill' => 'Eliminar factura ":name"', + 'delete_budget' => 'Eliminar presupuesto ":name"', + 'delete_category' => 'Eliminar categoría ":name"', + 'delete_currency' => 'Eliminar divisa ":name"', + 'delete_journal' => 'Eliminar la transacción con descripción ":description"', + 'delete_attachment' => 'Eliminar adjunto ":name"', + 'delete_rule' => 'Eliminar regla ":title"', + 'delete_rule_group' => 'Eliminar grupo de reglas ":title"', + 'delete_link_type' => 'Eliminar tipo de enlace ":name"', + 'delete_user' => 'Eliminar usuario ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Si elimina usuario ":email", todo desaparecerá. No hay deshacer, recuperar ni nada. Si te eliminas, perderás el acceso a esta instancia de Firefly III.', + 'attachment_areYouSure' => '¿Seguro que quieres eliminar el archivo adjunto llamado "name"?', + 'account_areYouSure' => '¿Seguro que quieres eliminar la cuenta llamada ":name"?', + 'bill_areYouSure' => '¿Seguro que quieres eliminar la factura llamada ":name"?', + 'rule_areYouSure' => '¿Seguro que quieres eliminar la regla titulada ":title"?', + 'ruleGroup_areYouSure' => '¿Seguro que quieres eliminar el grupo de reglas titulado ":title"?', + 'budget_areYouSure' => '¿Seguro que quieres eliminar el presupuesto llamado ":name"?', + 'category_areYouSure' => '¿Seguro que quieres eliminar la categoría llamada ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => '¿Está seguro que desea eliminar la moneda denominada ":name"?', + 'piggyBank_areYouSure' => '¿Está seguro que desea eliminar la hucha llamada ":name"?', + 'journal_areYouSure' => '¿Estás seguro de que deseas eliminar la transacción descrita ":description"?', + 'mass_journal_are_you_sure' => '¿Usted esta seguro de querer eliminar estas transacciones?', + 'tag_areYouSure' => '¿Seguro que quieres eliminar la etiqueta ":tag"?', + 'journal_link_areYouSure' => '¿Seguro que quieres eliminar el vínculo entre :source y :destination?', + 'linkType_areYouSure' => '¿Estás seguro de que deseas eliminar el tipo de vínculo ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', + 'mass_make_selection' => 'Aún puede evitar que se eliminen elementos quitando la casilla de verificación.', + 'delete_all_permanently' => 'Eliminar selección permanentemente', + 'update_all_journals' => 'Actualiza estas transacciones', + 'also_delete_transactions' => 'La única transacción conectada a esta cuenta también se eliminará. | Todas las :count transacciones conectadas a esta cuenta también se eliminarán.', + 'also_delete_connections' => 'La única transacción vinculada con este tipo de enlace perderá esta conexión. | Todas las :count transacciones vinculadas con este tipo de enlace perderán su conexión.', + 'also_delete_rules' => 'La única regla conectada a este grupo de reglas también se eliminará. | Todas las :count reglas conectadas a este grupo de reglas también se eliminarán.', + 'also_delete_piggyBanks' => 'La única alcancía conectada a esta cuenta también se eliminará. | Todas las :count alcancías conectadas a esta cuenta también se eliminará.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Ver actualizaciones', 'email' => 'Correo electrónico', 'password' => 'Contraseña', @@ -216,11 +219,23 @@ return [ 'country_code' => 'Código del país', 'provider_code' => 'Banco o proveedor de datos', - 'due_date' => 'Fecha de vencimiento', - 'payment_date' => 'Fecha de pago', - 'invoice_date' => 'Fecha de la factura', - 'internal_reference' => 'Referencia interna', - 'inward' => 'Descripción interna', - 'outward' => 'Descripción externa', - 'rule_group_id' => 'Grupo de reglas', + 'due_date' => 'Fecha de vencimiento', + 'payment_date' => 'Fecha de pago', + 'invoice_date' => 'Fecha de la factura', + 'internal_reference' => 'Referencia interna', + 'inward' => 'Descripción interna', + 'outward' => 'Descripción externa', + 'rule_group_id' => 'Grupo de reglas', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + 'weekend' => 'Weekend', + ]; diff --git a/resources/lang/es_ES/import.php b/resources/lang/es_ES/import.php index 8a812e4689..3676742295 100644 --- a/resources/lang/es_ES/import.php +++ b/resources/lang/es_ES/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/es_ES/list.php b/resources/lang/es_ES/list.php index 2aa21df77b..ea67eabb8a 100644 --- a/resources/lang/es_ES/list.php +++ b/resources/lang/es_ES/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 227ebdc572..106e5f668c 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'iban' => 'Este no es un IBAN válido.', - 'source_equals_destination' => 'The source account equals the destination account', + 'source_equals_destination' => 'The source account equals the destination account.', 'unique_account_number_for_user' => 'Parece que este número de cuenta ya está en uso.', 'unique_iban_for_user' => 'It looks like this IBAN is already in use.', 'deleted_user' => 'Debido a restricciones de seguridad, no se puede registrar utilizando esta dirección de correo electrónico.', @@ -34,16 +34,22 @@ return [ 'file_attached' => 'Archivo cargado con exito ":name".', 'must_exist' => 'ID introducido en :attribute el atributo no existe en la base de datos.', 'all_accounts_equal' => 'Todas las cuentas en este campo deben ser iguales.', - 'invalid_selection' => 'Your selection is invalid', + 'invalid_selection' => 'Your selection is invalid.', 'belongs_user' => 'Este valor no es válido para este campo.', 'at_least_one_transaction' => 'Se necesita al menos una transacción.', + 'at_least_one_repetition' => 'Need at least one repetition.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'El contenido de este campo no es válido sin la información montearia.', 'equal_description' => 'Transaction description should not equal global description.', 'file_invalid_mime' => 'El archivo ":name" es de tipo ":mime", el cual no se acepta.', 'file_too_large' => 'El archivo ":name" es demasiado grande.', - 'belongs_to_user' => 'El valor de :attribute es desconocido', + 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => 'El :attribute debe ser aceptado.', 'bic' => 'Esto no es un BIC válido.', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute debe ser mayor que cero.', 'active_url' => 'El campo :attribute no es una URL válida.', 'after' => 'El campo :attribute debe ser una fecha posterior a :date.', @@ -53,8 +59,8 @@ return [ 'array' => 'El campo :attribute debe ser un arreglo.', 'unique_for_user' => 'Ya hay una entrada con esto :attribute.', 'before' => 'El campo :attribute debe contener una fecha anterior a :date.', - 'unique_object_for_user' => 'Este nombre ya está en uso', - 'unique_account_for_user' => 'Este nombre cuenta ya está en uso', + 'unique_object_for_user' => 'This name is already in use.', + 'unique_account_for_user' => 'This account name is already in use.', 'between.numeric' => 'El atributo :attribute debe estar entre :min y :max.', 'between.file' => 'El atributo :attribute debe estar entre :min y :max kilobytes.', 'between.string' => 'El atributo :attribute debe estar entre :min y :max caracteres.', @@ -85,6 +91,9 @@ return [ 'min.array' => 'El campo :attribute debe tener al menos :min elementos.', 'not_in' => 'El campo :attribute seleccionado es incorrecto.', 'numeric' => 'El campo :attribute debe ser un número.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'El formato del campo :attribute no es válido.', 'required' => 'El campo :attribute es obligatorio.', 'required_if' => 'El campo :attribute es obligatorio cuando el campo :other es :value.', @@ -109,9 +118,12 @@ return [ 'file' => 'El campo :attribute debe ser un fichero.', 'in_array' => 'El campo :attribute no existe en :other.', 'present' => 'El campo :attribute debe estar presente.', - 'amount_zero' => 'La cantidad total no puede ser cero', + 'amount_zero' => 'The total amount cannot be zero.', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security.', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions.', + 'valid_recurrence_rep_moment' => 'Invalid repetition moment for this type of repetition.', + 'invalid_account_info' => 'Invalid account information.', 'attributes' => [ 'email' => 'dirección de correo electrónico', 'description' => 'descripcion', diff --git a/resources/lang/fr_FR/auth.php b/resources/lang/fr_FR/auth.php index 689b6966af..974345567e 100644 --- a/resources/lang/fr_FR/auth.php +++ b/resources/lang/fr_FR/auth.php @@ -24,5 +24,5 @@ declare(strict_types=1); return [ 'failed' => 'Ces identifiants n\'ont aucune correspondance.', - 'throttle' => 'Trop de tentatives de connexion. Veuillez essayer à nouveau dans :seconds secondes.', + 'throttle' => 'Trop de tentatives de connexion. Veuillez réessayer dans :seconds secondes.', ]; diff --git a/resources/lang/fr_FR/config.php b/resources/lang/fr_FR/config.php index 763721bdba..05283eb88f 100644 --- a/resources/lang/fr_FR/config.php +++ b/resources/lang/fr_FR/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'fr', - 'locale' => 'fr, French, fr_FR, fr_FR.utf8, fr_FR.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%B %e %Y @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Semaine %W %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'MMMM Do, YYYY', - 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'fr', + 'locale' => 'fr, French, fr_FR, fr_FR.utf8, fr_FR.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%B %e %Y @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Semaine %W %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'Do MMMM YYYY', + 'date_time_js' => 'Do MMMM YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/fr_FR/demo.php b/resources/lang/fr_FR/demo.php index 94171d0aa6..6464f29911 100644 --- a/resources/lang/fr_FR/demo.php +++ b/resources/lang/fr_FR/demo.php @@ -33,5 +33,7 @@ return [ 'currencies-index' => 'Firefly III prend en charge plusieurs devises. Bien que l\'Euro soit la devise par défaut, cette dernière peut être changée pour le Dollar américain et de nombreuses autres devises. Comme vous pouvez le remarquer une petite sélection des monnaies a été incluse, mais vous pouvez ajouter vos propres devises si vous le souhaitez. Gardez à l\'esprit que la modification de la devise par défaut ne modifie pas la monnaie des transactions existantes : Firefly III prend en charge l’utilisation de plusieurs devises en même temps.', 'transactions-index' => 'Ces dépenses, dépôts et transferts ne sont pas particulièrement imaginatifs. Ils ont été générés automatiquement.', 'piggy-banks-index' => 'Comme vous pouvez le voir, il y a trois tirelires. Utilisez les boutons plus et moins pour influer sur le montant d’argent dans chaque tirelire. Cliquez sur le nom de la tirelire pour voir l’administration pour chaque tirelire.', - 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'import-index' => 'Tout fichier CSV peut être importé dans Firefly III. L\'importation de données depuis bunq et Specter est également prise en charge. D\'autres banques et agrégateurs financiers seront mis en place dans le futur. En tant qu\'utilisateur du site de démonstration, vous ne pouvez voir que le «faux» service en action. Il va générer des transactions aléatoires pour vous montrer comment se déroule le processus.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 9366f83e3d..f082d0631f 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scanner le code QR avec une application sur votre téléphone comme Authy ou Google Authenticator et entrez le code généré.', 'pref_two_factor_auth_reset_code' => 'Réinitialiser le code de vérification', 'pref_two_factor_auth_disable_2fa' => 'Désactiver l\'authentification en deux étapes', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Enregistrer les paramètres', 'saved_preferences' => 'Préférences enregistrées!', 'preferences_general' => 'Général', @@ -820,7 +821,7 @@ return [ 'language' => 'Langage', 'new_savings_account' => ':bank_name compte d\'épargne', 'cash_wallet' => 'Porte-monnaie', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'Si la devise que vous utilisez habituellement n\'est pas répertoriée, ne vous inquiétez pas. Vous pouvez créer vos propres devises dans Options > Devises.', // home page: 'yourAccounts' => 'Vos comptes', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Solde à la fin de la période', 'splitByAccount' => 'Divisé par compte', 'coveredWithTags' => 'Recouvert de tags', - 'leftUnbalanced' => 'Restant déséquilibré', 'leftInBudget' => 'Budget restant', 'sumOfSums' => 'Montant des sommes', 'noCategory' => '(aucune catégorie)', @@ -1018,7 +1018,7 @@ return [ 'remove_money_from_piggy_title' => 'Retirer l’argent de la tirelire ":name"', 'add' => 'Ajouter', 'no_money_for_piggy' => 'Vous n\'avez pas d\'argent à placer dans cette tirelire.', - 'suggested_savings_per_month' => 'Suggested per month', + 'suggested_savings_per_month' => 'Suggéré par mois', 'remove' => 'Enlever', 'max_amount_add' => 'Le montant maximum que vous pouvez ajouter est', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Configuration', 'firefly_instance_configuration' => 'Options de configuration pour Firefly III', 'setting_single_user_mode' => 'Mode utilisateur unique', - 'setting_single_user_mode_explain' => 'Par défaut, Firefly III accepte uniquement un (1) enregistrement : vous. Il s\'agit d\'une mesure de sécurité qui empêche les autres d\'utiliser votre instance, à moins que vous ne les autorisiez. Les enregistrements futurs sont bloqués. Lorsque vous désactivez cette case, d\'autres personnes peuvent utiliser votre instance aussi bien, en supposant qu\'elles puissent l\'atteindre (quand il est connecté à Internet).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Sauvegarder la configuration', 'single_user_administration' => 'Gestion de l\'utilisateur pour :email', 'edit_user' => 'Modifier l\'utilisateur :email', @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => 'est (partiellement) remboursé par', 'is (partially) paid for by_inward' => 'est (partiellement) payé par', 'is (partially) reimbursed by_inward' => 'est (partiellement) remboursé par', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'se rapporte à', '(partially) refunds_outward' => 'rembourse (partiellement)', '(partially) pays for_outward' => 'paye (partiellement) pour', @@ -1155,8 +1157,9 @@ return [ 'cannot_convert_split_journal' => 'Vous ne pouvez pas convertir une transaction ventilée', // Import page (general strings only) - 'import_index_title' => 'Importer des données dans Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Importer des données', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Cette fonction n\'est pas disponible lorsque vous utilisez Firefly III dans un environnement Sandstorm.io.', @@ -1206,4 +1209,68 @@ return [ 'no_bills_intro_default' => 'Vous n\'avez pas encore de factures. Vous pouvez créer des factures pour suivre les dépenses ordinaires, comme votre loyer ou l\'assurance.', 'no_bills_imperative_default' => 'Avez-vous des factures régulières ? Créez une facture et suivez vos paiements :', 'no_bills_create_default' => 'Créer une facture', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/fr_FR/form.php b/resources/lang/fr_FR/form.php index 5c57f4717a..574c59bd45 100644 --- a/resources/lang/fr_FR/form.php +++ b/resources/lang/fr_FR/form.php @@ -24,66 +24,66 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Nom de la banque', - 'bank_balance' => 'Solde', - 'savings_balance' => 'Solde de l\'épargne', - 'credit_card_limit' => 'Limite de carte de crédit', - 'automatch' => 'Correspondre automatiquement', - 'skip' => 'Ignorer', - 'name' => 'Nom', - 'active' => 'Actif', - 'amount_min' => 'Montant minimum', - 'amount_max' => 'Montant maximum', - 'match' => 'Correspondre à', - 'strict' => 'Strict mode', - 'repeat_freq' => 'Répétitions', - 'journal_currency_id' => 'Devise', - 'currency_id' => 'Devise', - 'transaction_currency_id' => 'Currency', - 'external_ip' => 'Your server\'s external IP', - 'attachments' => 'Documents joints', - 'journal_amount' => 'Montant', - 'journal_source_account_name' => 'Compte de recettes (source)', - 'journal_source_account_id' => 'Compte d’actif (source)', - 'BIC' => 'Code BIC', - 'verify_password' => 'Vérifiez la sécurité du mot de passe', - 'source_account' => 'Compte d\'origine', - 'destination_account' => 'Compte destinataire', - 'journal_destination_account_id' => 'Compte d’actif (destination)', - 'asset_destination_account' => 'Compte d’actif (destination)', - 'asset_source_account' => 'Compte d’actif (source)', - 'journal_description' => 'Description', - 'note' => 'Notes', - 'split_journal' => 'Ventiler cette opération', - 'split_journal_explanation' => 'Diviser cette opération en plusieurs parties', - 'currency' => 'Devise', - 'account_id' => 'Compte d’actif', - 'budget_id' => 'Budget', - 'openingBalance' => 'Solde initial', - 'tagMode' => 'Mode tag', - 'tag_position' => 'Localisation de l\'étiquette', - 'virtualBalance' => 'Solde virtuel', - 'targetamount' => 'Montant cible', - 'accountRole' => 'Rôle du compte', - 'openingBalanceDate' => 'Date du solde initial', - 'ccType' => 'Plan de paiement de carte de crédit', - 'ccMonthlyPaymentDate' => 'Date de paiement mensuelle de carte de crédit', - 'piggy_bank_id' => 'Tirelire', - 'returnHere' => 'Retourner ici', - 'returnHereExplanation' => 'Après enregistrement, revenir ici pour en créer un nouveau.', - 'returnHereUpdateExplanation' => 'Après mise à jour, revenir ici.', - 'description' => 'Description', - 'expense_account' => 'Compte de dépenses', - 'revenue_account' => 'Compte de recettes', - 'decimal_places' => 'Chiffres après la virgule', - 'exchange_rate_instruction' => 'Devises étrangères', - 'source_amount' => 'Montant (source)', - 'destination_amount' => 'Montant (destination)', - 'native_amount' => 'Montant natif', - 'new_email_address' => 'Nouvelle adresse email', - 'verification' => 'Vérification', - 'api_key' => 'Clé API', - 'remember_me' => 'Se souvenir de moi', + 'bank_name' => 'Nom de la banque', + 'bank_balance' => 'Solde', + 'savings_balance' => 'Solde de l\'épargne', + 'credit_card_limit' => 'Limite de carte de crédit', + 'automatch' => 'Correspondre automatiquement', + 'skip' => 'Ignorer', + 'name' => 'Nom', + 'active' => 'Actif', + 'amount_min' => 'Montant minimum', + 'amount_max' => 'Montant maximum', + 'match' => 'Correspondre à', + 'strict' => 'Mode strict', + 'repeat_freq' => 'Répétitions', + 'journal_currency_id' => 'Devise', + 'currency_id' => 'Devise', + 'transaction_currency_id' => 'Devise', + 'external_ip' => 'L\'adresse IP externe de votre serveur', + 'attachments' => 'Documents joints', + 'journal_amount' => 'Montant', + 'journal_source_name' => 'Revenue account (source)', + 'journal_source_id' => 'Asset account (source)', + 'BIC' => 'Code BIC', + 'verify_password' => 'Vérifiez la sécurité du mot de passe', + 'source_account' => 'Compte d\'origine', + 'destination_account' => 'Compte destinataire', + 'journal_destination_id' => 'Asset account (destination)', + 'asset_destination_account' => 'Compte d’actif (destination)', + 'asset_source_account' => 'Compte d’actif (source)', + 'journal_description' => 'Description', + 'note' => 'Notes', + 'split_journal' => 'Ventiler cette opération', + 'split_journal_explanation' => 'Diviser cette opération en plusieurs parties', + 'currency' => 'Devise', + 'account_id' => 'Compte d’actif', + 'budget_id' => 'Budget', + 'openingBalance' => 'Solde initial', + 'tagMode' => 'Mode tag', + 'tag_position' => 'Localisation de l\'étiquette', + 'virtualBalance' => 'Solde virtuel', + 'targetamount' => 'Montant cible', + 'accountRole' => 'Rôle du compte', + 'openingBalanceDate' => 'Date du solde initial', + 'ccType' => 'Plan de paiement de carte de crédit', + 'ccMonthlyPaymentDate' => 'Date de paiement mensuelle de carte de crédit', + 'piggy_bank_id' => 'Tirelire', + 'returnHere' => 'Retourner ici', + 'returnHereExplanation' => 'Après enregistrement, revenir ici pour en créer un nouveau.', + 'returnHereUpdateExplanation' => 'Après mise à jour, revenir ici.', + 'description' => 'Description', + 'expense_account' => 'Compte de dépenses', + 'revenue_account' => 'Compte de recettes', + 'decimal_places' => 'Chiffres après la virgule', + 'exchange_rate_instruction' => 'Devises étrangères', + 'source_amount' => 'Montant (source)', + 'destination_amount' => 'Montant (destination)', + 'native_amount' => 'Montant natif', + 'new_email_address' => 'Nouvelle adresse email', + 'verification' => 'Vérification', + 'api_key' => 'Clé API', + 'remember_me' => 'Se souvenir de moi', 'source_account_asset' => 'Compte source (compte d\'actif)', 'destination_account_expense' => 'Compte de destination (compte de dépenses)', @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Convertir le dépôt', 'convert_Transfer' => 'Convertir le transfert', - 'amount' => 'Montant', - 'foreign_amount' => 'Foreign amount', - 'existing_attachments' => 'Existing attachments', - 'date' => 'Date', - 'interest_date' => 'Date de l’intérêt', - 'book_date' => 'Date de réservation', - 'process_date' => 'Date de traitement', - 'category' => 'Catégorie', - 'tags' => 'Mots-clés', - 'deletePermanently' => 'Supprimer définitivement', - 'cancel' => 'Annuler', - 'targetdate' => 'Date cible', - 'startdate' => 'Date de début', - 'tag' => 'Mot-clé', - 'under' => 'En dessous de', - 'symbol' => 'Symbole', - 'code' => 'Code', - 'iban' => 'Numéro IBAN', - 'accountNumber' => 'N° de compte', - 'creditCardNumber' => 'Numéro de carte de crédit', - 'has_headers' => 'Entêtes ', - 'date_format' => 'Format de la date', - 'specifix' => 'Banque - ou déposer des corrections spécifiques', - 'attachments[]' => 'Pièces jointes', - 'store_new_withdrawal' => 'Enregistrer un nouveau retrait', - 'store_new_deposit' => 'Enregistrer un nouveau dépôt', - 'store_new_transfer' => 'Enregistrer un nouveau transfert', - 'add_new_withdrawal' => 'Ajouter un nouveau retrait', - 'add_new_deposit' => 'Ajouter un nouveau dépôt', - 'add_new_transfer' => 'Ajouter un nouveau transfert', - 'title' => 'Titre', - 'notes' => 'Notes', - 'filename' => 'Nom du fichier', - 'mime' => 'Type Mime', - 'size' => 'Taille', - 'trigger' => 'Déclencheur', - 'stop_processing' => 'Arrêter le traitement', - 'start_date' => 'Début de l\'étendue', - 'end_date' => 'Fin de l\'étendue', - 'export_start_range' => 'Début de l’étendue d’exportation', - 'export_end_range' => 'Fin de l’étendue d\'exportation', - 'export_format' => 'Format de fichier', - 'include_attachments' => 'Inclure des pièces jointes téléchargées', - 'include_old_uploads' => 'Inclure les données importées', - 'accounts' => 'Exporter les opérations depuis ces comptes', - 'delete_account' => 'Supprimer le compte ":name"', - 'delete_bill' => 'Supprimer la facture ":name"', - 'delete_budget' => 'Supprimer le budget ":name"', - 'delete_category' => 'Supprimer la catégorie ":name"', - 'delete_currency' => 'Supprimer la devise ":name"', - 'delete_journal' => 'Supprimer l\'opération ayant comme description ":description"', - 'delete_attachment' => 'Supprimer la pièce jointe ":name"', - 'delete_rule' => 'Supprimer la règle ":title"', - 'delete_rule_group' => 'Supprimer le groupe de filtres ":title"', - 'delete_link_type' => 'Supprimer le type de lien ":name"', - 'delete_user' => 'Supprimer l\'utilisateur ":email"', - 'user_areYouSure' => 'Si vous supprimez l\'utilisateur ":email", tout disparaitra. Il n\'y a pas d\'annulation, de "dé-suppression" ou quoi que ce soit de la sorte. Si vous supprimez votre propre compte, vous n\'aurez plus accès à cette instance de Firefly III.', - 'attachment_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la pièce jointe nommée ":name" ?', - 'account_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le compte nommé ":name" ?', - 'bill_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la facture nommée ":name" ?', - 'rule_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la règle intitulée ":title" ?', - 'ruleGroup_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le groupe de règles intitulé ":title" ?', - 'budget_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le budget nommé ":name" ?', - 'category_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la catégorie nommée ":name" ?', - 'currency_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la devise nommée ":name" ?', - 'piggyBank_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la tirelire nommée ":name" ?', - 'journal_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la description de l\'opération ":description" ?', - 'mass_journal_are_you_sure' => 'Êtes-vous sûr de que vouloir supprimer ces opérations ?', - 'tag_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le tag ":tag" ?', - 'journal_link_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le lien entre :source et :destination?', - 'linkType_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le type de lien ":name" (":inward" / ":outward") ?', - 'permDeleteWarning' => 'Supprimer quelque chose dans Firefly est permanent et ne peut pas être annulé.', - 'mass_make_selection' => 'Vous pouvez toujours empêcher des éléments d’être supprimés en décochant la case à cocher.', - 'delete_all_permanently' => 'Supprimer la selection définitivement', - 'update_all_journals' => 'Mettre à jour ces opérations', - 'also_delete_transactions' => 'La seule opération liée à ce compte sera aussi supprimée.|Les :count opérations liées à ce compte seront aussi supprimées.', - 'also_delete_connections' => 'La seule transaction liée à ce type de lien perdra cette connexion. | Toutes les transactions :count liées à ce type de lien perdront leur connexion.', - 'also_delete_rules' => 'La seule règle liée à ce groupe de règles sera aussi supprimée.|Les :count règles liées à ce groupe de règles seront aussi supprimées.', - 'also_delete_piggyBanks' => 'La seule tirelire liée à ce compte sera aussi supprimée.|Les :count tirelires liées à ce compte seront aussi supprimées.', - 'bill_keep_transactions' => 'La seule opération liée à cette facture ne sera pas supprimée.|Les :count opérations liées à cette facture ne seront pas supprimées.', - 'budget_keep_transactions' => 'La seule opération liée à ce budget ne sera pas supprimée.|Les :count opérations liées à ce budget ne seront pas supprimées.', - 'category_keep_transactions' => 'La seule opération liée à cette catégorie ne sera pas supprimée.|Les :count opérations liées à cette catégorie ne seront pas supprimées.', - 'tag_keep_transactions' => 'La seule opération liée à ce tag ne sera pas supprimée.|Les :count opérations liées à ce tag ne seront pas supprimées.', - 'check_for_updates' => 'Vérifier les mises à jour', + 'amount' => 'Montant', + 'foreign_amount' => 'Montant externe', + 'existing_attachments' => 'Pièces jointes existantes', + 'date' => 'Date', + 'interest_date' => 'Date de l’intérêt', + 'book_date' => 'Date de réservation', + 'process_date' => 'Date de traitement', + 'category' => 'Catégorie', + 'tags' => 'Mots-clés', + 'deletePermanently' => 'Supprimer définitivement', + 'cancel' => 'Annuler', + 'targetdate' => 'Date cible', + 'startdate' => 'Date de début', + 'tag' => 'Mot-clé', + 'under' => 'En dessous de', + 'symbol' => 'Symbole', + 'code' => 'Code', + 'iban' => 'Numéro IBAN', + 'accountNumber' => 'N° de compte', + 'creditCardNumber' => 'Numéro de carte de crédit', + 'has_headers' => 'Entêtes ', + 'date_format' => 'Format de la date', + 'specifix' => 'Banque - ou déposer des corrections spécifiques', + 'attachments[]' => 'Pièces jointes', + 'store_new_withdrawal' => 'Enregistrer un nouveau retrait', + 'store_new_deposit' => 'Enregistrer un nouveau dépôt', + 'store_new_transfer' => 'Enregistrer un nouveau transfert', + 'add_new_withdrawal' => 'Ajouter un nouveau retrait', + 'add_new_deposit' => 'Ajouter un nouveau dépôt', + 'add_new_transfer' => 'Ajouter un nouveau transfert', + 'title' => 'Titre', + 'notes' => 'Notes', + 'filename' => 'Nom du fichier', + 'mime' => 'Type Mime', + 'size' => 'Taille', + 'trigger' => 'Déclencheur', + 'stop_processing' => 'Arrêter le traitement', + 'start_date' => 'Début de l\'étendue', + 'end_date' => 'Fin de l\'étendue', + 'export_start_range' => 'Début de l’étendue d’exportation', + 'export_end_range' => 'Fin de l’étendue d\'exportation', + 'export_format' => 'Format de fichier', + 'include_attachments' => 'Inclure des pièces jointes téléchargées', + 'include_old_uploads' => 'Inclure les données importées', + 'accounts' => 'Exporter les opérations depuis ces comptes', + 'delete_account' => 'Supprimer le compte ":name"', + 'delete_bill' => 'Supprimer la facture ":name"', + 'delete_budget' => 'Supprimer le budget ":name"', + 'delete_category' => 'Supprimer la catégorie ":name"', + 'delete_currency' => 'Supprimer la devise ":name"', + 'delete_journal' => 'Supprimer l\'opération ayant comme description ":description"', + 'delete_attachment' => 'Supprimer la pièce jointe ":name"', + 'delete_rule' => 'Supprimer la règle ":title"', + 'delete_rule_group' => 'Supprimer le groupe de filtres ":title"', + 'delete_link_type' => 'Supprimer le type de lien ":name"', + 'delete_user' => 'Supprimer l\'utilisateur ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Si vous supprimez l\'utilisateur ":email", tout disparaitra. Il n\'y a pas d\'annulation, de "dé-suppression" ou quoi que ce soit de la sorte. Si vous supprimez votre propre compte, vous n\'aurez plus accès à cette instance de Firefly III.', + 'attachment_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la pièce jointe nommée ":name" ?', + 'account_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le compte nommé ":name" ?', + 'bill_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la facture nommée ":name" ?', + 'rule_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la règle intitulée ":title" ?', + 'ruleGroup_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le groupe de règles intitulé ":title" ?', + 'budget_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le budget nommé ":name" ?', + 'category_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la catégorie nommée ":name" ?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la devise nommée ":name" ?', + 'piggyBank_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la tirelire nommée ":name" ?', + 'journal_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la description de l\'opération ":description" ?', + 'mass_journal_are_you_sure' => 'Êtes-vous sûr de que vouloir supprimer ces opérations ?', + 'tag_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le tag ":tag" ?', + 'journal_link_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le lien entre :source et :destination?', + 'linkType_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le type de lien ":name" (":inward" / ":outward") ?', + 'permDeleteWarning' => 'Supprimer quelque chose dans Firefly est permanent et ne peut pas être annulé.', + 'mass_make_selection' => 'Vous pouvez toujours empêcher des éléments d’être supprimés en décochant la case à cocher.', + 'delete_all_permanently' => 'Supprimer la selection définitivement', + 'update_all_journals' => 'Mettre à jour ces opérations', + 'also_delete_transactions' => 'La seule opération liée à ce compte sera aussi supprimée.|Les :count opérations liées à ce compte seront aussi supprimées.', + 'also_delete_connections' => 'La seule transaction liée à ce type de lien perdra cette connexion. | Toutes les transactions :count liées à ce type de lien perdront leur connexion.', + 'also_delete_rules' => 'La seule règle liée à ce groupe de règles sera aussi supprimée.|Les :count règles liées à ce groupe de règles seront aussi supprimées.', + 'also_delete_piggyBanks' => 'La seule tirelire liée à ce compte sera aussi supprimée.|Les :count tirelires liées à ce compte seront aussi supprimées.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Vérifier les mises à jour', 'email' => 'Adresse Email', 'password' => 'Mot de passe', @@ -186,10 +189,10 @@ return [ 'blocked_code' => 'Raison du blocage', // import - 'apply_rules' => 'Apply rules', - 'artist' => 'Artist', + 'apply_rules' => 'Appliquer les règles', + 'artist' => 'Artiste', 'album' => 'Album', - 'song' => 'Song', + 'song' => 'Titre', // admin @@ -210,17 +213,29 @@ return [ 'client_id' => 'Identifiant', 'service_secret' => 'Secret de service', 'app_secret' => 'Secret d\'application', - 'app_id' => 'App ID', + 'app_id' => 'ID App', 'secret' => 'Secret', 'public_key' => 'Clé publique', 'country_code' => 'Code pays', 'provider_code' => 'Banque ou fournisseur de données', - 'due_date' => 'Échéance', - 'payment_date' => 'Date de paiement', - 'invoice_date' => 'Date de facturation', - 'internal_reference' => 'Référence interne', - 'inward' => 'Description vers l’intérieur', - 'outward' => 'Description de l’extérieur', - 'rule_group_id' => 'Groupe de règles', + 'due_date' => 'Échéance', + 'payment_date' => 'Date de paiement', + 'invoice_date' => 'Date de facturation', + 'internal_reference' => 'Référence interne', + 'inward' => 'Description vers l’intérieur', + 'outward' => 'Description de l’extérieur', + 'rule_group_id' => 'Groupe de règles', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + 'weekend' => 'Weekend', + ]; diff --git a/resources/lang/fr_FR/import.php b/resources/lang/fr_FR/import.php index a111bb834e..609ca1d477 100644 --- a/resources/lang/fr_FR/import.php +++ b/resources/lang/fr_FR/import.php @@ -24,33 +24,33 @@ declare(strict_types=1); return [ // ALL breadcrumbs and subtitles: - 'index_breadcrumb' => 'Import data into Firefly III', - 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', - 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', - 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', - 'job_configuration_breadcrumb' => 'Configuration for ":key"', - 'job_status_breadcrumb' => 'Import status for ":key"', - 'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.', + 'index_breadcrumb' => 'Importer des données dans Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prérequis pour la simulation d\'importation', + 'prerequisites_breadcrumb_spectre' => 'Prérequis pour Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prérequis pour bunq', + 'job_configuration_breadcrumb' => 'Configuration pour ":key"', + 'job_status_breadcrumb' => 'Statut d\'importation pour ":key"', + 'cannot_create_for_provider' => 'Firefly III ne peut pas créer de tâche pour le fournisseur ":provider".', // index page: - 'general_index_title' => 'Import a file', - 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', + 'general_index_title' => 'Importer un fichier', + 'general_index_intro' => 'Bienvenue dans la routine d\'importation de Firefly III. Il existe différentes façons d\'importer des données dans Firefly III, affichées ici sous forme de boutons.', // import provider strings (index): - 'button_fake' => 'Fake an import', - 'button_file' => 'Import a file', - 'button_bunq' => 'Import from bunq', - 'button_spectre' => 'Import using Spectre', - 'button_plaid' => 'Import using Plaid', - 'button_yodlee' => 'Import using Yodlee', - 'button_quovo' => 'Import using Quovo', + 'button_fake' => 'Simuler une importation', + 'button_file' => 'Importer un fichier', + 'button_bunq' => 'Importer depuis bunq', + 'button_spectre' => 'Importer en utilisant Spectre', + 'button_plaid' => 'Importer en utilisant Plaid', + 'button_yodlee' => 'Importer en utilisant Yodlee', + 'button_quovo' => 'Importer en utilisant Quovo', // global config box (index) - 'global_config_title' => 'Global import configuration', - 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + 'global_config_title' => 'Configuration d\'importation globale', + 'global_config_text' => 'À l\'avenir, cette boîte contiendra les préférences qui s\'appliquent à TOUTES les sources d\'importation ci-dessus.', // prerequisites box (index) - 'need_prereq_title' => 'Import prerequisites', - 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', - 'do_prereq_fake' => 'Prerequisites for the fake provider', - 'do_prereq_file' => 'Prerequisites for file imports', + 'need_prereq_title' => 'Prérequis d\'importation', + 'need_prereq_intro' => 'Certaines méthodes d\'importation nécessitent votre attention avant de pouvoir être utilisées. Par exemple, elles peuvent nécessiter des clés d\'API spéciales ou des clés secrètes. Vous pouvez les configurer ici. L\'icône indique si ces conditions préalables ont été remplies.', + 'do_prereq_fake' => 'Prérequis pour la simulation', + 'do_prereq_file' => 'Prérequis pour les importations de fichiers', 'do_prereq_bunq' => 'Prerequisites for imports from bunq', 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -204,61 +207,61 @@ return [ 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', 'unknown_import_result' => 'Unknown import result', 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', - 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', - 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', + 'result_one_transaction' => 'Une seule transaction a été importée. Elle est stockée sous le tag :tag où vous pouvez l\'afficher en détail.', + 'result_many_transactions' => 'Firefly III a importé :count transactions. Elles sont stockées sous le tag :tag où vous pouvez les afficher en détail.', // general errors and warnings: - 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', + 'bad_job_status' => 'Vous ne pouvez pas accéder à cette page tant que l\'importation a le statut ":status".', // column roles for CSV import: 'column__ignore' => '(ignorer cette colonne)', 'column_account-iban' => 'Compte d’actif (IBAN)', - 'column_account-id' => 'Asset account ID (matching FF3)', + 'column_account-id' => 'Compte d\'actif (ID correspondant à FF3)', 'column_account-name' => 'Compte d’actif (nom)', 'column_amount' => 'Montant', - 'column_amount_foreign' => 'Amount (in foreign currency)', + 'column_amount_foreign' => 'Montant (en devise étrangère)', 'column_amount_debit' => 'Montant (colonne débit)', 'column_amount_credit' => 'Montant (colonne de crédit)', - 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'column_bill-id' => 'Bill ID (matching FF3)', + 'column_amount-comma-separated' => 'Montant (virgule comme séparateur décimal)', + 'column_bill-id' => 'Facture (ID correspondant à FF3)', 'column_bill-name' => 'Nom de la facture', - 'column_budget-id' => 'Budget ID (matching FF3)', + 'column_budget-id' => 'Budget (ID correspondant à FF3)', 'column_budget-name' => 'Nom du budget', - 'column_category-id' => 'Category ID (matching FF3)', + 'column_category-id' => 'Catégorie (ID correspondant à FF3)', 'column_category-name' => 'Nom de catégorie', - 'column_currency-code' => 'Currency code (ISO 4217)', - 'column_foreign-currency-code' => 'Foreign currency code (ISO 4217)', - 'column_currency-id' => 'Currency ID (matching FF3)', - 'column_currency-name' => 'Currency name (matching FF3)', - 'column_currency-symbol' => 'Currency symbol (matching FF3)', - 'column_date-interest' => 'Interest calculation date', - 'column_date-book' => 'Transaction booking date', - 'column_date-process' => 'Transaction process date', + 'column_currency-code' => 'Code de la devise (ISO 4217)', + 'column_foreign-currency-code' => 'Code de devise étrangère (ISO 4217)', + 'column_currency-id' => 'Devise (ID correspondant à FF3)', + 'column_currency-name' => 'Nom de la devise (correspondant à FF3)', + 'column_currency-symbol' => 'Symbole de la devise (correspondant à FF3)', + 'column_date-interest' => 'Date de calcul des intérêts', + 'column_date-book' => 'Date d\'enregistrement de la transaction', + 'column_date-process' => 'Date de traitement de la transaction', 'column_date-transaction' => 'Date', - 'column_date-due' => 'Transaction due date', - 'column_date-payment' => 'Transaction payment date', - 'column_date-invoice' => 'Transaction invoice date', + 'column_date-due' => 'Date d\'échéance de la transaction', + 'column_date-payment' => 'Date de paiement de la transaction', + 'column_date-invoice' => 'Date de facturation de la transaction', 'column_description' => 'Description', - 'column_opposing-iban' => 'Opposing account (IBAN)', - 'column_opposing-bic' => 'Opposing account (BIC)', - 'column_opposing-id' => 'Opposing account ID (matching FF3)', + 'column_opposing-iban' => 'Compte destinataire (IBAN)', + 'column_opposing-bic' => 'Compte destinataire (BIC)', + 'column_opposing-id' => 'Compte destinataire (ID correspondant à FF3)', 'column_external-id' => 'ID externe', - 'column_opposing-name' => 'Opposing account (name)', - 'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator', - 'column_ing-debit-credit' => 'ING specific debit/credit indicator', - 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', - 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'column_sepa-db' => 'SEPA Mandate Identifier', - 'column_sepa-cc' => 'SEPA Clearing Code', - 'column_sepa-ci' => 'SEPA Creditor Identifier', - 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'SEPA Country Code', + 'column_opposing-name' => 'Compte destinataire (nom)', + 'column_rabo-debit-credit' => 'Indicateur de débit/crédit spécifique à Rabobank', + 'column_ing-debit-credit' => 'Indicateur de débit/crédit spécifique à ING', + 'column_sepa-ct-id' => 'Référence de bout en bout SEPA', + 'column_sepa-ct-op' => 'Référence SEPA du compte destinataire', + 'column_sepa-db' => 'Référence Unique de Mandat SEPA', + 'column_sepa-cc' => 'Code de rapprochement SEPA', + 'column_sepa-ci' => 'Identifiant Créancier SEPA', + 'column_sepa-ep' => 'Objectif externe SEPA', + 'column_sepa-country' => 'Code de pays SEPA', 'column_tags-comma' => 'Tags (séparés par des virgules)', 'column_tags-space' => 'Tags (séparé par un espace)', - 'column_account-number' => 'Asset account (account number)', - 'column_opposing-number' => 'Opposing account (account number)', + 'column_account-number' => 'Compte d’actif (numéro de compte)', + 'column_opposing-number' => 'Compte destinataire (numéro de compte)', 'column_note' => 'Note(s)', - 'column_internal-reference' => 'Internal reference', + 'column_internal-reference' => 'Référence interne', ]; diff --git a/resources/lang/fr_FR/intro.php b/resources/lang/fr_FR/intro.php index 103c316b23..cfed1ec1ca 100644 --- a/resources/lang/fr_FR/intro.php +++ b/resources/lang/fr_FR/intro.php @@ -93,7 +93,7 @@ return [ 'piggy-banks_show_piggyEvents' => 'Des ajouts ou suppressions sont également répertoriées ici.', // bill index - 'bills_index_rules' => 'Here you see which rules will check if this bill is hit', + 'bills_index_rules' => 'Ici, vous voyez quelles règles vont s\'appliquer si cette facture est payée', 'bills_index_paid_in_period' => 'Ce champ indique quand la facture a été payée pour la dernière fois.', 'bills_index_expected_in_period' => 'Ce champ indique pour chaque facture si et quand la facture suivante est attendue.', @@ -103,12 +103,12 @@ return [ 'bills_show_billChart' => 'Ce tableau montre les transactions liées à cette facture.', // create bill - 'bills_create_intro' => 'Use bills to track the amount of money you\'re due every period. Think about expenses like rent, insurance or mortgage payments.', + 'bills_create_intro' => 'Utilisez des factures pour suivre les sommes que vous avez à payer à chaque période. Pensez aux dépenses comme le loyer, l\'assurance ou les remboursements d\'emprunts.', 'bills_create_name' => 'Utilisez un nom équivoque tel que "Loyer" ou "Assurance maladie".', //'bills_create_match' => 'To match transactions, use terms from those transactions or the expense account involved. All words must match.', 'bills_create_amount_min_holder' => 'Sélectionnez un montant minimum et maximum pour cette facture.', 'bills_create_repeat_freq_holder' => 'La plupart des factures sont mensuelles, mais vous pouvez définir une autre fréquence ici.', - 'bills_create_skip_holder' => 'If a bill repeats every 2 weeks, the "skip"-field should be set to "1" to skip every other week.', + 'bills_create_skip_holder' => 'Si une facture se répète toutes les 2 semaines, le champ "sauter" doit être réglé sur "1" pour sauter une semaine sur deux.', // rules index 'rules_index_intro' => 'Firefly III vous permet de gérer les règles, qui seront automagiquement appliquées à toute transaction que vous créez ou modifiez.', diff --git a/resources/lang/fr_FR/list.php b/resources/lang/fr_FR/list.php index ccafd1e385..c2d0261328 100644 --- a/resources/lang/fr_FR/list.php +++ b/resources/lang/fr_FR/list.php @@ -34,7 +34,7 @@ return [ 'name' => 'Nom', 'role' => 'Rôle', 'currentBalance' => 'Solde courant', - 'linked_to_rules' => 'Relevant rules', + 'linked_to_rules' => 'Règles applicables', 'active' => 'Actif ?', 'lastActivity' => 'Activité récente', 'balanceDiff' => 'Différence d\'équilibre', @@ -112,15 +112,20 @@ return [ 'sepa-cc' => 'Code de compensation SEPA', 'sepa-ep' => 'Objectif externe SEPA', 'sepa-ci' => 'Identifiant SEPA Creditor', - 'external_id' => 'External ID', + 'external_id' => 'ID externe', 'account_at_bunq' => 'Compte avec bunq', - 'file_name' => 'File name', - 'file_size' => 'File size', - 'file_type' => 'File type', - 'attached_to' => 'Attached to', - 'file_exists' => 'File exists', - 'spectre_bank' => 'Bank', - 'spectre_last_use' => 'Last login', - 'spectre_status' => 'Status', - 'bunq_payment_id' => 'bunq payment ID', + 'file_name' => 'Nom du fichier', + 'file_size' => 'Taille du fichier', + 'file_type' => 'Type de fichier', + 'attached_to' => 'Attaché à', + 'file_exists' => 'Le fichier existe', + 'spectre_bank' => 'Banque', + 'spectre_last_use' => 'Dernière connexion', + 'spectre_status' => 'Statut', + 'bunq_payment_id' => 'ID de paiement bunq', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php index 6ce0d06474..e5277aa087 100644 --- a/resources/lang/fr_FR/validation.php +++ b/resources/lang/fr_FR/validation.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'iban' => 'Il ne s\'agit pas d\'un IBAN valide.', - 'source_equals_destination' => 'Le compte source est égal au compte de destination', + 'source_equals_destination' => 'The source account equals the destination account.', 'unique_account_number_for_user' => 'Il semble que ce numéro de compte est déjà utilisé.', 'unique_iban_for_user' => 'Il semble que cet IBAN soit déjà utilisé.', 'deleted_user' => 'Compte tenu des contraintes de sécurité, vous ne pouvez pas vous inscrire en utilisant cette adresse e-mail.', @@ -34,16 +34,22 @@ return [ 'file_attached' => 'Envoi du fichier ":name" avec succès.', 'must_exist' => 'L\'ID dans le champ :attribute n\'existe pas dans la base de données.', 'all_accounts_equal' => 'Tous les comptes dans ce champ doivent être égaux.', - 'invalid_selection' => 'Votre sélection est invalide', + 'invalid_selection' => 'Your selection is invalid.', 'belongs_user' => 'Cette valeur n\'est pas valide pour ce champ.', 'at_least_one_transaction' => 'Besoin d\'au moins une transaction.', + 'at_least_one_repetition' => 'Need at least one repetition.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'Le contenu de ce champ n\'est pas valide sans informations sur la devise.', 'equal_description' => 'La description de la transaction ne doit pas être égale à la description globale.', 'file_invalid_mime' => 'Le fichier ":name" est du type ":mime" ce qui n\'est pas accepté pour un nouvel envoi.', 'file_too_large' => 'Le fichier ":name" est trop grand.', - 'belongs_to_user' => 'La valeur de :attribute est inconnue', + 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => 'Le champ :attribute doit être accepté.', 'bic' => 'Ce n’est pas un code BIC valide.', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute doit être supérieur à zéro.', 'active_url' => 'Le champ :attribute n\'est pas une URL valide.', 'after' => 'Le champ :attribute doit être une date postérieure à :date.', @@ -53,8 +59,8 @@ return [ 'array' => 'Le champ :attribute doit être un tableau.', 'unique_for_user' => 'Il existe déjà une entrée avec ceci :attribute.', 'before' => 'Le champ :attribute doit être une date antérieure à :date.', - 'unique_object_for_user' => 'Ce nom est déjà utilisé', - 'unique_account_for_user' => 'Ce nom de compte est déjà utilisé', + 'unique_object_for_user' => 'This name is already in use.', + 'unique_account_for_user' => 'This account name is already in use.', 'between.numeric' => 'La valeur de :attribute doit être comprise entre :min et :max.', 'between.file' => 'Le fichier :attribute doit avoir une taille entre :min et :max kilo-octets.', 'between.string' => 'Le texte :attribute doit avoir entre :min et :max caractères.', @@ -85,6 +91,9 @@ return [ 'min.array' => 'Le tableau :attribute doit avoir au moins :min éléments.', 'not_in' => 'Le champ :attribute sélectionné n\'est pas valide.', 'numeric' => 'Le champ :attribute doit contenir un nombre.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'Le format du champ :attribute est invalide.', 'required' => 'Le champ :attribute est obligatoire.', 'required_if' => 'Le champ :attribute est obligatoire quand la valeur de :other est :value.', @@ -109,9 +118,12 @@ return [ 'file' => 'Le :attribute doit être un fichier.', 'in_array' => 'Le champ :attribute n\'existe pas dans :other.', 'present' => 'Le champs :attribute doit être rempli.', - 'amount_zero' => 'Le montant total ne peut pas être zéro', - 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'amount_zero' => 'The total amount cannot be zero.', + 'unique_piggy_bank_for_user' => 'Le nom de la tirelire doit être unique.', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security.', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions.', + 'valid_recurrence_rep_moment' => 'Invalid repetition moment for this type of repetition.', + 'invalid_account_info' => 'Invalid account information.', 'attributes' => [ 'email' => 'adresse email', 'description' => 'description', diff --git a/resources/lang/id_ID/config.php b/resources/lang/id_ID/config.php index 64659774cd..42f77dcca9 100644 --- a/resources/lang/id_ID/config.php +++ b/resources/lang/id_ID/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'id', - 'locale' => 'id, Bahasa Indonesia, id_ID, id_ID.utf8, id_ID.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Minggu %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'MMMM Do, YYYY', - 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'id', + 'locale' => 'id, Bahasa Indonesia, id_ID, id_ID.utf8, id_ID.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Minggu %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'MMMM Do, YYYY', + 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/id_ID/demo.php b/resources/lang/id_ID/demo.php index a62c929dc0..0489b67c41 100644 --- a/resources/lang/id_ID/demo.php +++ b/resources/lang/id_ID/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Biaya ini, deposito dan transfer tidak terlalu imajinatif. Mereka telah dihasilkan secara otomatis.', 'piggy-banks-index' => 'Seperti yang bisa Anda lihat, ada tiga celengan. Gunakan tombol plus dan minus untuk mempengaruhi jumlah uang di setiap celengan. Klik nama celengan untuk melihat administrasi masing-masing celengan.', 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index 348ed93b3a..d5a358e8a8 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', 'pref_two_factor_auth_reset_code' => 'Setel ulang kode verifikasi', 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Simpan Pengaturan', 'saved_preferences' => 'Preferensi disimpan!', 'preferences_general' => 'Umum', @@ -820,7 +821,7 @@ return [ 'language' => 'Language', 'new_savings_account' => ':bank_name savings account', 'cash_wallet' => 'Cash wallet', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Akun anda', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Saldo akhir periode', 'splitByAccount' => 'Dibagi oleh akun', 'coveredWithTags' => 'Ditutupi dengan tag', - 'leftUnbalanced' => 'Meninggalkan tidak seimbang', 'leftInBudget' => 'Yang tersisa di anggaran', 'sumOfSums' => 'Jumlah dari jumlah', 'noCategory' => '(Tidak ada kategori)', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Konfigurasi', 'firefly_instance_configuration' => 'Pilihan konfigurasi untuk Firefly III', 'setting_single_user_mode' => 'Mode pengguna tunggal', - 'setting_single_user_mode_explain' => 'Secara default, Firefly III hanya menerima satu (1) registrasi: anda. Ini adalah tindakan pengamanan, mencegah orang lain menggunakan contoh Anda kecuali jika Anda mengizinkannya melakukannya. Pendaftaran di masa depan diblokir Bila Anda tidak mencentang kotak ini, orang lain dapat menggunakan contoh Anda dengan baik, dengan asumsi mereka dapat mencapainya (bila terhubung ke internet).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Konfigurasi toko', 'single_user_administration' => 'Administrasi pengguna untuk :email', 'edit_user' => 'Edit pengguna :email', @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => '(sebagian) dikembalikan oleh', 'is (partially) paid for by_inward' => 'adalah (sebagian) dibayar oleh', 'is (partially) reimbursed by_inward' => '(sebagian) diganti oleh', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'berhubungan dengan', '(partially) refunds_outward' => '(sebagian) pengembalian uang', '(partially) pays for_outward' => '(sebagian) membayar', @@ -1155,8 +1157,9 @@ return [ 'cannot_convert_split_journal' => 'Tidak dapat mengonversi transaksi split', // Import page (general strings only) - 'import_index_title' => 'Impor data ke Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Impor data', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Fungsi ini tidak tersedia saat Anda menggunakan Firefly III di dalam lingkungan Sandstorm.io.', @@ -1206,4 +1209,68 @@ return [ 'no_bills_intro_default' => 'Anda belum memiliki tagihan. Anda bisa membuat tagihan untuk mencatat pengeluaran rutin, seperti sewa atau asuransi Anda.', 'no_bills_imperative_default' => 'Apakah Anda memiliki tagihan reguler seperti itu? Buat tagihan dan lacak pembayaran Anda:', 'no_bills_create_default' => 'Buat tagihan', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/id_ID/form.php b/resources/lang/id_ID/form.php index 6dd307210f..081ce56d1b 100644 --- a/resources/lang/id_ID/form.php +++ b/resources/lang/id_ID/form.php @@ -24,66 +24,66 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Nama Bank', - 'bank_balance' => 'Keseimbangan', - 'savings_balance' => 'Saldo tabungan', - 'credit_card_limit' => 'Batas kartu kredit', - 'automatch' => 'Cocokkan secara otomatis', - 'skip' => 'Melewatkan', - 'name' => 'Nama', - 'active' => 'Aktif', - 'amount_min' => 'Jumlah minimal', - 'amount_max' => 'Jumlah maksimum', - 'match' => 'Cocok di', - 'strict' => 'Strict mode', - 'repeat_freq' => 'Berulang', - 'journal_currency_id' => 'Mata uang', - 'currency_id' => 'Mata uang', - 'transaction_currency_id' => 'Currency', - 'external_ip' => 'Your server\'s external IP', - 'attachments' => 'Lampiran', - 'journal_amount' => 'Jumlah', - 'journal_source_account_name' => 'Akun pendapatan (sumber)', - 'journal_source_account_id' => 'Akun aset (sumber)', - 'BIC' => 'BIC', - 'verify_password' => 'Verifikasi keamanan kata sandi', - 'source_account' => 'Akun sumber', - 'destination_account' => 'Akun tujuan', - 'journal_destination_account_id' => 'Akun aset (tujuan)', - 'asset_destination_account' => 'Akun aset (tujuan)', - 'asset_source_account' => 'Akun aset (sumber)', - 'journal_description' => 'Deskripsi', - 'note' => 'Catatan', - 'split_journal' => 'Pisahkan transaksi ini', - 'split_journal_explanation' => 'Split transaksi ini di banyak bagian', - 'currency' => 'Mata uang', - 'account_id' => 'Akun aset', - 'budget_id' => 'Anggaran', - 'openingBalance' => 'Saldo awal', - 'tagMode' => 'Mode Tag', - 'tag_position' => 'Lokasi tag', - 'virtualBalance' => 'Saldo virtual', - 'targetamount' => 'Jumlah target', - 'accountRole' => 'Peran akun', - 'openingBalanceDate' => 'Membuka tanggal saldo', - 'ccType' => 'Rencana pembayaran kartu kredit', - 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', - 'piggy_bank_id' => 'Celengan', - 'returnHere' => 'Kembali ke sini', - 'returnHereExplanation' => 'Setelah menyimpan, kembali ke sini untuk membuat yang lain.', - 'returnHereUpdateExplanation' => 'Setelah update, kembali ke sini.', - 'description' => 'Deskripsi', - 'expense_account' => 'Rekening pengeluaran', - 'revenue_account' => 'Akun pendapatan', - 'decimal_places' => 'Tempat desimal', - 'exchange_rate_instruction' => 'Mata uang asing', - 'source_amount' => 'Jumlah (sumber)', - 'destination_amount' => 'Jumlah (tujuan)', - 'native_amount' => 'Jumlah asli', - 'new_email_address' => 'Alamat email baru', - 'verification' => 'Verifikasi', - 'api_key' => 'Kunci API', - 'remember_me' => 'Remember me', + 'bank_name' => 'Nama Bank', + 'bank_balance' => 'Keseimbangan', + 'savings_balance' => 'Saldo tabungan', + 'credit_card_limit' => 'Batas kartu kredit', + 'automatch' => 'Cocokkan secara otomatis', + 'skip' => 'Melewatkan', + 'name' => 'Nama', + 'active' => 'Aktif', + 'amount_min' => 'Jumlah minimal', + 'amount_max' => 'Jumlah maksimum', + 'match' => 'Cocok di', + 'strict' => 'Strict mode', + 'repeat_freq' => 'Berulang', + 'journal_currency_id' => 'Mata uang', + 'currency_id' => 'Mata uang', + 'transaction_currency_id' => 'Currency', + 'external_ip' => 'Your server\'s external IP', + 'attachments' => 'Lampiran', + 'journal_amount' => 'Jumlah', + 'journal_source_name' => 'Revenue account (source)', + 'journal_source_id' => 'Asset account (source)', + 'BIC' => 'BIC', + 'verify_password' => 'Verifikasi keamanan kata sandi', + 'source_account' => 'Akun sumber', + 'destination_account' => 'Akun tujuan', + 'journal_destination_id' => 'Asset account (destination)', + 'asset_destination_account' => 'Akun aset (tujuan)', + 'asset_source_account' => 'Akun aset (sumber)', + 'journal_description' => 'Deskripsi', + 'note' => 'Catatan', + 'split_journal' => 'Pisahkan transaksi ini', + 'split_journal_explanation' => 'Split transaksi ini di banyak bagian', + 'currency' => 'Mata uang', + 'account_id' => 'Akun aset', + 'budget_id' => 'Anggaran', + 'openingBalance' => 'Saldo awal', + 'tagMode' => 'Mode Tag', + 'tag_position' => 'Lokasi tag', + 'virtualBalance' => 'Saldo virtual', + 'targetamount' => 'Jumlah target', + 'accountRole' => 'Peran akun', + 'openingBalanceDate' => 'Membuka tanggal saldo', + 'ccType' => 'Rencana pembayaran kartu kredit', + 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', + 'piggy_bank_id' => 'Celengan', + 'returnHere' => 'Kembali ke sini', + 'returnHereExplanation' => 'Setelah menyimpan, kembali ke sini untuk membuat yang lain.', + 'returnHereUpdateExplanation' => 'Setelah update, kembali ke sini.', + 'description' => 'Deskripsi', + 'expense_account' => 'Rekening pengeluaran', + 'revenue_account' => 'Akun pendapatan', + 'decimal_places' => 'Tempat desimal', + 'exchange_rate_instruction' => 'Mata uang asing', + 'source_amount' => 'Jumlah (sumber)', + 'destination_amount' => 'Jumlah (tujuan)', + 'native_amount' => 'Jumlah asli', + 'new_email_address' => 'Alamat email baru', + 'verification' => 'Verifikasi', + 'api_key' => 'Kunci API', + 'remember_me' => 'Remember me', 'source_account_asset' => 'Akun sumber (akun aset)', 'destination_account_expense' => 'Akun tujuan (akun pengeluaran)', @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Convert deposit', 'convert_Transfer' => 'Mengkonversi transfer', - 'amount' => 'Jumlah', - 'foreign_amount' => 'Foreign amount', - 'existing_attachments' => 'Existing attachments', - 'date' => 'Tanggal', - 'interest_date' => 'Tanggal bunga', - 'book_date' => 'Tanggal buku', - 'process_date' => 'Tanggal pemrosesan', - 'category' => 'Kategori', - 'tags' => 'Tag', - 'deletePermanently' => 'Hapus secara permanen', - 'cancel' => 'Membatalkan', - 'targetdate' => 'Tanggal target', - 'startdate' => 'Mulai tanggal', - 'tag' => 'Menandai', - 'under' => 'Dibawah', - 'symbol' => 'Simbol', - 'code' => 'Kode', - 'iban' => 'IBAN', - 'accountNumber' => 'Nomor akun', - 'creditCardNumber' => 'Nomor kartu kredit', - 'has_headers' => 'Judul', - 'date_format' => 'Format tanggal', - 'specifix' => 'Perbaikan spesifik bank atau berkas', - 'attachments[]' => 'Lampiran', - 'store_new_withdrawal' => 'Simpan penarikan baru', - 'store_new_deposit' => 'Simpan deposit baru', - 'store_new_transfer' => 'Simpan transfer baru', - 'add_new_withdrawal' => 'Tambahkan penarikan baru', - 'add_new_deposit' => 'Tambahkan deposit baru', - 'add_new_transfer' => 'Tambahkan transfer baru', - 'title' => 'Judul', - 'notes' => 'Catatan', - 'filename' => 'Nama file', - 'mime' => 'Tipe mime', - 'size' => 'Ukuran', - 'trigger' => 'Pelatuk', - 'stop_processing' => 'Berhenti memproses', - 'start_date' => 'Mulai dari jangkauan', - 'end_date' => 'Akhir rentang', - 'export_start_range' => 'Mulai dari rentang ekspor', - 'export_end_range' => 'Akhir rentang ekspor', - 'export_format' => 'Format file', - 'include_attachments' => 'Sertakan lampiran yang diunggah', - 'include_old_uploads' => 'Sertakan data yang diimpor', - 'accounts' => 'Mengekspor transaksi dari akun ini', - 'delete_account' => 'Delete account ":name"', - 'delete_bill' => 'Hapus tagihan ":name"', - 'delete_budget' => 'Hapus anggaran ":name"', - 'delete_category' => 'Hapus kategori ":name"', - 'delete_currency' => 'Hapus mata uang ":name"', - 'delete_journal' => 'Hapus transaksi dengan deskripsi ":description"', - 'delete_attachment' => 'Hapus lampiran ":name"', - 'delete_rule' => 'Hapus aturan ":title"', - 'delete_rule_group' => 'Hapus grup aturan ":title"', - 'delete_link_type' => 'Hapus jenis tautan ":name"', - 'delete_user' => 'Hapus pengguna ":email"', - 'user_areYouSure' => 'Jika Anda menghapus pengguna ":email", semuanya akan hilang. Tidak ada undo, undelete atau apapun. Jika Anda menghapus diri Anda sendiri, Anda akan kehilangan akses ke Firefly III ini.', - 'attachment_areYouSure' => 'Yakin ingin menghapus lampiran yang bernama ":name"?', - 'account_areYouSure' => 'Yakin ingin menghapus akun dengan nama ":name"?', - 'bill_areYouSure' => 'Yakin ingin menghapus tagihan yang bernama ":name"?', - 'rule_areYouSure' => 'Yakin ingin menghapus aturan yang berjudul ":title"?', - 'ruleGroup_areYouSure' => 'Yakin ingin menghapus grup aturan yang berjudul ":title"?', - 'budget_areYouSure' => 'Yakin ingin menghapus anggaran dengan nama ":name"?', - 'category_areYouSure' => 'Yakin ingin menghapus kategori yang bernama ":name"?', - 'currency_areYouSure' => 'Yakin ingin menghapus mata uang dengan nama ":name"?', - 'piggyBank_areYouSure' => 'Yakin ingin menghapus piggy bank yang bernama ":name"?', - 'journal_areYouSure' => 'Yakin ingin menghapus transaksi yang dijelaskan ":description"?', - 'mass_journal_are_you_sure' => 'Yakin ingin menghapus transaksi ini?', - 'tag_areYouSure' => 'Yakin ingin menghapus tag ":tag"?', - 'journal_link_areYouSure' => 'Yakin ingin menghapus tautan antara :source and :destination?', - 'linkType_areYouSure' => 'Yakin ingin menghapus jenis tautan ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', - 'mass_make_selection' => 'Anda masih dapat mencegah agar item dihapus dengan menghapus kotak centang.', - 'delete_all_permanently' => 'Hapus yang dipilih secara permanen', - 'update_all_journals' => 'Perbarui transaksi ini', - 'also_delete_transactions' => 'Satu-satunya transaksi yang terhubung ke akun ini akan dihapus juga. | Semua :count transaksi yang terhubung ke akun ini akan dihapus juga.', - 'also_delete_connections' => 'Satu-satunya transaksi yang terkait dengan jenis link ini akan kehilangan koneksi ini. Semua :count transaksi yang terkait dengan jenis link ini akan kehilangan koneksi mereka.', - 'also_delete_rules' => 'Aturan satu-satunya yang terhubung ke grup aturan ini akan dihapus juga. Aturan All :count yang terhubung ke grup aturan ini akan dihapus juga.', - 'also_delete_piggyBanks' => 'Satu-satunya piggy bank yang terhubung ke akun ini akan dihapus juga. Semua :count piggy bank yang terhubung ke akun ini akan dihapus juga.', - 'bill_keep_transactions' => 'Satu-satunya transaksi yang terhubung dengan tagihan ini tidak akan dihapus. Semua :count transaksi yang terhubung ke tagihan ini akan terhindar dari penghapusan.', - 'budget_keep_transactions' => 'Satu-satunya transaksi yang terhubung dengan anggaran ini tidak akan dihapus. Semua :count transaksi yang terhubung dengan anggaran ini akan terhindar dari penghapusan.', - 'category_keep_transactions' => 'Satu-satunya transaksi yang terhubung ke kategori ini tidak akan dihapus. Semua :count transaksi yang terhubung ke kategori ini akan terhindar dari penghapusan.', - 'tag_keep_transactions' => 'Satu-satunya transaksi yang terhubung ke tag ini tidak akan dihapus. Semua :count transaksi yang terhubung ke tag ini akan terhindar dari penghapusan.', - 'check_for_updates' => 'Check for updates', + 'amount' => 'Jumlah', + 'foreign_amount' => 'Foreign amount', + 'existing_attachments' => 'Existing attachments', + 'date' => 'Tanggal', + 'interest_date' => 'Tanggal bunga', + 'book_date' => 'Tanggal buku', + 'process_date' => 'Tanggal pemrosesan', + 'category' => 'Kategori', + 'tags' => 'Tag', + 'deletePermanently' => 'Hapus secara permanen', + 'cancel' => 'Membatalkan', + 'targetdate' => 'Tanggal target', + 'startdate' => 'Mulai tanggal', + 'tag' => 'Menandai', + 'under' => 'Dibawah', + 'symbol' => 'Simbol', + 'code' => 'Kode', + 'iban' => 'IBAN', + 'accountNumber' => 'Nomor akun', + 'creditCardNumber' => 'Nomor kartu kredit', + 'has_headers' => 'Judul', + 'date_format' => 'Format tanggal', + 'specifix' => 'Perbaikan spesifik bank atau berkas', + 'attachments[]' => 'Lampiran', + 'store_new_withdrawal' => 'Simpan penarikan baru', + 'store_new_deposit' => 'Simpan deposit baru', + 'store_new_transfer' => 'Simpan transfer baru', + 'add_new_withdrawal' => 'Tambahkan penarikan baru', + 'add_new_deposit' => 'Tambahkan deposit baru', + 'add_new_transfer' => 'Tambahkan transfer baru', + 'title' => 'Judul', + 'notes' => 'Catatan', + 'filename' => 'Nama file', + 'mime' => 'Tipe mime', + 'size' => 'Ukuran', + 'trigger' => 'Pelatuk', + 'stop_processing' => 'Berhenti memproses', + 'start_date' => 'Mulai dari jangkauan', + 'end_date' => 'Akhir rentang', + 'export_start_range' => 'Mulai dari rentang ekspor', + 'export_end_range' => 'Akhir rentang ekspor', + 'export_format' => 'Format file', + 'include_attachments' => 'Sertakan lampiran yang diunggah', + 'include_old_uploads' => 'Sertakan data yang diimpor', + 'accounts' => 'Mengekspor transaksi dari akun ini', + 'delete_account' => 'Delete account ":name"', + 'delete_bill' => 'Hapus tagihan ":name"', + 'delete_budget' => 'Hapus anggaran ":name"', + 'delete_category' => 'Hapus kategori ":name"', + 'delete_currency' => 'Hapus mata uang ":name"', + 'delete_journal' => 'Hapus transaksi dengan deskripsi ":description"', + 'delete_attachment' => 'Hapus lampiran ":name"', + 'delete_rule' => 'Hapus aturan ":title"', + 'delete_rule_group' => 'Hapus grup aturan ":title"', + 'delete_link_type' => 'Hapus jenis tautan ":name"', + 'delete_user' => 'Hapus pengguna ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Jika Anda menghapus pengguna ":email", semuanya akan hilang. Tidak ada undo, undelete atau apapun. Jika Anda menghapus diri Anda sendiri, Anda akan kehilangan akses ke Firefly III ini.', + 'attachment_areYouSure' => 'Yakin ingin menghapus lampiran yang bernama ":name"?', + 'account_areYouSure' => 'Yakin ingin menghapus akun dengan nama ":name"?', + 'bill_areYouSure' => 'Yakin ingin menghapus tagihan yang bernama ":name"?', + 'rule_areYouSure' => 'Yakin ingin menghapus aturan yang berjudul ":title"?', + 'ruleGroup_areYouSure' => 'Yakin ingin menghapus grup aturan yang berjudul ":title"?', + 'budget_areYouSure' => 'Yakin ingin menghapus anggaran dengan nama ":name"?', + 'category_areYouSure' => 'Yakin ingin menghapus kategori yang bernama ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Yakin ingin menghapus mata uang dengan nama ":name"?', + 'piggyBank_areYouSure' => 'Yakin ingin menghapus piggy bank yang bernama ":name"?', + 'journal_areYouSure' => 'Yakin ingin menghapus transaksi yang dijelaskan ":description"?', + 'mass_journal_are_you_sure' => 'Yakin ingin menghapus transaksi ini?', + 'tag_areYouSure' => 'Yakin ingin menghapus tag ":tag"?', + 'journal_link_areYouSure' => 'Yakin ingin menghapus tautan antara :source and :destination?', + 'linkType_areYouSure' => 'Yakin ingin menghapus jenis tautan ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', + 'mass_make_selection' => 'Anda masih dapat mencegah agar item dihapus dengan menghapus kotak centang.', + 'delete_all_permanently' => 'Hapus yang dipilih secara permanen', + 'update_all_journals' => 'Perbarui transaksi ini', + 'also_delete_transactions' => 'Satu-satunya transaksi yang terhubung ke akun ini akan dihapus juga. | Semua :count transaksi yang terhubung ke akun ini akan dihapus juga.', + 'also_delete_connections' => 'Satu-satunya transaksi yang terkait dengan jenis link ini akan kehilangan koneksi ini. Semua :count transaksi yang terkait dengan jenis link ini akan kehilangan koneksi mereka.', + 'also_delete_rules' => 'Aturan satu-satunya yang terhubung ke grup aturan ini akan dihapus juga. Aturan All :count yang terhubung ke grup aturan ini akan dihapus juga.', + 'also_delete_piggyBanks' => 'Satu-satunya piggy bank yang terhubung ke akun ini akan dihapus juga. Semua :count piggy bank yang terhubung ke akun ini akan dihapus juga.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Check for updates', 'email' => 'Alamat email', 'password' => 'Kata sandi', @@ -216,11 +219,23 @@ return [ 'country_code' => 'Kode negara', 'provider_code' => 'Bank atau penyedia data', - 'due_date' => 'Batas tanggal terakhir', - 'payment_date' => 'Tanggal pembayaran', - 'invoice_date' => 'Tanggal faktur', - 'internal_reference' => 'Referensi internal', - 'inward' => 'Deskripsi dalam', - 'outward' => 'Deskripsi luar', - 'rule_group_id' => 'Kelompok aturan', + 'due_date' => 'Batas tanggal terakhir', + 'payment_date' => 'Tanggal pembayaran', + 'invoice_date' => 'Tanggal faktur', + 'internal_reference' => 'Referensi internal', + 'inward' => 'Deskripsi dalam', + 'outward' => 'Deskripsi luar', + 'rule_group_id' => 'Kelompok aturan', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + 'weekend' => 'Weekend', + ]; diff --git a/resources/lang/id_ID/import.php b/resources/lang/id_ID/import.php index 93809b1375..e726d3e7a7 100644 --- a/resources/lang/id_ID/import.php +++ b/resources/lang/id_ID/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/id_ID/list.php b/resources/lang/id_ID/list.php index d64698938c..98106680de 100644 --- a/resources/lang/id_ID/list.php +++ b/resources/lang/id_ID/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/id_ID/validation.php b/resources/lang/id_ID/validation.php index 00be6153a5..3175395fc5 100644 --- a/resources/lang/id_ID/validation.php +++ b/resources/lang/id_ID/validation.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'iban' => 'Ini bukan IBAN yang valid.', - 'source_equals_destination' => 'The source account equals the destination account', + 'source_equals_destination' => 'The source account equals the destination account.', 'unique_account_number_for_user' => 'Sepertinya nomor rekening ini sudah digunakan.', 'unique_iban_for_user' => 'It looks like this IBAN is already in use.', 'deleted_user' => 'Kerena kendala keamanan, anda tidak bisa mendaftar menggunkan alamat email ini.', @@ -34,16 +34,22 @@ return [ 'file_attached' => 'File yang diupload dengan sukses ":name.', 'must_exist' => 'The ID in field :attribute does not exist in the database.', 'all_accounts_equal' => 'All accounts in this field must be equal.', - 'invalid_selection' => 'Your selection is invalid', + 'invalid_selection' => 'Your selection is invalid.', 'belongs_user' => 'This value is invalid for this field.', 'at_least_one_transaction' => 'Need at least one transaction.', + 'at_least_one_repetition' => 'Need at least one repetition.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'The content of this field is invalid without currency information.', 'equal_description' => 'Transaction description should not equal global description.', 'file_invalid_mime' => 'File ":name" adalah tipe ":mime" yang tidak diterima sebagai upload baru.', 'file_too_large' => 'File "; name" terlalu besar.', - 'belongs_to_user' => 'Nilai dari :attribute tidak diketahui', + 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => ':attribute harus diterima.', 'bic' => 'Ini bukan BIC yang valid.', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute harus lebih besar dari nol.', 'active_url' => ':attribute bukan URL yang valid.', 'after' => ':attribute harus tanggal setelah :date.', @@ -53,8 +59,8 @@ return [ 'array' => ':attribute harus berupa array.', 'unique_for_user' => 'Sudah ada entri dengan :attribute ini.', 'before' => ':attribute harus tanggal sebelum :date.', - 'unique_object_for_user' => 'Nama ini sudah digunakan', - 'unique_account_for_user' => 'Nama akun ini sudah digunakan', + 'unique_object_for_user' => 'This name is already in use.', + 'unique_account_for_user' => 'This account name is already in use.', 'between.numeric' => ':attribute harus antara :min dan :max.', 'between.file' => ':attribute harus antara :min dan :max kilobyte.', 'between.string' => ':attribute harus antara :min dan :max karakter.', @@ -85,6 +91,9 @@ return [ 'min.array' => ':attribute harus minimal item :min.', 'not_in' => ':attribute yang dipilih tidak valid.', 'numeric' => ':attribute harus angka.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'Format :attribute tidak valid.', 'required' => 'Bidang :attribute diperlukan.', 'required_if' => 'Bidang :attribute diperlukan ketika :other adalah :value.', @@ -109,9 +118,12 @@ return [ 'file' => ':attribute harus berupa file.', 'in_array' => 'Bidang :attribute tidak ada in :other.', 'present' => 'Bidang :attribute harus ada.', - 'amount_zero' => 'Jumlah total tidak boleh nol', + 'amount_zero' => 'The total amount cannot be zero.', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security.', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions.', + 'valid_recurrence_rep_moment' => 'Invalid repetition moment for this type of repetition.', + 'invalid_account_info' => 'Invalid account information.', 'attributes' => [ 'email' => 'email address', 'description' => 'description', diff --git a/resources/lang/it_IT/breadcrumbs.php b/resources/lang/it_IT/breadcrumbs.php index f631ee70e8..aa8ff8bb13 100644 --- a/resources/lang/it_IT/breadcrumbs.php +++ b/resources/lang/it_IT/breadcrumbs.php @@ -23,7 +23,7 @@ declare(strict_types=1); return [ - 'home' => 'Home', + 'home' => 'Pagina principale', 'edit_currency' => 'Modifica valuta ":name"', 'delete_currency' => 'Elimina valuta ":name"', 'newPiggyBank' => 'Crea un nuovo salvadanaio', @@ -39,7 +39,7 @@ return [ 'reports' => 'Resoconti', 'search_result' => 'Risultati di ricerca per ":query"', 'withdrawal_list' => 'Spese', - 'deposit_list' => 'Reddito, entrate e depositi', + 'deposit_list' => 'Redditi, entrate e depositi', 'transfer_list' => 'Trasferimenti', 'transfers_list' => 'Trasferimenti', 'reconciliation_list' => 'Riconciliazioni', diff --git a/resources/lang/it_IT/config.php b/resources/lang/it_IT/config.php index 667e347c13..8ba46bb310 100644 --- a/resources/lang/it_IT/config.php +++ b/resources/lang/it_IT/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'it', - 'locale' => 'it, Italiano, it_IT, it_IT.utf8, it_IT.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Settimana %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM AAAA', - 'month_and_day_js' => 'Do MMMM YYYY', - 'date_time_js' => 'Do MMMM YYYY, @ HH:mm:ss', - 'specific_day_js' => 'G MMMM AAAA', - 'week_in_year_js' => '[Week] s, AAAA', - 'year_js' => 'AAAA', - 'half_year_js' => 'T AAAA', + 'html_language' => 'it', + 'locale' => 'it, Italiano, it_IT, it_IT.utf8, it_IT.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Settimana %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM AAAA', + 'month_and_day_js' => 'Do MMMM YYYY', + 'date_time_js' => 'Do MMMM YYYY, @ HH:mm:ss', + 'specific_day_js' => 'G MMMM AAAA', + 'week_in_year_js' => '[Week] s, AAAA', + 'year_js' => 'AAAA', + 'half_year_js' => 'T AAAA', + 'dow_1' => 'Lunedì', + 'dow_2' => 'Martedì', + 'dow_3' => 'Mercoledì', + 'dow_4' => 'Giovedì', + 'dow_5' => 'Venerdì', + 'dow_6' => 'Sabato', + 'dow_7' => 'Domenica', ]; diff --git a/resources/lang/it_IT/demo.php b/resources/lang/it_IT/demo.php index 38c210d8fb..3a3f7fda89 100644 --- a/resources/lang/it_IT/demo.php +++ b/resources/lang/it_IT/demo.php @@ -25,8 +25,8 @@ declare(strict_types=1); return [ 'no_demo_text' => 'Spiacenti, non esiste un testo dimostrativo aggiuntivo per questa pagina.', 'see_help_icon' => 'Tuttavia, l\'icona i> in alto a destra potrebbe dirti di più.', - 'index' => 'Benvenuto in Firefly III! In questa pagina ottieni una rapida panoramica delle tue finanze. Per ulteriori informazioni, controlla Account e → Account asset e, naturalmente, i budget e i resoconti. O semplicemente dai un\'occhiata in giro e vedi dove finisci.', - 'accounts-index' => 'I conti degli asset sono i tuoi conti bancari personali. I conti spese sono gli account a cui si spendono soldi, come negozi e amici. I conti delle entrate sono conti da cui ricevi denaro, come il tuo lavoro, il governo o altre fonti di reddito. In questa pagina puoi modificarli o rimuoverli.', + 'index' => 'Benvenuto in Firefly III! In questa pagina ottieni una rapida panoramica delle tue finanze. Per ulteriori informazioni, controlla Conti → Conti attività e, naturalmente, le pagine Budget e Resoconti. O semplicemente dai un\'occhiata in giro e vedi dove finisci.', + 'accounts-index' => 'I conti attività sono i conti bancari personali. I conti spese sono i conti verso cui si spendono soldi, come negozi e amici. I conti entrate sono conti da cui ricevi denaro, come il tuo lavoro, il governo o altre fonti di reddito. In questa pagina puoi modificarli o rimuoverli.', 'budgets-index' => 'Questa pagina ti mostra una panoramica dei tuoi budget. La barra in alto mostra l\'importo disponibile per essere preventivato. Questo può essere personalizzato per qualsiasi periodo facendo clic sull\'importo a destra. La quantità che hai effettivamente speso è mostrata nella barra sottostante. Di seguito sono indicate le spese per budget e ciò che hai preventivato per loro.', 'reports-index-start' => 'Firefly III supporta un certo numero di tipi di resoconto. Leggi facendo clic sull\'icona in alto a destra.', 'reports-index-examples' => 'Assicurati di dare un occhiata a questi esempi: una panoramica finanziaria mensile , una panoramica finanziaria annuale e una panoramica del budget .', @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Queste spese, depositi e trasferimenti non sono particolarmente fantasiosi. Sono stati generati automaticamente.', 'piggy-banks-index' => 'Come puoi vedere, ci sono tre salvadanai. Utilizzare i pulsanti più e meno per influenzare la quantità di denaro in ogni salvadanaio. Fare clic sul nome del salvadanaio per visualizzare la gestione per ciascun salvadanaio.', 'import-index' => 'Qualsiasi file CSV può essere importato in Firefly III. Supporta anche l\'importazione di dati da bunq e Spectre. Altre banche e aggregatori finanziari saranno implementati in futuro. Tuttavia, come utente demo, puoi vedere solo il provider "fittizio" in azione. Genererà alcune transazioni casuali per mostrarti come funziona il processo.', + 'recurring-index' => 'Questa caratteristica è in corso di sviluppo e potrebbe non funzionare come ci si aspetta.', + 'recurring-create' => 'Questa caratteristica è in corso di sviluppo e potrebbe non funzionare come ci si aspetta.', ]; diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index 0532dcd126..e8ac7bec0d 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -33,8 +33,8 @@ return [ 'today' => 'oggi', 'customRange' => 'Range personalizzato', 'apply' => 'Applica', - 'select_date' => 'Seleziona data..', - 'cancel' => 'Cancella', + 'select_date' => 'Seleziona data...', + 'cancel' => 'Annulla', 'from' => 'Da', 'to' => 'A', 'showEverything' => 'Mostra tutto', @@ -47,7 +47,7 @@ return [ 'create_new_stuff' => 'Crea nuove cose', 'new_withdrawal' => 'Nuovo prelievo', 'create_new_transaction' => 'Crea nuova transazione', - 'go_to_asset_accounts' => 'Visualizza i tuoi movimenti', + 'go_to_asset_accounts' => 'Visualizza i tuoi conti attività', 'go_to_budgets' => 'Vai ai tuoi budget', 'go_to_categories' => 'Vai alle tue categorie', 'go_to_bills' => 'Vai alle tue bollette', @@ -57,9 +57,9 @@ return [ 'new_deposit' => 'Nuova entrata', 'new_transfer' => 'Nuovo trasferimento', 'new_transfers' => 'Nuovo trasferimento', - 'new_asset_account' => 'Nuova attività conto', - 'new_expense_account' => 'Nuova spesa conto', - 'new_revenue_account' => 'Nuova entrata conto', + 'new_asset_account' => 'Nuovo conto attività', + 'new_expense_account' => 'Nuovo conto spese', + 'new_revenue_account' => 'Nuovo conto entrate', 'new_budget' => 'Nuovo budget', 'new_bill' => 'Nuova bolletta', 'block_account_logout' => 'Sei stato disconnesso. Gli account bloccati non possono utilizzare questo sito. Ti sei registrato con un indirizzo email valido?', @@ -69,44 +69,44 @@ return [ 'flash_error' => 'Errore!', 'flash_info_multiple' => 'C\'è un messaggio | Ci sono :count messages', 'flash_error_multiple' => 'C\'è un errore | Ci sono :count errors', - 'net_worth' => 'Valore netto', + 'net_worth' => 'Patrimonio', 'route_has_no_help' => 'Non c\'è aiuto per questa rotta.', 'help_for_this_page' => 'Aiuto per questa pagina', 'no_help_could_be_found' => 'Non è stato trovato alcun testo di aiuto.', 'no_help_title' => 'Ci scusiamo, si è verificato un errore.', 'two_factor_welcome' => 'Ciao, :user!', - 'two_factor_enter_code' => 'Per continuare, inserisci il tuo codice di autenticazione a due fattori. La tua applicazione può generarlo per te.', - 'two_factor_code_here' => 'Inserisci codice qui', + 'two_factor_enter_code' => 'Per continuare inserisci il tuo codice di autenticazione a due fattori. La tua applicazione può generarlo per te.', + 'two_factor_code_here' => 'Inserisci qui il codice', 'two_factor_title' => 'Autenticazione a due fattori', - 'authenticate' => 'Autenticato', + 'authenticate' => 'Autenticati', 'two_factor_forgot_title' => 'Autenticazione a due fattori persa', 'two_factor_forgot' => 'Ho dimenticato la mia chiave a due fattori.', 'two_factor_lost_header' => 'Hai perso l\'autenticazione a due fattori?', 'two_factor_lost_intro' => 'Sfortunatamente, questo non è qualcosa che puoi resettare dall\'interfaccia web. Hai due scelte.', 'two_factor_lost_fix_self' => 'Se si esegue la propria istanza di Firefly III, controllare i log in storage/logs per istruzioni.', - 'two_factor_lost_fix_owner' => 'In caso contrario, invia un mail al proprietario del sito :site_owner e chiedi loro di ripristinare la possibilità di autenticarsi a due fattori.', + 'two_factor_lost_fix_owner' => 'In caso contrario, invia un mail al proprietario del sito, :site_owner, e chiedi loro di resettare l\'autenticazione a due fattori.', 'warning_much_data' => ':days di caricamento dei dati potrebbero richiedere un pò di tempo.', 'registered' => 'Ti sei registrato con successo!', - 'Default asset account' => 'Attività conto predefinito', - 'no_budget_pointer' => 'Sembra che tu non abbia ancora dei budget. Dovresti crearne alcuni nella pagina budget. I budget possono aiutarti a tenere traccia delle spese.', + 'Default asset account' => 'Conto attività predefinito', + 'no_budget_pointer' => 'Sembra che tu non abbia ancora dei budget. Dovresti crearne alcuni nella pagina dei budget. I budget possono aiutarti a tenere traccia delle spese.', 'Savings account' => 'Conti risparmio', 'Credit card' => 'Carta di Credito', - 'source_accounts' => 'Origine conto(i)', - 'destination_accounts' => 'Destinazione Conto(i)', + 'source_accounts' => 'Conti origine', + 'destination_accounts' => 'Conti destinazione', 'user_id_is' => 'Il tuo ID utente è :user', 'field_supports_markdown' => 'Questo campo supporta Markdown.', 'need_more_help' => 'Se hai bisogno di ulteriore aiuto con Firefly III, ti preghiamo di aprire un ticket su Github.', 'reenable_intro_text' => 'Puoi anche riattivare la guida introduttiva.', 'intro_boxes_after_refresh' => 'Le caselle di introduzione riappariranno quando si aggiorna la pagina.', 'show_all_no_filter' => 'Mostra tutte le transazioni senza raggrupparle per data.', - 'expenses_by_category' => 'Spese per Categorie', + 'expenses_by_category' => 'Spese per categorie', 'expenses_by_budget' => 'Spese per budget', - 'income_by_category' => 'Reddito per categoria', - 'expenses_by_asset_account' => 'Spese per attività conto', + 'income_by_category' => 'Entrate per categoria', + 'expenses_by_asset_account' => 'Spese per conto attività', 'expenses_by_expense_account' => 'Spese per conto spese', 'cannot_redirect_to_account' => 'Spiacente ma Firefly III non può reindirizzarti alla pagina corretta.', 'sum_of_expenses' => 'Totale spese', - 'sum_of_income' => 'Totale reddito', + 'sum_of_income' => 'Somma delle entrate', 'spent_in_specific_budget' => 'Speso nel budget ":budget"', 'sum_of_expenses_in_budget' => 'Spesa totale nel budget ":budget"', 'left_in_budget_limit' => 'Lasciato a spendere in base al budget', @@ -125,10 +125,10 @@ return [ 'multi_select_select_all' => 'Seleziona tutto', 'multi_select_n_selected' => 'selezionato', 'multi_select_all_selected' => 'Seleziona tutto', - 'multi_select_filter_placeholder' => 'Cerca..', + 'multi_select_filter_placeholder' => 'Cerca...', 'intro_next_label' => 'Avanti', 'intro_prev_label' => 'Indietro', - 'intro_skip_label' => 'Salta', + 'intro_skip_label' => 'Salta ogni', 'intro_done_label' => 'Fatto', 'between_dates_breadcrumb' => 'Fra :start e :end', 'all_journals_without_budget' => 'Tutte le transazioni senza un budget', @@ -147,19 +147,19 @@ return [ 'all_transfers' => 'Tutti i trasferimenti', 'title_transfers_between' => 'Tutti i trasferimenti fra :start e :end', 'all_transfer' => 'Tutti i trasferimenti', - 'all_journals_for_tag' => 'Tutte le transazioni per Etichetta ":tag"', + 'all_journals_for_tag' => 'Tutte le transazioni per l\'etichetta ":tag"', 'title_transfer_between' => 'Tutti i trasferimenti fra :start e :end', 'all_journals_for_category' => 'Turre le transazioni per categoria :name', 'all_journals_for_budget' => 'Tutte le transazione per budget :name', 'chart_all_journals_for_budget' => 'Grafico di tutte le transazioni per budget :name', 'journals_in_period_for_category' => 'Tutte le transazioni per Categoria :name fra :start e :end', - 'journals_in_period_for_tag' => 'Tutte le transazioni per Etichetta :tag fra :start e :end', + 'journals_in_period_for_tag' => 'Tutte le transazioni per l\'etichetta :tag fra :start e :end', 'not_available_demo_user' => 'La funzione a cui tenti di accedere non è disponibile per gli utenti demo.', - 'exchange_rate_instructions' => 'Conto attività "@name" accetta solo transazioni in @native_currency. Se invece desideri utilizzare @foreign_currency, assicurati che anche l\'importo in @native_currency sia noto:', - 'transfer_exchange_rate_instructions' => 'Il conto attività di origine "@source_name" accetta solo transazioni in @source_currency.Il conto attività di destinazione "@dest_name" accetta solo transazioni in @dest_currency. È necessario fornire l\'importo trasferito correttamente in entrambe le valute.', + 'exchange_rate_instructions' => 'Il conto attività "@name" accetta solo transazioni in @native_currency. Se invece desideri utilizzare @foreign_currency, assicurati che anche l\'importo in @native_currency sia noto:', + 'transfer_exchange_rate_instructions' => 'Il conto attività di origine "@source_name" accetta solo transazioni in @source_currency. Il conto attività di destinazione "@dest_name" accetta solo transazioni in @dest_currency. È necessario fornire l\'importo trasferito correttamente in entrambe le valute.', 'transaction_data' => 'Data Transazione', - 'invalid_server_configuration' => 'Configurazione Server non corretta', - 'invalid_locale_settings' => 'Firefly III non è in grado di formattare importi monetari perché al server mancano i pacchetti richiesti. Ci sono istruzioni su come eseguire questa operazione.', + 'invalid_server_configuration' => 'Configurazione del server non corretta', + 'invalid_locale_settings' => 'Firefly III non è in grado di formattare gli importi monetari perché al server mancano i pacchetti richiesti. Ci sono istruzioni su come eseguire questa operazione.', 'quickswitch' => 'Interruttore veloce', 'sign_in_to_start' => 'Accedi per iniziare la sessione', 'sign_in' => 'Accedi', @@ -189,14 +189,14 @@ return [ 'check_for_updates_permission' => 'Firefly III può controllare gli aggiornamenti, ma è necessario il tuo permesso per farlo. Vai alla amministrazione per indicare se desideri che questa funzione sia abilitata.', 'updates_ask_me_later' => 'Chiedimelo più tardi', 'updates_do_not_check' => 'Non controllare gli aggiornamenti', - 'updates_enable_check' => 'Abilita il controllo per gli aggiornamenti', + 'updates_enable_check' => 'Abilita il controllo degli aggiornamenti', 'admin_update_check_now_title' => 'Controlla gli aggiornamenti ora', - 'admin_update_check_now_explain' => 'Se si preme il pulsante, Firefly III vedrà se la versione corrente è la più recente.', + 'admin_update_check_now_explain' => 'Se si preme il pulsante, Firefly III controllerà se la versione corrente è la più recente.', 'check_for_updates_button' => 'Controlla ora!', 'update_new_version_alert' => 'È disponibile una nuova versione di Firefly III. Stai eseguendo v:your_version, l\'ultima versione è v:new_version che è stata rilasciata :date.', 'update_current_version_alert' => 'Stai eseguendo v:version, che è l\'ultima versione disponibile.', 'update_newer_version_alert' => 'Stai eseguendo v:your_version, che è più recente rispetto all\'ultima versione, v:new_version.', - 'update_check_error' => 'Si è verificato un errore durante il controllo degli aggiornamenti. Si prega di visualizzare i file di registro.', + 'update_check_error' => 'Si è verificato un errore durante il controllo degli aggiornamenti. Si prega di visualizzare i file di log.', // search 'search' => 'Cerca', @@ -206,15 +206,15 @@ return [ 'search_box' => 'Ricerca', 'search_box_intro' => 'Benvenuto nella funzione di ricerca di Firefly III. Inserisci la query di ricerca nella casella. Assicurati di controllare il file della guida perché la ricerca è abbastanza avanzata.', 'search_error' => 'Errore durante la ricerca', - 'search_searching' => 'Ricerca ...', + 'search_searching' => 'Ricerca in corso...', 'search_results' => 'Risultati ricerca', // repeat frequencies: - 'repeat_freq_yearly' => 'annuale', + 'repeat_freq_yearly' => 'annualmente', 'repeat_freq_half-year' => 'ogni sei mesi', - 'repeat_freq_quarterly' => 'trimestrale', - 'repeat_freq_monthly' => 'mensile', - 'repeat_freq_weekly' => 'settimanale', + 'repeat_freq_quarterly' => 'trimestralmente', + 'repeat_freq_monthly' => 'mensilmente', + 'repeat_freq_weekly' => 'settimanalmente', 'weekly' => 'settimanale', 'quarterly' => 'trimestrale', 'half-year' => 'ogni sei mesi', @@ -224,71 +224,71 @@ return [ 'import_and_export' => 'Importa e esporta', 'export_data' => 'Esporta dati', 'export_and_backup_data' => 'Esporta dati', - 'export_data_intro' => 'Utilizzare i dati esportati per passare a una nuova applicazione finanziaria. Si noti che questi file non sono intesi come backup. Non contengono abbastanza metadati per ripristinare completamente una nuova installazione di Firefly III. Se si desidera eseguire un backup dei dati, eseguire direttamente il backup del database.', - 'export_format' => 'Esporta formato', + 'export_data_intro' => 'Utilizza i dati esportati per passare a una nuova applicazione finanziaria. Si noti che questi file non sono intesi come backup. Non contengono abbastanza metadati per ripristinare completamente una nuova installazione di Firefly III. Se desideri eseguire un backup dei dati, esegui direttamente il backup del database.', + 'export_format' => 'Formato esportazione', 'export_format_csv' => 'Valori separati da virgola (file CSV)', 'export_format_mt940' => 'Formato compatibile MT940', 'include_old_uploads_help' => 'Firefly III non getta via i file CSV originali importati in passato. Puoi includerli nell\'esportazione.', 'do_export' => 'Esporta', 'export_status_never_started' => 'L\'esportazione non è ancora iniziata', - 'export_status_make_exporter' => 'Creare una cosa esportatore...', + 'export_status_make_exporter' => 'Creazione esportatore...', 'export_status_collecting_journals' => 'Raccolta delle tue transazioni...', 'export_status_collected_journals' => 'Raccolto le tue transazioni!', - 'export_status_converting_to_export_format' => 'Convertire le tue transazioni...', + 'export_status_converting_to_export_format' => 'Conversione delle tue transazioni...', 'export_status_converted_to_export_format' => 'Convertite le vostre transazioni!', - 'export_status_creating_journal_file' => 'Creare il file di esportazione...', - 'export_status_created_journal_file' => 'Creato il file di esportazione!', - 'export_status_collecting_attachments' => 'Raccogli tutti i tuoi allegati...', + 'export_status_creating_journal_file' => 'Creazione del file di esportazione...', + 'export_status_created_journal_file' => 'File di esportazione creato!', + 'export_status_collecting_attachments' => 'Raccolta di tutti i tuoi allegati...', 'export_status_collected_attachments' => 'Raccolti tutti i tuoi allegati!', - 'export_status_collecting_old_uploads' => 'Raccogli tutti i tuoi caricamenti precedenti...', + 'export_status_collecting_old_uploads' => 'Raccolta di tutti i tuoi caricamenti precedenti...', 'export_status_collected_old_uploads' => 'Raccolti tutti i tuoi caricamenti precedenti!', - 'export_status_creating_zip_file' => 'Creare un file zip...', + 'export_status_creating_zip_file' => 'Creazione di un file zip...', 'export_status_created_zip_file' => 'File zip creato!', - 'export_status_finished' => 'Esportazione terminata correttamente!!', + 'export_status_finished' => 'L\'esportazione è terminata con successo! Evviva!', 'export_data_please_wait' => 'Attendere prego...', // rules 'rules' => 'Regole', 'rule_name' => 'Nome regola', 'rule_triggers' => 'La regola si innesca quando', - 'rule_actions' => 'La regola lo farà', + 'rule_actions' => 'La regola eseguirà', 'new_rule' => 'Nuova regola', 'new_rule_group' => 'Nuovo gruppo di regole', - 'rule_priority_up' => 'Dare maggiore priorità alla regola', - 'rule_priority_down' => 'Dare alla regola meno priorità', + 'rule_priority_up' => 'Dai maggiore priorità alla regola', + 'rule_priority_down' => 'Dai minore priorità alla regola', 'make_new_rule_group' => 'Crea un nuovo gruppo di regole', 'store_new_rule_group' => 'Memorizza un nuovo gruppo di regole', 'created_new_rule_group' => 'Nuovo gruppo di regole ":title" memorizzate!', - 'updated_rule_group' => 'Gruppo di regole aggiornato con successo ":title".', + 'updated_rule_group' => 'Gruppo di regole ":title" aggiornato con successo.', 'edit_rule_group' => 'Modifica il gruppo di regole ":title"', 'delete_rule_group' => 'Elimina il gruppo di regole ":title"', 'deleted_rule_group' => 'Gruppo regole eliminato ":title"', 'update_rule_group' => 'Aggiorna gruppo di regole', 'no_rules_in_group' => 'Non ci sono regole in questo gruppo', - 'move_rule_group_up' => 'Sposta il gruppo di regole su', - 'move_rule_group_down' => 'Sposta il gruppo di regole in basso', - 'save_rules_by_moving' => 'Salva questa(e) regola(e) spostandola(e) in un altro gruppo di regole:', + 'move_rule_group_up' => 'Sposta sopra il gruppo di regole', + 'move_rule_group_down' => 'Sposta sotto il gruppo di regole', + 'save_rules_by_moving' => 'Salva queste regole spostandole in un altro gruppo di regole:', 'make_new_rule' => 'Crea una nuova regola nel gruppo di regole ":title"', 'rule_is_strict' => 'regola severa', 'rule_is_not_strict' => 'regola non severa', 'rule_help_stop_processing' => 'Quando selezioni questa casella, le regole successive in questo gruppo non verranno eseguite.', 'rule_help_strict' => 'Nelle regole severe TUTTI i trigger devono venire azionati perché l\'azione venga eseguita. Nelle regole non severe, è sufficiente UN QUALSIASI trigger perché l\'azione venga eseguita.', - 'rule_help_active' => 'Le regole non attive non spareranno mai.', + 'rule_help_active' => 'Le regole non attive non verranno mai eseguite.', 'stored_new_rule' => 'Nuova regola memorizzata con titolo ":title"', 'deleted_rule' => 'Regola eliminata con titolo ":title"', 'store_new_rule' => 'Salva nuova regola', 'updated_rule' => 'Regola aggiornata con titolo ":title"', 'default_rule_group_name' => 'Regole predefinite', - 'default_rule_group_description' => 'Tutte le tue regole non in un gruppo particolare.', + 'default_rule_group_description' => 'Tutte le tue regole che non sono in un gruppo specifico.', 'default_rule_name' => 'La tua prima regola predefinita', - 'default_rule_description' => 'Questa regola è un esempio. Puoi tranquillamente cancellarla.', + 'default_rule_description' => 'Questa regola è un esempio. Puoi tranquillamente eliminarla.', 'default_rule_trigger_description' => 'L\'uomo che vendette il mondo', 'default_rule_trigger_from_account' => 'David Bowie', 'default_rule_action_prepend' => 'Comprato il mondo da ', 'default_rule_action_set_category' => 'Grandi spese', 'trigger' => 'Trigger', 'trigger_value' => 'Attiva al valore', - 'stop_processing_other_triggers' => 'Interrompi l\'elaborazione di altri trigger', + 'stop_processing_other_triggers' => 'Smetti di elaborare altri trigger', 'add_rule_trigger' => 'Aggiungi un nuovo trigger', 'action' => 'Azione', 'action_value' => 'Valore azione', @@ -332,19 +332,19 @@ return [ 'rule_trigger_transaction_type' => 'La transazione è di tipo ":trigger_value"', 'rule_trigger_category_is_choice' => 'La categoria è...', 'rule_trigger_category_is' => 'La categoria è ":trigger_value"', - 'rule_trigger_amount_less_choice' => 'L\'importo è inferiore a..', + 'rule_trigger_amount_less_choice' => 'L\'importo è inferiore a...', 'rule_trigger_amount_less' => 'L\'importo è inferiore a :trigger_value', - 'rule_trigger_amount_exactly_choice' => 'L\'importo è..', + 'rule_trigger_amount_exactly_choice' => 'L\'importo è...', 'rule_trigger_amount_exactly' => 'L\'importo è :trigger_value', - 'rule_trigger_amount_more_choice' => 'L\'importo è più di..', + 'rule_trigger_amount_more_choice' => 'L\'importo è più di...', 'rule_trigger_amount_more' => 'L\'importo è più di :trigger_value', - 'rule_trigger_description_starts_choice' => 'La descrizione inizia con..', + 'rule_trigger_description_starts_choice' => 'La descrizione inizia con...', 'rule_trigger_description_starts' => 'La descrizione inizia con ":trigger_value"', - 'rule_trigger_description_ends_choice' => 'La descrizione termina con..', + 'rule_trigger_description_ends_choice' => 'La descrizione termina con...', 'rule_trigger_description_ends' => 'La descrizione termina con ":trigger_value"', - 'rule_trigger_description_contains_choice' => 'La descrizione contiene..', + 'rule_trigger_description_contains_choice' => 'La descrizione contiene...', 'rule_trigger_description_contains' => 'La descrizione contiene ":trigger_value"', - 'rule_trigger_description_is_choice' => 'La descrizione è..', + 'rule_trigger_description_is_choice' => 'La descrizione è...', 'rule_trigger_description_is' => 'La descrizione è ":trigger_value"', 'rule_trigger_budget_is_choice' => 'Il budget è...', 'rule_trigger_budget_is' => 'Il budget è ":trigger_value"', @@ -352,8 +352,8 @@ return [ 'rule_trigger_tag_is' => 'Una etichetta è ":trigger_value"', 'rule_trigger_currency_is_choice' => 'La valuta della transazione è...', 'rule_trigger_currency_is' => 'La valuta della transazione è ":trigger_value"', - 'rule_trigger_has_attachments_choice' => 'Ha almeno questo molti allegati', - 'rule_trigger_has_attachments' => 'Almeno :trigger_value allegato (i)', + 'rule_trigger_has_attachments_choice' => 'Ha almeno così tanti allegati', + 'rule_trigger_has_attachments' => 'Ha almeno :trigger_value allegati', 'rule_trigger_store_journal' => 'Quando viene creata una transazione', 'rule_trigger_update_journal' => 'Quando una transazione viene aggiornata', 'rule_trigger_has_no_category_choice' => 'Non ha categoria', @@ -364,53 +364,53 @@ return [ 'rule_trigger_has_no_budget' => 'La transazione non ha un budget', 'rule_trigger_has_any_budget_choice' => 'Ha un (qualsiasi) budget', 'rule_trigger_has_any_budget' => 'La transazione ha un (qualsiasi) budget', - 'rule_trigger_has_no_tag_choice' => 'Non ha etichetta(e)', - 'rule_trigger_has_no_tag' => 'La transazione non ha etichetta(e)', + 'rule_trigger_has_no_tag_choice' => 'Non ha etichette', + 'rule_trigger_has_no_tag' => 'La transazione non ha etichette', 'rule_trigger_has_any_tag_choice' => 'Ha una o più etichette (qualsiasi)', - 'rule_trigger_has_any_tag' => 'La transazione ha una (qualsiasi) o più etichette', - 'rule_trigger_any_notes_choice' => 'Ha (qualsiasi) note', - 'rule_trigger_any_notes' => 'La transazione ha (qualsiasi) note', + 'rule_trigger_has_any_tag' => 'La transazione ha una o più etichette (qualsiasi)', + 'rule_trigger_any_notes_choice' => 'Ha una (qualsiasi) nota', + 'rule_trigger_any_notes' => 'La transazione ha una (qualsiasi) nota', 'rule_trigger_no_notes_choice' => 'Non ha note', 'rule_trigger_no_notes' => 'La transazione non ha note', - 'rule_trigger_notes_are_choice' => 'Le note sono..', + 'rule_trigger_notes_are_choice' => 'Le note sono...', 'rule_trigger_notes_are' => 'Le note sono ":trigger_value"', - 'rule_trigger_notes_contain_choice' => 'Le note contengono..', + 'rule_trigger_notes_contain_choice' => 'Le note contengono...', 'rule_trigger_notes_contain' => 'Le note contengono ":trigger_value"', - 'rule_trigger_notes_start_choice' => 'Le note iniziano con..', + 'rule_trigger_notes_start_choice' => 'Le note iniziano con...', 'rule_trigger_notes_start' => 'Le note iniziano con ":trigger_value"', - 'rule_trigger_notes_end_choice' => 'Le note finiscono con..', + 'rule_trigger_notes_end_choice' => 'Le note finiscono con...', 'rule_trigger_notes_end' => 'Le note finiscono con ":trigger_value"', 'rule_action_set_category' => 'Imposta categoria a ":action_value"', - 'rule_action_clear_category' => 'Cancella categoria', + 'rule_action_clear_category' => 'Rimuovi dalla categoria', 'rule_action_set_budget' => 'Imposta il budget su ":action_value"', - 'rule_action_clear_budget' => 'Cancella budget', + 'rule_action_clear_budget' => 'Rimuovi dal budget', 'rule_action_add_tag' => 'Aggiungi etichetta ":action_value"', - 'rule_action_remove_tag' => 'Rimuovi etichetta ":action_value"', + 'rule_action_remove_tag' => 'Rimuovi l\'etichetta ":action_value"', 'rule_action_remove_all_tags' => 'Rimuovi tutte le etichette', 'rule_action_set_description' => 'Imposta la descrizione a ":action_value"', - 'rule_action_append_description' => 'Aggiungi descrizione con ":action_value"', - 'rule_action_prepend_description' => 'Anteporre descrizione con ":action_value"', - 'rule_action_set_category_choice' => 'Imposta categoria a..', - 'rule_action_clear_category_choice' => 'Cancella qualsiasi categoria', + 'rule_action_append_description' => 'Aggiungi alla descrizione ":action_value"', + 'rule_action_prepend_description' => 'Anteponi alla descrizione ":action_value"', + 'rule_action_set_category_choice' => 'Imposta come categoria...', + 'rule_action_clear_category_choice' => 'Rimuovi da tutte le categorie', 'rule_action_set_budget_choice' => 'Imposta il budget su...', - 'rule_action_clear_budget_choice' => 'Cancella qualsiasi budget', - 'rule_action_add_tag_choice' => 'Aggiungi etichetta..', - 'rule_action_remove_tag_choice' => 'Rimuovi etichetta..', + 'rule_action_clear_budget_choice' => 'Rimuovi da tutti i budget', + 'rule_action_add_tag_choice' => 'Aggiungi l\'etichetta...', + 'rule_action_remove_tag_choice' => 'Rimuovi l\'etichetta...', 'rule_action_remove_all_tags_choice' => 'Rimuovi tutte le etichette', - 'rule_action_set_description_choice' => 'Imposta la descrizione a..', - 'rule_action_append_description_choice' => 'Aggiungi descrizione con..', - 'rule_action_prepend_description_choice' => 'Anteporre descrizione..', - 'rule_action_set_source_account_choice' => 'Imposta l\'account di origine su...', - 'rule_action_set_source_account' => 'Imposta l\'account di origine su :action_value', - 'rule_action_set_destination_account_choice' => 'Imposta l\'account di destinazione su...', - 'rule_action_set_destination_account' => 'Imposta l\'account di destinazione su :action_value', - 'rule_action_append_notes_choice' => 'Aggiungi note con..', - 'rule_action_append_notes' => 'Aggiungi note con ":action_value"', - 'rule_action_prepend_notes_choice' => 'Anteponi note con..', - 'rule_action_prepend_notes' => 'Anteponi note con ":action_value"', - 'rule_action_clear_notes_choice' => 'Rimuovi eventuali note', - 'rule_action_clear_notes' => 'Rimuovi eventuali note', - 'rule_action_set_notes_choice' => 'Imposta le note su..', + 'rule_action_set_description_choice' => 'Imposta come descrizione...', + 'rule_action_append_description_choice' => 'Aggiungi alla descrizione...', + 'rule_action_prepend_description_choice' => 'Anteponi alla descrizione...', + 'rule_action_set_source_account_choice' => 'Imposta come conto di origine...', + 'rule_action_set_source_account' => 'Imposta come conto di origine :action_value', + 'rule_action_set_destination_account_choice' => 'Imposta come conto di destinazione...', + 'rule_action_set_destination_account' => 'Imposta come conto di destinazione :action_value', + 'rule_action_append_notes_choice' => 'Aggiungi alle note...', + 'rule_action_append_notes' => 'Aggiungi alle note ":action_value"', + 'rule_action_prepend_notes_choice' => 'Anteponi alle note...', + 'rule_action_prepend_notes' => 'Anteponi alle note ":action_value"', + 'rule_action_clear_notes_choice' => 'Rimuovi tutte le note', + 'rule_action_clear_notes' => 'Rimuovi tutte le note', + 'rule_action_set_notes_choice' => 'Imposta come note...', 'rule_action_link_to_bill_choice' => 'Collega ad una bolletta...', 'rule_action_link_to_bill' => 'Collegamento alla bolletta ":action_value"', 'rule_action_set_notes' => 'Imposta le note su ":action_value"', @@ -422,7 +422,7 @@ return [ 'rule_for_bill_title' => 'Regole generata automaticamente per la bolletta ":name"', 'rule_for_bill_description' => 'Questa regola è generata automaticamente per l\'abbinamento con la bolletta ":name".', 'create_rule_for_bill' => 'Crea una nuova regola per la bolletta ":name"', - 'create_rule_for_bill_txt' => 'Contratulazioni, hai appena creato una nuova bolletta chiamata ":name"! Firefly III può automagicamente abbinare le nuove uscite a questa bolletta. Per esempio, ogni volta che paghi l\'affitto la bolletta "affitto" verrà collegata a questa spesa. In questo modo Firefly III può visualizzare con accuratezza quali bollette sono in scadenza e quali no. Per far ciò è necessario creare una nuova regola. Firefly III ha inserito al posto tuo alcuni dettagli ragionevoli. Assicurati che questi siano corretti. Se questi valori sono corretti, Firefly III automaticamente collegherà il prelievo giusto alla bolletta giusta. Controlla che i trigger siano corretti e aggiungene altri se sono sbagliati.', + 'create_rule_for_bill_txt' => 'Congratulazioni, hai appena creato una nuova bolletta chiamata ":name"! Firefly III può automagicamente abbinare le nuove uscite a questa bolletta. Per esempio, ogni volta che paghi l\'affitto la bolletta "affitto" verrà collegata a questa spesa. In questo modo Firefly III può visualizzare con accuratezza quali bollette sono in scadenza e quali no. Per far ciò è necessario creare una nuova regola. Firefly III ha inserito al posto tuo alcuni dettagli ragionevoli. Assicurati che questi siano corretti. Se questi valori sono corretti, Firefly III automaticamente collegherà il prelievo giusto alla bolletta giusta. Controlla che i trigger siano corretti e aggiungine altri se sono sbagliati.', 'new_rule_for_bill_title' => 'Regola per la bolletta ":name"', 'new_rule_for_bill_description' => 'Questa regola contrassegna le transazioni per la bolletta ":name".', @@ -437,16 +437,16 @@ return [ 'sums_apply_to_range' => 'Tutte le somme si applicano all\'intervallo selezionato', 'mapbox_api_key' => 'Per utilizzare la mappa, ottieni una chiave API da Mapbox. Apri il tuo file .env e inserisci questo codice dopo MAPBOX_API_KEY=.', 'press_tag_location' => 'Fai clic destro o premi a lungo per impostare la posizione dell\'erichetta.', - 'clear_location' => 'Cancella posizione', + 'clear_location' => 'Rimuovi dalla posizione', // preferences - 'pref_home_screen_accounts' => 'Conti nella schermata iniziale', - 'pref_home_screen_accounts_help' => 'Quali conti dovrebbero essere visualizzati sulla home page?', - 'pref_view_range' => 'Visualizza gamma', + 'pref_home_screen_accounts' => 'Conti nella pagina iniziale', + 'pref_home_screen_accounts_help' => 'Quali conti vuoi che vengano visualizzati nella pagina principale?', + 'pref_view_range' => 'Intervallo di visualizzazione', 'pref_view_range_help' => 'Alcuni grafici sono raggruppati automaticamente in periodi. Che periodo preferiresti?', - 'pref_1D' => '1 Giorno', - 'pref_1W' => '1 Settimana', - 'pref_1M' => '1 Mese', + 'pref_1D' => 'Un giorno', + 'pref_1W' => 'Una settimana', + 'pref_1M' => 'Un mese', 'pref_3M' => 'Tre mesi (trimestre)', 'pref_6M' => 'Sei mesi', 'pref_1Y' => 'Un anno', @@ -454,10 +454,10 @@ return [ 'pref_languages_help' => 'Firefly III supporta diverse lingue.', 'pref_custom_fiscal_year' => 'Impostazioni anno fiscale', 'pref_custom_fiscal_year_label' => 'Abilita', - 'pref_custom_fiscal_year_help' => 'Nei paesi che utilizzano un anno finanziario diverso dal 1 ° gennaio al 31 dicembre, è possibile attivarlo e specificare i giorni di inizio / fine anno fiscale', + 'pref_custom_fiscal_year_help' => 'Nei paesi che utilizzano un anno finanziario diverso rispetto al dal 1 gennaio al 31 dicembre, è possibile attivarlo e specificare i giorni di inizio / fine dell\'anno fiscale', 'pref_fiscal_year_start_label' => 'Data di inizio anno fiscale', 'pref_two_factor_auth' => 'Verifica in due passaggi', - 'pref_two_factor_auth_help' => 'Quando abiliti la verifica in due passaggi (nota anche come autenticazione a due fattori), aggiungi un ulteriore livello di sicurezza al tuo account. Accedi con qualcosa che conosci (la tua password) e qualcosa che hai (un codice di verifica). I codici di verifica sono generati da un\'applicazione sul telefono, come Authy o Autenticatore Google.', + 'pref_two_factor_auth_help' => 'Quando abiliti la verifica a due passaggi (nota anche come autenticazione a due fattori), aggiungi un ulteriore livello di sicurezza al tuo account. Accedi con qualcosa che conosci (la tua password) e qualcosa che hai (un codice di verifica). I codici di verifica sono generati da un\'applicazione sul telefono, come Authy o Google Authenticator.', 'pref_enable_two_factor_auth' => 'Abilita la verifica in due passaggi', 'pref_two_factor_auth_disabled' => 'Codice di verifica in due passaggi rimosso e disabilitato', 'pref_two_factor_auth_remove_it' => 'Non dimenticare di rimuovere l\'account dalla tua app di autenticazione!', @@ -465,18 +465,19 @@ return [ 'pref_two_factor_auth_code_help' => 'Esegui la scansione del codice QR con un\'applicazione sul tuo telefono come Authy o Google Authenticator e inserisci il codice generato.', 'pref_two_factor_auth_reset_code' => 'Reimposta il codice di verifica', 'pref_two_factor_auth_disable_2fa' => 'Disattiva 2FA', + '2fa_use_secret_instead' => 'Se non puoi scansionare il codice QR puoi utilizzare il segreto: :secret.', 'pref_save_settings' => 'Salva le impostazioni', 'saved_preferences' => 'Preferenze salvate!', 'preferences_general' => 'Generale', - 'preferences_frontpage' => 'Schermata Home', + 'preferences_frontpage' => 'Pagina principale', 'preferences_security' => 'Sicurezza', 'preferences_layout' => 'Impaginazione', - 'pref_home_show_deposits' => 'Mostra i depositi sulla schermata principale', - 'pref_home_show_deposits_info' => 'La schermata iniziale mostra già i tuoi conti spese. Dovrebbe mostrare anche i tuoi account delle entrate?', + 'pref_home_show_deposits' => 'Mostra i depositi nella pagina principale', + 'pref_home_show_deposits_info' => 'La pagina iniziale mostra già i tuoi conti spese. Vuoi che mostri anche i tuoi conti entrate?', 'pref_home_do_show_deposits' => 'Sì, mostrali', 'successful_count' => 'di cui :count con successo', 'list_page_size_title' => 'Dimensioni pagina', - 'list_page_size_help' => 'Qualsiasi elenco di cose (conti, transazioni, ecc.) Mostra al massimo questo numero per pagina.', + 'list_page_size_help' => 'Ogni elenco (di conti, di transazioni, ecc) mostra al massimo questo numero di elementi per pagina.', 'list_page_size_label' => 'Dimensioni pagina', 'between_dates' => '(:start e :end)', 'pref_optional_fields_transaction' => 'Campi opzionali per le transazioni', @@ -484,12 +485,12 @@ return [ 'optional_tj_date_fields' => 'Campi data', 'optional_tj_business_fields' => 'Campi aziendali', 'optional_tj_attachment_fields' => 'Campi allegati', - 'pref_optional_tj_interest_date' => 'Data di interesse', - 'pref_optional_tj_book_date' => 'Data del libro', - 'pref_optional_tj_process_date' => 'Data di lavorazione', - 'pref_optional_tj_due_date' => 'Scadenza', - 'pref_optional_tj_payment_date' => 'Data di pagamento', - 'pref_optional_tj_invoice_date' => 'Data bolletta', + 'pref_optional_tj_interest_date' => 'Data interessi', + 'pref_optional_tj_book_date' => 'Data contabile', + 'pref_optional_tj_process_date' => 'Data elaborazione', + 'pref_optional_tj_due_date' => 'Data scadenza', + 'pref_optional_tj_payment_date' => 'Data pagamento', + 'pref_optional_tj_invoice_date' => 'Data fatturazione', 'pref_optional_tj_internal_reference' => 'Riferimento interno', 'pref_optional_tj_notes' => 'Note', 'pref_optional_tj_attachments' => 'Allegati', @@ -503,9 +504,9 @@ return [ 'delete_account' => 'Elimina account', 'current_password' => 'Password corrente', 'new_password' => 'Nuova password', - 'new_password_again' => 'Nuova password (ancora)', - 'delete_your_account' => 'cancella il tuo account', - 'delete_your_account_help' => 'L\'eliminazione del tuo account eliminerà anche conti, transazioni, qualsiasi cosa che potresti aver salvato in Firefly III. Sarà ELIMINATO.', + 'new_password_again' => 'Nuova password (ripeti)', + 'delete_your_account' => 'Elimina il tuo account', + 'delete_your_account_help' => 'L\'eliminazione del tuo account eliminerà anche conti, transazioni, qualsiasi cosa che potresti aver salvato in Firefly III. Sarà tutto PERDUTO.', 'delete_your_account_password' => 'Inserisci la tua password per continuare.', 'password' => 'Password', 'are_you_sure' => 'Sei sicuro? Non puoi annullare questo.', @@ -516,20 +517,20 @@ return [ 'invalid_password' => 'Password non valida!', 'what_is_pw_security' => 'Che cos\'è "verifica la sicurezza della password"?', 'secure_pw_title' => 'Come scegliere una password sicura', - 'secure_pw_history' => 'Nell\'agosto 2017, il noto ricercatore di sicurezza Troy Hunt ha pubblicato una lista di 306 milioni di password rubate. Queste password sono state rubate durante i break in aziende come LinkedIn, Adobe e NeoPets (e molte altre).', + 'secure_pw_history' => 'Nell\'agosto 2017 il noto ricercatore di sicurezza Troy Hunt ha pubblicato una lista di 306 milioni di password rubate. Queste password sono state rubate durante incursioni in aziende come LinkedIn, Adobe e NeoPets (e molte altre).', 'secure_pw_check_box' => 'Selezionando la casella, Firefly III invierà l\'hash SHA1 della tua password a il sito web di Troy Hunt per vedere se è presente nell\'elenco. Questo ti impedirà di usare password non sicure come raccomandato nell\'ultima Pubblicazione speciale NIST su questo argomento.', 'secure_pw_sha1' => 'Ma pensavo che SHA1 fosse rotto?', 'secure_pw_hash_speed' => 'Sì, ma non in questo contesto. Come puoi leggere su il sito web che spiega come hanno rotto SHA1 , ora è leggermente più facile trovare una "collisione": un\'altra stringa che risulta nello stesso hash SHA1. Ora ci vogliono solo 10.000 anni usando una macchina a GPU singola.', - 'secure_pw_hash_security' => 'Questa collisione non sarebbe uguale alla tua password, né sarebbe utile su (un sito come) Firefly III. Questa applicazione non utilizza SHA1 per la verifica della password. Quindi è sicuro controllare questa casella. La tua password viene sottoposta a hash e inviata tramite HTTPS.', + 'secure_pw_hash_security' => 'Questa collisione non sarebbe uguale alla tua password, né sarebbe utile su (un sito come) Firefly III. Questa applicazione non utilizza SHA1 per la verifica della password. Quindi è sicuro selezionare questa casella. La tua password viene sottoposta a hash e solo i primi cinque caratteri di questo hash vengono inviati tramite HTTPS.', 'secure_pw_should' => 'Devo controllare la scatola?', 'secure_pw_long_password' => 'Se hai appena generato una password lunga e monouso per Firefly III utilizzando un qualche tipo di generatore di password: no.', 'secure_pw_short' => 'Se hai appena inserito la password, usi sempre: Si prega di.', 'command_line_token' => 'Token della riga di comando', - 'explain_command_line_token' => 'È necessario questo token per eseguire le opzioni della riga di comando, come l\'importazione o l\'esportazione di dati. Senza di esso, tali comandi sensibili non funzioneranno. Non condividere il token della riga di comando. Nessuno ti chiederà questo segno, nemmeno io. Se temi di aver perso questo, o quando sei insicuro, rigenera questo token usando il pulsante.', + 'explain_command_line_token' => 'È necessario questo token per eseguire le opzioni dalla riga di comando, come l\'importazione o l\'esportazione di dati. Senza di esso tali comandi sensibili non funzioneranno. Non condividere il token della riga di comando. Nessuno ti chiederà questo token, nemmeno io. Se temi di averlo perso, o se sei paranoico, rigenera questo token usando il pulsante.', 'regenerate_command_line_token' => 'Rigenera il token della riga di comando', 'token_regenerated' => 'È stato generato un nuovo token della riga di comando', 'change_your_email' => 'Cambia il tuo indirizzo email', - 'email_verification' => 'Un messaggio di posta elettronica verrà inviato al tuo vecchio e nuovo indirizzo email. Per motivi di sicurezza, non potrai accedere fino a quando non avrai verificato il tuo nuovo indirizzo email. Se non si è sicuri che l\'installazione di Firefly III sia in grado di inviare e-mail, si prega di non utilizzare questa funzione. Se sei un amministratore, puoi verificarlo nella amministrazione.', + 'email_verification' => 'Un messaggio di posta elettronica verrà inviato al vecchio E al nuovo indirizzo email. Per motivi di sicurezza, non potrai accedere fino a quando non avrai verificato il tuo nuovo indirizzo email. Se non si è sicuri che l\'installazione di Firefly III sia in grado di inviare e-mail, si prega di non utilizzare questa funzione. Se sei un amministratore, puoi verificarlo in Amministrazione.', 'email_changed_logout' => 'Fino a quando non verifichi il tuo indirizzo email, non puoi effettuare il login.', 'login_with_new_email' => 'Ora puoi accedere con il tuo nuovo indirizzo email.', 'login_with_old_email' => 'Ora puoi accedere nuovamente con il tuo vecchio indirizzo email.', @@ -541,15 +542,15 @@ return [ 'update_attachment' => 'Aggiorna allegati', 'delete_attachment' => 'Elimina allegato ":name"', 'attachment_deleted' => 'Allegato eliminato ":name"', - 'attachment_updated' => 'Allegato aggiornato ":name"', + 'attachment_updated' => 'Allegato ":name" aggiornato', 'upload_max_file_size' => 'Dimensione massima del file: :size', 'list_all_attachments' => 'Lista di tutti gli allegati', // transaction index 'title_expenses' => 'Spese', 'title_withdrawal' => 'Spese', - 'title_revenue' => 'Reddito / Entrata', - 'title_deposit' => 'Reddito / Entrata', + 'title_revenue' => 'Entrate', + 'title_deposit' => 'Redditi / entrate', 'title_transfer' => 'Trasferimenti', 'title_transfers' => 'Trasferimenti', @@ -573,19 +574,19 @@ return [ 'convert_Transfer_to_deposit' => 'Converti questo trasferimento in un deposito', 'convert_Transfer_to_withdrawal' => 'Converti questo trasferimento in un prelievo', 'convert_please_set_revenue_source' => 'Si prega di scegliere il conto delle entrate da dove verranno i soldi.', - 'convert_please_set_asset_destination' => 'Si prega di scegliere il conto patrimoniale dove andranno i soldi.', + 'convert_please_set_asset_destination' => 'Scegli il conto attività in cui andranno i soldi.', 'convert_please_set_expense_destination' => 'Si prega di scegliere il conto spese dove andranno i soldi.', - 'convert_please_set_asset_source' => 'Si prega di scegliere il conto patrimoniale da dove verranno i soldi.', + 'convert_please_set_asset_source' => 'Scegli il conto attività da cui verranno i soldi.', 'convert_explanation_withdrawal_deposit' => 'Se converti questo prelievo in un deposito, l\'importo verrà :amount depositato in :sourceName anziché prelevato da esso.', 'convert_explanation_withdrawal_transfer' => 'Se converti questo prelievo in un trasferimento, l\'importo verrà :amount trasferito da :sourceName a un nuovo conto attività, invece di essere pagato a :destinationName.', 'convert_explanation_deposit_withdrawal' => 'Se converti questo deposito in un prelievo, l\'importo verrà rimosso :amount da :destinationName anziché aggiunto ad esso.', 'convert_explanation_deposit_transfer' => 'Se converti questo deposito in un trasferimento, :amount verrà trasferito da un conto attivo di tua scelta in :destinationName.', 'convert_explanation_transfer_withdrawal' => 'Se converti questo trasferimento in un prelievo, l\'importo :amount andrà da :sourceName a una nuova destinazione a titolo di spesa, anziché a :destinationName come trasferimento.', - 'convert_explanation_transfer_deposit' => 'Se converti questo trasferimento in un deposito, :amount verrà depositato nell\'account :destinationName anziché essere trasferito lì.', + 'convert_explanation_transfer_deposit' => 'Se converti questo trasferimento in un deposito, :amount verranno depositati nel conto :destinationName anziché esservi trasferiti.', 'converted_to_Withdrawal' => 'La transazione è stata convertita in un prelievo', 'converted_to_Deposit' => 'La transazione è stata convertita in un deposito', 'converted_to_Transfer' => 'La transazione è stata convertita in un trasferimento', - 'invalid_convert_selection' => 'Tl\'account che hai selezionato è già utilizzato in questa transazione o non esiste.', + 'invalid_convert_selection' => 'Il conto che hai selezionato è già utilizzato in questa transazione o non esiste.', 'source_or_dest_invalid' => 'Impossibile trovare i dettagli corretti della transazione. Non è possibile effettuare la conversione.', // create new stuff: @@ -594,7 +595,7 @@ return [ 'create_new_transfer' => 'Crea nuovo trasferimento', 'create_new_asset' => 'Crea un nuovo conto attività', 'create_new_expense' => 'Crea un nuovo conto di spesa', - 'create_new_revenue' => 'Crea un nuovo conto di entrate', + 'create_new_revenue' => 'Crea un nuovo conto entrate', 'create_new_piggy_bank' => 'Crea un nuovo salvadanaio', 'create_new_bill' => 'Crea una nuova bolletta', @@ -609,8 +610,8 @@ return [ 'updated_currency' => 'Valuta :name aggiornata', 'ask_site_owner' => 'Chiedi a :owner di aggiungere, rimuovere o modificare valute.', 'currencies_intro' => 'Firefly III supporta varie valute che è possibile impostare e abilitare qui.', - 'make_default_currency' => 'rendere predefinito', - 'default_currency' => 'predefinito', + 'make_default_currency' => 'rendi predefinita', + 'default_currency' => 'predefinita', // forms: 'mandatoryFields' => 'Campi obbligatori', @@ -631,11 +632,11 @@ return [ 'delete_budget' => 'Elimina budget ":name"', 'deleted_budget' => 'Budget eliminato ":name"', 'edit_budget' => 'Modifica budget ":name"', - 'updated_budget' => 'Budget aggiornato ":name"', - 'update_amount' => 'Importo aggiornato', + 'updated_budget' => 'Budget ":name" aggiornato', + 'update_amount' => 'Aggiorna importo', 'update_budget' => 'Budget aggiornato', - 'update_budget_amount_range' => 'Aggiornamento (previsto) importo disponibile tra :start and :end', - 'budget_period_navigator' => 'Navigatore periodico', + 'update_budget_amount_range' => 'Aggiorna l\'importo disponibile (previsto) tra il :start e il :end', + 'budget_period_navigator' => 'Navigatore dei periodi', 'info_on_available_amount' => 'Cosa ho a disposizione?', 'available_amount_indication' => 'Utilizza questi importi per ottenere un\'indicazione di quale potrebbe essere il tuo budget totale.', 'suggested' => 'Consigliato', @@ -645,11 +646,11 @@ return [ // bills: 'match_between_amounts' => 'La bolletta abbina le transazioni tra :low e :high.', 'bill_related_rules' => 'Regole relative a questa bolletta', - 'repeats' => 'Ripeti', + 'repeats' => 'Si ripete', 'connected_journals' => 'Transazioni connesse', 'auto_match_on' => 'Abbinato automaticamente da Firefly III', 'auto_match_off' => 'Non abbinato automaticamente a Firefly III', - 'next_expected_match' => 'Prossima partita prevista', + 'next_expected_match' => 'Prossimo abbinamento previsto', 'delete_bill' => 'Elimina bolletta ":name"', 'deleted_bill' => 'Bolletta eliminata ":name"', 'edit_bill' => 'Modifica bolletta ":name"', @@ -673,8 +674,8 @@ return [ // accounts: 'details_for_asset' => 'Dettagli per conto attività ":name"', 'details_for_expense' => 'Dettagli per conto spese ":name"', - 'details_for_revenue' => 'Dettagli per conto delle entrate ":name"', - 'details_for_cash' => 'Dettagli per conto in contanti ":name"', + 'details_for_revenue' => 'Dettagli per conto entrate ":name"', + 'details_for_cash' => 'Dettagli per il conto contanti ":name"', 'store_new_asset_account' => 'Salva nuovo conto attività', 'store_new_expense_account' => 'Salva il nuovo conto spese', 'store_new_revenue_account' => 'Salva il nuovo conto entrate', @@ -684,31 +685,31 @@ return [ 'delete_asset_account' => 'Elimina conto attività ":name"', 'delete_expense_account' => 'Elimina conto spese ":name"', 'delete_revenue_account' => 'Elimina conto entrate ":name"', - 'asset_deleted' => 'Conto attività eliminato correttamente ":name"', - 'expense_deleted' => 'Conto spese eliminato correttamente ":name"', - 'revenue_deleted' => 'Conto entrate eliminato correttamente ":name"', + 'asset_deleted' => 'Conto attività ":name" eliminato correttamente', + 'expense_deleted' => 'Conto spese ":name" eliminato correttamente', + 'revenue_deleted' => 'Conto entrate ":name" eliminato correttamente', 'update_asset_account' => 'Aggiorna conto attività', 'update_expense_account' => 'Aggiorna conto spese', 'update_revenue_account' => 'Aggiorna conto entrate', 'make_new_asset_account' => 'Crea un nuovo conto attività', 'make_new_expense_account' => 'Crea un nuovo conto spesa', 'make_new_revenue_account' => 'Crea nuovo conto entrate', - 'asset_accounts' => 'Conti patrimoniali', + 'asset_accounts' => 'Conti attività', 'expense_accounts' => 'Conti spese', 'revenue_accounts' => 'Conti entrate', - 'cash_accounts' => 'Conti cassa', - 'Cash account' => 'Conto cassa', + 'cash_accounts' => 'Conti contanti', + 'Cash account' => 'Conto contanti', 'reconcile_account' => 'Riconciliazione conto ":account"', 'delete_reconciliation' => 'Elimina riconciliazione', 'update_reconciliation' => 'Aggiorna riconciliazione', 'amount_cannot_be_zero' => 'L\'importo non può essere zero', 'end_of_reconcile_period' => 'Fine periodo riconciliazione: :period', 'start_of_reconcile_period' => 'Inizio periodo riconciliazione: :period', - 'start_balance' => 'Saldo inizio', - 'end_balance' => 'Saldo fine', + 'start_balance' => 'Saldo iniziale', + 'end_balance' => 'Saldo finale', 'update_balance_dates_instruction' => 'Abbina gli importi e le date sopra al tuo estratto conto e premi "Inizia la riconciliazione"', 'select_transactions_instruction' => 'Seleziona le transazioni che appaiono sul tuo estratto conto.', - 'select_range_and_balance' => 'Innanzitutto verifica l\'intervallo di date e i saldi. Quindi premere "Inizia riconciliazione"', + 'select_range_and_balance' => 'Innanzitutto verifica l\'intervallo di date e i saldi. Quindi premi "Inizia riconciliazione"', 'date_change_instruction' => 'Se cambi ora l\'intervallo di date, qualsiasi progresso andrà perso.', 'update_selection' => 'Aggiorna selezione', 'store_reconcile' => 'Memorizza la riconciliazione', @@ -718,9 +719,9 @@ return [ 'reconcile_options' => 'Opzioni di riconciliazione', 'reconcile_range' => 'Intervallo di riconciliazione', 'start_reconcile' => 'Avvia la riconciliazione', - 'cash' => 'cassa', + 'cash' => 'contanti', 'account_type' => 'Tipo conto', - 'save_transactions_by_moving' => 'Salva questa(e) transazione(i) spostandola(e) in un altro conto:', + 'save_transactions_by_moving' => 'Salva queste transazioni spostandole in un altro conto:', 'stored_new_account' => 'Nuovo conto ":name" stored!', 'updated_account' => 'Aggiorna conto ":name"', 'credit_card_options' => 'Opzioni Carta di Credito', @@ -734,19 +735,19 @@ return [ 'reconcile_has_more' => 'Il tuo conto Firefly III ha più denaro irispetto a quanto afferma la tua banca. Ci sono diverse opzioni Si prega di scegliere cosa fare. Quindi, premere "Conferma riconciliazione".', 'reconcile_has_less' => 'Il tuo conto Firefly III ha meno denaro rispetto a quanto afferma la tua banca. Ci sono diverse opzioni Si prega di scegliere cosa fare. Quindi, premi "Conferma riconciliazione".', 'reconcile_is_equal' => 'La tua contabilità di Firefly III e le tue coordinate bancarie corrispondono. Non c\'è niente da fare. Si prega di premere "Conferma riconciliazione" per confermare l\'inserimento.', - 'create_pos_reconcile_transaction' => 'Cancella le transazioni selezionate e crea una correzione aggiungendo :amount a questo conto attività.', - 'create_neg_reconcile_transaction' => 'Cancella le transazioni selezionate e crea una correzione rimuovendo :amount da questo conto attività.', - 'reconcile_do_nothing' => 'Cancella le transazioni selezionate, ma non correggere.', + 'create_pos_reconcile_transaction' => 'Concilia le transazioni selezionate e crea una correzione aggiungendo :amount a questo conto attività.', + 'create_neg_reconcile_transaction' => 'Concilia le transazioni selezionate e crea una correzione rimuovendo :amount da questo conto attività.', + 'reconcile_do_nothing' => 'Concilia le transazioni selezionate, ma non correggere.', 'reconcile_go_back' => 'Puoi sempre modificare o eliminare una correzione in un secondo momento.', 'must_be_asset_account' => 'È possibile riconciliare solo i conti attività', 'reconciliation_stored' => 'Riconciliazione memorizzata', 'reconcilliation_transaction_title' => 'Riconciliazione (:from a :to)', 'reconcile_this_account' => 'Riconcilia questo conto', 'confirm_reconciliation' => 'Conferma riconciliazione', - 'submitted_start_balance' => 'Bilancio di partenza presentato', + 'submitted_start_balance' => 'Saldo iniziale presentato', 'selected_transactions' => 'Transazioni selezionate (:count)', - 'already_cleared_transactions' => 'Transazioni selezionate (:count)', - 'submitted_end_balance' => 'Bilancio finale inviato', + 'already_cleared_transactions' => 'Transazioni già conciliate (:count)', + 'submitted_end_balance' => 'Saldo finale inviato', 'initial_balance_description' => 'Saldo iniziale per ":account"', // categories: @@ -766,45 +767,45 @@ return [ 'without_category_between' => 'Senza categoria tra :start e :end', // transactions: - 'update_withdrawal' => 'Aggiorna spesa', + 'update_withdrawal' => 'Aggiorna prelievo', 'update_deposit' => 'Aggiorna entrata', 'update_transfer' => 'Aggiorna trasferimento', - 'updated_withdrawal' => 'Spesa aggiornata ":description"', + 'updated_withdrawal' => 'Prelievo ":description" aggiornato', 'updated_deposit' => 'Entrata aggiornata ":description"', 'updated_transfer' => 'Trasferimento ":description" aggiornato', - 'delete_withdrawal' => 'Elimina spesa ":description"', + 'delete_withdrawal' => 'Elimina prelievo ":description"', 'delete_deposit' => 'Elimina entrata ":description"', 'delete_transfer' => 'Elimina trasferimento ":description"', - 'deleted_withdrawal' => 'Spesa eliminata correttamente ":description"', - 'deleted_deposit' => 'Entrata eliminata correttamente ":description"', + 'deleted_withdrawal' => 'Prelievo ":description" eliminato correttamente', + 'deleted_deposit' => 'Entrata ":description" eliminata correttamente', 'deleted_transfer' => 'Trasferimento ":description" eliminato correttamente', - 'stored_journal' => 'Nuova transazione creata correttamente ":description"', + 'stored_journal' => 'Nuova transazione ":description" creata correttamente', 'select_transactions' => 'Seleziona transazioni', 'rule_group_select_transactions' => 'Applica ":title" a transazioni', 'rule_select_transactions' => 'Applica ":title" a transazioni', 'stop_selection' => 'Smetti di selezionare le transazioni', 'reconcile_selected' => 'Riconcilia', - 'mass_delete_journals' => 'Elimina un numero di transazioni', - 'mass_edit_journals' => 'Modifica un numero di transazioni', - 'mass_bulk_journals' => 'Modifica in blocco un numero di transazioni', - 'mass_bulk_journals_explain' => 'Se non si desidera modificare le transazioni una alla volta utilizzando la funzione di modifica di massa, è possibile aggiornarle in una volta sola. Basta selezionare le categorie, le etichette o i budget preferiti nei campi sottostanti e tutte le transazioni nella tabella verranno aggiornate.', + 'mass_delete_journals' => 'Elimina un certo numero di transazioni', + 'mass_edit_journals' => 'Modifica un certo numero di transazioni', + 'mass_bulk_journals' => 'Modifica in blocco un certo numero di transazioni', + 'mass_bulk_journals_explain' => 'Se non si desidera modificare le transazioni una alla volta utilizzando la funzione di modifica in blocco, è possibile aggiornarle in una volta sola. Basta selezionare le categorie, le etichette o i budget preferiti nei campi sottostanti e tutte le transazioni nella tabella verranno aggiornate.', 'bulk_set_new_values' => 'Usa gli inserimenti qui sotto per impostare nuovi valori. Se li lasci vuoti, saranno resi vuoti per tutti. Inoltre, si noti che solo i prelievi avranno un budget.', 'no_bulk_category' => 'Non aggiornare la categoria', 'no_bulk_budget' => 'Non aggiornare il budget', - 'no_bulk_tags' => 'Non aggiornare la(e) etichetta(e)', - 'bulk_edit' => 'Modifica collettiva', - 'cannot_edit_other_fields' => 'Non puoi modificare in massa altri campi oltre a quelli qui, perché non c\'è spazio per mostrarli. Segui il link e modificali uno per uno, se è necessario modificare questi campi.', + 'no_bulk_tags' => 'Non aggiornare le etichette', + 'bulk_edit' => 'Modifica in blocco', + 'cannot_edit_other_fields' => 'Non puoi modificare in blocco altri campi oltre a quelli presenti perché non c\'è spazio per mostrarli. Segui il link e modificali uno per uno se è necessario modificare questi campi.', 'no_budget' => 'nessuno', - 'no_budget_squared' => '(nessun bilancio)', + 'no_budget_squared' => '(nessun budget)', 'perm-delete-many' => 'Eliminare molti oggetti in una volta sola può essere molto pericoloso. Per favore sii cauto.', - 'mass_deleted_transactions_success' => 'Transazione(i) eliminata(e) :amount.', - 'mass_edited_transactions_success' => 'Transazione(i) aggiornata(e) :amount.', + 'mass_deleted_transactions_success' => ':amount transazioni eliminate .', + 'mass_edited_transactions_success' => ':amount transazioni aggiornate', 'opt_group_no_account_type' => '(nessun tipo di conto)', 'opt_group_defaultAsset' => 'Conto attività predefinito', 'opt_group_savingAsset' => 'Conti risparmio', - 'opt_group_sharedAsset' => 'Conti risorse condivise', + 'opt_group_sharedAsset' => 'Conti attività condivisi', 'opt_group_ccAsset' => 'Carte di credito', - 'opt_group_cashWalletAsset' => 'Contanti', + 'opt_group_cashWalletAsset' => 'Portafogli', 'notes' => 'Note', 'unknown_journal_error' => 'Impossibile memorizzare la transazione. Controllare i file di log.', @@ -812,19 +813,19 @@ return [ 'welcome' => 'Benvenuto in Firefly III!', 'submit' => 'Invia', 'getting_started' => 'Inizia', - 'to_get_started' => 'È bello vedere che hai installato Firefly III con successo. Per iniziare con questo strumento, inserisci il nome della tua banca e il saldo del tuo conto corrente principale. Non preoccuparti se hai più account. È possibile aggiungere quelli più tardi. Firefly III ha bisogno di qualcosa per iniziare.', - 'savings_balance_text' => 'Firefly III creerà automaticamente un conto di risparmio per te. Per impostazione predefinita, non ci saranno soldi nel tuo conto di risparmio, ma se comunichi a Firefly III il saldo verrà archiviato come tale.', + 'to_get_started' => 'È bello vedere che hai installato Firefly III con successo. Per iniziare con questo strumento, inserisci il nome della tua banca e il saldo del tuo conto corrente principale. Non preoccuparti se hai più conti. È possibile aggiungere quelli più tardi. Firefly III ha bisogno di qualcosa per iniziare.', + 'savings_balance_text' => 'Firefly III creerà automaticamente un conto di risparmio per te. Per impostazione predefinita, non ci saranno soldi nel tuo conto di risparmio, ma se comunichi a Firefly III il saldo verrà salvato come tale.', 'finish_up_new_user' => 'Questo è tutto! Puoi continuare premendo Invia. Verrai indirizzato all\'indice di Firefly III.', 'stored_new_accounts_new_user' => 'I tuoi nuovi conti sono stati salvati.', 'set_preferred_language' => 'Se preferisci usare Firefly III in un\'altra lingua, impostala qui.', 'language' => 'Lingua', 'new_savings_account' => 'Conto di risparmio :bank_name', - 'cash_wallet' => 'Contanti', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'cash_wallet' => 'Portafoglio', + 'currency_not_present' => 'Se la valuta che usi normalmente non è elencata, non preoccuparti. Puoi creare le tue valute in Opzioni > Valute.', // home page: 'yourAccounts' => 'I tuoi conti', - 'budgetsAndSpending' => 'Bilanci e Spese', + 'budgetsAndSpending' => 'Budget e spese', 'savings' => 'Risparmi', 'newWithdrawal' => 'Nuova uscita', 'newDeposit' => 'Nuovo deposito', @@ -845,26 +846,26 @@ return [ 'currencies' => 'Valute', 'accounts' => 'Conti', 'Asset account' => 'Conto attività', - 'Default account' => 'Conto predefinito', + 'Default account' => 'Conto attività', 'Expense account' => 'Conto spese', 'Revenue account' => 'Conto entrate', 'Initial balance account' => 'Saldo iniziale conto', - 'budgets' => 'Bilanci', + 'budgets' => 'Budget', 'tags' => 'Etichette', 'reports' => 'Resoconti', 'transactions' => 'Transazioni', 'expenses' => 'Spese', - 'income' => 'Reddito / Entrata', + 'income' => 'Redditi / entrate', 'transfers' => 'Trasferimenti', - 'moneyManagement' => 'Gestione danaro', + 'moneyManagement' => 'Gestione denaro', 'piggyBanks' => 'Salvadanai', 'bills' => 'Bollette', - 'withdrawal' => 'Uscite', + 'withdrawal' => 'Prelievo', 'opening_balance' => 'Saldo di apertura', 'deposit' => 'Entrata', 'account' => 'Conto', 'transfer' => 'Trasferimento', - 'Withdrawal' => 'Spesa', + 'Withdrawal' => 'Prelievo', 'Deposit' => 'Entrata', 'Transfer' => 'Trasferimento', 'bill' => 'Bolletta', @@ -896,18 +897,17 @@ return [ 'reports_can_bookmark' => 'Ricorda che i resoconti possono essere aggiunti ai segnalibri.', 'incomeVsExpenses' => 'Entrate verso spese', 'accountBalances' => 'Saldo dei conti', - 'balanceStart' => 'Saldo inizio periodo', - 'balanceEnd' => 'Saldo fine del periodo', + 'balanceStart' => 'Saldo all\'inizio del periodo', + 'balanceEnd' => 'Saldo alla fine del periodo', 'splitByAccount' => 'Dividi per conto', 'coveredWithTags' => 'Coperto con etichetta', - 'leftUnbalanced' => 'Sbilanciato a sinistra', - 'leftInBudget' => 'Rimasto nel budget', + 'leftInBudget' => 'Rimanenza nel budget', 'sumOfSums' => 'Somma dei conti', 'noCategory' => '(nessuna categoria)', - 'notCharged' => 'Non caricato (ancora)', + 'notCharged' => 'Non (ancora) addebitato', 'inactive' => 'Disattivo', 'active' => 'Attivo', - 'difference' => 'Differenze', + 'difference' => 'Differenza', 'money_flowing_in' => 'Entrate', 'money_flowing_out' => 'Uscite', 'topX' => 'Superiore :number', @@ -915,13 +915,13 @@ return [ 'show_only_top' => 'Mostra solo in alto :number', 'report_type' => 'Tipo resoconto', 'report_type_default' => 'Resoconto finanziario predefinito', - 'report_type_audit' => 'Panoramica cronologica delle transazioni (controllo)', + 'report_type_audit' => 'Panoramica cronologica delle transazioni (verifica)', 'report_type_category' => 'Resoconto categoria', 'report_type_budget' => 'Resoconto budget', 'report_type_tag' => 'Resoconto etichetta', 'report_type_account' => 'Resoconto conto spese/entrate', 'more_info_help' => 'Ulteriori informazioni su questi tipi di resoconti sono disponibili nelle pagine della guida. Premi l\'icona (?) nell\'angolo in alto a destra.', - 'report_included_accounts' => 'Conti inclusi', + 'report_included_accounts' => 'Inclusi i conti', 'report_date_range' => 'Date intervallo', 'report_preset_ranges' => 'Intervalli preimpostati', 'shared' => 'Condiviso', @@ -930,37 +930,37 @@ return [ 'expense_entry' => 'Spese per il conto ":name" tra :start e :end', 'category_entry' => 'Spese nella categoria ":name" tra :start e :end', 'budget_spent_amount' => 'Spese nel budget ":budget" tra :start e :end', - 'balance_amount' => 'Spese nel budget ":budget" pagate dal conto ":account" tra :start e :end', - 'no_audit_activity' => 'Nessuna attività è stata registrata nel conto :account_name tra :start e :end.', - 'audit_end_balance' => 'Il saldo del conto di :account_name alla fine di :end era: :balance', + 'balance_amount' => 'Spese nel budget ":budget" pagate dal conto ":account" tra il :start e il :end', + 'no_audit_activity' => 'Nessuna attività è stata registrata nel conto :account_name tra il :start e il :end.', + 'audit_end_balance' => 'Il saldo del conto :account_name alla fine di :end era: :balance', 'reports_extra_options' => 'Opzioni extra', 'report_has_no_extra_options' => 'Questo resoconto non ha opzioni extra', 'reports_submit' => 'Mostra resoconto', 'end_after_start_date' => 'La data della fine del resoconto deve essere successiva alla data di inizio.', - 'select_category' => 'Seleziona la(e) categoria (e)', + 'select_category' => 'Seleziona le categorie', 'select_budget' => 'Seleziona i budget.', - 'select_tag' => 'Seleziona etichetta(e).', - 'income_per_category' => 'Reddito per categoria', - 'expense_per_category' => 'Spese per categoria', + 'select_tag' => 'Seleziona etichette.', + 'income_per_category' => 'Entrate per categoria', + 'expense_per_category' => 'Spesa per categoria', 'expense_per_budget' => 'Spese per budget', - 'income_per_account' => 'Reddito per conto', + 'income_per_account' => 'Entrate per conto', 'expense_per_account' => 'Spese per conto', 'expense_per_tag' => 'Spese per etichetta', - 'income_per_tag' => 'Reddito per etichetta', - 'include_expense_not_in_budget' => 'Spese non incluse nel(nei) bilancio(i) selezionato(i)', - 'include_expense_not_in_account' => 'Spese non incluse nel(nei) conto(i) selezionato(i)', - 'include_expense_not_in_category' => 'Spese incluse / non incluse nella(e) categoria(e) selezionata(e)', - 'include_income_not_in_category' => 'Entrate non incluse nella(e) categoria(e) selezionata(e)', - 'include_income_not_in_account' => 'Entrate non incluse nel(i) conto(i) selezionato(i)', - 'include_income_not_in_tags' => 'Entrate incluse / non incluse nella(e) etichetta(e) selezionata(e)', - 'include_expense_not_in_tags' => 'Spese incluse / non incluse nella(e) etichetta(e) selezionata(e)', + 'income_per_tag' => 'Entrate per etichetta', + 'include_expense_not_in_budget' => 'Incluse le spese non appartenenti ai budget selezionati', + 'include_expense_not_in_account' => 'Incluse le spese non appartenenti ai conti selezionati', + 'include_expense_not_in_category' => 'Incluse le spese non appartenenti alle categorie selezionate', + 'include_income_not_in_category' => 'Incluse le entrate non appartenenti alle categorie selezionate', + 'include_income_not_in_account' => 'Incluse le entrate non appartenenti ai conti selezionati', + 'include_income_not_in_tags' => 'Incluse le entrate non appartenenti alle etichette selezionate', + 'include_expense_not_in_tags' => 'Incluse le spese non appartenenti alle etichette selezionate', 'everything_else' => 'Tutto il resto', 'income_and_expenses' => 'Entrate e spese', - 'spent_average' => 'Speso (media)', - 'income_average' => 'Reddito (media)', + 'spent_average' => 'Spesi (media)', + 'income_average' => 'Entrate (media)', 'transaction_count' => 'Conteggio transazioni', 'average_spending_per_account' => 'Spesa media per conto', - 'average_income_per_account' => 'Reddito medio per conto', + 'average_income_per_account' => 'Media entrate per conto', 'total' => 'Totale', 'description' => 'Descrizione', 'sum_of_period' => 'Somma del periodo', @@ -975,7 +975,7 @@ return [ 'in_out_accounts' => 'Guadagnati e spesi per combinazione', 'in_out_per_category' => 'Guadagnati e spesi per categoria', 'out_per_budget' => 'Speso per budget', - 'select_expense_revenue' => 'Seleziona conto spese / entrate', + 'select_expense_revenue' => 'Seleziona conto spese/entrate', // charts: 'chart' => 'Grafico', @@ -992,7 +992,7 @@ return [ 'journal-amount' => 'Entrata fattura corrente', 'name' => 'Nome', 'date' => 'Data', - 'paid' => 'Pagato', + 'paid' => 'Pagati', 'unpaid' => 'Da pagare', 'day' => 'Giorno', 'budgeted' => 'Preventivato', @@ -1018,7 +1018,7 @@ return [ 'remove_money_from_piggy_title' => 'Rimuovi i soldi dal salvadanaio ":name"', 'add' => 'Aggiungi', 'no_money_for_piggy' => 'non hai soldi da mettere in questo salvadanaio.', - 'suggested_savings_per_month' => 'Suggested per month', + 'suggested_savings_per_month' => 'Suggeriti per mese', 'remove' => 'Rimuovi', 'max_amount_add' => 'L\'importo massimo che puoi aggiungere è', @@ -1043,16 +1043,16 @@ return [ // tags 'delete_tag' => 'Elimina etichetta ":tag"', - 'deleted_tag' => 'Etichetta eliminata ":tag"', + 'deleted_tag' => 'Etichetta ":tag" eliminata', 'new_tag' => 'Crea nuova etichetta', 'edit_tag' => 'Modifica etichetta ":tag"', - 'updated_tag' => 'Etichetta aggiornata ":tag"', + 'updated_tag' => 'Etichetta ":tag" aggiornata', 'created_tag' => 'Etichetta ":tag" creata correttamente', 'transaction_journal_information' => 'Informazione Transazione', 'transaction_journal_meta' => 'Meta informazioni', 'total_amount' => 'Importo totale', - 'number_of_decimals' => 'Numero decimali', + 'number_of_decimals' => 'Cifre decimali', // administration 'administration' => 'Amministrazione', @@ -1060,7 +1060,7 @@ return [ 'list_all_users' => 'Tutti gli utenti', 'all_users' => 'Tutti gli utenti', 'instance_configuration' => 'Configurazione', - 'firefly_instance_configuration' => 'Opzioni Configurazione di Firefly III', + 'firefly_instance_configuration' => 'Opzioni di configurazione di Firefly III', 'setting_single_user_mode' => 'Modo utente singolo', 'setting_single_user_mode_explain' => 'Per impostazione predefinita, Firefly III accetta solo una (1) registrazione: tu. Questa è una misura di sicurezza, che impedisce ad altri di usare la tua istanza a meno che tu non le autorizzi. Le future registrazioni sono bloccate. Bene! quando deselezioni questa casella, gli altri possono usare la tua istanza, supponendo che possano raggiungerla (quando è connessa a Internet).', 'store_configuration' => 'Salva configurazione', @@ -1072,19 +1072,19 @@ return [ 'total_size' => 'dimensione totale', 'budget_or_budgets' => 'budget', 'budgets_with_limits' => 'budget con importo configurato', - 'nr_of_rules_in_total_groups' => 'regola(e) :count_rules in gruppo(i) :count_groups', - 'tag_or_tags' => 'etichetta(e)', + 'nr_of_rules_in_total_groups' => ':count_rules regole in :count_groups gruppi di regole', + 'tag_or_tags' => 'etichette', 'configuration_updated' => 'La configurazione è stata aggiornata', 'setting_is_demo_site' => 'Sito Demo', 'setting_is_demo_site_explain' => 'Se si seleziona questa casella, questa installazione si comporterà come se fosse il sito demo, che può avere strani effetti collaterali.', - 'block_code_bounced' => 'Il(i) messaggio(i) email rimbalzati', + 'block_code_bounced' => 'Messaggi email respinti', 'block_code_expired' => 'Conto demo scaduto', 'no_block_code' => 'Nessun motivo per bloccare o non bloccare un utente', 'block_code_email_changed' => 'L\'utente non ha ancora confermato il nuovo indirizzo emails', 'admin_update_email' => 'Contrariamente alla pagina del profilo, l\'utente NON riceverà alcuna notifica al proprio indirizzo email!', 'update_user' => 'Aggiorna utente', 'updated_user' => 'I dati dell\'utente sono stati modificati.', - 'delete_user' => 'Elimia utente :email', + 'delete_user' => 'Elimina utente :email', 'user_deleted' => 'L\'utente è stato eliminato', 'send_test_email' => 'Invia un messaggio di posta elettronica di prova', 'send_test_email_text' => 'Per vedere se la tua installazione è in grado di inviare e-mail, ti preghiamo di premere questo pulsante. Qui non vedrai un errore (se presente), i file di log rifletteranno eventuali errori. Puoi premere questo pulsante tutte le volte che vuoi. Non c\'è controllo dello spam. Il messaggio verrà inviato a :email e dovrebbe arrivare a breve.', @@ -1092,12 +1092,12 @@ return [ 'send_test_triggered' => 'Il test è stato attivato. Controlla la tua casella di posta e i file di log.', // links - 'journal_link_configuration' => 'Configurazione dei collegamenti di transazione', + 'journal_link_configuration' => 'Configurazione dei collegamenti di una transazione', 'create_new_link_type' => 'Crea un nuovo tipo di collegamento', 'store_new_link_type' => 'Memorizza nuovo tipo di collegamento', 'update_link_type' => 'Aggiorna il tipo di collegamento', 'edit_link_type' => 'Modifica il tipo di collegamento ":name"', - 'updated_link_type' => 'Tipo di collegamento aggiornato ":name"', + 'updated_link_type' => 'Tipo del collegamento ":name" aggiornato', 'delete_link_type' => 'Elimina il tipo di collegamento ":name"', 'deleted_link_type' => 'Tipo di collegamento eliminato ":name"', 'stored_new_link_type' => 'Memorizza nuovo tipo di collegamento ":name"', @@ -1105,7 +1105,7 @@ return [ 'link_type_help_name' => 'Ie. "Duplicati"', 'link_type_help_inward' => 'Ie. "duplicati"', 'link_type_help_outward' => 'Ie. "è duplicato da"', - 'save_connections_by_moving' => 'Salvare il collegamento tra questa(e) transazione(i) spostandola(e) su un altro tipo di collegamento:', + 'save_connections_by_moving' => 'Salva il collegamento tra queste transazioni spostandole su un altro tipo di collegamento:', 'do_not_save_connection' => '(non salvare la connessione)', 'link_transaction' => 'Collega transazione', 'link_to_other_transaction' => 'Collega questa transazione a un\'altra transazione', @@ -1119,7 +1119,7 @@ return [ 'journals_error_linked' => 'Queste transazioni sono già collegate.', 'journals_link_to_self' => 'Non puoi collegare una transazione con se stessa', 'journal_links' => 'Collegamenti di transazione', - 'this_withdrawal' => 'Questa spesa', + 'this_withdrawal' => 'Questo prelievo', 'this_deposit' => 'Questa entrata', 'this_transfer' => 'Questo trasferimento', 'overview_for_link' => 'Panoramica per tipo di collegamento ":name"', @@ -1131,11 +1131,13 @@ return [ // link translations: 'relates to_inward' => 'inerente a', - 'is (partially) refunded by_inward' => 'è (parzialmente) rimborsato da', - 'is (partially) paid for by_inward' => 'è (parzialmente) pagato da', - 'is (partially) reimbursed by_inward' => 'è (parzialmente) rimborsato da', + 'is (partially) refunded by_inward' => 'è (parzialmente) rimborsata da', + 'is (partially) paid for by_inward' => 'è (parzialmente) pagata da', + 'is (partially) reimbursed by_inward' => 'è (parzialmente) rimborsata da', + 'inward_transaction' => 'Transazione in ingresso', + 'outward_transaction' => 'Transazione in uscita', 'relates to_outward' => 'inerente a', - '(partially) refunds_outward' => '(parzialmente) rimborsi', + '(partially) refunds_outward' => '(parzialmente) rimborsa', '(partially) pays for_outward' => '(parzialmente) paga per', '(partially) reimburses_outward' => '(parzialmente) rimborsa', @@ -1144,35 +1146,36 @@ return [ 'add_another_split' => 'Aggiungi un\'altra divisione', 'split-transactions' => 'Dividi le transazioni', 'do_split' => 'Fai una divisione', - 'split_this_withdrawal' => 'Dividi questa spesa', + 'split_this_withdrawal' => 'Dividi questo prelievo', 'split_this_deposit' => 'Dividi questa entrata', 'split_this_transfer' => 'Dividi questo trasferimento', 'cannot_edit_multiple_source' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché contiene più conti di origine.', 'cannot_edit_multiple_dest' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché contiene più conti di destinazione.', 'cannot_edit_reconciled' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché è stata contrassegnata come riconciliata.', - 'cannot_edit_opening_balance' => 'Non è possibile modificare il bilancio di apertura di un conto.', + 'cannot_edit_opening_balance' => 'Non è possibile modificare il saldo di apertura di un conto.', 'no_edit_multiple_left' => 'Non hai selezionato transazioni valide da modificare.', 'cannot_convert_split_journal' => 'Impossibile convertire una transazione divisa', // Import page (general strings only) - 'import_index_title' => 'Importa i dati in Firefly III', - 'import_data' => 'Importa i dati', + 'import_index_title' => 'Importa le transazioni in Firefly III', + 'import_data' => 'Importa dati', + 'import_transactions' => 'Importa transazioni', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Questa funzione non è disponibile quando si utilizza Firefly III in un ambiente Sandstorm.io.', // empty lists? no objects? instructions: 'no_accounts_title_asset' => 'Creiamo un conto attività!', - 'no_accounts_intro_asset' => 'Non hai ancora un conto attività. I conti cespiti sono i tuoi conti principali: il tuo conto corrente, il tuo conto di risparmio, il conto condiviso o persino la tua carta di credito.', - 'no_accounts_imperative_asset' => 'Per iniziare a utilizzare Firefly III è necessario creare almeno un conto attività. Facciamo così ora:', + 'no_accounts_intro_asset' => 'Non hai ancora un conto attività. I conti attività sono i tuoi conti principali: il tuo conto corrente, il tuo conto di risparmio, il conto condiviso o persino la tua carta di credito.', + 'no_accounts_imperative_asset' => 'Per iniziare a utilizzare Firefly III è necessario creare almeno un conto attività. Facciamolo ora:', 'no_accounts_create_asset' => 'Crea un conto attività', 'no_accounts_title_expense' => 'Creiamo un conto spese!', 'no_accounts_intro_expense' => 'Non hai ancora un conto spesa. I conti spese sono i luoghi in cui si spendono soldi, come negozi e supermercati.', 'no_accounts_imperative_expense' => 'I conti spesa vengono creati automaticamente quando si creano le transazioni, ma è possibile crearne anche manualmente, se lo si desidera. Ne creiamo uno ora:', 'no_accounts_create_expense' => 'Crea conto spesa', 'no_accounts_title_revenue' => 'Creiamo un conto entrate!', - 'no_accounts_intro_revenue' => 'Non hai ancora un conto entrate. I conti delle entrate sono i luoghi in cui ricevi denaro, come il tuo stipendio e altre entrate.', - 'no_accounts_imperative_revenue' => 'I conti entrate vengono creati automaticamente quando si creano le transazioni, ma è possibile crearne anche manualmente, se lo si desidera. Ne creiamo uno ora:', + 'no_accounts_intro_revenue' => 'Non hai ancora un conto entrate. I conti delle entrate sono i luoghi da cui ricevi denaro, come il tuo datore di lavoro.', + 'no_accounts_imperative_revenue' => 'I conti entrate vengono creati automaticamente quando si creano le transazioni, ma è possibile crearne anche manualmente, se lo si desidera. Creiamone uno ora:', 'no_accounts_create_revenue' => 'Crea conto entrate', 'no_budgets_title_default' => 'Creiamo un budget', 'no_budgets_intro_default' => 'Non hai ancora budget. I budget sono usati per organizzare le tue spese in gruppi logici, ai quali puoi dare un tetto indicativo per limitare le tue spese.', @@ -1191,7 +1194,7 @@ return [ 'no_transactions_imperative_withdrawal' => 'Hai effettuato delle spese? Dovresti registrarle:', 'no_transactions_create_withdrawal' => 'Crea spese', 'no_transactions_title_deposit' => 'Creiamo delle entrate!', - 'no_transactions_intro_deposit' => 'non hai ancora entrate registrate. È necessario creare voci di reddito per iniziare a gestire le tue finanze.', + 'no_transactions_intro_deposit' => 'Non hai ancora delle entrate registrate. Dovresti creare delle voci di entrata per iniziare a gestire le tue finanze.', 'no_transactions_imperative_deposit' => 'Hai ricevuto dei soldi? Dovresti scriverlo:', 'no_transactions_create_deposit' => 'Crea una entrata', 'no_transactions_title_transfers' => 'Creiamo un trasferimento!', @@ -1206,4 +1209,68 @@ return [ 'no_bills_intro_default' => 'Non hai ancora nessuna bolletta. Puoi creare bollette per tenere traccia delle spese regolari, come il tuo affitto o l\'assicurazione.', 'no_bills_imperative_default' => 'Hai delle bollette regolari? Crea una bolletta e tieni traccia dei tuoi pagamenti:', 'no_bills_create_default' => 'Crea bolletta', + + // recurring transactions + 'recurrences' => 'Transazioni ricorrenti', + 'no_recurring_title_default' => 'Creiamo una transazione ricorrente!', + 'no_recurring_intro_default' => 'Non hai ancora una transazione ricorrente. Puoi utilizzare queste per lasciare che Firefly III crei automaticamente le transazioni per te.', + 'no_recurring_imperative_default' => 'Questa è una caratteristica piuttosto avanzata che può essere estremamente utile. Assicurati di leggere la documentazione (l\'icona (?) nell\'angolo in alto a destra) prima di continuare.', + 'no_recurring_create_default' => 'Crea una transazione ricorrente', + 'make_new_recurring' => 'Crea una transazione ricorrente', + 'recurring_daily' => 'Ogni giorno', + 'recurring_weekly' => 'Ogni settimana di :weekday', + 'recurring_monthly' => 'Ogni mese il giorno :dayOfMonth', + 'recurring_ndom' => 'Ogni :dayOfMonth° :weekday del mese', + 'recurring_yearly' => 'Ogni anno il :date', + 'overview_for_recurrence' => 'Panoramica per la transazione ricorrente ":title"', + 'warning_duplicates_repetitions' => 'In alcuni casi rari è possibile che delle date appaiano due volte in questa lista. Questo può succedere quando ripetizioni multiple collidono. Firefly III si assicurerà sempre di generare una sola transazione per giorno.', + 'created_transactions' => 'Transazioni collegate', + 'expected_Withdrawals' => 'Prelievi previsti', + 'expected_Deposits' => 'Deposito previsto', + 'expected_Transfers' => 'Trasferimento previsto', + 'created_Withdrawals' => 'Prelievi creati', + 'created_Deposits' => 'Depositi creati', + 'created_Transfers' => 'Trasferimenti creati', + 'created_from_recurrence' => 'Creata dalla transazione ricorrente ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Etichette', + 'recurring_meta_field_notes' => 'Note', + 'recurring_meta_field_bill_id' => 'Bolletta', + 'recurring_meta_field_piggy_bank_id' => 'Salvadanaio', + 'create_new_recurrence' => 'Crea una nuova transazione ricorrente', + 'help_first_date' => 'Indica quando la ricorrenza dovrebbe avvenire per la prima volta. Questo deve essere nel futuro.', + 'help_first_date_no_past' => 'Indica quando la ricorrenza dovrebbe avvenire per la prima volta. Firefly III non creerà transazioni nel passato.', + 'no_currency' => '(nessuna valuta)', + 'mandatory_for_recurring' => 'Informazioni obbligatorie sulla ricorrenza', + 'mandatory_for_transaction' => 'Informazioni obbligatorie sulla transazione', + 'optional_for_recurring' => 'Informazioni facoltative sulla ricorrenza', + 'optional_for_transaction' => 'Informazioni facoltative sulla transazione', + 'change_date_other_options' => 'Cambia la "prima volta" per visualizzare maggiori opzioni.', + 'mandatory_fields_for_tranaction' => 'Il valore qui presente finirà nella transazione che verrà creata', + 'click_for_calendar' => 'Clicca qui per visualizzare in un calendario quando la transazione si ripete.', + 'repeat_forever' => 'Ripeti per sempre', + 'repeat_until_date' => 'Ripeti fino alla data', + 'repeat_times' => 'Ripeti per un certo numero di volte', + 'recurring_skips_one' => 'Una volta sì e una volta no', + 'recurring_skips_more' => 'Salta per :count volte', + 'store_new_recurrence' => 'Salva transazione ricorrente', + 'stored_new_recurrence' => 'La transazione ricorrente ":title" è stata salvata con successo.', + 'edit_recurrence' => 'Modifica transazione ricorrente ":title"', + 'recurring_repeats_until' => 'Si ripete fino al :date', + 'recurring_repeats_forever' => 'Si ripete per sempre', + 'recurring_repeats_x_times' => 'Si ripete per :count volte', + 'update_recurrence' => 'Aggiorna transazione ricorrente', + 'updated_recurrence' => 'Transazione ricorrente ":title" aggiornata', + 'recurrence_is_inactive' => 'Questa transazione ricorrente non è attiva e non genererà nuove transazioni.', + 'delete_recurring' => 'Elimina transazione ricorrente ":title"', + 'new_recurring_transaction' => 'Nuova transazione ricorrente', + 'help_weekend' => 'Cosa vuoi che Firefly III faccia quando la transazione ricorrente cade di sabato o domenica?', + 'do_nothing' => 'Crea la transazione', + 'skip_transaction' => 'Salta l\'occorrenza', + 'jump_to_friday' => 'Crea la transazione il venerdì precedente', + 'jump_to_monday' => 'Crea la transazione il lunedì successivo', + 'will_jump_friday' => 'Verrà creata il venerdì anziché nel fine settimana.', + 'will_jump_monday' => 'Verrà creata il lunedì anziché il fine settimana.', + 'except_weekends' => 'Tranne il fine settimana', + 'recurrence_deleted' => 'La transazione ricorrente ":title" è stata eliminata', ]; diff --git a/resources/lang/it_IT/form.php b/resources/lang/it_IT/form.php index 2c47c1601f..41360638d9 100644 --- a/resources/lang/it_IT/form.php +++ b/resources/lang/it_IT/form.php @@ -24,162 +24,165 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Nome banca', - 'bank_balance' => 'Saldo', - 'savings_balance' => 'Saldo risparmio', - 'credit_card_limit' => 'Limite Carta di Credito', - 'automatch' => 'Partita automaticamente', - 'skip' => 'Salta', - 'name' => 'Nome', - 'active' => 'Attivo', - 'amount_min' => 'Importo minimo', - 'amount_max' => 'Importo massimo', - 'match' => 'Partite su', - 'strict' => 'Modalità severa', - 'repeat_freq' => 'Ripetizioni', - 'journal_currency_id' => 'Valuta', - 'currency_id' => 'Valuta', - 'transaction_currency_id' => 'Valuta', - 'external_ip' => 'L\'IP esterno del tuo server', - 'attachments' => 'Allegati', - 'journal_amount' => 'Importo', - 'journal_source_account_name' => 'Conto entrate (origine)', - 'journal_source_account_id' => 'Conto attività (origine)', - 'BIC' => 'BIC', - 'verify_password' => 'Verifica password di sicurezza', - 'source_account' => 'Conto sorgente', - 'destination_account' => 'Conto destinazione', - 'journal_destination_account_id' => 'Conto attività (destinatione)', - 'asset_destination_account' => 'Conto attività (destinatione)', - 'asset_source_account' => 'Conto attività (sorgente)', - 'journal_description' => 'Descrizione', - 'note' => 'Note', - 'split_journal' => 'Dividi questa transazione', - 'split_journal_explanation' => 'Dividi questa transazione in più parti', - 'currency' => 'Valuta', - 'account_id' => 'Conto attività', - 'budget_id' => 'Budget', - 'openingBalance' => 'Saldo di apertura', - 'tagMode' => 'Modalità etichetta', - 'tag_position' => 'Posizione etichetta', - 'virtualBalance' => 'Saldo virtuale', - 'targetamount' => 'Importo obiettivo', - 'accountRole' => 'Ruolo del conto', - 'openingBalanceDate' => 'Data di apertura del bilancio', - 'ccType' => 'Piano di pagamento con carta di credito', - 'ccMonthlyPaymentDate' => 'Data di pagamento mensile della carta di credito', - 'piggy_bank_id' => 'Salvadanaio', - 'returnHere' => 'Ritorna qui', - 'returnHereExplanation' => 'Dopo averlo archiviato, torna qui per crearne un altro.', - 'returnHereUpdateExplanation' => 'Dopo l\'aggiornamento, torna qui.', - 'description' => 'Descrizione', - 'expense_account' => 'Conto spese', - 'revenue_account' => 'Conto entrate', - 'decimal_places' => 'Decimali', - 'exchange_rate_instruction' => 'Valuta straniera', - 'source_amount' => 'Importo (origine)', - 'destination_amount' => 'Importo (destinazione)', - 'native_amount' => 'Importo nativo', - 'new_email_address' => 'Nuovo indirizzo email', - 'verification' => 'Verifica', - 'api_key' => 'Chiave API', - 'remember_me' => 'Ricordami', + 'bank_name' => 'Nome banca', + 'bank_balance' => 'Saldo', + 'savings_balance' => 'Saldo risparmio', + 'credit_card_limit' => 'Limite Carta di Credito', + 'automatch' => 'Abbina automaticamente', + 'skip' => 'Salta ogni', + 'name' => 'Nome', + 'active' => 'Attivo', + 'amount_min' => 'Importo minimo', + 'amount_max' => 'Importo massimo', + 'match' => 'Abbina con', + 'strict' => 'Modalità severa', + 'repeat_freq' => 'Si ripete', + 'journal_currency_id' => 'Valuta', + 'currency_id' => 'Valuta', + 'transaction_currency_id' => 'Valuta', + 'external_ip' => 'L\'IP esterno del tuo server', + 'attachments' => 'Allegati', + 'journal_amount' => 'Importo', + 'journal_source_name' => 'Conto entrate (origine)', + 'journal_source_id' => 'Conto attività (origine)', + 'BIC' => 'BIC', + 'verify_password' => 'Verifica password di sicurezza', + 'source_account' => 'Conto sorgente', + 'destination_account' => 'Conto destinazione', + 'journal_destination_id' => 'Conto attività (destinazione)', + 'asset_destination_account' => 'Conto attività (destinazione)', + 'asset_source_account' => 'Conto attività (sorgente)', + 'journal_description' => 'Descrizione', + 'note' => 'Note', + 'split_journal' => 'Dividi questa transazione', + 'split_journal_explanation' => 'Dividi questa transazione in più parti', + 'currency' => 'Valuta', + 'account_id' => 'Conto attività', + 'budget_id' => 'Budget', + 'openingBalance' => 'Saldo di apertura', + 'tagMode' => 'Modalità etichetta', + 'tag_position' => 'Posizione etichetta', + 'virtualBalance' => 'Saldo virtuale', + 'targetamount' => 'Importo obiettivo', + 'accountRole' => 'Ruolo del conto', + 'openingBalanceDate' => 'Data saldo di apertura', + 'ccType' => 'Piano di pagamento con carta di credito', + 'ccMonthlyPaymentDate' => 'Data di addebito mensile della carta di credito', + 'piggy_bank_id' => 'Salvadanaio', + 'returnHere' => 'Ritorna qui', + 'returnHereExplanation' => 'Dopo averlo archiviato, torna qui per crearne un altro.', + 'returnHereUpdateExplanation' => 'Dopo l\'aggiornamento, torna qui.', + 'description' => 'Descrizione', + 'expense_account' => 'Conto spese', + 'revenue_account' => 'Conto entrate', + 'decimal_places' => 'Decimali', + 'exchange_rate_instruction' => 'Valuta straniera', + 'source_amount' => 'Importo (origine)', + 'destination_amount' => 'Importo (destinazione)', + 'native_amount' => 'Importo nativo', + 'new_email_address' => 'Nuovo indirizzo email', + 'verification' => 'Verifica', + 'api_key' => 'Chiave API', + 'remember_me' => 'Ricordami', - 'source_account_asset' => 'Conto origine (conto risorse)', + 'source_account_asset' => 'Conto origine (conto attività)', 'destination_account_expense' => 'Conto destinazione (conto spese)', - 'destination_account_asset' => 'Conto destinazione (conto risorse)', + 'destination_account_asset' => 'Conto destinazione (conto attività)', 'source_account_revenue' => 'Conto sorgente (conto entrate)', 'type' => 'Tipo', - 'convert_Withdrawal' => 'Converti spesa', + 'convert_Withdrawal' => 'Converti prelievo', 'convert_Deposit' => 'Converti entrata', 'convert_Transfer' => 'Converti trasferimento', - 'amount' => 'Importo', - 'foreign_amount' => 'Importo estero', - 'existing_attachments' => 'Allegati esistenti', - 'date' => 'Data', - 'interest_date' => 'Data interesse', - 'book_date' => 'Agenda', - 'process_date' => 'Data di lavorazione', - 'category' => 'Categoria', - 'tags' => 'Etichette', - 'deletePermanently' => 'Elimina definitivamente', - 'cancel' => 'Cancella', - 'targetdate' => 'Data fine', - 'startdate' => 'Data inizio', - 'tag' => 'Etichetta', - 'under' => 'Sotto', - 'symbol' => 'Simbolo', - 'code' => 'Codice', - 'iban' => 'IBAN', - 'accountNumber' => 'Numero conto', - 'creditCardNumber' => 'Numero Carta di credito', - 'has_headers' => 'Intestazioni', - 'date_format' => 'Formato data', - 'specifix' => 'Correzioni bancarie o file specifiche', - 'attachments[]' => 'Allegati', - 'store_new_withdrawal' => 'Salva nuovo prelievo', - 'store_new_deposit' => 'Salva nuovo deposito', - 'store_new_transfer' => 'Salva nuova trasferimento', - 'add_new_withdrawal' => 'Aggiungi nuovo prelievo', - 'add_new_deposit' => 'Aggiungi nuovo deposito', - 'add_new_transfer' => 'Aggiungi nuovo giroconto', - 'title' => 'Titolo', - 'notes' => 'Note', - 'filename' => 'Nome file', - 'mime' => 'Tipo Mime', - 'size' => 'Dimensione', - 'trigger' => 'Trigger', - 'stop_processing' => 'Interrompere l\'elaborazione', - 'start_date' => 'Inizio periodo', - 'end_date' => 'Fine periodo', - 'export_start_range' => 'Inizio intervallo esportazione', - 'export_end_range' => 'Fine intervallo esportazione', - 'export_format' => 'Formato file', - 'include_attachments' => 'Includi allegati caricati', - 'include_old_uploads' => 'Includi dati importati', - 'accounts' => 'Esporta le transazioni da questi conti', - 'delete_account' => 'Elimina conto ":name"', - 'delete_bill' => 'Elimina bolletta ":name"', - 'delete_budget' => 'Elimina budget ":name"', - 'delete_category' => 'Elimina categoria ":name"', - 'delete_currency' => 'Elimina valuta ":name"', - 'delete_journal' => 'Elimina transazione con descrizione ":description"', - 'delete_attachment' => 'Elimina allegato ":name"', - 'delete_rule' => 'Elimina regola ":title"', - 'delete_rule_group' => 'Elimina gruppo regole":title"', - 'delete_link_type' => 'Elimina tipo collegamento ":name"', - 'delete_user' => 'Elimina utente ":email"', - 'user_areYouSure' => 'Se cancelli l\'utente ":email", verrà eliminato tutto. Non sarà più possibile recuperare i dati eliminati. Se cancelli te stesso, perderai l\'accesso a questa istanza di Firefly III.', - 'attachment_areYouSure' => 'Sei sicuro di voler eliminare l\'allegato ":name"?', - 'account_areYouSure' => 'Sei sicuro di voler eliminare il conto ":name"?', - 'bill_areYouSure' => 'Sei sicuro di voler eliminare il conto ":name"?', - 'rule_areYouSure' => 'Sei sicuro di voler eliminare la regola ":title"?', - 'ruleGroup_areYouSure' => 'Sei sicuro di voler eliminare il gruppo regole ":title"?', - 'budget_areYouSure' => 'Sei sicuro di voler eliminare il budget ":name"?', - 'category_areYouSure' => 'Sei sicuro di voler eliminare categoria ":name"?', - 'currency_areYouSure' => 'Sei sicuro di voler eliminare la valuta ":name"?', - 'piggyBank_areYouSure' => 'Sei sicuro di voler eliminare il salvadanaio ":name"?', - 'journal_areYouSure' => 'Sei sicuro di voler eliminare la transazione ":description"?', - 'mass_journal_are_you_sure' => 'Sei sicuro di voler eliminare queste transazioni?', - 'tag_areYouSure' => 'Sei sicuro di voler eliminare l\'etichetta ":tag"?', - 'journal_link_areYouSure' => 'Sei sicuro di voler eliminare il collegamento tra :source e :destination?', - 'linkType_areYouSure' => 'Sei sicuro di voler eliminare il tipo di collegamento ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'L\'eliminazione dei dati da Firefly III è permanente e non può essere annullata.', - 'mass_make_selection' => 'Puoi comunque impedire l\'eliminazione degli elementi rimuovendo la spunta nella casella di controllo.', - 'delete_all_permanently' => 'Elimina selezionato in modo permanente', - 'update_all_journals' => 'Aggiorna queste transazioni', - 'also_delete_transactions' => 'Bene! verrà cancellata anche l\'unica transazione connessa a questo conto. | Bene! Tutte :count le transazioni di conteggio collegate a questo conto verranno eliminate.', - 'also_delete_connections' => 'L\'unica transazione collegata a questo tipo di collegamento perderà questa connessione. | Tutto :count le transazioni di conteggio collegate a questo tipo di collegamento perderanno la connessione.', - 'also_delete_rules' => 'Anche l\'unica regola collegata a questo gruppo di regole verrà eliminata. | Tutto :count verranno eliminate anche le regole di conteggio collegate a questo gruppo di regole.', - 'also_delete_piggyBanks' => 'Verrà eliminato anche l\'unico salvadanaio collegato a questo conto. | Tutti :count il conteggio del salvadanaio collegato a questo conto verrà eliminato.', - 'bill_keep_transactions' => 'L\'unica transazione connessa a questa bolletta non verrà eliminata. | Tutte le :count transazioni del conto collegate a questa bolletta non verranno cancellate.', - 'budget_keep_transactions' => 'L\'unica transazione collegata a questo budget non verrà eliminata. | Tutte le :count transazioni del conto collegate a questo budget non verranno cancellate.', - 'category_keep_transactions' => 'L\'unica transazione collegata a questa categoria non verrà eliminata. | Tutto :count le transazioni del conto collegate a questa categoria non verranno cancellate.', - 'tag_keep_transactions' => 'L\'unica transazione connessa a questa etichetta non verrà eliminata. | Tutto :count le transazioni del conto collegate a questa etichetta non verranno cancellate.', - 'check_for_updates' => 'Controlla gli aggiornamenti', + 'amount' => 'Importo', + 'foreign_amount' => 'Importo estero', + 'existing_attachments' => 'Allegati esistenti', + 'date' => 'Data', + 'interest_date' => 'Data interesse', + 'book_date' => 'Agenda', + 'process_date' => 'Data elaborazione', + 'category' => 'Categoria', + 'tags' => 'Etichette', + 'deletePermanently' => 'Elimina definitivamente', + 'cancel' => 'Annulla', + 'targetdate' => 'Data fine', + 'startdate' => 'Data inizio', + 'tag' => 'Etichetta', + 'under' => 'Sotto', + 'symbol' => 'Simbolo', + 'code' => 'Codice', + 'iban' => 'IBAN', + 'accountNumber' => 'Numero conto', + 'creditCardNumber' => 'Numero carta di credito', + 'has_headers' => 'Intestazioni', + 'date_format' => 'Formato data', + 'specifix' => 'Correzioni bancarie o file specifiche', + 'attachments[]' => 'Allegati', + 'store_new_withdrawal' => 'Salva nuovo prelievo', + 'store_new_deposit' => 'Salva nuovo deposito', + 'store_new_transfer' => 'Salva nuova trasferimento', + 'add_new_withdrawal' => 'Aggiungi nuovo prelievo', + 'add_new_deposit' => 'Aggiungi nuovo deposito', + 'add_new_transfer' => 'Aggiungi un nuovo trasferimento', + 'title' => 'Titolo', + 'notes' => 'Note', + 'filename' => 'Nome file', + 'mime' => 'Tipo Mime', + 'size' => 'Dimensione', + 'trigger' => 'Trigger', + 'stop_processing' => 'Interrompere l\'elaborazione', + 'start_date' => 'Inizio periodo', + 'end_date' => 'Fine periodo', + 'export_start_range' => 'Data iniziale dell\'intervallo d\'esportazione', + 'export_end_range' => 'Data finale dell\'intervallo d\'esportazione', + 'export_format' => 'Formato file', + 'include_attachments' => 'Includi allegati caricati', + 'include_old_uploads' => 'Includi dati importati', + 'accounts' => 'Esporta le transazioni da questi conti', + 'delete_account' => 'Elimina conto ":name"', + 'delete_bill' => 'Elimina bolletta ":name"', + 'delete_budget' => 'Elimina budget ":name"', + 'delete_category' => 'Elimina categoria ":name"', + 'delete_currency' => 'Elimina valuta ":name"', + 'delete_journal' => 'Elimina transazione con descrizione ":description"', + 'delete_attachment' => 'Elimina allegato ":name"', + 'delete_rule' => 'Elimina regola ":title"', + 'delete_rule_group' => 'Elimina gruppo regole":title"', + 'delete_link_type' => 'Elimina tipo collegamento ":name"', + 'delete_user' => 'Elimina utente ":email"', + 'delete_recurring' => 'Elimina transazione ricorrente ":title"', + 'user_areYouSure' => 'Se cancelli l\'utente ":email", verrà eliminato tutto. Non sarà più possibile recuperare i dati eliminati. Se cancelli te stesso, perderai l\'accesso a questa istanza di Firefly III.', + 'attachment_areYouSure' => 'Sei sicuro di voler eliminare l\'allegato ":name"?', + 'account_areYouSure' => 'Sei sicuro di voler eliminare il conto ":name"?', + 'bill_areYouSure' => 'Sei sicuro di voler eliminare il conto ":name"?', + 'rule_areYouSure' => 'Sei sicuro di voler eliminare la regola ":title"?', + 'ruleGroup_areYouSure' => 'Sei sicuro di voler eliminare il gruppo regole ":title"?', + 'budget_areYouSure' => 'Sei sicuro di voler eliminare il budget ":name"?', + 'category_areYouSure' => 'Sei sicuro di voler eliminare categoria ":name"?', + 'recurring_areYouSure' => 'Sei sicuro di voler eliminare la transazione ricorrente ":title"?', + 'currency_areYouSure' => 'Sei sicuro di voler eliminare la valuta ":name"?', + 'piggyBank_areYouSure' => 'Sei sicuro di voler eliminare il salvadanaio ":name"?', + 'journal_areYouSure' => 'Sei sicuro di voler eliminare la transazione ":description"?', + 'mass_journal_are_you_sure' => 'Sei sicuro di voler eliminare queste transazioni?', + 'tag_areYouSure' => 'Sei sicuro di voler eliminare l\'etichetta ":tag"?', + 'journal_link_areYouSure' => 'Sei sicuro di voler eliminare il collegamento tra :source e :destination?', + 'linkType_areYouSure' => 'Sei sicuro di voler eliminare il tipo di collegamento ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'L\'eliminazione dei dati da Firefly III è definitiva e non può essere annullata.', + 'mass_make_selection' => 'Puoi comunque impedire l\'eliminazione degli elementi rimuovendo la spunta nella casella di controllo.', + 'delete_all_permanently' => 'Elimina selezionati definitamente', + 'update_all_journals' => 'Aggiorna queste transazioni', + 'also_delete_transactions' => 'Anche l\'unica transazione collegata a questo conto verrà eliminata.|Anche tutte le :count transazioni collegate a questo conto verranno eliminate.', + 'also_delete_connections' => 'L\'unica transazione collegata a questo tipo di collegamento perderà questa connessione. | Tutto :count le transazioni di conteggio collegate a questo tipo di collegamento perderanno la connessione.', + 'also_delete_rules' => 'Anche l\'unica regola collegata a questo gruppo di regole verrà eliminata. | Tutto :count verranno eliminate anche le regole di conteggio collegate a questo gruppo di regole.', + 'also_delete_piggyBanks' => 'Verrà eliminato anche l\'unico salvadanaio collegato a questo conto. | Tutti :count il conteggio del salvadanaio collegato a questo conto verrà eliminato.', + 'bill_keep_transactions' => 'L\'unica transazione connessa a questa bolletta non verrà eliminata.|Tutte le :count transazioni del conto collegate a questa bolletta non verranno cancellate.', + 'budget_keep_transactions' => 'L\'unica transazione collegata a questo budget non verrà eliminata.|Tutte le :count transazioni del conto collegate a questo budget non verranno cancellate.', + 'category_keep_transactions' => 'L\'unica transazione collegata a questa categoria non verrà eliminata.|Tutte le :count transazioni del conto collegate a questa categoria non verranno cancellate.', + 'recurring_keep_transactions' => 'L\'unica transazione creata da questa transazione ricorrente non verrà eliminata.|Tutte le :count transazioni create da questa transazione ricorrente non verranno eliminate.', + 'tag_keep_transactions' => 'L\'unica transazione connessa a questa etichetta non verrà eliminata.|Tutte le :count transazioni del conto collegate a questa etichetta non verranno eliminate.', + 'check_for_updates' => 'Controlla gli aggiornamenti', - 'email' => 'Indirizzo Email', + 'email' => 'Indirizzo email', 'password' => 'Password', 'password_confirmation' => 'Password (ancora)', 'blocked' => 'È bloccato?', @@ -201,13 +204,13 @@ return [ 'import_file' => 'Importa file', 'configuration_file' => 'Configurazione file', 'import_file_type' => 'Importa tipo file', - 'csv_comma' => 'A virgola (,)', - 'csv_semicolon' => 'A punto e virgola (;)', - 'csv_tab' => 'A spazio (invisibile)', + 'csv_comma' => 'Una virgola (,)', + 'csv_semicolon' => 'Un punto e virgola (;)', + 'csv_tab' => 'Una tabulazione (invisibile)', 'csv_delimiter' => 'Delimitatore campi CSV', 'csv_import_account' => 'Conto di importazione predefinito', 'csv_config' => 'Configurazione importa CSV', - 'client_id' => 'ID Client', + 'client_id' => 'Client ID', 'service_secret' => 'Servizio segreto', 'app_secret' => 'App segreto', 'app_id' => 'ID dell\'app', @@ -216,11 +219,23 @@ return [ 'country_code' => 'Codice Nazione', 'provider_code' => 'Banca o fornitore di dati', - 'due_date' => 'Data scadenza', - 'payment_date' => 'Data pagamento', - 'invoice_date' => 'Data bolletta', - 'internal_reference' => 'Referenze interne', - 'inward' => 'Descrizione interna', - 'outward' => 'Descrizione esterna', - 'rule_group_id' => 'Gruppo regole', + 'due_date' => 'Data scadenza', + 'payment_date' => 'Data pagamento', + 'invoice_date' => 'Data fatturazione', + 'internal_reference' => 'Referenze interne', + 'inward' => 'Descrizione interna', + 'outward' => 'Descrizione esterna', + 'rule_group_id' => 'Gruppo regole', + 'transaction_description' => 'Descrizione transazione', + 'first_date' => 'Prima volta', + 'transaction_type' => 'Tipo transazione', + 'repeat_until' => 'Ripeti fino a', + 'recurring_description' => 'Descrizione transazione ricorrente', + 'repetition_type' => 'Tipo ripetizione', + 'foreign_currency_id' => 'Valuta estera', + 'repetition_end' => 'La ripetizione termina il', + 'repetitions' => 'Ripetizioni', + 'calendar' => 'Calendario', + 'weekend' => 'Fine settimana', + ]; diff --git a/resources/lang/it_IT/import.php b/resources/lang/it_IT/import.php index d91182b40d..5fd4aa332c 100644 --- a/resources/lang/it_IT/import.php +++ b/resources/lang/it_IT/import.php @@ -30,7 +30,7 @@ return [ 'prerequisites_breadcrumb_bunq' => 'Prerequisiti per bunq', 'job_configuration_breadcrumb' => 'Configurazione per ":key"', 'job_status_breadcrumb' => 'Stato di importazione per ":key"', - 'cannot_create_for_provider' => 'Firefly III non può creare un lavoro per il provider ":provider".', + 'cannot_create_for_provider' => 'Firefly III non può creare un\'operazione per il provider ":provider".', // index page: 'general_index_title' => 'Importa un file', @@ -82,7 +82,7 @@ return [ 'prerequisites_saved_for_bunq' => 'Chiave API e IP memorizzati!', // job configuration: - 'job_config_apply_rules_title' => 'Configurazione del lavoro - applicare le tue regole?', + 'job_config_apply_rules_title' => 'Configurazione dell\'operazione - applicare le tue regole?', 'job_config_apply_rules_text' => 'Una volta avviato il fornitore fittizio, le tue regole possono essere applicate alle transazioni. Questo aggiunge del tempo all\'importazione.', 'job_config_input' => 'Il tuo input', // job configuration for the fake provider: @@ -121,26 +121,29 @@ return [ 'spectre_login_status_disabled' => 'Disabilitato', 'spectre_login_new_login' => 'Accedi con un\'altra banca o con una di queste banche con credenziali diverse.', 'job_config_spectre_accounts_title' => 'Seleziona i conti dai quali importare', - 'job_config_spectre_accounts_text' => 'Hai selezionato ":name" (:country). Hai :count conti disponibili da questo fornitore. Seleziona i conti attività di Firefly III in cui devono essere memorizzate le transazioni da questi conti. Ricorda che, per importare i dati, sia l\'account Firefly III sia l\'account ":name" devono avere la stessa valuta.', + 'job_config_spectre_accounts_text' => 'Hai selezionato ":name" (:country). Hai :count conti disponibili da questo fornitore. Seleziona i conti attività di Firefly III in cui devono essere memorizzate le transazioni da questi conti. Ricorda che, per importare i dati, sia il conto di Firefly III sia il conto ":name" devono avere la stessa valuta.', 'spectre_no_supported_accounts' => 'Non puoi importare da questo conto perché le valute non corrispondono.', 'spectre_do_not_import' => '(non importare)', 'spectre_no_mapping' => 'Sembra che tu non abbia selezionato nessun account da cui importare.', 'imported_from_account' => 'Importato da ":account"', 'spectre_account_with_number' => 'Conto :number', + 'job_config_spectre_apply_rules' => 'Applica regole', + 'job_config_spectre_apply_rules_text' => 'Per impostazione predefinita le tue regole verranno applicate alle transazioni create durante questa procedura di importazione. Se non vuoi che questo accada, deseleziona questa casella di controllo.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'Account bunq', 'job_config_bunq_accounts_text' => 'Questi sono i conti associati al tuo account bunq. Seleziona i conti dai quali vuoi effettuare l\'importazione e in quale conto devono essere importate le transazioni.', - 'bunq_no_mapping' => 'Sembra che tu non abbia selezionato nessun account.', - 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', - 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'bunq_no_mapping' => 'Sembra che tu non abbia selezionato alcun conto.', + 'should_download_config' => 'Ti consigliamo di scaricare il file di configurazione per questa operazione. Ciò renderà le importazioni future più facili.', + 'share_config_file' => 'Se hai importato dati da una banca pubblica, dovresti condividere il tuo file di configurazione così da rendere più facile per gli altri utenti importare i loro dati. La condivisione del file di configurazione non espone i tuoi dettagli finanziari.', + 'job_config_bunq_apply_rules' => 'Applica regole', + 'job_config_bunq_apply_rules_text' => 'Per impostazione predefinita le tue regole verranno applicate alle transazioni create durante questa procedura di importazione. Se non vuoi che questo accada, deseleziona questa casella di controllo.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', 'spectre_extra_key_status' => 'Stato', 'spectre_extra_key_card_type' => 'Tipo carta', 'spectre_extra_key_account_name' => 'Nome conto', - 'spectre_extra_key_client_name' => 'Nome cliente', + 'spectre_extra_key_client_name' => 'Nome client', 'spectre_extra_key_account_number' => 'Numero conto', 'spectre_extra_key_blocked_amount' => 'Importo bloccato', 'spectre_extra_key_available_amount' => 'Importo disponibile', @@ -176,7 +179,7 @@ return [ 'job_config_roles_do_map_value' => 'Mappa questi valori', 'job_config_roles_no_example' => 'Nessun dato di esempio disponibile', 'job_config_roles_fa_warning' => 'Se contrassegni una colonna come contenente un importo in una valuta straniera, devi anche impostare la colonna che contiene di quale valuta si tratta.', - 'job_config_roles_rwarning' => 'Come minimo, contrassegna una colonna come colonna dell\'importo. Si consiglia di selezionare anche una colonna per la descrizione, la data e il conto opposto.', + 'job_config_roles_rwarning' => 'Come minimo contrassegna una colonna come colonna dell\'importo. Si consiglia di selezionare anche una colonna per la descrizione, la data e il conto della controparte.', 'job_config_roles_colum_count' => 'Colonna', // job config for the file provider (stage: mapping): 'job_config_map_title' => 'Configurazione di importazione (4/4) - Collega i dati importati con i dati di Firefly III', @@ -209,29 +212,29 @@ return [ // general errors and warnings: - 'bad_job_status' => 'Per accedere a questa pagina, il tuo lavoro di importazione non può avere lo stato ":status".', + 'bad_job_status' => 'Per accedere a questa pagina l\'operazione di importazione non può avere lo stato ":status".', // column roles for CSV import: 'column__ignore' => '(ignora questa colonna)', - 'column_account-iban' => 'Conto patrimonio (IBAN)', - 'column_account-id' => 'Conto patrimonio ID (matching FF3)', - 'column_account-name' => 'Conto patrimonio (nome)', + 'column_account-iban' => 'Conto attività (IBAN)', + 'column_account-id' => 'ID conto attività (mappa FF3)', + 'column_account-name' => 'Conto attività (nome)', 'column_amount' => 'Importo', 'column_amount_foreign' => 'Importo (in altra valuta)', 'column_amount_debit' => 'Importo (colonna debito)', 'column_amount_credit' => 'Importo (colonna credito)', 'column_amount-comma-separated' => 'Importo (virgola come separatore decimale)', - 'column_bill-id' => 'ID conto (matching FF3)', + 'column_bill-id' => 'ID bolletta (mappa FF3)', 'column_bill-name' => 'Nome conto', - 'column_budget-id' => 'ID budget (matching FF3)', + 'column_budget-id' => 'ID budget (mappa FF3)', 'column_budget-name' => 'Nome budget', - 'column_category-id' => 'ID Categoria (matching FF3)', + 'column_category-id' => 'ID categoria (mappa FF3)', 'column_category-name' => 'Nome categoria', 'column_currency-code' => 'Codice valuta (ISO 4217)', 'column_foreign-currency-code' => 'Codice valuta straniera (ISO 4217)', - 'column_currency-id' => 'ID valuta (matching FF3)', - 'column_currency-name' => 'Nome valuta (matching FF3)', - 'column_currency-symbol' => 'Simbolo valuta (matching FF3)', + 'column_currency-id' => 'ID valuta (mappa FF3)', + 'column_currency-name' => 'Nome valuta (mappa FF3)', + 'column_currency-symbol' => 'Simbolo valuta (mappa FF3)', 'column_date-interest' => 'Data calcolo interessi', 'column_date-book' => 'Data prenotazione della transazione', 'column_date-process' => 'Data processo della transazione', @@ -240,15 +243,15 @@ return [ 'column_date-payment' => 'Data di pagamento della transazione', 'column_date-invoice' => 'Data di fatturazione della transazione', 'column_description' => 'Descrizione', - 'column_opposing-iban' => 'Conto opposto (IBAN)', - 'column_opposing-bic' => 'Conto della controparte (BIC)', - 'column_opposing-id' => 'ID Conto opposto (matching FF3)', + 'column_opposing-iban' => 'Conto controparte (IBAN)', + 'column_opposing-bic' => 'Conto controparte (BIC)', + 'column_opposing-id' => 'ID conto controparte (mappa FF3)', 'column_external-id' => 'ID esterno', - 'column_opposing-name' => 'Conto opposto (nome)', + 'column_opposing-name' => 'Conto controparte (nome)', 'column_rabo-debit-credit' => 'Indicatore Rabo di addebito / accredito specifico della banca', 'column_ing-debit-credit' => 'Indicatore di debito / credito specifico ING', 'column_sepa-ct-id' => 'ID end-to-end del bonifico SEPA', - 'column_sepa-ct-op' => 'Conto opposto bonifico SEPA', + 'column_sepa-ct-op' => 'Identificatore SEPA conto controparte', 'column_sepa-db' => 'Addebito diretto SEPA', 'column_sepa-cc' => 'Codice Compensazione SEPA', 'column_sepa-ci' => 'Identificativo Creditore SEPA', @@ -256,9 +259,9 @@ return [ 'column_sepa-country' => 'Codice Paese SEPA', 'column_tags-comma' => 'Etichette (separate da virgola)', 'column_tags-space' => 'Etichette (separate con spazio)', - 'column_account-number' => 'Conto patrimonio (numero conto)', - 'column_opposing-number' => 'Conto opposto (numero conto)', - 'column_note' => 'Nota(e)', + 'column_account-number' => 'Conto attività (numero conto)', + 'column_opposing-number' => 'Conto controparte (numero conto)', + 'column_note' => 'Note', 'column_internal-reference' => 'Riferimento interno', ]; diff --git a/resources/lang/it_IT/intro.php b/resources/lang/it_IT/intro.php index a5b2a44c83..70721ef6e1 100644 --- a/resources/lang/it_IT/intro.php +++ b/resources/lang/it_IT/intro.php @@ -25,7 +25,7 @@ declare(strict_types=1); return [ // index 'index_intro' => 'Benvenuti nella pagina indice di Firefly III. Si prega di prendersi il tempo necessario per questa introduzione per avere un\'idea di come funziona Firefly III.', - 'index_accounts-chart' => 'Questo grafico mostra il saldo attuale dei contit risorse. Puoi selezionare gli conti visibili qui nelle tue preferenze.', + 'index_accounts-chart' => 'Questo grafico mostra il saldo attuale dei conti attività. Puoi selezionare i conti visibili qui nelle tue preferenze.', 'index_box_out_holder' => 'Questa piccola casella e le caselle accanto a questa ti daranno una rapida panoramica della tua situazione finanziaria.', 'index_help' => 'Se hai bisogno di aiuto per una pagina o un modulo, premi questo pulsante.', 'index_outro' => 'La maggior parte delle pagine di Firefly III inizieranno con un piccolo tour come questo. Vi prego di contattarci quando avete domande o commenti. Grazie!', @@ -35,7 +35,7 @@ return [ 'accounts_create_iban' => 'Dai ai tuoi conti un IBAN valido. Ciò potrebbe rendere molto facile l\'importazione dei dati in futuro.', 'accounts_create_asset_opening_balance' => 'I conti attività possono avere un "saldo di apertura", che indica l\'inizio della cronologia di questo conto in Firefly III.', 'accounts_create_asset_currency' => 'Firefly III supporta più valute. I conti attività hanno una valuta principale, che devi impostare qui.', - 'accounts_create_asset_virtual' => 'A volte può aiutare a fornire al tuo conto un saldo virtuale: un importo aggiuntivo sempre aggiunto o rimosso dal saldo effettivo.', + 'accounts_create_asset_virtual' => 'A volte può aiutare a fornire al tuo conto un saldo virtuale: un ulteriore importo sempre aggiunto o rimosso dal saldo effettivo.', // budgets index 'budgets_index_intro' => 'I budget sono usati per gestire le tue finanze e formano una delle funzioni principali di Firefly III.', @@ -59,11 +59,11 @@ return [ 'reports_report_audit_optionsBox' => 'Utilizza queste caselle di controllo per mostrare o nascondere le colonne che ti interessano.', 'reports_report_category_intro' => 'Questo resoconto ti fornirà informazioni su una o più categorie.', - 'reports_report_category_pieCharts' => 'Questi grafici ti daranno un\'idea delle spese e del reddito per categoria o per conto.', - 'reports_report_category_incomeAndExpensesChart' => 'Questo grafico mostra le tue spese e il reddito per categoria.', + 'reports_report_category_pieCharts' => 'Questi grafici ti daranno un\'idea delle spese e delle entrate per categoria o per conto.', + 'reports_report_category_incomeAndExpensesChart' => 'Questo grafico mostra le tue spese e le tue entrate per categoria.', 'reports_report_tag_intro' => 'Questo resoconto ti fornirà informazioni su uno o più etichette.', - 'reports_report_tag_pieCharts' => 'Questi grafici ti daranno un\'idea delle spese e del reddito per etichetta, conto, categoria o budget.', + 'reports_report_tag_pieCharts' => 'Questi grafici ti daranno un\'idea delle spese e delle entrate per etichetta, conto, categoria o budget.', 'reports_report_tag_incomeAndExpensesChart' => 'Questo grafico mostra le tue spese e entrate per etichetta.', 'reports_report_budget_intro' => 'Questo resoconto ti fornirà informazioni su uno o più budget.', @@ -85,7 +85,7 @@ return [ // create piggy 'piggy-banks_create_name' => 'Qual è il tuo obiettivo? Un nuovo divano, una macchina fotografica, soldi per le emergenze?', - 'piggy-banks_create_date' => 'È possibile impostare una data di destinazione o una scadenza per il salvadanaio.', + 'piggy-banks_create_date' => 'È possibile impostare una data come obiettivo o una scadenza per il salvadanaio.', // show piggy 'piggy-banks_show_piggyChart' => 'Questo grafico mostrerà lo storico di questo salvadanaio.', @@ -94,7 +94,7 @@ return [ // bill index 'bills_index_rules' => 'Qui puoi vedere quali regole verranno controllate se questa bolletta viene "toccata"', - 'bills_index_paid_in_period' => 'Questo campo indica quando il conto è stato pagato l\'ultima volta.', + 'bills_index_paid_in_period' => 'Questo campo indica quando la bolletta è stato pagata l\'ultima volta.', 'bills_index_expected_in_period' => 'Questo campo indica per ciascuna bolletta se e quando ci si aspetta che la bolletta successiva arrivi.', // show bill @@ -111,7 +111,7 @@ return [ 'bills_create_skip_holder' => 'Se una bolletta si ripete ogni 2 settimane, il campo "salta" deve essere impostato a "1" per saltare ogni volta una settimana.', // rules index - 'rules_index_intro' => 'Firefly III ti consente di gestire le regole, che verranno automaticamente applicate a qualsiasi transazione creata o modificata.', + 'rules_index_intro' => 'Firefly III ti consente di gestire delle regole che verranno automaticamente applicate a qualsiasi transazione creata o modificata.', 'rules_index_new_rule_group' => 'È possibile combinare le regole in gruppi per una gestione più semplice.', 'rules_index_new_rule' => 'Crea quante regole desideri.', 'rules_index_prio_buttons' => 'Ordinali come meglio credi.', diff --git a/resources/lang/it_IT/list.php b/resources/lang/it_IT/list.php index 4b53c93bf8..a4d4346fd7 100644 --- a/resources/lang/it_IT/list.php +++ b/resources/lang/it_IT/list.php @@ -26,21 +26,21 @@ return [ 'buttons' => 'Pulsanti', 'icon' => 'Icona', 'id' => 'ID', - 'create_date' => 'Creato a', - 'update_date' => 'Aggiornato a', - 'updated_at' => 'Aggiornato a', + 'create_date' => 'Creato il', + 'update_date' => 'Aggiornato il', + 'updated_at' => 'Aggiornato il', 'balance_before' => 'Bilancio prima', 'balance_after' => 'Bilancio dopo', 'name' => 'Nome', 'role' => 'Ruolo', - 'currentBalance' => 'Bilancio corrente', + 'currentBalance' => 'Saldo corrente', 'linked_to_rules' => 'Regole rilevanti', 'active' => 'Attivo', 'lastActivity' => 'Ultima attività', - 'balanceDiff' => 'Differenze bilancio', - 'matchesOn' => 'Abbinato', + 'balanceDiff' => 'Differenze saldi', + 'matchesOn' => 'Abbinato con', 'account_type' => 'Tipo conto', - 'created_at' => 'Creato a', + 'created_at' => 'Creato il', 'account' => 'Conto', 'matchingAmount' => 'Importo', 'split_number' => 'Diviso #', @@ -48,7 +48,7 @@ return [ 'source' => 'Sorgente', 'next_expected_match' => 'Prossimo abbinamento previsto', 'automatch' => 'Abbinamento automatico?', - 'repeat_freq' => 'Ripetizioni', + 'repeat_freq' => 'Si ripete', 'description' => 'Descrizione', 'amount' => 'Importo', 'internal_reference' => 'Referenze interne', @@ -58,7 +58,7 @@ return [ 'process_date' => 'Data lavorazione', 'due_date' => 'Data scadenza', 'payment_date' => 'Data pagamento', - 'invoice_date' => 'Data bolletta', + 'invoice_date' => 'Data fatturazione', 'interal_reference' => 'Referenze interne', 'notes' => 'Note', 'from' => 'Da', @@ -67,46 +67,46 @@ return [ 'budget' => 'Budget', 'category' => 'Categoria', 'bill' => 'Conto', - 'withdrawal' => 'Spesa', + 'withdrawal' => 'Prelievo', 'deposit' => 'Deposito', 'transfer' => 'Trasferimento', 'type' => 'Tipo', 'completed' => 'Completato', 'iban' => 'IBAN', - 'paid_current_period' => 'Pagato in questo periodo', + 'paid_current_period' => 'Pagati in questo periodo', 'email' => 'Email', - 'registered_at' => 'Registrato a', + 'registered_at' => 'Registrato il', 'is_blocked' => 'È bloccato', 'is_admin' => 'È amministratore', - 'has_two_factor' => 'Ha 2 Fattori', + 'has_two_factor' => 'Ha 2FA', 'blocked_code' => 'Codice blocco', 'source_account' => 'Conto sorgente', 'destination_account' => 'Conto destinazione', - 'accounts_count' => 'Numero conti', - 'journals_count' => 'Numero transazioni', - 'attachments_count' => 'Numero allegati', - 'bills_count' => 'Numero conti', - 'categories_count' => 'Numero categorie', - 'export_jobs_count' => 'Numero esportazioni', - 'import_jobs_count' => 'Numero importazioni', + 'accounts_count' => 'Numero di conti', + 'journals_count' => 'Numero di transazioni', + 'attachments_count' => 'Numero di allegati', + 'bills_count' => 'Numero di bollette', + 'categories_count' => 'Numero di categorie', + 'export_jobs_count' => 'Numero delle operazioni di esportazione', + 'import_jobs_count' => 'Numero delle operazioni di importazione', 'budget_count' => 'Numero di budget', - 'rule_and_groups_count' => 'Numero regole e gruppi regole', - 'tags_count' => 'Numero etichette', + 'rule_and_groups_count' => 'Numero di regole e gruppi di regole', + 'tags_count' => 'Numero di etichette', 'tags' => 'Etichette', 'inward' => 'Descrizione interna', 'outward' => 'Descrizione esterna', - 'number_of_transactions' => 'Numero transazioni', + 'number_of_transactions' => 'Numero di transazioni', 'total_amount' => 'Importo totale', 'sum' => 'Somma', 'sum_excluding_transfers' => 'Somma (esclusi i trasferimenti)', - 'sum_withdrawals' => 'Somma prelievi', + 'sum_withdrawals' => 'Somma dei prelievi', 'sum_deposits' => 'Somma versamenti', 'sum_transfers' => 'Somma dei trasferimenti', 'reconcile' => 'Riconcilia', 'account_on_spectre' => 'Conto (Spectre)', 'do_import' => 'Importo da questo conto', 'sepa-ct-id' => 'Identificativo End-To-End SEPA', - 'sepa-ct-op' => 'Identificativo Conto Oppositore SEPA', + 'sepa-ct-op' => 'Identificativo SEPA Conto Controparte', 'sepa-db' => 'Identificativo Mandato SEPA', 'sepa-country' => 'Paese SEPA', 'sepa-cc' => 'Codice Compensazione SEPA', @@ -122,5 +122,10 @@ return [ 'spectre_bank' => 'Banca', 'spectre_last_use' => 'Ultimo accesso', 'spectre_status' => 'Stato', - 'bunq_payment_id' => 'bunq payment ID', + 'bunq_payment_id' => 'ID pagamento bunq', + 'repetitions' => 'Ripetizioni', + 'title' => 'Titolo', + 'transaction_s' => 'Transazioni', + 'field' => 'Campo', + 'value' => 'Valore', ]; diff --git a/resources/lang/it_IT/passwords.php b/resources/lang/it_IT/passwords.php index 3e44682679..5261554c24 100644 --- a/resources/lang/it_IT/passwords.php +++ b/resources/lang/it_IT/passwords.php @@ -24,9 +24,9 @@ declare(strict_types=1); return [ 'password' => 'Le password devono contenere almeno sei caratteri e devono corrispondere alla conferma.', - 'user' => 'Non possiamo trovare un utente con questo indirizzo e-mail.', + 'user' => 'Non riusciamo a trovare un utente con questo indirizzo e-mail.', 'token' => 'Questo token di reimpostazione della password non è valido.', - 'sent' => 'Abbiamo inviato via e-mail il tuo link per la reimpostazione della password!', + 'sent' => 'Abbiamo inviato via e-mail il link per la reimpostazione della password!', 'reset' => 'La tua password è stata resettata!', 'blocked' => 'Riprova.', ]; diff --git a/resources/lang/it_IT/validation.php b/resources/lang/it_IT/validation.php index f6c286443a..c5d8699a6b 100644 --- a/resources/lang/it_IT/validation.php +++ b/resources/lang/it_IT/validation.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'iban' => 'Questo non è un IBAN valido.', - 'source_equals_destination' => 'Il conto di origine è uguale al conto di destinazione', + 'source_equals_destination' => 'Il conto di origine è uguale al conto di destinazione.', 'unique_account_number_for_user' => 'Sembra che questo numero di conto sia già in uso.', 'unique_iban_for_user' => 'Sembra che questo IBAN sia già in uso.', 'deleted_user' => 'A causa dei vincoli di sicurezza, non è possibile registrarsi utilizzando questo indirizzo email.', @@ -34,16 +34,22 @@ return [ 'file_attached' => 'File caricato con successo ":name".', 'must_exist' => 'L\'ID nel campo :attribute non esiste nel database.', 'all_accounts_equal' => 'Tutti i conti in questo campo devono essere uguali.', - 'invalid_selection' => 'La tua selezione non è valida', + 'invalid_selection' => 'La tua selezione non è valida.', 'belongs_user' => 'Questo valore non è valido per questo campo.', 'at_least_one_transaction' => 'Hai bisogno di almeno una transazione.', + 'at_least_one_repetition' => 'È necessaria almeno una ripetizione.', + 'require_repeat_until' => 'Richiede un numero di ripetizioni o una data di fine (ripeti fino al), non entrambi.', 'require_currency_info' => 'Il contenuto di questo campo non è valido senza informazioni sulla valuta.', 'equal_description' => 'La descrizione della transazione non deve essere uguale alla descrizione globale.', 'file_invalid_mime' => 'Il file ":name" è di tipo ":mime" che non è accettato come nuovo caricamento.', 'file_too_large' => 'Il file ":name" è troppo grande.', - 'belongs_to_user' => 'Il valore di :attribute è sconosciuto', + 'belongs_to_user' => 'Il valore di :attribute è sconosciuto.', 'accepted' => 'L\' :attribute deve essere accettato.', 'bic' => 'Questo non è un BIC valido.', + 'at_least_one_trigger' => 'Una regola deve avere almeno un trigger.', + 'at_least_one_action' => 'Una regola deve avere almeno una azione.', + 'base64' => 'Questi non sono dati codificati in base64 validi.', + 'model_id_invalid' => 'L\'ID fornito sembra non essere valido per questo modello.', 'more' => ':attribute deve essere maggiore di zero.', 'active_url' => ':attribute non è un URL valido.', 'after' => ':attribute deve essere una data dopo :date.', @@ -53,8 +59,8 @@ return [ 'array' => ':attribute deve essere una matrice.', 'unique_for_user' => 'C\'è già una voce con questo :attribute.', 'before' => ':attribute deve essere una data prima :date.', - 'unique_object_for_user' => 'Questo nome è già in uso', - 'unique_account_for_user' => 'Questo nome conto è già in uso', + 'unique_object_for_user' => 'Questo nome è già in uso.', + 'unique_account_for_user' => 'Il nome del conto è già in uso.', 'between.numeric' => ':attribute con questo nome conto è già in uso :min e :max.', 'between.file' => ':attribute deve essere :min e :max kilobyte.', 'between.string' => ':attribute deve essere tra :min e :max caratteri.', @@ -67,7 +73,7 @@ return [ 'digits' => ':attribute deve essere :digits cifre.', 'digits_between' => ':attribute deve essere :min e :max cifre.', 'email' => ':attribute deve essere un indirizzo email valido.', - 'filled' => ':attribute il campo è obbligatiorio.', + 'filled' => 'Il campo :attribute è obbligatorio.', 'exists' => ':attribute selezionato non è valido.', 'image' => ':attribute deve essere un\'immagine.', 'in' => ':attribute selezionato non è valido.', @@ -85,14 +91,17 @@ return [ 'min.array' => ':attribute deve avere almeno :min voci.', 'not_in' => ':attribute selezionato è invalido.', 'numeric' => ':attribute deve essere un numero.', + 'numeric_native' => 'L\'importo nativo deve essere un numero.', + 'numeric_destination' => 'L\'importo di destinazione deve essere un numero.', + 'numeric_source' => 'L\'importo sorgente deve essere un numero.', 'regex' => ':attribute formato non valido', - 'required' => ':attribute il campo è obbligatiorio.', - 'required_if' => ':attribute il campo è richiesto quando :other is :value.', - 'required_unless' => ':attribute il campo è richiesto a meno che :other è in :values.', - 'required_with' => ':attribute il campo è richiesto quando :values è presente.', - 'required_with_all' => ':attribute il campo è richiesto quando :values è presente.', - 'required_without' => ':attribute il campo è richiesto quando :values non è presente.', - 'required_without_all' => ':attribute il campo è richiesto quando nessuno di :values sono presenti.', + 'required' => 'Il campo :attribute è obbligatorio.', + 'required_if' => 'Il campo :attribute è obbligatorio quando :other è :value.', + 'required_unless' => 'Il campo :attribute è obbligatorio a meno che :other è in :values.', + 'required_with' => 'Il campo :attribute è obbligatorio quando :values è presente.', + 'required_with_all' => 'Il campo :attribute è obbligatorio quando :values è presente.', + 'required_without' => 'Il campo :attribute è obbligatorio quando :values non è presente.', + 'required_without_all' => 'Il campo :attribute è obbligatorio quando nessuno di :values è presente.', 'same' => ':attribute e :other deve combaciare.', 'size.numeric' => ':attribute deve essere :size.', 'amount_min_over_max' => 'L\'importo minimo non può essere maggiore dell\'importo massimo.', @@ -103,15 +112,18 @@ return [ 'string' => ':attribute deve essere una stringa.', 'url' => ':attribute il formato non è valido.', 'timezone' => ':attribute deve essere una zona valida.', - '2fa_code' => ':attribute il campo non è valido.', + '2fa_code' => 'Il campo :attribute non è valido.', 'dimensions' => ':attribute ha dimensioni di immagine non valide.', 'distinct' => ':attribute il campo ha un valore doppio.', 'file' => ':attribute deve essere un file.', 'in_array' => ':attribute il campo non esiste in :other.', 'present' => ':attribute il campo deve essere presente.', - 'amount_zero' => 'L\'importo totale non può essere zero', - 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', - 'secure_password' => 'Questa non è una password sicura. Per favore riprova. Per ulteriori informazioni, visitare http://bit.ly/FF3-password-security', + 'amount_zero' => 'L\'importo totale non può essere zero.', + 'unique_piggy_bank_for_user' => 'Il nome del salvadanaio deve essere unico.', + 'secure_password' => 'Questa non è una password sicura. Riprova. Per maggiori informazioni visita http://bit.ly/FF3-password-security.', + 'valid_recurrence_rep_type' => 'Il tipo di ripetizione della transazione ricorrente non è valido.', + 'valid_recurrence_rep_moment' => 'Il momento di ripetizione per questo tipo di ripetizione non è valido.', + 'invalid_account_info' => 'Informazione sul conto non valida.', 'attributes' => [ 'email' => 'indirizzo email', 'description' => 'descrizione', @@ -119,9 +131,9 @@ return [ 'name' => 'nome', 'piggy_bank_id' => 'ID salvadanaio', 'targetamount' => 'importo obiettivo', - 'openingBalanceDate' => 'data bilancio apertura', + 'openingBalanceDate' => 'data saldo di apertura', 'openingBalance' => 'saldo apertura', - 'match' => 'partita', + 'match' => 'abbinamento', 'amount_min' => 'importo minimo', 'amount_max' => 'importo massimo', 'title' => 'titolo', diff --git a/resources/lang/nl_NL/config.php b/resources/lang/nl_NL/config.php index d2e66fcf86..aab20ae1cb 100644 --- a/resources/lang/nl_NL/config.php +++ b/resources/lang/nl_NL/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'nl', - 'locale' => 'nl, Dutch, nl_NL, nl_NL.utf8, nl_NL.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'week %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'D MMMM YYYY', - 'date_time_js' => 'D MMMM YYYY @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'nl', + 'locale' => 'nl, Dutch, nl_NL, nl_NL.utf8, nl_NL.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %e %B %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'week %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'D MMMM YYYY', + 'date_time_js' => 'D MMMM YYYY @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Maandag', + 'dow_2' => 'Dinsdag', + 'dow_3' => 'Woensdag', + 'dow_4' => 'Donderdag', + 'dow_5' => 'Vrijdag', + 'dow_6' => 'Zaterdag', + 'dow_7' => 'Zondag', ]; diff --git a/resources/lang/nl_NL/demo.php b/resources/lang/nl_NL/demo.php index 5f6ed1f0b2..d8b739c605 100644 --- a/resources/lang/nl_NL/demo.php +++ b/resources/lang/nl_NL/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Deze uitgaven, inkomsten en overschrijvingen zijn niet heel fantasierijk. Ze zijn automatisch gegenereerd.', 'piggy-banks-index' => 'Zoals je kan zien zijn er drie spaarpotjes. Gebruik de plus- en minknoppen om het bedrag in de spaarpotjes te veranderen. Klik op de naam van het spaarpotje om er de geschiedenis van te zien.', 'import-index' => 'Je kan elk CSV bestand importeren met Firefly III. Het is ook mogelijk om transacties te importeren van bunq en Spectre. Andere verzamelbedrijven worden ook geïntegreerd. De demo-gebruiker mag alleen de "nep"-importhulp gebruiken. Deze genereert een paar willekeurige transacties om te laten zien hoe het werkt.', + 'recurring-index' => 'Denk er aan dat deze functie nog ontwikkeld wordt en misschien niet lekker werkt.', + 'recurring-create' => 'Denk er aan dat deze functie nog ontwikkeld wordt en misschien niet lekker werkt.', ]; diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 051852c28d..6e4acdc31b 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scan deze QR code met een app op je telefoon (zoals Authy of Google Authenticator). Vul de code die je terug krijgt hier in.', 'pref_two_factor_auth_reset_code' => 'Reset de verificatiecode', 'pref_two_factor_auth_disable_2fa' => '2FA uitzetten', + '2fa_use_secret_instead' => 'Als je de QR code niet kan scannen gebruik dan de geheime code: :secret.', 'pref_save_settings' => 'Instellingen opslaan', 'saved_preferences' => 'Voorkeuren opgeslagen!', 'preferences_general' => 'Algemeen', @@ -820,7 +821,7 @@ return [ 'language' => 'Taal', 'new_savings_account' => ':bank_name spaarrekening', 'cash_wallet' => 'Cash-rekening', - 'currency_not_present' => 'Geen zorgen als de valuta die je gewend bent er niet tussen staat. Je kan je eigen valuta maken onder Opties > Valuta.', + 'currency_not_present' => 'Geen zorgen als de valuta die je gewend bent er niet tussen staat. Je kan je eigen valuta maken onder Opties > Valuta.', // home page: 'yourAccounts' => 'Je betaalrekeningen', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Saldo aan het einde van de periode', 'splitByAccount' => 'Per betaalrekening', 'coveredWithTags' => 'Gecorrigeerd met tags', - 'leftUnbalanced' => 'Ongecorrigeerd', 'leftInBudget' => 'Over van budget', 'sumOfSums' => 'Alles bij elkaar', 'noCategory' => '(zonder categorie)', @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => 'wordt (deels) terugbetaald door', 'is (partially) paid for by_inward' => 'wordt (deels) betaald door', 'is (partially) reimbursed by_inward' => 'wordt (deels) vergoed door', + 'inward_transaction' => 'Actieve transactie', + 'outward_transaction' => 'Passieve transactie', 'relates to_outward' => 'gerelateerd aan', '(partially) refunds_outward' => 'is een (gedeeltelijke) terugbetaling voor', '(partially) pays for_outward' => 'betaalt (deels) voor', @@ -1155,8 +1157,9 @@ return [ 'cannot_convert_split_journal' => 'Kan geen gesplitste transactie omzetten', // Import page (general strings only) - 'import_index_title' => 'Gegevens importeren in Firefly III', + 'import_index_title' => 'Transacties importeren in Firefly III', 'import_data' => 'Importeer data', + 'import_transactions' => 'Importeer transacties', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Deze functie werkt niet als je Firefly III gebruikt in combinatie met Sandstorm.IO.', @@ -1206,4 +1209,68 @@ return [ 'no_bills_intro_default' => 'Je hebt nog geen contracten. Je kan contracten gebruiken om terugkerende uitgaven bij te houden, zoals de huur of verzekeringen.', 'no_bills_imperative_default' => 'Heb je zulke uitgaven? Maak dan een contract en houd de betalingen bij:', 'no_bills_create_default' => 'Maak een contract', + + // recurring transactions + 'recurrences' => 'Periodieke transacties', + 'no_recurring_title_default' => 'Maak een periodieke transactie!', + 'no_recurring_intro_default' => 'Je hebt nog geen periodieke transacties. Je kan deze gebruiken om er voor te zorgen dat Firefly III automatisch nieuwe transacties voor je maakt.', + 'no_recurring_imperative_default' => 'Dit is een behoorlijk geadvanceerde functie maar het kan vreselijk handig zijn. Lees ook zeker de documentatie (?)-icoontje rechtsboven) voor je verder gaat.', + 'no_recurring_create_default' => 'Maak een periodieke transactie', + 'make_new_recurring' => 'Maak een periodieke transactie', + 'recurring_daily' => 'Elke dag', + 'recurring_weekly' => 'Elke week op :weekday', + 'recurring_monthly' => 'Elke maand op de :dayOfMonth(e) dag', + 'recurring_ndom' => 'Elke maand op de :dayOfMonth(e) :weekday', + 'recurring_yearly' => 'Elk jaar op :date', + 'overview_for_recurrence' => 'Overzicht voor periodieke transactie ":title"', + 'warning_duplicates_repetitions' => 'Soms zie je hier datums dubbel staan. Dat kan als meerdere herhalingen in elkaars vaarwater zitten. Firefly III maakt altijd maar één transactie per dag.', + 'created_transactions' => 'Gerelateerde transacties', + 'expected_Withdrawals' => 'Verwachte uitgaven', + 'expected_Deposits' => 'Verwachte inkomsten', + 'expected_Transfers' => 'Verwachte overschrijvingen', + 'created_Withdrawals' => 'Gemaakte uitgaven', + 'created_Deposits' => 'Gemaakte inkomsten', + 'created_Transfers' => 'Gemaakte overschrijvingen', + 'created_from_recurrence' => 'Gemaakt door periodieke transactie ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notities', + 'recurring_meta_field_bill_id' => 'Contract', + 'recurring_meta_field_piggy_bank_id' => 'Spaarpotje', + 'create_new_recurrence' => 'Maak een nieuwe periodieke transactie', + 'help_first_date' => 'Geef aan wanneer je de eerste transactie verwacht. Dit moet in de toekomst zijn.', + 'help_first_date_no_past' => 'Geef aan wanneer je de eerste transactie verwacht. Firefly III zal geen transacties in het verleden maken.', + 'no_currency' => '(geen valuta)', + 'mandatory_for_recurring' => 'Verplichte periodieke informatie', + 'mandatory_for_transaction' => 'Verplichte transactieinformatie', + 'optional_for_recurring' => 'Optionele periodieke informatie', + 'optional_for_transaction' => 'Optionele transactieinformatie', + 'change_date_other_options' => 'Wijzig de "eerste datum" om meer opties te zien.', + 'mandatory_fields_for_tranaction' => 'De waarden die je hier invult worden gebruikt om de transactie(s) te maken', + 'click_for_calendar' => 'Hier vind je een kalender die laat zien wanneer de transactie zal herhalen.', + 'repeat_forever' => 'Voor altijd herhalen', + 'repeat_until_date' => 'Herhalen tot een datum', + 'repeat_times' => 'Een aantal maal herhalen', + 'recurring_skips_one' => 'Elke tweede', + 'recurring_skips_more' => 'Slaat :count keer over', + 'store_new_recurrence' => 'Sla periodieke transactie op', + 'stored_new_recurrence' => 'Periodieke transactie ":title" is opgeslagen.', + 'edit_recurrence' => 'Wijzig periodieke transactie ":title"', + 'recurring_repeats_until' => 'Herhaalt tot :date', + 'recurring_repeats_forever' => 'Blijft herhalen', + 'recurring_repeats_x_times' => 'Herhaalt :count keer', + 'update_recurrence' => 'Wijzig periodieke transactie', + 'updated_recurrence' => 'Periodieke transactie ":title" is gewijzigd', + 'recurrence_is_inactive' => 'Deze periodieke transactie is niet actief en maakt geen transacties aan.', + 'delete_recurring' => 'Verwijder periodieke transactie ":title"', + 'new_recurring_transaction' => 'Nieuwe periodieke transactie', + 'help_weekend' => 'Wat moet Firefly III doen als de periodieke transactie in het weekend valt?', + 'do_nothing' => 'Gewoon transactie maken', + 'skip_transaction' => 'Transactie overslaan', + 'jump_to_friday' => 'Transactie maken op de vrijdag ervoor', + 'jump_to_monday' => 'Transactie maken op de maandag erna', + 'will_jump_friday' => 'Wordt op de vrijdag ervoor gemaakt i.p.v. in het weekend.', + 'will_jump_monday' => 'Wordt op de maandag erna gemaakt i. p. v. in het weekend.', + 'except_weekends' => 'Behalve de weekenden', + 'recurrence_deleted' => 'Periodieke transactie ":title" verwijderd', ]; diff --git a/resources/lang/nl_NL/form.php b/resources/lang/nl_NL/form.php index 748af68a19..f19ff53daa 100644 --- a/resources/lang/nl_NL/form.php +++ b/resources/lang/nl_NL/form.php @@ -24,66 +24,66 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Banknaam', - 'bank_balance' => 'Saldo', - 'savings_balance' => 'Saldo van spaarrekening', - 'credit_card_limit' => 'Credit card limiet', - 'automatch' => 'Automatisch herkennen', - 'skip' => 'Overslaan', - 'name' => 'Naam', - 'active' => 'Actief', - 'amount_min' => 'Minimumbedrag', - 'amount_max' => 'Maximumbedrag', - 'match' => 'Reageert op', - 'strict' => 'Strikte modus', - 'repeat_freq' => 'Herhaling', - 'journal_currency_id' => 'Valuta', - 'currency_id' => 'Valuta', - 'transaction_currency_id' => 'Valuta', - 'external_ip' => 'Het externe IP-adres van je server', - 'attachments' => 'Bijlagen', - 'journal_amount' => 'Bedrag', - 'journal_source_account_name' => 'Debiteur (bron)', - 'journal_source_account_id' => 'Betaalrekening (bron)', - 'BIC' => 'BIC', - 'verify_password' => 'Bevestig wachtwoordsterkte', - 'source_account' => 'Bronrekening', - 'destination_account' => 'Doelrekening', - 'journal_destination_account_id' => 'Betaalrekening (doel)', - 'asset_destination_account' => 'Betaalrekening (doel)', - 'asset_source_account' => 'Betaalrekening (bron)', - 'journal_description' => 'Omschrijving', - 'note' => 'Notities', - 'split_journal' => 'Splits deze transactie', - 'split_journal_explanation' => 'Splits deze transactie in meerdere stukken', - 'currency' => 'Valuta', - 'account_id' => 'Betaalrekening', - 'budget_id' => 'Budget', - 'openingBalance' => 'Startsaldo', - 'tagMode' => 'Tag modus', - 'tag_position' => 'Taglocatie', - 'virtualBalance' => 'Virtuele saldo', - 'targetamount' => 'Doelbedrag', - 'accountRole' => 'Rol van rekening', - 'openingBalanceDate' => 'Startsaldodatum', - 'ccType' => 'Betaalplan', - 'ccMonthlyPaymentDate' => 'Betaaldatum', - 'piggy_bank_id' => 'Spaarpotje', - 'returnHere' => 'Keer terug', - 'returnHereExplanation' => 'Terug naar deze pagina na het opslaan.', - 'returnHereUpdateExplanation' => 'Terug naar deze pagina na het wijzigen.', - 'description' => 'Omschrijving', - 'expense_account' => 'Crediteur', - 'revenue_account' => 'Debiteur', - 'decimal_places' => 'Aantal decimalen', - 'exchange_rate_instruction' => 'Vreemde valuta', - 'source_amount' => 'Bronbedrag', - 'destination_amount' => 'Doelbedrag', - 'native_amount' => 'Origineel bedrag', - 'new_email_address' => 'Nieuw emailadres', - 'verification' => 'Verificatie', - 'api_key' => 'API sleutel', - 'remember_me' => 'Aangemeld blijven', + 'bank_name' => 'Banknaam', + 'bank_balance' => 'Saldo', + 'savings_balance' => 'Saldo van spaarrekening', + 'credit_card_limit' => 'Credit card limiet', + 'automatch' => 'Automatisch herkennen', + 'skip' => 'Overslaan', + 'name' => 'Naam', + 'active' => 'Actief', + 'amount_min' => 'Minimumbedrag', + 'amount_max' => 'Maximumbedrag', + 'match' => 'Reageert op', + 'strict' => 'Strikte modus', + 'repeat_freq' => 'Herhaling', + 'journal_currency_id' => 'Valuta', + 'currency_id' => 'Valuta', + 'transaction_currency_id' => 'Valuta', + 'external_ip' => 'Het externe IP-adres van je server', + 'attachments' => 'Bijlagen', + 'journal_amount' => 'Bedrag', + 'journal_source_name' => 'Debiteur (bron)', + 'journal_source_id' => 'Betaalrekening (bron)', + 'BIC' => 'BIC', + 'verify_password' => 'Bevestig wachtwoordsterkte', + 'source_account' => 'Bronrekening', + 'destination_account' => 'Doelrekening', + 'journal_destination_id' => 'Betaalrekening (doel)', + 'asset_destination_account' => 'Betaalrekening (doel)', + 'asset_source_account' => 'Betaalrekening (bron)', + 'journal_description' => 'Omschrijving', + 'note' => 'Notities', + 'split_journal' => 'Splits deze transactie', + 'split_journal_explanation' => 'Splits deze transactie in meerdere stukken', + 'currency' => 'Valuta', + 'account_id' => 'Betaalrekening', + 'budget_id' => 'Budget', + 'openingBalance' => 'Startsaldo', + 'tagMode' => 'Tag modus', + 'tag_position' => 'Taglocatie', + 'virtualBalance' => 'Virtuele saldo', + 'targetamount' => 'Doelbedrag', + 'accountRole' => 'Rol van rekening', + 'openingBalanceDate' => 'Startsaldodatum', + 'ccType' => 'Betaalplan', + 'ccMonthlyPaymentDate' => 'Betaaldatum', + 'piggy_bank_id' => 'Spaarpotje', + 'returnHere' => 'Keer terug', + 'returnHereExplanation' => 'Terug naar deze pagina na het opslaan.', + 'returnHereUpdateExplanation' => 'Terug naar deze pagina na het wijzigen.', + 'description' => 'Omschrijving', + 'expense_account' => 'Crediteur', + 'revenue_account' => 'Debiteur', + 'decimal_places' => 'Aantal decimalen', + 'exchange_rate_instruction' => 'Vreemde valuta', + 'source_amount' => 'Bronbedrag', + 'destination_amount' => 'Doelbedrag', + 'native_amount' => 'Origineel bedrag', + 'new_email_address' => 'Nieuw emailadres', + 'verification' => 'Verificatie', + 'api_key' => 'API sleutel', + 'remember_me' => 'Aangemeld blijven', 'source_account_asset' => 'Bronrekening (betaalrekening)', 'destination_account_expense' => 'Doelrekening (crediteur)', @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Verander inkomsten', 'convert_Transfer' => 'Verander overschrijving', - 'amount' => 'Bedrag', - 'foreign_amount' => 'Bedrag in vreemde valuta', - 'existing_attachments' => 'Bestaande bijlagen', - 'date' => 'Datum', - 'interest_date' => 'Rentedatum', - 'book_date' => 'Boekdatum', - 'process_date' => 'Verwerkingsdatum', - 'category' => 'Categorie', - 'tags' => 'Tags', - 'deletePermanently' => 'Verwijderen', - 'cancel' => 'Annuleren', - 'targetdate' => 'Doeldatum', - 'startdate' => 'Startdatum', - 'tag' => 'Tag', - 'under' => 'Onder', - 'symbol' => 'Symbool', - 'code' => 'Code', - 'iban' => 'IBAN', - 'accountNumber' => 'Rekeningnummer', - 'creditCardNumber' => 'Creditcardnummer', - 'has_headers' => 'Kolomnamen op de eerste rij?', - 'date_format' => 'Datumformaat', - 'specifix' => 'Bank- or of bestandsspecifieke opties', - 'attachments[]' => 'Bijlagen', - 'store_new_withdrawal' => 'Nieuwe uitgave opslaan', - 'store_new_deposit' => 'Nieuwe inkomsten opslaan', - 'store_new_transfer' => 'Nieuwe overschrijving opslaan', - 'add_new_withdrawal' => 'Maak nieuwe uitgave', - 'add_new_deposit' => 'Maak nieuwe inkomsten', - 'add_new_transfer' => 'Maak nieuwe overschrijving', - 'title' => 'Titel', - 'notes' => 'Notities', - 'filename' => 'Bestandsnaam', - 'mime' => 'Bestandstype', - 'size' => 'Grootte', - 'trigger' => 'Trigger', - 'stop_processing' => 'Stop met verwerken', - 'start_date' => 'Start van bereik', - 'end_date' => 'Einde van bereik', - 'export_start_range' => 'Start van exportbereik', - 'export_end_range' => 'Einde van exportbereik', - 'export_format' => 'Bestandsformaat', - 'include_attachments' => 'Sla ook geüploade bijlagen op', - 'include_old_uploads' => 'Sla ook geïmporteerde bestanden op', - 'accounts' => 'Exporteer boekingen van deze rekeningen', - 'delete_account' => 'Verwijder rekening ":name"', - 'delete_bill' => 'Verwijder contract ":name"', - 'delete_budget' => 'Verwijder budget ":name"', - 'delete_category' => 'Verwijder categorie ":name"', - 'delete_currency' => 'Verwijder valuta ":name"', - 'delete_journal' => 'Verwijder transactie met omschrijving ":description"', - 'delete_attachment' => 'Verwijder bijlage ":name"', - 'delete_rule' => 'Verwijder regel ":title"', - 'delete_rule_group' => 'Verwijder regelgroep ":title"', - 'delete_link_type' => 'Verwijder linktype ":name"', - 'delete_user' => 'Verwijder gebruiker ":email"', - 'user_areYouSure' => 'Als je gebruiker ":email" verwijdert is alles weg. Je kan dit niet ongedaan maken of ont-verwijderen of wat dan ook. Als je jezelf verwijdert ben je ook je toegang tot deze installatie van Firefly III kwijt.', - 'attachment_areYouSure' => 'Weet je zeker dat je de bijlage met naam ":name" wilt verwijderen?', - 'account_areYouSure' => 'Weet je zeker dat je de rekening met naam ":name" wilt verwijderen?', - 'bill_areYouSure' => 'Weet je zeker dat je het contract met naam ":name" wilt verwijderen?', - 'rule_areYouSure' => 'Weet je zeker dat je regel ":title" wilt verwijderen?', - 'ruleGroup_areYouSure' => 'Weet je zeker dat je regelgroep ":title" wilt verwijderen?', - 'budget_areYouSure' => 'Weet je zeker dat je het budget met naam ":name" wilt verwijderen?', - 'category_areYouSure' => 'Weet je zeker dat je het category met naam ":name" wilt verwijderen?', - 'currency_areYouSure' => 'Weet je zeker dat je de valuta met naam ":name" wilt verwijderen?', - 'piggyBank_areYouSure' => 'Weet je zeker dat je het spaarpotje met naam ":name" wilt verwijderen?', - 'journal_areYouSure' => 'Weet je zeker dat je de transactie met naam ":description" wilt verwijderen?', - 'mass_journal_are_you_sure' => 'Weet je zeker dat je al deze transacties wilt verwijderen?', - 'tag_areYouSure' => 'Weet je zeker dat je de tag met naam ":tag" wilt verwijderen?', - 'journal_link_areYouSure' => 'Weet je zeker dat je de koppeling tussen :source en :destination wilt verwijderen?', - 'linkType_areYouSure' => 'Weet je zeker dat je linktype ":name" (":inward" / ":outward") wilt verwijderen?', - 'permDeleteWarning' => 'Dingen verwijderen uit Firefly III is permanent en kan niet ongedaan gemaakt worden.', - 'mass_make_selection' => 'Je kan items alsnog redden van de ondergang door het vinkje weg te halen.', - 'delete_all_permanently' => 'Verwijder geselecteerde items permanent', - 'update_all_journals' => 'Wijzig deze transacties', - 'also_delete_transactions' => 'Ook de enige transactie verbonden aan deze rekening wordt verwijderd.|Ook alle :count transacties verbonden aan deze rekening worden verwijderd.', - 'also_delete_connections' => 'De enige transactie gelinkt met dit linktype zal deze verbinding verliezen. | Alle :count transacties met dit linktype zullen deze verbinding verliezen.', - 'also_delete_rules' => 'De enige regel in deze regelgroep wordt ook verwijderd.|Alle :count regels in deze regelgroep worden ook verwijderd.', - 'also_delete_piggyBanks' => 'Ook het spaarpotje verbonden aan deze rekening wordt verwijderd.|Ook alle :count spaarpotjes verbonden aan deze rekening worden verwijderd.', - 'bill_keep_transactions' => 'De transactie verbonden aan dit contract blijft bewaard.|De :count transacties verbonden aan dit contract blijven bewaard.', - 'budget_keep_transactions' => 'De transactie verbonden aan dit budget blijft bewaard.|De :count transacties verbonden aan dit budget blijven bewaard.', - 'category_keep_transactions' => 'De transactie verbonden aan deze categorie blijft bewaard.|De :count transacties verbonden aan deze categorie blijven bewaard.', - 'tag_keep_transactions' => 'De transactie verbonden aan deze tag blijft bewaard.|De :count transacties verbonden aan deze tag blijven bewaard.', - 'check_for_updates' => 'Op updates controleren', + 'amount' => 'Bedrag', + 'foreign_amount' => 'Bedrag in vreemde valuta', + 'existing_attachments' => 'Bestaande bijlagen', + 'date' => 'Datum', + 'interest_date' => 'Rentedatum', + 'book_date' => 'Boekdatum', + 'process_date' => 'Verwerkingsdatum', + 'category' => 'Categorie', + 'tags' => 'Tags', + 'deletePermanently' => 'Verwijderen', + 'cancel' => 'Annuleren', + 'targetdate' => 'Doeldatum', + 'startdate' => 'Startdatum', + 'tag' => 'Tag', + 'under' => 'Onder', + 'symbol' => 'Symbool', + 'code' => 'Code', + 'iban' => 'IBAN', + 'accountNumber' => 'Rekeningnummer', + 'creditCardNumber' => 'Creditcardnummer', + 'has_headers' => 'Kolomnamen op de eerste rij?', + 'date_format' => 'Datumformaat', + 'specifix' => 'Bank- or of bestandsspecifieke opties', + 'attachments[]' => 'Bijlagen', + 'store_new_withdrawal' => 'Nieuwe uitgave opslaan', + 'store_new_deposit' => 'Nieuwe inkomsten opslaan', + 'store_new_transfer' => 'Nieuwe overschrijving opslaan', + 'add_new_withdrawal' => 'Maak nieuwe uitgave', + 'add_new_deposit' => 'Maak nieuwe inkomsten', + 'add_new_transfer' => 'Maak nieuwe overschrijving', + 'title' => 'Titel', + 'notes' => 'Notities', + 'filename' => 'Bestandsnaam', + 'mime' => 'Bestandstype', + 'size' => 'Grootte', + 'trigger' => 'Trigger', + 'stop_processing' => 'Stop met verwerken', + 'start_date' => 'Start van bereik', + 'end_date' => 'Einde van bereik', + 'export_start_range' => 'Start van exportbereik', + 'export_end_range' => 'Einde van exportbereik', + 'export_format' => 'Bestandsformaat', + 'include_attachments' => 'Sla ook geüploade bijlagen op', + 'include_old_uploads' => 'Sla ook geïmporteerde bestanden op', + 'accounts' => 'Exporteer boekingen van deze rekeningen', + 'delete_account' => 'Verwijder rekening ":name"', + 'delete_bill' => 'Verwijder contract ":name"', + 'delete_budget' => 'Verwijder budget ":name"', + 'delete_category' => 'Verwijder categorie ":name"', + 'delete_currency' => 'Verwijder valuta ":name"', + 'delete_journal' => 'Verwijder transactie met omschrijving ":description"', + 'delete_attachment' => 'Verwijder bijlage ":name"', + 'delete_rule' => 'Verwijder regel ":title"', + 'delete_rule_group' => 'Verwijder regelgroep ":title"', + 'delete_link_type' => 'Verwijder linktype ":name"', + 'delete_user' => 'Verwijder gebruiker ":email"', + 'delete_recurring' => 'Periodieke transactie ":title" verwijderen', + 'user_areYouSure' => 'Als je gebruiker ":email" verwijdert is alles weg. Je kan dit niet ongedaan maken of ont-verwijderen of wat dan ook. Als je jezelf verwijdert ben je ook je toegang tot deze installatie van Firefly III kwijt.', + 'attachment_areYouSure' => 'Weet je zeker dat je de bijlage met naam ":name" wilt verwijderen?', + 'account_areYouSure' => 'Weet je zeker dat je de rekening met naam ":name" wilt verwijderen?', + 'bill_areYouSure' => 'Weet je zeker dat je het contract met naam ":name" wilt verwijderen?', + 'rule_areYouSure' => 'Weet je zeker dat je regel ":title" wilt verwijderen?', + 'ruleGroup_areYouSure' => 'Weet je zeker dat je regelgroep ":title" wilt verwijderen?', + 'budget_areYouSure' => 'Weet je zeker dat je het budget met naam ":name" wilt verwijderen?', + 'category_areYouSure' => 'Weet je zeker dat je het category met naam ":name" wilt verwijderen?', + 'recurring_areYouSure' => 'Weet je zeker dat je periodieke transactie ":title" wilt verwijderen?', + 'currency_areYouSure' => 'Weet je zeker dat je de valuta met naam ":name" wilt verwijderen?', + 'piggyBank_areYouSure' => 'Weet je zeker dat je het spaarpotje met naam ":name" wilt verwijderen?', + 'journal_areYouSure' => 'Weet je zeker dat je de transactie met naam ":description" wilt verwijderen?', + 'mass_journal_are_you_sure' => 'Weet je zeker dat je al deze transacties wilt verwijderen?', + 'tag_areYouSure' => 'Weet je zeker dat je de tag met naam ":tag" wilt verwijderen?', + 'journal_link_areYouSure' => 'Weet je zeker dat je de koppeling tussen :source en :destination wilt verwijderen?', + 'linkType_areYouSure' => 'Weet je zeker dat je linktype ":name" (":inward" / ":outward") wilt verwijderen?', + 'permDeleteWarning' => 'Dingen verwijderen uit Firefly III is permanent en kan niet ongedaan gemaakt worden.', + 'mass_make_selection' => 'Je kan items alsnog redden van de ondergang door het vinkje weg te halen.', + 'delete_all_permanently' => 'Verwijder geselecteerde items permanent', + 'update_all_journals' => 'Wijzig deze transacties', + 'also_delete_transactions' => 'Ook de enige transactie verbonden aan deze rekening wordt verwijderd.|Ook alle :count transacties verbonden aan deze rekening worden verwijderd.', + 'also_delete_connections' => 'De enige transactie gelinkt met dit linktype zal deze verbinding verliezen. | Alle :count transacties met dit linktype zullen deze verbinding verliezen.', + 'also_delete_rules' => 'De enige regel in deze regelgroep wordt ook verwijderd.|Alle :count regels in deze regelgroep worden ook verwijderd.', + 'also_delete_piggyBanks' => 'Ook het spaarpotje verbonden aan deze rekening wordt verwijderd.|Ook alle :count spaarpotjes verbonden aan deze rekening worden verwijderd.', + 'bill_keep_transactions' => 'De enige transactie verbonden aan dit contract blijft bewaard.|De :count transacties verbonden aan dit contract blijven bewaard.', + 'budget_keep_transactions' => 'De enige transactie verbonden aan dit budget blijft bewaard.|De :count transacties verbonden aan dit budget blijven bewaard.', + 'category_keep_transactions' => 'De enige transactie verbonden aan deze categorie blijft bewaard.|De :count transacties verbonden aan deze categorie blijven bewaard.', + 'recurring_keep_transactions' => 'De enige transactie verbonden aan deze periode transactie blijft bewaard.|De :count transacties verbonden aan deze periode transactie blijven bewaard.', + 'tag_keep_transactions' => 'De enige transactie verbonden aan deze tag blijft bewaard.|De :count transacties verbonden aan deze tag blijven bewaard.', + 'check_for_updates' => 'Op updates controleren', 'email' => 'E-mailadres', 'password' => 'Wachtwoord', @@ -216,11 +219,23 @@ return [ 'country_code' => 'Landcode', 'provider_code' => 'Bank of gegevensprovider', - 'due_date' => 'Vervaldatum', - 'payment_date' => 'Betalingsdatum', - 'invoice_date' => 'Factuurdatum', - 'internal_reference' => 'Interne verwijzing', - 'inward' => 'Binnenwaartse beschrijving', - 'outward' => 'Buitenwaartse beschrijving', - 'rule_group_id' => 'Regelgroep', + 'due_date' => 'Vervaldatum', + 'payment_date' => 'Betalingsdatum', + 'invoice_date' => 'Factuurdatum', + 'internal_reference' => 'Interne verwijzing', + 'inward' => 'Binnenwaartse beschrijving', + 'outward' => 'Buitenwaartse beschrijving', + 'rule_group_id' => 'Regelgroep', + 'transaction_description' => 'Transactiebeschrijving', + 'first_date' => 'Eerste datum', + 'transaction_type' => 'Transactietype', + 'repeat_until' => 'Herhalen tot', + 'recurring_description' => 'Beschrijving van de periodieke transactie', + 'repetition_type' => 'Type herhaling', + 'foreign_currency_id' => 'Vreemde valuta', + 'repetition_end' => 'Stopt met herhalen', + 'repetitions' => 'Herhalingen', + 'calendar' => 'Kalender', + 'weekend' => 'Weekend', + ]; diff --git a/resources/lang/nl_NL/import.php b/resources/lang/nl_NL/import.php index 89381f4bf5..b68492eeaf 100644 --- a/resources/lang/nl_NL/import.php +++ b/resources/lang/nl_NL/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'Je hebt geen rekeningen geselecteerd om uit te importeren.', 'imported_from_account' => 'Geïmporteerd uit ":account"', 'spectre_account_with_number' => 'Rekening :number', + 'job_config_spectre_apply_rules' => 'Regels toepassen', + 'job_config_spectre_apply_rules_text' => 'Standaard worden je regels toegepast op de transacties die je tijdens deze routine importeert. Als je wilt dat dat niet gebeurt, zet dan het vinkje uit.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq rekeningen', 'job_config_bunq_accounts_text' => 'Deze rekeningen zijn geassocieerd met je bunq-account. Kies de rekeningen waar je van wilt importeren, en geef aan waar de gegevens geïmporteerd moeten worden.', 'bunq_no_mapping' => 'Je hebt geen rekeningen geselecteerd om uit te importeren.', 'should_download_config' => 'Download het configuratiebestand voor deze import. Dit maakt toekomstige imports veel eenvoudiger.', 'share_config_file' => 'Als je gegevens hebt geimporteerd van een gewone bank, deel dan je configuratiebestand zodat het makkelijk is voor andere gebruikers om hun gegevens te importeren. Als je je bestand deelt deel je natuurlijk géén privé-gegevens.', - + 'job_config_bunq_apply_rules' => 'Regels toepassen', + 'job_config_bunq_apply_rules_text' => 'Standaard worden je regels toegepast op de transacties die je tijdens deze routine importeert. Als je wilt dat dat niet gebeurt, zet dan het vinkje uit.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/nl_NL/list.php b/resources/lang/nl_NL/list.php index cd17c28646..db765f8c4f 100644 --- a/resources/lang/nl_NL/list.php +++ b/resources/lang/nl_NL/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Laatst ingelogd', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq betalings-ID', + 'repetitions' => 'Herhalingen', + 'title' => 'Titel', + 'transaction_s' => 'Transactie(s)', + 'field' => 'Veld', + 'value' => 'Waarde', ]; diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php index d5d0537e2b..add99c5012 100644 --- a/resources/lang/nl_NL/validation.php +++ b/resources/lang/nl_NL/validation.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'iban' => 'Dit is niet een geldige IBAN.', - 'source_equals_destination' => 'De bronrekening is gelijk aan de doelrekening', + 'source_equals_destination' => 'De bronrekening is gelijk aan de doelrekening.', 'unique_account_number_for_user' => 'Het lijkt erop dat dit rekeningnummer al in gebruik is.', 'unique_iban_for_user' => 'Het lijkt erop dat deze IBAN al in gebruik is.', 'deleted_user' => 'Je kan je niet registreren met dit e-mailadres.', @@ -34,16 +34,22 @@ return [ 'file_attached' => 'Bestand met naam ":name" is met succes geuploaded.', 'must_exist' => 'Het ID in veld :attribute bestaat niet.', 'all_accounts_equal' => 'Alle rekeningen in dit veld moeten gelijk zijn.', - 'invalid_selection' => 'Ongeldige selectie', + 'invalid_selection' => 'Ongeldige selectie.', 'belongs_user' => 'Deze waarde is ongeldig voor dit veld.', 'at_least_one_transaction' => 'Er is op zijn minst één transactie nodig.', + 'at_least_one_repetition' => 'Er is op zijn minst één herhaling nodig.', + 'require_repeat_until' => 'Je moet een aantal herhalingen opgeven, of een einddatum (repeat_until). Niet beide.', 'require_currency_info' => 'De inhoud van dit veld is ongeldig zonder valutagegevens.', 'equal_description' => 'Transactiebeschrijving mag niet gelijk zijn aan globale beschrijving.', 'file_invalid_mime' => 'Bestand ":name" is van het type ":mime", en die kan je niet uploaden.', 'file_too_large' => 'Bestand ":name" is te groot.', - 'belongs_to_user' => 'De waarde van :attribute is onbekend', + 'belongs_to_user' => 'De waarde van :attribute is onbekend.', 'accepted' => ':attribute moet geaccepteerd zijn.', 'bic' => 'Dit is geen geldige BIC.', + 'at_least_one_trigger' => 'De regel moet minstens één trigger hebben.', + 'at_least_one_action' => 'De regel moet minstens één actie hebben.', + 'base64' => 'Dit is geen geldige base64 gecodeerde data.', + 'model_id_invalid' => 'Dit ID past niet bij dit object.', 'more' => ':attribute moet groter zijn dan nul.', 'active_url' => ':attribute is geen geldige URL.', 'after' => ':attribute moet een datum na :date zijn.', @@ -53,8 +59,8 @@ return [ 'array' => ':attribute moet geselecteerde elementen bevatten.', 'unique_for_user' => 'Er is al een entry met deze :attribute.', 'before' => ':attribute moet een datum voor :date zijn.', - 'unique_object_for_user' => 'Deze naam is al in gebruik', - 'unique_account_for_user' => 'This rekeningnaam is already in use', + 'unique_object_for_user' => 'Deze naam is al in gebruik.', + 'unique_account_for_user' => 'Deze rekeningnaam is al in gebruik.', 'between.numeric' => ':attribute moet tussen :min en :max zijn.', 'between.file' => ':attribute moet tussen :min en :max kilobytes zijn.', 'between.string' => ':attribute moet tussen :min en :max karakters zijn.', @@ -85,6 +91,9 @@ return [ 'min.array' => ':attribute moet minimaal :min items bevatten.', 'not_in' => 'Het formaat van :attribute is ongeldig.', 'numeric' => ':attribute moet een nummer zijn.', + 'numeric_native' => 'Het originele bedrag moet een getal zijn.', + 'numeric_destination' => 'Het doelbedrag moet een getal zijn.', + 'numeric_source' => 'Het bronbedrag moet een getal zijn.', 'regex' => ':attribute formaat is ongeldig.', 'required' => ':attribute is verplicht.', 'required_if' => ':attribute is verplicht indien :other gelijk is aan :value.', @@ -109,9 +118,12 @@ return [ 'file' => ':attribute moet een bestand zijn.', 'in_array' => 'Het :attribute veld bestaat niet in :other.', 'present' => 'Het :attribute veld moet aanwezig zijn.', - 'amount_zero' => 'Het totaalbedrag kan niet nul zijn', + 'amount_zero' => 'Het totaalbedrag kan niet nul zijn.', 'unique_piggy_bank_for_user' => 'De naam van de spaarpot moet uniek zijn.', - 'secure_password' => 'Dit is geen sterk wachtwoord. Probeer het nog een keer. Zie ook: http://bit.ly/FF3-password-security', + 'secure_password' => 'Dit is geen sterk wachtwoord. Probeer het nog een keer. Zie ook: http://bit.ly/FF3-password-security.', + 'valid_recurrence_rep_type' => 'Dit is geen geldige herhaling voor periodieke transacties.', + 'valid_recurrence_rep_moment' => 'Ongeldig herhaalmoment voor dit type herhaling.', + 'invalid_account_info' => 'Ongeldige rekeninginformatie.', 'attributes' => [ 'email' => 'e-mailadres', 'description' => 'omschrijving', diff --git a/resources/lang/pl_PL/config.php b/resources/lang/pl_PL/config.php index eedc86c9af..d3fcf79ace 100644 --- a/resources/lang/pl_PL/config.php +++ b/resources/lang/pl_PL/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'pl', - 'locale' => 'pl, Polish, polski, pl_PL, pl_PL.utf8, pl_PL.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y o %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Tydzień %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'D MMMM YYYY', - 'date_time_js' => 'D MMMM YYYY [o] HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Tydzień] w. YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'pl', + 'locale' => 'pl, Polish, polski, pl_PL, pl_PL.utf8, pl_PL.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y o %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Tydzień %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'D MMMM YYYY', + 'date_time_js' => 'D MMMM YYYY [o] HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Tydzień] w. YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Poniedziałek', + 'dow_2' => 'Wtorek', + 'dow_3' => 'Środa', + 'dow_4' => 'Czwartek', + 'dow_5' => 'Piątek', + 'dow_6' => 'Sobota', + 'dow_7' => 'Niedziela', ]; diff --git a/resources/lang/pl_PL/demo.php b/resources/lang/pl_PL/demo.php index b4ca24333f..9c1d4678a3 100644 --- a/resources/lang/pl_PL/demo.php +++ b/resources/lang/pl_PL/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Te wydatki, depozyty i transfery nie są szczególnie pomysłowe. Zostały wygenerowane automatycznie.', 'piggy-banks-index' => 'Jak widać, istnieją trzy skarbonki. Użyj przycisków plus i minus, aby wpłynąć na ilość pieniędzy w każdej skarbonce. Kliknij nazwę skarbonki, aby zobaczyć administrację każdej skarbonki.', 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index c904d80cd6..c23c3c6557 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -180,7 +180,7 @@ return [ 'authorization_request_intro' => ':client prosi o pozwolenie na dostęp do Twojej administracji finansowej. Czy chcesz pozwolić :client na dostęp do tych danych?', 'scopes_will_be_able' => 'Ta aplikacja będzie mogła:', 'button_authorize' => 'Autoryzuj', - 'none_in_select_list' => '(none)', + 'none_in_select_list' => '(żadne)', // check for updates: 'update_check_title' => 'Sprawdź aktualizacje', @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Zeskanuj kod QR za pomocą aplikacji w telefonie, takiej jak Authy lub Google Authenticator i wprowadź wygenerowany kod.', 'pref_two_factor_auth_reset_code' => 'Zresetuj kod weryfikacyjny', 'pref_two_factor_auth_disable_2fa' => 'Wyłącz weryfikację dwuetapową', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Zapisz ustawienia', 'saved_preferences' => 'Preferencje zostały zapisane!', 'preferences_general' => 'Ogólne', @@ -668,7 +669,7 @@ return [ 'bill_will_automatch' => 'Rachunek będzie automatycznie powiązany z pasującymi transakcjami', 'skips_over' => 'pomija', 'bill_store_error' => 'Wystąpił nieoczekiwany błąd podczas zapisywania nowego rachunku. Sprawdź pliki dziennika', - 'list_inactive_rule' => 'inactive rule', + 'list_inactive_rule' => 'nieaktywna reguła', // accounts: 'details_for_asset' => 'Szczegóły konta aktywów ":name"', @@ -820,7 +821,7 @@ return [ 'language' => 'Język', 'new_savings_account' => 'Konto oszczędnościowe :bank_name', 'cash_wallet' => 'Portfel gotówkowy', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Twoje konta', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Saldo na końcu okresu', 'splitByAccount' => 'Podziel według konta', 'coveredWithTags' => 'Objęte tagami', - 'leftUnbalanced' => 'Pozostawiono niewyważone', 'leftInBudget' => 'Pozostało w budżecie', 'sumOfSums' => 'Suma sum', 'noCategory' => '(bez kategorii)', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Konfiguracja', 'firefly_instance_configuration' => 'Opcje konfiguracji dla Firefly III', 'setting_single_user_mode' => 'Tryb pojedynczego użytkownika', - 'setting_single_user_mode_explain' => 'Domyślnie, Firefly III pozwala na jednego (1) użytkownika: Ciebie. Jest to środek bezpieczeństwa uniemożliwiający innym używanie Twojej instalacji, chyba że im pozwolisz. Kolejne rejestracje są zablokowane. Jeżeli odznaczysz to pole, inne osoby będą mogły używać Twojej instalacji Firefly III (zakładając, że jest ona dostępna w Internecie).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Zapisz konfigurację', 'single_user_administration' => 'Administracja użytkownika dla :email', 'edit_user' => 'Modyfikuj użytkownika :email', @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => 'jest (częściowo) zwracane przez', 'is (partially) paid for by_inward' => 'jest (częściowo) opłacane przez', 'is (partially) reimbursed by_inward' => 'jest (częściowo) refundowany przez', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'odnosi się do', '(partially) refunds_outward' => '(częściowo) refundowany', '(partially) pays for_outward' => '(częściowo) płaci za', @@ -1155,8 +1157,9 @@ return [ 'cannot_convert_split_journal' => 'Nie można przekonwertować podzielonej transakcji', // Import page (general strings only) - 'import_index_title' => 'Importuj dane do Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Importuj dane', + 'import_transactions' => 'Importuj transakcje', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Ta funkcja nie jest dostępna, gdy używasz Firefly III w środowisku Sandstorm.io.', @@ -1206,4 +1209,68 @@ return [ 'no_bills_intro_default' => 'Nie masz jeszcze żadnych rachunków. Można tworzyć rachunki, aby śledzić regularne wydatki, takie jak czynsz czy ubezpieczenie.', 'no_bills_imperative_default' => 'Czy masz takie regularne rachunki? Utwórz rachunek i śledź swoje płatności:', 'no_bills_create_default' => 'Utwórz rachunek', + + // recurring transactions + 'recurrences' => 'Cykliczne transakcje', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Utwórz cykliczną transakcję', + 'make_new_recurring' => 'Utwórz cykliczną transakcję', + 'recurring_daily' => 'Codziennie', + 'recurring_weekly' => 'Co tydzień w :weekday', + 'recurring_monthly' => 'Co miesiąc w :dayOfMonth dzień', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Co rok w dniu :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Powiązane transakcje', + 'expected_Withdrawals' => 'Oczekiwane wypłaty', + 'expected_Deposits' => 'Oczekiwane wpłaty', + 'expected_Transfers' => 'Oczekiwane transfery', + 'created_Withdrawals' => 'Utworzone wypłaty', + 'created_Deposits' => 'Utworzone wpłaty', + 'created_Transfers' => 'Utworzone transfery', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tagi', + 'recurring_meta_field_notes' => 'Notatki', + 'recurring_meta_field_bill_id' => 'Rachunek', + 'recurring_meta_field_piggy_bank_id' => 'Skarbonka', + 'create_new_recurrence' => 'Utwórz nową cykliczną transakcję', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(brak waluty)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Powtarzaj aż do dnia', + 'repeat_times' => 'Powtarzaj określoną liczbę razy', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/pl_PL/form.php b/resources/lang/pl_PL/form.php index e72c91843f..fe09cafffd 100644 --- a/resources/lang/pl_PL/form.php +++ b/resources/lang/pl_PL/form.php @@ -24,66 +24,66 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Nazwa banku', - 'bank_balance' => 'Saldo', - 'savings_balance' => 'Saldo konta oszczędnościowego', - 'credit_card_limit' => 'Limit karty kredytowej', - 'automatch' => 'Dopasuj automatycznie', - 'skip' => 'Pomiń', - 'name' => 'Nazwa', - 'active' => 'Aktywny', - 'amount_min' => 'Minimalna kwota', - 'amount_max' => 'Maksymalna kwota', - 'match' => 'Dopasowanie', - 'strict' => 'Tryb ścisły', - 'repeat_freq' => 'Powtarza się', - 'journal_currency_id' => 'Waluta', - 'currency_id' => 'Waluta', - 'transaction_currency_id' => 'Waluta', - 'external_ip' => 'Zewnętrzny adres IP Twojego serwera', - 'attachments' => 'Załączniki', - 'journal_amount' => 'Kwota', - 'journal_source_account_name' => 'Konto przychodów (źródło)', - 'journal_source_account_id' => 'Konto aktywów (źródło)', - 'BIC' => 'BIC', - 'verify_password' => 'Sprawdź bezpieczeństwo hasła', - 'source_account' => 'Konto źródłowe', - 'destination_account' => 'Konto docelowe', - 'journal_destination_account_id' => 'Konto aktywów (przeznaczenie)', - 'asset_destination_account' => 'Konto aktywów (przeznaczenie)', - 'asset_source_account' => 'Konto aktywów (źródło)', - 'journal_description' => 'Opis', - 'note' => 'Notatki', - 'split_journal' => 'Podziel tę transakcję', - 'split_journal_explanation' => 'Podziel transakcję na wiele części', - 'currency' => 'Waluta', - 'account_id' => 'Konto aktywów', - 'budget_id' => 'Budżet', - 'openingBalance' => 'Bilans otwarcia', - 'tagMode' => 'Tryb tagów', - 'tag_position' => 'Lokalizacja taga', - 'virtualBalance' => 'Wirtualne saldo', - 'targetamount' => 'Kwota docelowa', - 'accountRole' => 'Rola konta', - 'openingBalanceDate' => 'Data salda otwarcia', - 'ccType' => 'Plan płatności kartą kredytową', - 'ccMonthlyPaymentDate' => 'Miesięczny termin spłaty karty kredytowej', - 'piggy_bank_id' => 'Skarbonka', - 'returnHere' => 'Wróć tutaj', - 'returnHereExplanation' => 'Po zapisaniu, wrócić tutaj.', - 'returnHereUpdateExplanation' => 'Po aktualizacji, wróć tutaj.', - 'description' => 'Opis', - 'expense_account' => 'Konto wydatków', - 'revenue_account' => 'Konto przychodów', - 'decimal_places' => 'Miejsca dziesiętne', - 'exchange_rate_instruction' => 'Zagraniczne waluty', - 'source_amount' => 'Kwota (źródło)', - 'destination_amount' => 'Kwota (przeznaczenie)', - 'native_amount' => 'Źródłowa kwota', - 'new_email_address' => 'Nowy adres e-mail', - 'verification' => 'Weryfikacja', - 'api_key' => 'Klucz API', - 'remember_me' => 'Zapamiętaj mnie', + 'bank_name' => 'Nazwa banku', + 'bank_balance' => 'Saldo', + 'savings_balance' => 'Saldo konta oszczędnościowego', + 'credit_card_limit' => 'Limit karty kredytowej', + 'automatch' => 'Dopasuj automatycznie', + 'skip' => 'Pomiń', + 'name' => 'Nazwa', + 'active' => 'Aktywny', + 'amount_min' => 'Minimalna kwota', + 'amount_max' => 'Maksymalna kwota', + 'match' => 'Dopasowanie', + 'strict' => 'Tryb ścisły', + 'repeat_freq' => 'Powtarza się', + 'journal_currency_id' => 'Waluta', + 'currency_id' => 'Waluta', + 'transaction_currency_id' => 'Waluta', + 'external_ip' => 'Zewnętrzny adres IP Twojego serwera', + 'attachments' => 'Załączniki', + 'journal_amount' => 'Kwota', + 'journal_source_name' => 'Revenue account (source)', + 'journal_source_id' => 'Asset account (source)', + 'BIC' => 'BIC', + 'verify_password' => 'Sprawdź bezpieczeństwo hasła', + 'source_account' => 'Konto źródłowe', + 'destination_account' => 'Konto docelowe', + 'journal_destination_id' => 'Asset account (destination)', + 'asset_destination_account' => 'Konto aktywów (przeznaczenie)', + 'asset_source_account' => 'Konto aktywów (źródło)', + 'journal_description' => 'Opis', + 'note' => 'Notatki', + 'split_journal' => 'Podziel tę transakcję', + 'split_journal_explanation' => 'Podziel transakcję na wiele części', + 'currency' => 'Waluta', + 'account_id' => 'Konto aktywów', + 'budget_id' => 'Budżet', + 'openingBalance' => 'Bilans otwarcia', + 'tagMode' => 'Tryb tagów', + 'tag_position' => 'Lokalizacja taga', + 'virtualBalance' => 'Wirtualne saldo', + 'targetamount' => 'Kwota docelowa', + 'accountRole' => 'Rola konta', + 'openingBalanceDate' => 'Data salda otwarcia', + 'ccType' => 'Plan płatności kartą kredytową', + 'ccMonthlyPaymentDate' => 'Miesięczny termin spłaty karty kredytowej', + 'piggy_bank_id' => 'Skarbonka', + 'returnHere' => 'Wróć tutaj', + 'returnHereExplanation' => 'Po zapisaniu, wrócić tutaj.', + 'returnHereUpdateExplanation' => 'Po aktualizacji, wróć tutaj.', + 'description' => 'Opis', + 'expense_account' => 'Konto wydatków', + 'revenue_account' => 'Konto przychodów', + 'decimal_places' => 'Miejsca dziesiętne', + 'exchange_rate_instruction' => 'Zagraniczne waluty', + 'source_amount' => 'Kwota (źródło)', + 'destination_amount' => 'Kwota (przeznaczenie)', + 'native_amount' => 'Źródłowa kwota', + 'new_email_address' => 'Nowy adres e-mail', + 'verification' => 'Weryfikacja', + 'api_key' => 'Klucz API', + 'remember_me' => 'Zapamiętaj mnie', 'source_account_asset' => 'Konto źródłowe (konto aktywów)', 'destination_account_expense' => 'Konto docelowe (konto wydatków)', @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Konwertuj wpłatę', 'convert_Transfer' => 'Konwertuj transfer', - 'amount' => 'Kwota', - 'foreign_amount' => 'Kwota zagraniczna', - 'existing_attachments' => 'Istniejące załączniki', - 'date' => 'Data', - 'interest_date' => 'Data odsetek', - 'book_date' => 'Data księgowania', - 'process_date' => 'Data przetworzenia', - 'category' => 'Kategoria', - 'tags' => 'Tagi', - 'deletePermanently' => 'Usuń trwale', - 'cancel' => 'Anuluj', - 'targetdate' => 'Data docelowa', - 'startdate' => 'Data rozpoczęcia', - 'tag' => 'Tag', - 'under' => 'Poniżej', - 'symbol' => 'Symbol', - 'code' => 'Kod', - 'iban' => 'IBAN', - 'accountNumber' => 'Numer konta', - 'creditCardNumber' => 'Numer karty kredytowej', - 'has_headers' => 'Nagłówki', - 'date_format' => 'Format daty', - 'specifix' => 'Poprawki dla banku lub pliku', - 'attachments[]' => 'Załączniki', - 'store_new_withdrawal' => 'Zapisz nową wypłatę', - 'store_new_deposit' => 'Zapisz nową wpłatę', - 'store_new_transfer' => 'Zapisz nowy transfer', - 'add_new_withdrawal' => 'Dodaj nową wypłatę', - 'add_new_deposit' => 'Dodaj nową wpłatę', - 'add_new_transfer' => 'Dodaj nowy transfer', - 'title' => 'Tytuł', - 'notes' => 'Notatki', - 'filename' => 'Nazwa pliku', - 'mime' => 'Typ MIME', - 'size' => 'Rozmiar', - 'trigger' => 'Wyzwalacz', - 'stop_processing' => 'Zatrzymaj przetwarzanie', - 'start_date' => 'Początek zakresu', - 'end_date' => 'Koniec zakresu', - 'export_start_range' => 'Początek okresu eksportu', - 'export_end_range' => 'Koniec okresu eksportu', - 'export_format' => 'Format pliku', - 'include_attachments' => 'Uwzględnij dołączone załączniki', - 'include_old_uploads' => 'Dołącz zaimportowane dane', - 'accounts' => 'Eksportuj transakcje z tych kont', - 'delete_account' => 'Usuń konto ":name"', - 'delete_bill' => 'Usuń rachunek ":name"', - 'delete_budget' => 'Usuń budżet ":name"', - 'delete_category' => 'Usuń kategorię ":name"', - 'delete_currency' => 'Usuń walutę ":name"', - 'delete_journal' => 'Usuń transakcję z opisem ":description"', - 'delete_attachment' => 'Usuń załącznik ":name"', - 'delete_rule' => 'Usuń regułę ":title"', - 'delete_rule_group' => 'Usuń grupę reguł ":title"', - 'delete_link_type' => 'Usuń typ łącza ":name"', - 'delete_user' => 'Usuń użytkownika ":email"', - 'user_areYouSure' => 'Jeśli usuniesz użytkownika ":email", wszystko zniknie. Nie ma cofania, przywracania ani czegokolwiek. Jeśli usuniesz siebie, stracisz dostęp do tej instalacji Firefly III.', - 'attachment_areYouSure' => 'Czy na pewno chcesz usunąć załącznik o nazwie ":name"?', - 'account_areYouSure' => 'Czy na pewno chcesz usunąć konto o nazwie ":name"?', - 'bill_areYouSure' => 'Czy na pewno chcesz usunąć rachunek o nazwie ":name"?', - 'rule_areYouSure' => 'Czy na pewno chcesz usunąć regułę o nazwie ":name"?', - 'ruleGroup_areYouSure' => 'Czy na pewno chcesz usunąć grupę reguł o nazwie ":name"?', - 'budget_areYouSure' => 'Czy na pewno chcesz usunąć budżet o nazwie ":name"?', - 'category_areYouSure' => 'Czy na pewno chcesz usunąć kategorię o nazwie ":name"?', - 'currency_areYouSure' => 'Czy na pewno chcesz usunąć walutę o nazwie ":name"?', - 'piggyBank_areYouSure' => 'Czy na pewno chcesz usunąć skarbonkę o nazwie ":name"?', - 'journal_areYouSure' => 'Czy na pewno chcesz usunąć transakcję opisaną ":description"?', - 'mass_journal_are_you_sure' => 'Czy na pewno chcesz usunąć te transakcje?', - 'tag_areYouSure' => 'Czy na pewno chcesz usunąć tag ":tag"?', - 'journal_link_areYouSure' => 'Czy na pewno chcesz usunąć powiązanie między :source a :destination?', - 'linkType_areYouSure' => 'Czy na pewno chcesz usunąć typ łącza ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Usuwanie rzeczy z Firefly III jest trwałe i nie można tego cofnąć.', - 'mass_make_selection' => 'Nadal możesz zapobiec usunięciu elementów, odznaczając je.', - 'delete_all_permanently' => 'Trwale usuń zaznaczone', - 'update_all_journals' => 'Zmodyfikuj te transakcje', - 'also_delete_transactions' => 'Jedyna transakcja powiązana z tym kontem zostanie również usunięta.|Wszystkie transakcje (:count) powiązane z tym kontem zostaną również usunięta.', - 'also_delete_connections' => 'Jedyna transakcja połączona z tym typem łącza utraci to połączenie.|Wszystkie transakcje (:count) połączone tym typem łącza utracą swoje połączenie.', - 'also_delete_rules' => 'Jedyna reguła połączona z tą grupą reguł zostanie również usunięta.|Wszystkie reguły (:count) połączone tą grupą reguł zostaną również usunięte.', - 'also_delete_piggyBanks' => 'Jedyna skarbonka połączona z tym kontem zostanie również usunięta.|Wszystkie skarbonki (:count) połączone z tym kontem zostaną również usunięte.', - 'bill_keep_transactions' => 'Jedyna transakcja związana z tym rachunkiem nie zostanie usunięta. | Wszystkie :count transakcje związane z tym rachunkiem zostaną oszczędzone.', - 'budget_keep_transactions' => 'Jedyna transakcja związana z tym budżetem nie zostanie usunięta.|Wszystkie transakcje (:count) związane z tym budżetem zostaną oszczędzone.', - 'category_keep_transactions' => 'Jedyna transakcja związana z tą kategorią nie zostanie usunięta.|Wszystkie transakcje (:count) związane z tą kategorią zostaną oszczędzone.', - 'tag_keep_transactions' => 'Jedyna transakcja związana z tym tagiem nie zostanie usunięta.|Wszystkie transakcje (:count) związane z tym tagiem zostaną oszczędzone.', - 'check_for_updates' => 'Sprawdź aktualizacje', + 'amount' => 'Kwota', + 'foreign_amount' => 'Kwota zagraniczna', + 'existing_attachments' => 'Istniejące załączniki', + 'date' => 'Data', + 'interest_date' => 'Data odsetek', + 'book_date' => 'Data księgowania', + 'process_date' => 'Data przetworzenia', + 'category' => 'Kategoria', + 'tags' => 'Tagi', + 'deletePermanently' => 'Usuń trwale', + 'cancel' => 'Anuluj', + 'targetdate' => 'Data docelowa', + 'startdate' => 'Data rozpoczęcia', + 'tag' => 'Tag', + 'under' => 'Poniżej', + 'symbol' => 'Symbol', + 'code' => 'Kod', + 'iban' => 'IBAN', + 'accountNumber' => 'Numer konta', + 'creditCardNumber' => 'Numer karty kredytowej', + 'has_headers' => 'Nagłówki', + 'date_format' => 'Format daty', + 'specifix' => 'Poprawki dla banku lub pliku', + 'attachments[]' => 'Załączniki', + 'store_new_withdrawal' => 'Zapisz nową wypłatę', + 'store_new_deposit' => 'Zapisz nową wpłatę', + 'store_new_transfer' => 'Zapisz nowy transfer', + 'add_new_withdrawal' => 'Dodaj nową wypłatę', + 'add_new_deposit' => 'Dodaj nową wpłatę', + 'add_new_transfer' => 'Dodaj nowy transfer', + 'title' => 'Tytuł', + 'notes' => 'Notatki', + 'filename' => 'Nazwa pliku', + 'mime' => 'Typ MIME', + 'size' => 'Rozmiar', + 'trigger' => 'Wyzwalacz', + 'stop_processing' => 'Zatrzymaj przetwarzanie', + 'start_date' => 'Początek zakresu', + 'end_date' => 'Koniec zakresu', + 'export_start_range' => 'Początek okresu eksportu', + 'export_end_range' => 'Koniec okresu eksportu', + 'export_format' => 'Format pliku', + 'include_attachments' => 'Uwzględnij dołączone załączniki', + 'include_old_uploads' => 'Dołącz zaimportowane dane', + 'accounts' => 'Eksportuj transakcje z tych kont', + 'delete_account' => 'Usuń konto ":name"', + 'delete_bill' => 'Usuń rachunek ":name"', + 'delete_budget' => 'Usuń budżet ":name"', + 'delete_category' => 'Usuń kategorię ":name"', + 'delete_currency' => 'Usuń walutę ":name"', + 'delete_journal' => 'Usuń transakcję z opisem ":description"', + 'delete_attachment' => 'Usuń załącznik ":name"', + 'delete_rule' => 'Usuń regułę ":title"', + 'delete_rule_group' => 'Usuń grupę reguł ":title"', + 'delete_link_type' => 'Usuń typ łącza ":name"', + 'delete_user' => 'Usuń użytkownika ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Jeśli usuniesz użytkownika ":email", wszystko zniknie. Nie ma cofania, przywracania ani czegokolwiek. Jeśli usuniesz siebie, stracisz dostęp do tej instalacji Firefly III.', + 'attachment_areYouSure' => 'Czy na pewno chcesz usunąć załącznik o nazwie ":name"?', + 'account_areYouSure' => 'Czy na pewno chcesz usunąć konto o nazwie ":name"?', + 'bill_areYouSure' => 'Czy na pewno chcesz usunąć rachunek o nazwie ":name"?', + 'rule_areYouSure' => 'Czy na pewno chcesz usunąć regułę o nazwie ":name"?', + 'ruleGroup_areYouSure' => 'Czy na pewno chcesz usunąć grupę reguł o nazwie ":name"?', + 'budget_areYouSure' => 'Czy na pewno chcesz usunąć budżet o nazwie ":name"?', + 'category_areYouSure' => 'Czy na pewno chcesz usunąć kategorię o nazwie ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Czy na pewno chcesz usunąć walutę o nazwie ":name"?', + 'piggyBank_areYouSure' => 'Czy na pewno chcesz usunąć skarbonkę o nazwie ":name"?', + 'journal_areYouSure' => 'Czy na pewno chcesz usunąć transakcję opisaną ":description"?', + 'mass_journal_are_you_sure' => 'Czy na pewno chcesz usunąć te transakcje?', + 'tag_areYouSure' => 'Czy na pewno chcesz usunąć tag ":tag"?', + 'journal_link_areYouSure' => 'Czy na pewno chcesz usunąć powiązanie między :source a :destination?', + 'linkType_areYouSure' => 'Czy na pewno chcesz usunąć typ łącza ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Usuwanie rzeczy z Firefly III jest trwałe i nie można tego cofnąć.', + 'mass_make_selection' => 'Nadal możesz zapobiec usunięciu elementów, odznaczając je.', + 'delete_all_permanently' => 'Trwale usuń zaznaczone', + 'update_all_journals' => 'Zmodyfikuj te transakcje', + 'also_delete_transactions' => 'Jedyna transakcja powiązana z tym kontem zostanie również usunięta.|Wszystkie transakcje (:count) powiązane z tym kontem zostaną również usunięta.', + 'also_delete_connections' => 'Jedyna transakcja połączona z tym typem łącza utraci to połączenie.|Wszystkie transakcje (:count) połączone tym typem łącza utracą swoje połączenie.', + 'also_delete_rules' => 'Jedyna reguła połączona z tą grupą reguł zostanie również usunięta.|Wszystkie reguły (:count) połączone tą grupą reguł zostaną również usunięte.', + 'also_delete_piggyBanks' => 'Jedyna skarbonka połączona z tym kontem zostanie również usunięta.|Wszystkie skarbonki (:count) połączone z tym kontem zostaną również usunięte.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Sprawdź aktualizacje', 'email' => 'Adres email', 'password' => 'Hasło', @@ -186,10 +189,10 @@ return [ 'blocked_code' => 'Powód blokady', // import - 'apply_rules' => 'Apply rules', - 'artist' => 'Artist', + 'apply_rules' => 'Zastosuj reguły', + 'artist' => 'Artysta', 'album' => 'Album', - 'song' => 'Song', + 'song' => 'Piosenka', // admin @@ -216,11 +219,23 @@ return [ 'country_code' => 'Kod kraju', 'provider_code' => 'Dostawca banku lub danych', - 'due_date' => 'Termin realizacji', - 'payment_date' => 'Data płatności', - 'invoice_date' => 'Data faktury', - 'internal_reference' => 'Wewnętrzny numer', - 'inward' => 'Opis wewnętrzny', - 'outward' => 'Opis zewnętrzny', - 'rule_group_id' => 'Grupa reguł', + 'due_date' => 'Termin realizacji', + 'payment_date' => 'Data płatności', + 'invoice_date' => 'Data faktury', + 'internal_reference' => 'Wewnętrzny numer', + 'inward' => 'Opis wewnętrzny', + 'outward' => 'Opis zewnętrzny', + 'rule_group_id' => 'Grupa reguł', + 'transaction_description' => 'Opis transakcji', + 'first_date' => 'Data początkowa', + 'transaction_type' => 'Typ transakcji', + 'repeat_until' => 'Powtarzaj aż', + 'recurring_description' => 'Opis cyklicznej transakcji', + 'repetition_type' => 'Typ powtórzeń', + 'foreign_currency_id' => 'Zagraniczna waluta', + 'repetition_end' => 'Koniec powtórzeń', + 'repetitions' => 'Powtórzenia', + 'calendar' => 'Kalendarz', + 'weekend' => 'Weekend', + ]; diff --git a/resources/lang/pl_PL/import.php b/resources/lang/pl_PL/import.php index 6b2aa6c068..8e2b109e85 100644 --- a/resources/lang/pl_PL/import.php +++ b/resources/lang/pl_PL/import.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ // ALL breadcrumbs and subtitles: - 'index_breadcrumb' => 'Import data into Firefly III', + 'index_breadcrumb' => 'Importuj dane do Firefly III', 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', @@ -37,17 +37,17 @@ return [ 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', // import provider strings (index): 'button_fake' => 'Fake an import', - 'button_file' => 'Import a file', - 'button_bunq' => 'Import from bunq', - 'button_spectre' => 'Import using Spectre', - 'button_plaid' => 'Import using Plaid', - 'button_yodlee' => 'Import using Yodlee', - 'button_quovo' => 'Import using Quovo', + 'button_file' => 'Importuj plik', + 'button_bunq' => 'Importuj z bunq', + 'button_spectre' => 'Importuj za pomocą Spectre', + 'button_plaid' => 'Importuj za pomocą Plaid', + 'button_yodlee' => 'Importuj za pomocą Yodlee', + 'button_quovo' => 'Importuj za pomocą Quovo', // global config box (index) - 'global_config_title' => 'Global import configuration', + 'global_config_title' => 'Globalna konfiguracja importu', 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', // prerequisites box (index) - 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_title' => 'Wymagania importu', 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', 'do_prereq_fake' => 'Prerequisites for the fake provider', 'do_prereq_file' => 'Prerequisites for file imports', @@ -57,7 +57,7 @@ return [ 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', // provider config box (index) - 'can_config_title' => 'Import configuration', + 'can_config_title' => 'Importuj konfigurację', 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', 'do_config_fake' => 'Configuration for the fake provider', 'do_config_file' => 'Configuration for file imports', @@ -107,48 +107,51 @@ return [ 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as this page indicates. The default value will parse dates that look like this: :dateExample.', 'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', 'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.', - 'job_config_uc_apply_rules_title' => 'Apply rules', + 'job_config_uc_apply_rules_title' => 'Zastosuj reguły', 'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.', 'job_config_uc_specifics_title' => 'Bank-specific options', 'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files but it\'s not listed here, please open an issue on GitHub.', - 'job_config_uc_submit' => 'Continue', + 'job_config_uc_submit' => 'Kontynuuj', 'invalid_import_account' => 'You have selected an invalid account to import into.', // job configuration for Spectre: - 'job_config_spectre_login_title' => 'Choose your login', + 'job_config_spectre_login_title' => 'Wybierz swój login', 'job_config_spectre_login_text' => 'Firefly III has found :count existing login(s) in your Spectre account. Which one would you like to use to import from?', - 'spectre_login_status_active' => 'Active', - 'spectre_login_status_inactive' => 'Inactive', - 'spectre_login_status_disabled' => 'Disabled', + 'spectre_login_status_active' => 'Aktywny', + 'spectre_login_status_inactive' => 'Nieaktywny', + 'spectre_login_status_disabled' => 'Wyłączony', 'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', 'job_config_spectre_accounts_title' => 'Select accounts to import from', 'job_config_spectre_accounts_text' => 'You have selected ":name" (:country). You have :count account(s) available from this provider. Please select the Firefly III asset account(s) where the transactions from these accounts should be stored. Remember, in order to import data both the Firefly III account and the ":name"-account must have the same currency.', 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', - 'spectre_do_not_import' => '(do not import)', - 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', - 'imported_from_account' => 'Imported from ":account"', - 'spectre_account_with_number' => 'Account :number', + 'spectre_do_not_import' => '(nie importuj)', + 'spectre_no_mapping' => 'Wygląda na to, że nie wybrałeś żadnych kont z których można zaimportować dane.', + 'imported_from_account' => 'Zaimportowane z ":account"', + 'spectre_account_with_number' => ':number konta', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: - 'job_config_bunq_accounts_title' => 'bunq accounts', + 'job_config_bunq_accounts_title' => 'konta bunq', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', 'spectre_extra_key_status' => 'Status', - 'spectre_extra_key_card_type' => 'Card type', - 'spectre_extra_key_account_name' => 'Account name', - 'spectre_extra_key_client_name' => 'Client name', - 'spectre_extra_key_account_number' => 'Account number', - 'spectre_extra_key_blocked_amount' => 'Blocked amount', - 'spectre_extra_key_available_amount' => 'Available amount', - 'spectre_extra_key_credit_limit' => 'Credit limit', - 'spectre_extra_key_interest_rate' => 'Interest rate', + 'spectre_extra_key_card_type' => 'Typ karty', + 'spectre_extra_key_account_name' => 'Nazwa konta', + 'spectre_extra_key_client_name' => 'Nazwa klienta', + 'spectre_extra_key_account_number' => 'Numer konta', + 'spectre_extra_key_blocked_amount' => 'Zablokowana kwota', + 'spectre_extra_key_available_amount' => 'Dostępna kwota', + 'spectre_extra_key_credit_limit' => 'Limit kredytowy', + 'spectre_extra_key_interest_rate' => 'Oprocentowanie', 'spectre_extra_key_expiry_date' => 'Expiry date', - 'spectre_extra_key_open_date' => 'Open date', - 'spectre_extra_key_current_time' => 'Current time', + 'spectre_extra_key_open_date' => 'Data otwarcia', + 'spectre_extra_key_current_time' => 'Aktualny czas', 'spectre_extra_key_current_date' => 'Current date', 'spectre_extra_key_cards' => 'Cards', 'spectre_extra_key_units' => 'Units', @@ -177,15 +180,15 @@ return [ 'job_config_roles_no_example' => 'No example data available', 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', - 'job_config_roles_colum_count' => 'Column', + 'job_config_roles_colum_count' => 'Kolumna', // job config for the file provider (stage: mapping): 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', - 'job_config_field_value' => 'Field value', - 'job_config_field_mapped' => 'Mapped to', + 'job_config_field_value' => 'Wartość pola', + 'job_config_field_mapped' => 'Zmapowane na', 'map_do_not_map' => '(nie mapuj)', - 'job_config_map_submit' => 'Start the import', + 'job_config_map_submit' => 'Rozpocznij import', // import status page: @@ -196,11 +199,11 @@ return [ 'status_job_running' => 'Please wait, running the import...', 'status_job_storing' => 'Please wait, storing data...', 'status_job_rules' => 'Please wait, running rules...', - 'status_fatal_title' => 'Fatal error', + 'status_fatal_title' => 'Błąd krytyczny', 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', - 'status_finished_title' => 'Import finished', - 'status_finished_text' => 'The import has finished.', + 'status_finished_title' => 'Import zakończony', + 'status_finished_text' => 'Importowanie zostało zakończone.', 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', 'unknown_import_result' => 'Unknown import result', 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', diff --git a/resources/lang/pl_PL/list.php b/resources/lang/pl_PL/list.php index a18fe7286e..f2a7bfedff 100644 --- a/resources/lang/pl_PL/list.php +++ b/resources/lang/pl_PL/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transakcja(e)', + 'field' => 'Pole', + 'value' => 'Wartość', ]; diff --git a/resources/lang/pl_PL/validation.php b/resources/lang/pl_PL/validation.php index 0ae9b71c19..2cd06c8b85 100644 --- a/resources/lang/pl_PL/validation.php +++ b/resources/lang/pl_PL/validation.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'iban' => 'To nie jest prawidłowy IBAN.', - 'source_equals_destination' => 'Konto źródłowe jest równe kontu docelowemu', + 'source_equals_destination' => 'The source account equals the destination account.', 'unique_account_number_for_user' => 'Wygląda na to, że ten numer konta jest już w użyciu.', 'unique_iban_for_user' => 'Wygląda na to, że ten IBAN jest już w użyciu.', 'deleted_user' => 'Ze względu na zabezpieczenia nie możesz się zarejestrować używając tego adresu e-mail.', @@ -34,16 +34,22 @@ return [ 'file_attached' => 'Pomyślnie wgrano plik ":name".', 'must_exist' => 'Identyfikator w polu :attribute nie istnieje w bazie danych.', 'all_accounts_equal' => 'Wszystkie konta w tym polu muszą być takie same.', - 'invalid_selection' => 'Twój wybór jest nieprawidłowy', + 'invalid_selection' => 'Your selection is invalid.', 'belongs_user' => 'Ta wartość jest nieprawidłowa dla tego pola.', 'at_least_one_transaction' => 'Wymaga co najmniej jednej transakcji.', + 'at_least_one_repetition' => 'Need at least one repetition.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'Treść tego pola jest nieprawidłowa bez informacji o walucie.', 'equal_description' => 'Opis transakcji nie powinien być równy globalnemu opisowi.', 'file_invalid_mime' => 'Plik ":name" jest typu ":mime", który nie jest akceptowany jako nowy plik do przekazania.', 'file_too_large' => 'Plik ":name" jest zbyt duży.', - 'belongs_to_user' => 'Wartość :attribute jest nieznana', + 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => ':attribute musi zostać zaakceptowany.', 'bic' => 'To nie jest prawidłowy BIC.', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute musi być większy od zera.', 'active_url' => ':attribute nie jest prawidłowym adresem URL.', 'after' => ':attribute musi być datą późniejszą od :date.', @@ -53,8 +59,8 @@ return [ 'array' => ':attribute musi być tablicą.', 'unique_for_user' => 'Istnieje już wpis z tym :attribute.', 'before' => ':attribute musi być wcześniejszą datą w stosunku do :date.', - 'unique_object_for_user' => 'Ta nazwa jest już w użyciu', - 'unique_account_for_user' => 'Ta nazwa konta jest już w użyciu', + 'unique_object_for_user' => 'This name is already in use.', + 'unique_account_for_user' => 'This account name is already in use.', 'between.numeric' => ':attribute musi się mieścić w zakresie pomiędzy :min a :max.', 'between.file' => ':attribute musi się mieścić w zakresie pomiędzy :min oraz :max kilobajtów.', 'between.string' => ':attribute musi zawierać pomiędzy :min a :max znaków.', @@ -85,6 +91,9 @@ return [ 'min.array' => ':attribute musi zawierać przynajmniej :min elementów.', 'not_in' => 'Wybrany :attribute jest nieprawidłowy.', 'numeric' => ':attribute musi byc liczbą.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'Format :attribute jest nieprawidłowy.', 'required' => 'Pole :attribute jest wymagane.', 'required_if' => 'Pole :attribute jest wymagane gdy :other jest :value.', @@ -109,9 +118,12 @@ return [ 'file' => ':attribute musi być plikiem.', 'in_array' => 'Pole :attribute nie istnieje w :other.', 'present' => 'Pole :attribute musi być obecne.', - 'amount_zero' => 'Całkowita kwota nie może być zerem', + 'amount_zero' => 'The total amount cannot be zero.', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security.', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions.', + 'valid_recurrence_rep_moment' => 'Invalid repetition moment for this type of repetition.', + 'invalid_account_info' => 'Invalid account information.', 'attributes' => [ 'email' => 'adres e-mail', 'description' => 'opis', diff --git a/resources/lang/pt_BR/config.php b/resources/lang/pt_BR/config.php index ce507e8a05..f0401d93b3 100644 --- a/resources/lang/pt_BR/config.php +++ b/resources/lang/pt_BR/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'pt-br', - 'locale' => 'pt-br, pt_BR, pt_BR.utf8, pt_BR.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e de %B de %Y', - 'date_time' => '%B %e, %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Semana %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'MMMM Do, YYYY', - 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'pt-br', + 'locale' => 'pt-br, pt_BR, pt_BR.utf8, pt_BR.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e de %B de %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%B %e, %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Semana %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'MMMM Do, YYYY', + 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/pt_BR/demo.php b/resources/lang/pt_BR/demo.php index b6960bda12..a52519475f 100644 --- a/resources/lang/pt_BR/demo.php +++ b/resources/lang/pt_BR/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Estas despesas, depósitos e transferências não são fantasiosas. Elas foram geradas automaticamente.', 'piggy-banks-index' => 'Como você pode ver, existem três cofrinhos. Use o sinal de mais e menos botões para influenciar a quantidade de dinheiro em cada cofrinho. Clique no nome do cofrinho para ver a administração de cada cofrinho.', 'import-index' => 'Qualquer arquivo CSV pode ser importado para o Firefly III. Importações de dados de bunq e Specter também são suportadas. Outros bancos e agregadores financeiros serão implementados futuramente. Como usuário de demonstração, no entanto, você só pode ver o provedor "falso" em ação. Ele irá gerar transações aleatórias para lhe mostrar como funciona o processo.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index 988bf0b85a..41ad6e78ff 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scaneie o código QR com um aplicativo em seu telefone como Authy ou Google Authenticator e insira o código gerado.', 'pref_two_factor_auth_reset_code' => 'Redefinir o código de verificação', 'pref_two_factor_auth_disable_2fa' => 'Desativar verificação em duas etapas', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Salvar definições', 'saved_preferences' => 'Preferências salvas!', 'preferences_general' => 'Geral', @@ -820,7 +821,7 @@ return [ 'language' => 'Idioma', 'new_savings_account' => 'Conta de poupança :bank_name', 'cash_wallet' => 'Carteira de dinheiro', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Suas contas', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Saldo no final do período', 'splitByAccount' => 'Dividir por conta', 'coveredWithTags' => 'Coberto com tags', - 'leftUnbalanced' => 'Deixar desequilibrado', 'leftInBudget' => 'Deixou no orçamento', 'sumOfSums' => 'Soma dos montantes', 'noCategory' => '(sem categoria)', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Configuração', 'firefly_instance_configuration' => 'Opções de configuração para Firefly III', 'setting_single_user_mode' => 'Modo de usuário único', - 'setting_single_user_mode_explain' => 'Por padrão, o Firefly III aceita apenas um (1) registro: você. Esta é uma medida de segurança, impedindo que outros usem sua instância, a menos que você permita. Os registros futuros estão bloqueados. Quando você desmarca essa caixa, outros podem usar sua instância, assumindo que podem alcançá-la (quando conectados à Internet).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Configuração da Loja', 'single_user_administration' => 'Administração de usuários para :email', 'edit_user' => 'Editar usuário :email', @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => 'é (parcialmente) devolvido por', 'is (partially) paid for by_inward' => 'é (parcialmente) pago por', 'is (partially) reimbursed by_inward' => 'é (parcialmente) reembolsado por', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'relacionado a', '(partially) refunds_outward' => 'reembolsos (parcialmente)', '(partially) pays for_outward' => 'paga (parcialmente) por', @@ -1155,8 +1157,9 @@ return [ 'cannot_convert_split_journal' => 'Não é possível converter uma transação dividida', // Import page (general strings only) - 'import_index_title' => 'Importar dados para o Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Importar dados', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Esta função não está disponível quando você está usando o Firefly III dentro de um ambiente Sandstorm.io.', @@ -1206,4 +1209,68 @@ return [ 'no_bills_intro_default' => 'Você ainda não possui faturas. Você pode criar faturas para acompanhar as despesas regulares, como sua renda ou seguro.', 'no_bills_imperative_default' => 'Você tem essas faturas regulares? Crie uma fatura e acompanhe seus pagamentos:', 'no_bills_create_default' => 'Criar uma fatura', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/pt_BR/form.php b/resources/lang/pt_BR/form.php index f31a9d819a..1061a841a0 100644 --- a/resources/lang/pt_BR/form.php +++ b/resources/lang/pt_BR/form.php @@ -24,66 +24,66 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Nome do banco', - 'bank_balance' => 'Saldo', - 'savings_balance' => 'Salda da Poupança', - 'credit_card_limit' => 'Limite do Cartão de Crédito', - 'automatch' => 'Equivale automaticamente', - 'skip' => 'Pular', - 'name' => 'Nome', - 'active' => 'Ativar', - 'amount_min' => 'Valor Mínimo', - 'amount_max' => 'Valor Máximo', - 'match' => 'Corresponde em', - 'strict' => 'Modo estrito', - 'repeat_freq' => 'Repetições', - 'journal_currency_id' => 'Moeda', - 'currency_id' => 'Moeda', - 'transaction_currency_id' => 'Moeda', - 'external_ip' => 'O IP externo do seu servidor', - 'attachments' => 'Anexos', - 'journal_amount' => 'Quantia', - 'journal_source_account_name' => 'Conta de receita (fonte)', - 'journal_source_account_id' => 'Conta de ativo (fonte)', - 'BIC' => 'BIC', - 'verify_password' => 'Verificação da segurança da senha', - 'source_account' => 'Conta de origem', - 'destination_account' => 'Conta de destino', - 'journal_destination_account_id' => 'Conta de ativo (destino)', - 'asset_destination_account' => 'Conta de ativo (destino)', - 'asset_source_account' => 'Conta de ativo (fonte)', - 'journal_description' => 'Descrição', - 'note' => 'Notas', - 'split_journal' => 'Dividir essa transação', - 'split_journal_explanation' => 'Dividir essa transação em várias partes', - 'currency' => 'Moeda', - 'account_id' => 'Conta de ativo', - 'budget_id' => 'Orçamento', - 'openingBalance' => 'Saldo inicial', - 'tagMode' => 'Modo de tag', - 'tag_position' => 'Localização do indexador', - 'virtualBalance' => 'Saldo virtual', - 'targetamount' => 'Valor alvo', - 'accountRole' => 'Tipo de conta', - 'openingBalanceDate' => 'Data do Saldo inicial', - 'ccType' => 'Plano de pagamento do Cartão de Crédito', - 'ccMonthlyPaymentDate' => 'Data do pagamento mensal do Cartão de Crédito', - 'piggy_bank_id' => 'Cofrinho', - 'returnHere' => 'Retornar aqui', - 'returnHereExplanation' => 'Depois de armazenar, retorne aqui para criar outro.', - 'returnHereUpdateExplanation' => 'Depois da atualização, retorne aqui', - 'description' => 'Descrição', - 'expense_account' => 'Conta de Despesa', - 'revenue_account' => 'Conta de Receita', - 'decimal_places' => 'Casas décimais', - 'exchange_rate_instruction' => 'Moedas estrangeiras', - 'source_amount' => 'Quantidade (fonte)', - 'destination_amount' => 'Quantidade (destino)', - 'native_amount' => 'Montante original', - 'new_email_address' => 'Novo endereço de e-mail', - 'verification' => 'Verificação', - 'api_key' => 'Chave da API', - 'remember_me' => 'Lembrar-me', + 'bank_name' => 'Nome do banco', + 'bank_balance' => 'Saldo', + 'savings_balance' => 'Salda da Poupança', + 'credit_card_limit' => 'Limite do Cartão de Crédito', + 'automatch' => 'Equivale automaticamente', + 'skip' => 'Pular', + 'name' => 'Nome', + 'active' => 'Ativar', + 'amount_min' => 'Valor Mínimo', + 'amount_max' => 'Valor Máximo', + 'match' => 'Corresponde em', + 'strict' => 'Modo estrito', + 'repeat_freq' => 'Repetições', + 'journal_currency_id' => 'Moeda', + 'currency_id' => 'Moeda', + 'transaction_currency_id' => 'Moeda', + 'external_ip' => 'O IP externo do seu servidor', + 'attachments' => 'Anexos', + 'journal_amount' => 'Quantia', + 'journal_source_name' => 'Revenue account (source)', + 'journal_source_id' => 'Asset account (source)', + 'BIC' => 'BIC', + 'verify_password' => 'Verificação da segurança da senha', + 'source_account' => 'Conta de origem', + 'destination_account' => 'Conta de destino', + 'journal_destination_id' => 'Asset account (destination)', + 'asset_destination_account' => 'Conta de ativo (destino)', + 'asset_source_account' => 'Conta de ativo (fonte)', + 'journal_description' => 'Descrição', + 'note' => 'Notas', + 'split_journal' => 'Dividir essa transação', + 'split_journal_explanation' => 'Dividir essa transação em várias partes', + 'currency' => 'Moeda', + 'account_id' => 'Conta de ativo', + 'budget_id' => 'Orçamento', + 'openingBalance' => 'Saldo inicial', + 'tagMode' => 'Modo de tag', + 'tag_position' => 'Localização do indexador', + 'virtualBalance' => 'Saldo virtual', + 'targetamount' => 'Valor alvo', + 'accountRole' => 'Tipo de conta', + 'openingBalanceDate' => 'Data do Saldo inicial', + 'ccType' => 'Plano de pagamento do Cartão de Crédito', + 'ccMonthlyPaymentDate' => 'Data do pagamento mensal do Cartão de Crédito', + 'piggy_bank_id' => 'Cofrinho', + 'returnHere' => 'Retornar aqui', + 'returnHereExplanation' => 'Depois de armazenar, retorne aqui para criar outro.', + 'returnHereUpdateExplanation' => 'Depois da atualização, retorne aqui', + 'description' => 'Descrição', + 'expense_account' => 'Conta de Despesa', + 'revenue_account' => 'Conta de Receita', + 'decimal_places' => 'Casas décimais', + 'exchange_rate_instruction' => 'Moedas estrangeiras', + 'source_amount' => 'Quantidade (fonte)', + 'destination_amount' => 'Quantidade (destino)', + 'native_amount' => 'Montante original', + 'new_email_address' => 'Novo endereço de e-mail', + 'verification' => 'Verificação', + 'api_key' => 'Chave da API', + 'remember_me' => 'Lembrar-me', 'source_account_asset' => 'Conta de origem (conta de ativo)', 'destination_account_expense' => 'Conta de destino (conta de despesa)', @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Converter o depósito', 'convert_Transfer' => 'Converter a transferência', - 'amount' => 'Valor', - 'foreign_amount' => 'Montante em moeda estrangeira', - 'existing_attachments' => 'Anexos existentes', - 'date' => 'Data', - 'interest_date' => 'Data de interesse', - 'book_date' => 'Data reserva', - 'process_date' => 'Data de processamento', - 'category' => 'Categoria', - 'tags' => 'Etiquetas', - 'deletePermanently' => 'Apagar permanentemente', - 'cancel' => 'Cancelar', - 'targetdate' => 'Data Alvo', - 'startdate' => 'Data de Início', - 'tag' => 'Etiqueta', - 'under' => 'Debaixo', - 'symbol' => 'Símbolo', - 'code' => 'Código', - 'iban' => 'IBAN', - 'accountNumber' => 'Número de conta', - 'creditCardNumber' => 'Número do cartão de crédito', - 'has_headers' => 'Cabeçalhos', - 'date_format' => 'Formato da Data', - 'specifix' => 'Banco- ou arquivo específico corrigídos', - 'attachments[]' => 'Anexos', - 'store_new_withdrawal' => 'Armazenar nova retirada', - 'store_new_deposit' => 'Armazenar novo depósito', - 'store_new_transfer' => 'Armazenar nova transferência', - 'add_new_withdrawal' => 'Adicionar uma nova retirada', - 'add_new_deposit' => 'Adicionar um novo depósito', - 'add_new_transfer' => 'Adicionar uma nova transferência', - 'title' => 'Título', - 'notes' => 'Notas', - 'filename' => 'Nome do arquivo', - 'mime' => 'Tipo do Arquivo (MIME)', - 'size' => 'Tamanho', - 'trigger' => 'Disparo', - 'stop_processing' => 'Parar processamento', - 'start_date' => 'Início do intervalo', - 'end_date' => 'Final do intervalo', - 'export_start_range' => 'Início do intervalo de exportação', - 'export_end_range' => 'Fim do intervalo de exportação', - 'export_format' => 'Formato do arquivo', - 'include_attachments' => 'Incluir anexos enviados', - 'include_old_uploads' => 'Incluir dados importados', - 'accounts' => 'Exportar transações destas contas', - 'delete_account' => 'Apagar conta ":name"', - 'delete_bill' => 'Apagar fatura ":name"', - 'delete_budget' => 'Excluir o orçamento ":name"', - 'delete_category' => 'Excluir categoria ":name"', - 'delete_currency' => 'Excluir moeda ":name"', - 'delete_journal' => 'Excluir a transação com a descrição ":description"', - 'delete_attachment' => 'Apagar anexo ":name"', - 'delete_rule' => 'Excluir regra ":title"', - 'delete_rule_group' => 'Exclua o grupo de regras ":title"', - 'delete_link_type' => 'Excluir tipo de link ":name"', - 'delete_user' => 'Excluir o usuário ":email"', - 'user_areYouSure' => 'Se você excluir o usuário ":email", tudo desaparecerá. Não será possível desfazer a ação. Se excluir você mesmo, você perderá acesso total a essa instância do Firefly III.', - 'attachment_areYouSure' => 'Tem certeza que deseja excluir o anexo denominado ":name"?', - 'account_areYouSure' => 'Tem certeza que deseja excluir a conta denominada ":name"?', - 'bill_areYouSure' => 'Você tem certeza que quer apagar a fatura ":name"?', - 'rule_areYouSure' => 'Tem certeza que deseja excluir a regra intitulada ":title"?', - 'ruleGroup_areYouSure' => 'Tem certeza que deseja excluir o grupo de regras intitulado ":title"?', - 'budget_areYouSure' => 'Tem certeza que deseja excluir o orçamento chamado ":name"?', - 'category_areYouSure' => 'Tem certeza que deseja excluir a categoria com o nome ":name"?', - 'currency_areYouSure' => 'Tem certeza que deseja excluir a moeda chamada ":name"?', - 'piggyBank_areYouSure' => 'Tem certeza que deseja excluir o cofrinho chamado ":name"?', - 'journal_areYouSure' => 'Tem certeza que deseja excluir a transação descrita ":description"?', - 'mass_journal_are_you_sure' => 'Tem a certeza que pretende apagar estas transações?', - 'tag_areYouSure' => 'Você tem certeza que quer apagar a tag ":tag"?', - 'journal_link_areYouSure' => 'Tem certeza que deseja excluir a ligação entre :source e :destination?', - 'linkType_areYouSure' => 'Tem certeza que deseja excluir o tipo de link ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Exclusão de dados do Firefly III são permanentes e não podem ser desfeitos.', - 'mass_make_selection' => 'Você ainda pode evitar que itens sejam excluídos, removendo a caixa de seleção.', - 'delete_all_permanently' => 'Exclua os selecionados permanentemente', - 'update_all_journals' => 'Atualizar essas transações', - 'also_delete_transactions' => 'A única transação ligada a essa conta será excluída também.|Todas as :count transações ligadas a esta conta serão excluídas também.', - 'also_delete_connections' => 'A única transação relacionada com este tipo de link vai perder a conexão. | Todas as transações de :count ligadas com este tipo de link vão perder sua conexão.', - 'also_delete_rules' => 'A única regra que ligado a este grupo de regras será excluída também.|Todos as :count regras ligadas a este grupo de regras serão excluídas também.', - 'also_delete_piggyBanks' => 'O único cofrinho conectado a essa conta será excluído também.|Todos os :count cofrinhos conectados a esta conta serão excluídos também.', - 'bill_keep_transactions' => 'A única transação a esta conta não será excluída.|Todos as :count transações conectadas a esta fatura não serão excluídos.', - 'budget_keep_transactions' => 'A única transação conectada a este orçamento não será excluída.|Todos :count transações ligadas a este orçamento não serão excluídos.', - 'category_keep_transactions' => 'A única transação ligada a esta categoria não será excluída.|Todos :count transações ligadas a esta categoria não serão excluídos.', - 'tag_keep_transactions' => 'A única transação ligada a essa marca não será excluída.|Todos :count transações ligadas a essa marca não serão excluídos.', - 'check_for_updates' => 'Buscar atualizações', + 'amount' => 'Valor', + 'foreign_amount' => 'Montante em moeda estrangeira', + 'existing_attachments' => 'Anexos existentes', + 'date' => 'Data', + 'interest_date' => 'Data de interesse', + 'book_date' => 'Data reserva', + 'process_date' => 'Data de processamento', + 'category' => 'Categoria', + 'tags' => 'Etiquetas', + 'deletePermanently' => 'Apagar permanentemente', + 'cancel' => 'Cancelar', + 'targetdate' => 'Data Alvo', + 'startdate' => 'Data de Início', + 'tag' => 'Etiqueta', + 'under' => 'Debaixo', + 'symbol' => 'Símbolo', + 'code' => 'Código', + 'iban' => 'IBAN', + 'accountNumber' => 'Número de conta', + 'creditCardNumber' => 'Número do cartão de crédito', + 'has_headers' => 'Cabeçalhos', + 'date_format' => 'Formato da Data', + 'specifix' => 'Banco- ou arquivo específico corrigídos', + 'attachments[]' => 'Anexos', + 'store_new_withdrawal' => 'Armazenar nova retirada', + 'store_new_deposit' => 'Armazenar novo depósito', + 'store_new_transfer' => 'Armazenar nova transferência', + 'add_new_withdrawal' => 'Adicionar uma nova retirada', + 'add_new_deposit' => 'Adicionar um novo depósito', + 'add_new_transfer' => 'Adicionar uma nova transferência', + 'title' => 'Título', + 'notes' => 'Notas', + 'filename' => 'Nome do arquivo', + 'mime' => 'Tipo do Arquivo (MIME)', + 'size' => 'Tamanho', + 'trigger' => 'Disparo', + 'stop_processing' => 'Parar processamento', + 'start_date' => 'Início do intervalo', + 'end_date' => 'Final do intervalo', + 'export_start_range' => 'Início do intervalo de exportação', + 'export_end_range' => 'Fim do intervalo de exportação', + 'export_format' => 'Formato do arquivo', + 'include_attachments' => 'Incluir anexos enviados', + 'include_old_uploads' => 'Incluir dados importados', + 'accounts' => 'Exportar transações destas contas', + 'delete_account' => 'Apagar conta ":name"', + 'delete_bill' => 'Apagar fatura ":name"', + 'delete_budget' => 'Excluir o orçamento ":name"', + 'delete_category' => 'Excluir categoria ":name"', + 'delete_currency' => 'Excluir moeda ":name"', + 'delete_journal' => 'Excluir a transação com a descrição ":description"', + 'delete_attachment' => 'Apagar anexo ":name"', + 'delete_rule' => 'Excluir regra ":title"', + 'delete_rule_group' => 'Exclua o grupo de regras ":title"', + 'delete_link_type' => 'Excluir tipo de link ":name"', + 'delete_user' => 'Excluir o usuário ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Se você excluir o usuário ":email", tudo desaparecerá. Não será possível desfazer a ação. Se excluir você mesmo, você perderá acesso total a essa instância do Firefly III.', + 'attachment_areYouSure' => 'Tem certeza que deseja excluir o anexo denominado ":name"?', + 'account_areYouSure' => 'Tem certeza que deseja excluir a conta denominada ":name"?', + 'bill_areYouSure' => 'Você tem certeza que quer apagar a fatura ":name"?', + 'rule_areYouSure' => 'Tem certeza que deseja excluir a regra intitulada ":title"?', + 'ruleGroup_areYouSure' => 'Tem certeza que deseja excluir o grupo de regras intitulado ":title"?', + 'budget_areYouSure' => 'Tem certeza que deseja excluir o orçamento chamado ":name"?', + 'category_areYouSure' => 'Tem certeza que deseja excluir a categoria com o nome ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Tem certeza que deseja excluir a moeda chamada ":name"?', + 'piggyBank_areYouSure' => 'Tem certeza que deseja excluir o cofrinho chamado ":name"?', + 'journal_areYouSure' => 'Tem certeza que deseja excluir a transação descrita ":description"?', + 'mass_journal_are_you_sure' => 'Tem a certeza que pretende apagar estas transações?', + 'tag_areYouSure' => 'Você tem certeza que quer apagar a tag ":tag"?', + 'journal_link_areYouSure' => 'Tem certeza que deseja excluir a ligação entre :source e :destination?', + 'linkType_areYouSure' => 'Tem certeza que deseja excluir o tipo de link ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Exclusão de dados do Firefly III são permanentes e não podem ser desfeitos.', + 'mass_make_selection' => 'Você ainda pode evitar que itens sejam excluídos, removendo a caixa de seleção.', + 'delete_all_permanently' => 'Exclua os selecionados permanentemente', + 'update_all_journals' => 'Atualizar essas transações', + 'also_delete_transactions' => 'A única transação ligada a essa conta será excluída também.|Todas as :count transações ligadas a esta conta serão excluídas também.', + 'also_delete_connections' => 'A única transação relacionada com este tipo de link vai perder a conexão. | Todas as transações de :count ligadas com este tipo de link vão perder sua conexão.', + 'also_delete_rules' => 'A única regra que ligado a este grupo de regras será excluída também.|Todos as :count regras ligadas a este grupo de regras serão excluídas também.', + 'also_delete_piggyBanks' => 'O único cofrinho conectado a essa conta será excluído também.|Todos os :count cofrinhos conectados a esta conta serão excluídos também.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Buscar atualizações', 'email' => 'E-mail', 'password' => 'Senha', @@ -216,11 +219,23 @@ return [ 'country_code' => 'Código do país', 'provider_code' => 'Banco ou provedor de dados', - 'due_date' => 'Data de vencimento', - 'payment_date' => 'Data de pagamento', - 'invoice_date' => 'Data da Fatura', - 'internal_reference' => 'Referência interna', - 'inward' => 'Descrição interna', - 'outward' => 'Descrição externa', - 'rule_group_id' => 'Grupo de regras', + 'due_date' => 'Data de vencimento', + 'payment_date' => 'Data de pagamento', + 'invoice_date' => 'Data da Fatura', + 'internal_reference' => 'Referência interna', + 'inward' => 'Descrição interna', + 'outward' => 'Descrição externa', + 'rule_group_id' => 'Grupo de regras', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + 'weekend' => 'Weekend', + ]; diff --git a/resources/lang/pt_BR/import.php b/resources/lang/pt_BR/import.php index 377665273f..5766b66116 100644 --- a/resources/lang/pt_BR/import.php +++ b/resources/lang/pt_BR/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/pt_BR/list.php b/resources/lang/pt_BR/list.php index f65a5eee64..5c90f01f42 100644 --- a/resources/lang/pt_BR/list.php +++ b/resources/lang/pt_BR/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php index 841f7c2d55..eec02240af 100644 --- a/resources/lang/pt_BR/validation.php +++ b/resources/lang/pt_BR/validation.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'iban' => 'Este não é um válido IBAN.', - 'source_equals_destination' => 'A conta de origem é igual à conta de destino', + 'source_equals_destination' => 'The source account equals the destination account.', 'unique_account_number_for_user' => 'Parece que este número de conta já está em uso.', 'unique_iban_for_user' => 'Parece que este IBAN já está em uso.', 'deleted_user' => 'Devido a restrições de segurança, você não pode registrar usando este endereço de e-mail.', @@ -34,16 +34,22 @@ return [ 'file_attached' => 'Arquivo carregado com sucesso ":name".', 'must_exist' => 'O ID no campo :attribute não existe no banco de dados.', 'all_accounts_equal' => 'Todas as contas neste campo devem ser iguais.', - 'invalid_selection' => 'Sua seleção é inválida', + 'invalid_selection' => 'Your selection is invalid.', 'belongs_user' => 'Esse valor é inválido para este campo.', 'at_least_one_transaction' => 'Precisa de ao menos uma transação.', + 'at_least_one_repetition' => 'Need at least one repetition.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'O conteúdo deste campo é inválido sem informações de moeda.', 'equal_description' => 'A descrição da transação não pode ser igual à descrição global.', 'file_invalid_mime' => 'Arquivo ":name" é do tipo ":mime" que não é aceito como um novo upload.', 'file_too_large' => 'Arquivo ":name" é muito grande.', - 'belongs_to_user' => 'O valor de :attribute é desconhecido', + 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => 'O campo :attribute deve ser aceito.', 'bic' => 'Este não é um BIC válido.', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute deve ser maior que zero.', 'active_url' => 'O campo :attribute não contém um URL válido.', 'after' => 'O campo :attribute deverá conter uma data posterior a :date.', @@ -53,8 +59,8 @@ return [ 'array' => 'O campo :attribute precisa ser um conjunto.', 'unique_for_user' => 'Já existe uma entrada com este :attribute.', 'before' => 'O campo :attribute deverá conter uma data anterior a :date.', - 'unique_object_for_user' => 'Este nome já está em uso', - 'unique_account_for_user' => 'Este nome de conta já está em uso', + 'unique_object_for_user' => 'This name is already in use.', + 'unique_account_for_user' => 'This account name is already in use.', 'between.numeric' => 'O campo :attribute deverá ter um valor entre :min - :max.', 'between.file' => 'O campo :attribute deverá ter um tamanho entre :min - :max kilobytes.', 'between.string' => 'O campo :attribute deverá conter entre :min - :max caracteres.', @@ -85,6 +91,9 @@ return [ 'min.array' => 'O campo :attribute deve ter no mínimo :min itens.', 'not_in' => 'O campo :attribute contém um valor inválido.', 'numeric' => 'O campo :attribute deverá conter um valor numérico.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'O formato do valor para o campo :attribute é inválido.', 'required' => 'O campo :attribute é obrigatório.', 'required_if' => 'O campo :attribute é obrigatório quando o valor do campo :other é igual a :value.', @@ -109,9 +118,12 @@ return [ 'file' => 'O :attribute deve ser um arquivo.', 'in_array' => 'O campo :attribute não existe em :other.', 'present' => 'O campo :attribute deve estar presente.', - 'amount_zero' => 'A quantidade total não pode ser zero', + 'amount_zero' => 'The total amount cannot be zero.', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security.', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions.', + 'valid_recurrence_rep_moment' => 'Invalid repetition moment for this type of repetition.', + 'invalid_account_info' => 'Invalid account information.', 'attributes' => [ 'email' => 'endereço de e-mail', 'description' => 'descrição', diff --git a/resources/lang/ru_RU/config.php b/resources/lang/ru_RU/config.php index a524e3644c..58b0bfdd02 100644 --- a/resources/lang/ru_RU/config.php +++ b/resources/lang/ru_RU/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'ru', - 'locale' => 'ru, Russian, ru_RU, ru_RU.utf8, ru_RU.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Неделя %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'Do MMMM YYYY', - 'date_time_js' => 'Do MMMM YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Неделя] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'ru', + 'locale' => 'ru, Russian, ru_RU, ru_RU.utf8, ru_RU.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A, %B %e %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Неделя %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'Do MMMM YYYY', + 'date_time_js' => 'Do MMMM YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Неделя] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Понедельник', + 'dow_2' => 'Вторник', + 'dow_3' => 'Среда', + 'dow_4' => 'Четверг', + 'dow_5' => 'Пятница', + 'dow_6' => 'Суббота', + 'dow_7' => 'Воскресенье', ]; diff --git a/resources/lang/ru_RU/demo.php b/resources/lang/ru_RU/demo.php index 293ba441f0..8eb16e401b 100644 --- a/resources/lang/ru_RU/demo.php +++ b/resources/lang/ru_RU/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Эти расходы, доходы и переводы не очень интересны. Они были созданы автоматически.', 'piggy-banks-index' => 'Как вы можете видеть, здесь есть три копилки. Используйте кнопки «плюс» и «минус», чтобы влиять на количество денег в каждой копилке. Нажмите название копилки, чтобы увидеть её настройки.', 'import-index' => 'В Firefly III можно импортировать любой CSV-файл. Также поддерживается импорт данных из bunq и Spectre. Другие банки и финансовые агрегаторы будут реализованы в будущем. Однако, как демо-пользователь, вы можете видеть только как работает «поддельный»-провайдер. Он будет генерировать некоторые случайные транзакции, чтобы показать вам, как работает этот процесс.', + 'recurring-index' => 'Пожалуйста, обратите внимание, что эта функция находится в активной разработке и может работать не совcем так, как вы ожидаете.', + 'recurring-create' => 'Пожалуйста, обратите внимание, что эта функция находится в активной разработке и может работать не совcем так, как вы ожидаете.', ]; diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index 382410ca5c..c18e2716da 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Отсканируйте QR-код с помощью приложения на телефоне, например Authy или Google Authenticator, и введите сгенерированный код.', 'pref_two_factor_auth_reset_code' => 'Сбросить код верификации', 'pref_two_factor_auth_disable_2fa' => 'Выключить 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Сохранить настройки', 'saved_preferences' => 'Настройки сохранены!', 'preferences_general' => 'Основные', @@ -820,7 +821,7 @@ return [ 'language' => 'Язык', 'new_savings_account' => 'сберегательный счёт в :bank_name', 'cash_wallet' => 'Кошелёк с наличными', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'Если ваша основная валюта ещё не указана, не волнуйтесь. Вы можете создать собственные валюты в разделе Параметры > Валюты.', // home page: 'yourAccounts' => 'Ваши счета', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Остаток на конец периода', 'splitByAccount' => 'Разделить по разным счетам', 'coveredWithTags' => 'Присвоены метки', - 'leftUnbalanced' => 'Осталось вне баланса', 'leftInBudget' => 'Осталось в бюджете', 'sumOfSums' => 'Сумма сумм', 'noCategory' => '(без категории)', @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => '(частично) возвращён', 'is (partially) paid for by_inward' => '(частично) оплачен', 'is (partially) reimbursed by_inward' => '(частично) возмещён', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'относится к', '(partially) refunds_outward' => '(частично) возвращены', '(partially) pays for_outward' => '(частично) оплачены', @@ -1155,8 +1157,9 @@ return [ 'cannot_convert_split_journal' => 'Невозможно преобразовать раздельную транзакцию', // Import page (general strings only) - 'import_index_title' => 'Импорт данных в Firefly III', + 'import_index_title' => 'Импорт транзакций в Firefly III', 'import_data' => 'Импорт данных', + 'import_transactions' => 'Импорт транзакций', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Эта функция недоступна, если вы используете Firefly III в среде Sandstorm.io.', @@ -1206,4 +1209,68 @@ return [ 'no_bills_intro_default' => 'У вас пока нет счетов на оплату. Вы можете создавать счета для отслеживания регулярных расходов, таких как арендная плата или страхование.', 'no_bills_imperative_default' => 'У вас есть такие регулярные платежи? Создайте счёт на оплату и отслеживайте свои платежи:', 'no_bills_create_default' => 'Создать счет к оплате', + + // recurring transactions + 'recurrences' => 'Повторяющиеся транзакции', + 'no_recurring_title_default' => 'Давайте создадим повторяющуюся транзакцию!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Создать повторяющуюся транзакцию', + 'make_new_recurring' => 'Создать повторяющуюся транзакцию', + 'recurring_daily' => 'Каждый день', + 'recurring_weekly' => 'Каждую неделю в :weekday', + 'recurring_monthly' => 'Каждый месяц в :dayOfMonth(st/nd/rd/th) день', + 'recurring_ndom' => 'Каждый месяц в :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Каждый год на :date', + 'overview_for_recurrence' => 'Обзор повторяющейся транзакции ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Связанные транзакции', + 'expected_Withdrawals' => 'Просроченные расходы', + 'expected_Deposits' => 'Просроченные доходы', + 'expected_Transfers' => 'Просроченные переводы', + 'created_Withdrawals' => 'Расходы созданы', + 'created_Deposits' => 'Доходы созданы', + 'created_Transfers' => 'Переводы созданы', + 'created_from_recurrence' => 'Создано из повторяющейся транзакции ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Метки', + 'recurring_meta_field_notes' => 'Примечания', + 'recurring_meta_field_bill_id' => 'Счёт к оплате', + 'recurring_meta_field_piggy_bank_id' => 'Копилка', + 'create_new_recurrence' => 'Создать новую повторяющуюся транзакцию', + 'help_first_date' => 'Укажите первое ожидаемое повторение. Оно должно быть в будущем.', + 'help_first_date_no_past' => 'Укажите первое ожидаемое повторение. Firefly III не будет создавать транзакции ранее этой даты.', + 'no_currency' => '(нет валюты)', + 'mandatory_for_recurring' => 'Обязательные сведения о повторении', + 'mandatory_for_transaction' => 'Обязательные сведения о транзакции', + 'optional_for_recurring' => 'Опциональные сведения о повторении', + 'optional_for_transaction' => 'Опциональные сведения о транзакции', + 'change_date_other_options' => 'Измените "первую дату", чтобы увидеть больше опций.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Щёлкните здесь, чтобы открыт календарь и указать, когда транзакция будет повторяться.', + 'repeat_forever' => 'Повторять всегда', + 'repeat_until_date' => 'Повторять до указанной даты', + 'repeat_times' => 'Повторять указанное число раз', + 'recurring_skips_one' => 'Другой период', + 'recurring_skips_more' => 'Пропустить :count повторов', + 'store_new_recurrence' => 'Сохранить повторяющуюся транзакцию', + 'stored_new_recurrence' => 'Повторяющаяся транзакция ":title" успешно сохранена.', + 'edit_recurrence' => 'Изменить повторяющуюся транзакцию ":title"', + 'recurring_repeats_until' => 'Повторять до :date', + 'recurring_repeats_forever' => 'Повторять всегда', + 'recurring_repeats_x_times' => 'Повторить :count раз(а)', + 'update_recurrence' => 'Обновить повторяющуюся транзакцию', + 'updated_recurrence' => 'Повторяющаяся транзакция ":title" обновлена', + 'recurrence_is_inactive' => 'Эта повторяющаяся транзакция не активна и не создаёт новые транзакции.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/ru_RU/form.php b/resources/lang/ru_RU/form.php index a4b4ef54cf..625439e845 100644 --- a/resources/lang/ru_RU/form.php +++ b/resources/lang/ru_RU/form.php @@ -24,66 +24,66 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Название банка', - 'bank_balance' => 'Бaлaнc', - 'savings_balance' => 'Сберегательный баланс', - 'credit_card_limit' => 'Лимит кредитной карты', - 'automatch' => 'Автоматическое сопоставление', - 'skip' => 'Пропустить', - 'name' => 'Название', - 'active' => 'Активный', - 'amount_min' => 'Минимальная сумма', - 'amount_max' => 'Максимальная сумма', - 'match' => 'Ключи для связи', - 'strict' => 'Строгий режим', - 'repeat_freq' => 'Повторы', - 'journal_currency_id' => 'Валюта', - 'currency_id' => 'Валюта', - 'transaction_currency_id' => 'Валюта', - 'external_ip' => 'Внешний IP-адрес вашего сервера', - 'attachments' => 'Вложения', - 'journal_amount' => 'Сумма', - 'journal_source_account_name' => 'Доходный счет (источник)', - 'journal_source_account_id' => 'Основной счёт (источник)', - 'BIC' => 'BIC', - 'verify_password' => 'Проверка безопасности паролей', - 'source_account' => 'Исходный счёт', - 'destination_account' => 'Счёт назначения', - 'journal_destination_account_id' => 'Основной счёт (назначение)', - 'asset_destination_account' => 'Основной счёт (назначение)', - 'asset_source_account' => 'Основной счёт (источник)', - 'journal_description' => 'Описание', - 'note' => 'Заметки', - 'split_journal' => 'Разделить эту транзакцию', - 'split_journal_explanation' => 'Разделить эту транзакцию на несколько частей', - 'currency' => 'Валюта', - 'account_id' => 'Основной счёт', - 'budget_id' => 'Бюджет', - 'openingBalance' => 'Начальный баланс', - 'tagMode' => 'Режим метки', - 'tag_position' => 'Расположение метки', - 'virtualBalance' => 'Виртуальный баланс', - 'targetamount' => 'Целевая сумма', - 'accountRole' => 'Роль учётной записи', - 'openingBalanceDate' => 'Дата начального баланса', - 'ccType' => 'План оплаты по кредитной карте', - 'ccMonthlyPaymentDate' => 'Дата ежемесячного платежа по кредитной карте', - 'piggy_bank_id' => 'Копилка', - 'returnHere' => 'Вернуться сюда', - 'returnHereExplanation' => 'После сохранения вернуться сюда и создать ещё одну аналогичную запись.', - 'returnHereUpdateExplanation' => 'Вернуться на эту страницу после обновления.', - 'description' => 'Описание', - 'expense_account' => 'Счет расходов', - 'revenue_account' => 'Доходный счет', - 'decimal_places' => 'Количество цифр после точки', - 'exchange_rate_instruction' => 'Иностранные валюты', - 'source_amount' => 'Сумма (источник)', - 'destination_amount' => 'Сумма (назначение)', - 'native_amount' => 'Собственная сумма', - 'new_email_address' => 'Новый адрес электронной почты', - 'verification' => 'Проверка', - 'api_key' => 'API-ключ', - 'remember_me' => 'Запомнить меня', + 'bank_name' => 'Название банка', + 'bank_balance' => 'Бaлaнc', + 'savings_balance' => 'Сберегательный баланс', + 'credit_card_limit' => 'Лимит кредитной карты', + 'automatch' => 'Автоматическое сопоставление', + 'skip' => 'Пропустить', + 'name' => 'Название', + 'active' => 'Активный', + 'amount_min' => 'Минимальная сумма', + 'amount_max' => 'Максимальная сумма', + 'match' => 'Ключи для связи', + 'strict' => 'Строгий режим', + 'repeat_freq' => 'Повторы', + 'journal_currency_id' => 'Валюта', + 'currency_id' => 'Валюта', + 'transaction_currency_id' => 'Валюта', + 'external_ip' => 'Внешний IP-адрес вашего сервера', + 'attachments' => 'Вложения', + 'journal_amount' => 'Сумма', + 'journal_source_name' => 'Revenue account (source)', + 'journal_source_id' => 'Asset account (source)', + 'BIC' => 'BIC', + 'verify_password' => 'Проверка безопасности паролей', + 'source_account' => 'Исходный счёт', + 'destination_account' => 'Счёт назначения', + 'journal_destination_id' => 'Asset account (destination)', + 'asset_destination_account' => 'Основной счёт (назначение)', + 'asset_source_account' => 'Основной счёт (источник)', + 'journal_description' => 'Описание', + 'note' => 'Заметки', + 'split_journal' => 'Разделить эту транзакцию', + 'split_journal_explanation' => 'Разделить эту транзакцию на несколько частей', + 'currency' => 'Валюта', + 'account_id' => 'Основной счёт', + 'budget_id' => 'Бюджет', + 'openingBalance' => 'Начальный баланс', + 'tagMode' => 'Режим метки', + 'tag_position' => 'Расположение метки', + 'virtualBalance' => 'Виртуальный баланс', + 'targetamount' => 'Целевая сумма', + 'accountRole' => 'Роль учётной записи', + 'openingBalanceDate' => 'Дата начального баланса', + 'ccType' => 'План оплаты по кредитной карте', + 'ccMonthlyPaymentDate' => 'Дата ежемесячного платежа по кредитной карте', + 'piggy_bank_id' => 'Копилка', + 'returnHere' => 'Вернуться сюда', + 'returnHereExplanation' => 'После сохранения вернуться сюда и создать ещё одну аналогичную запись.', + 'returnHereUpdateExplanation' => 'Вернуться на эту страницу после обновления.', + 'description' => 'Описание', + 'expense_account' => 'Счет расходов', + 'revenue_account' => 'Доходный счет', + 'decimal_places' => 'Количество цифр после точки', + 'exchange_rate_instruction' => 'Иностранные валюты', + 'source_amount' => 'Сумма (источник)', + 'destination_amount' => 'Сумма (назначение)', + 'native_amount' => 'Собственная сумма', + 'new_email_address' => 'Новый адрес электронной почты', + 'verification' => 'Проверка', + 'api_key' => 'API-ключ', + 'remember_me' => 'Запомнить меня', 'source_account_asset' => 'Исходный счёт (основной счёт)', 'destination_account_expense' => 'Счёт назначения (счёт расхода)', @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Конвертировать доход', 'convert_Transfer' => 'Конвертировать перевод', - 'amount' => 'Сумма', - 'foreign_amount' => 'Сумму в иностранной валюте', - 'existing_attachments' => 'Существующие вложения', - 'date' => 'Дата', - 'interest_date' => 'Дата выплаты', - 'book_date' => 'Дата бронирования', - 'process_date' => 'Дата обработки', - 'category' => 'Категория', - 'tags' => 'Метки', - 'deletePermanently' => 'Удалить навсегда', - 'cancel' => 'Отмена', - 'targetdate' => 'Намеченная дата', - 'startdate' => 'Дата начала', - 'tag' => 'Тег', - 'under' => 'Под', - 'symbol' => 'Символ', - 'code' => 'Код', - 'iban' => 'IBAN', - 'accountNumber' => 'Номер счета', - 'creditCardNumber' => 'Номер кредитной карты', - 'has_headers' => 'Заголовки', - 'date_format' => 'Формат даты', - 'specifix' => 'Исправления, специфичные для банка или файла', - 'attachments[]' => 'Вложения', - 'store_new_withdrawal' => 'Сохранить новый расход', - 'store_new_deposit' => 'Сохранить новый доход', - 'store_new_transfer' => 'Сохранить новый перевод', - 'add_new_withdrawal' => 'Добавить новый расход', - 'add_new_deposit' => 'Добавить новый доход', - 'add_new_transfer' => 'Добавить новый перевод', - 'title' => 'Заголовок', - 'notes' => 'Заметки', - 'filename' => 'Имя файла', - 'mime' => 'Тип Mime', - 'size' => 'Размер', - 'trigger' => 'Триггер', - 'stop_processing' => 'Остановить обработку', - 'start_date' => 'Начало диапазона', - 'end_date' => 'Конец диапазона', - 'export_start_range' => 'Начало диапазона для экспорта', - 'export_end_range' => 'Конец диапазона для экспорта', - 'export_format' => 'Формат файла', - 'include_attachments' => 'Включить загруженные вложения', - 'include_old_uploads' => 'Включить импортированные данные', - 'accounts' => 'Экспорт транзакций с этих счетов', - 'delete_account' => 'Удалить счёт ":name"', - 'delete_bill' => 'Удаление счёта к оплате ":name"', - 'delete_budget' => 'Удалить бюджет ":name"', - 'delete_category' => 'Удалить категорию ":name"', - 'delete_currency' => 'Удалить валюту ":name"', - 'delete_journal' => 'Удалить транзакцию с описанием ":description"', - 'delete_attachment' => 'Удалить вложение ":name"', - 'delete_rule' => 'Удалить правило ":title"', - 'delete_rule_group' => 'Удалить группу правил ":title"', - 'delete_link_type' => 'Удалить тип ссылки ":name"', - 'delete_user' => 'Удалить пользователя ":email"', - 'user_areYouSure' => 'Если вы удалите пользователя ":email", все данные будут удалены. Это действие нельзя будет отменить. Если вы удалите себя, вы потеряете доступ к этому экземпляру Firefly III.', - 'attachment_areYouSure' => 'Вы действительно хотите удалить вложение с именем ":name"?', - 'account_areYouSure' => 'Вы действительно хотите удалить счёт с именем ":name"?', - 'bill_areYouSure' => 'Вы действительно хотите удалить счёт на оплату с именем ":name"?', - 'rule_areYouSure' => 'Вы действительно хотите удалить правило с названием ":title"?', - 'ruleGroup_areYouSure' => 'Вы действительно хотите удалить группу правил с названием ":title"?', - 'budget_areYouSure' => 'Вы действительно хотите удалить бюджет с именем ":name"?', - 'category_areYouSure' => 'Вы действительно хотите удалить категорию с именем ":name"?', - 'currency_areYouSure' => 'Вы уверены, что хотите удалить валюту ":name"?', - 'piggyBank_areYouSure' => 'Вы уверены, что хотите удалить копилку с именем ":name"?', - 'journal_areYouSure' => 'Вы действительно хотите удалить транзакцию с описанием ":description"?', - 'mass_journal_are_you_sure' => 'Вы действительно хотите удалить эти транзакции?', - 'tag_areYouSure' => 'Вы действительно хотите удалить метку ":tag"?', - 'journal_link_areYouSure' => 'Вы действительно хотите удалить связь между :source и :destination?', - 'linkType_areYouSure' => 'Вы уверены, что хотите удалить тип ссылки ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Удаление информации из Firefly III является постоянным и не может быть отменено.', - 'mass_make_selection' => 'Вы все же можете предотвратить удаление элементов, сняв флажок.', - 'delete_all_permanently' => 'Удалить выбранное навсегда', - 'update_all_journals' => 'Обновить эти транзакции', - 'also_delete_transactions' => 'Будет удалена только транзакция, связанная с этим счётом.|Будут удалены все :count транзакций, связанные с этим счётом.', - 'also_delete_connections' => 'Единственная транзакция, связанная с данным типом ссылки, потеряет это соединение. |Все :count транзакций, связанные с данным типом ссылки, потеряют свои соединения.', - 'also_delete_rules' => 'Единственное правило, связанное с данной группой правил, будет удалено. |Все :count правила, связанные с данной группой правил, будут удалены.', - 'also_delete_piggyBanks' => 'Единственная копилка, связанная с данным счётом, будет удалена.|Все :count копилки, связанные с данным счётом, будут удалены.', - 'bill_keep_transactions' => 'Единственная транзакция, связанная с данным счётом, не будет удалена. |Все :count транзакции, связанные с данным счётом, будут сохранены.', - 'budget_keep_transactions' => 'Единственная транзакция, связанная с данным бюджетом, не будет удалена.|Все :count транзакции, связанные с этим бюджетом, будут сохранены.', - 'category_keep_transactions' => 'Единственная транзакция, связанная с данной категорией, не будет удалена.|Все :count транзакции, связанные с этой категорией, будут сохранены.', - 'tag_keep_transactions' => 'Только транзакция, связанная с этой меткой, будет удалена.|Все :count транзакций, связанные с этой меткой, будут удалены.', - 'check_for_updates' => 'Проверить обновления', + 'amount' => 'Сумма', + 'foreign_amount' => 'Сумму в иностранной валюте', + 'existing_attachments' => 'Существующие вложения', + 'date' => 'Дата', + 'interest_date' => 'Дата выплаты', + 'book_date' => 'Дата бронирования', + 'process_date' => 'Дата обработки', + 'category' => 'Категория', + 'tags' => 'Метки', + 'deletePermanently' => 'Удалить навсегда', + 'cancel' => 'Отмена', + 'targetdate' => 'Намеченная дата', + 'startdate' => 'Дата начала', + 'tag' => 'Тег', + 'under' => 'Под', + 'symbol' => 'Символ', + 'code' => 'Код', + 'iban' => 'IBAN', + 'accountNumber' => 'Номер счета', + 'creditCardNumber' => 'Номер кредитной карты', + 'has_headers' => 'Заголовки', + 'date_format' => 'Формат даты', + 'specifix' => 'Исправления, специфичные для банка или файла', + 'attachments[]' => 'Вложения', + 'store_new_withdrawal' => 'Сохранить новый расход', + 'store_new_deposit' => 'Сохранить новый доход', + 'store_new_transfer' => 'Сохранить новый перевод', + 'add_new_withdrawal' => 'Добавить новый расход', + 'add_new_deposit' => 'Добавить новый доход', + 'add_new_transfer' => 'Добавить новый перевод', + 'title' => 'Заголовок', + 'notes' => 'Заметки', + 'filename' => 'Имя файла', + 'mime' => 'Тип Mime', + 'size' => 'Размер', + 'trigger' => 'Триггер', + 'stop_processing' => 'Остановить обработку', + 'start_date' => 'Начало диапазона', + 'end_date' => 'Конец диапазона', + 'export_start_range' => 'Начало диапазона для экспорта', + 'export_end_range' => 'Конец диапазона для экспорта', + 'export_format' => 'Формат файла', + 'include_attachments' => 'Включить загруженные вложения', + 'include_old_uploads' => 'Включить импортированные данные', + 'accounts' => 'Экспорт транзакций с этих счетов', + 'delete_account' => 'Удалить счёт ":name"', + 'delete_bill' => 'Удаление счёта к оплате ":name"', + 'delete_budget' => 'Удалить бюджет ":name"', + 'delete_category' => 'Удалить категорию ":name"', + 'delete_currency' => 'Удалить валюту ":name"', + 'delete_journal' => 'Удалить транзакцию с описанием ":description"', + 'delete_attachment' => 'Удалить вложение ":name"', + 'delete_rule' => 'Удалить правило ":title"', + 'delete_rule_group' => 'Удалить группу правил ":title"', + 'delete_link_type' => 'Удалить тип ссылки ":name"', + 'delete_user' => 'Удалить пользователя ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Если вы удалите пользователя ":email", все данные будут удалены. Это действие нельзя будет отменить. Если вы удалите себя, вы потеряете доступ к этому экземпляру Firefly III.', + 'attachment_areYouSure' => 'Вы действительно хотите удалить вложение с именем ":name"?', + 'account_areYouSure' => 'Вы действительно хотите удалить счёт с именем ":name"?', + 'bill_areYouSure' => 'Вы действительно хотите удалить счёт на оплату с именем ":name"?', + 'rule_areYouSure' => 'Вы действительно хотите удалить правило с названием ":title"?', + 'ruleGroup_areYouSure' => 'Вы действительно хотите удалить группу правил с названием ":title"?', + 'budget_areYouSure' => 'Вы действительно хотите удалить бюджет с именем ":name"?', + 'category_areYouSure' => 'Вы действительно хотите удалить категорию с именем ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Вы уверены, что хотите удалить валюту ":name"?', + 'piggyBank_areYouSure' => 'Вы уверены, что хотите удалить копилку с именем ":name"?', + 'journal_areYouSure' => 'Вы действительно хотите удалить транзакцию с описанием ":description"?', + 'mass_journal_are_you_sure' => 'Вы действительно хотите удалить эти транзакции?', + 'tag_areYouSure' => 'Вы действительно хотите удалить метку ":tag"?', + 'journal_link_areYouSure' => 'Вы действительно хотите удалить связь между :source и :destination?', + 'linkType_areYouSure' => 'Вы уверены, что хотите удалить тип ссылки ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Удаление информации из Firefly III является постоянным и не может быть отменено.', + 'mass_make_selection' => 'Вы все же можете предотвратить удаление элементов, сняв флажок.', + 'delete_all_permanently' => 'Удалить выбранное навсегда', + 'update_all_journals' => 'Обновить эти транзакции', + 'also_delete_transactions' => 'Будет удалена только транзакция, связанная с этим счётом.|Будут удалены все :count транзакций, связанные с этим счётом.', + 'also_delete_connections' => 'Единственная транзакция, связанная с данным типом ссылки, потеряет это соединение. |Все :count транзакций, связанные с данным типом ссылки, потеряют свои соединения.', + 'also_delete_rules' => 'Единственное правило, связанное с данной группой правил, будет удалено. |Все :count правила, связанные с данной группой правил, будут удалены.', + 'also_delete_piggyBanks' => 'Единственная копилка, связанная с данным счётом, будет удалена.|Все :count копилки, связанные с данным счётом, будут удалены.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Проверить обновления', 'email' => 'Адрес электронной почты', 'password' => 'Пароль', @@ -216,11 +219,23 @@ return [ 'country_code' => 'Код страны', 'provider_code' => 'Банк или поставщик данных', - 'due_date' => 'Срок', - 'payment_date' => 'Дата платежа', - 'invoice_date' => 'Дата выставления счёта', - 'internal_reference' => 'Внутренняя ссылка', - 'inward' => 'Внутреннее описание', - 'outward' => 'Внешнее описание', - 'rule_group_id' => 'Группа правил', + 'due_date' => 'Срок', + 'payment_date' => 'Дата платежа', + 'invoice_date' => 'Дата выставления счёта', + 'internal_reference' => 'Внутренняя ссылка', + 'inward' => 'Внутреннее описание', + 'outward' => 'Внешнее описание', + 'rule_group_id' => 'Группа правил', + 'transaction_description' => 'Описание транзакции', + 'first_date' => 'Первая дата', + 'transaction_type' => 'Тип транзакции', + 'repeat_until' => 'Повторять до тех пор, пока', + 'recurring_description' => 'Описание повторяющейся транзакции', + 'repetition_type' => 'Тип повторения', + 'foreign_currency_id' => 'Иностранная валюта', + 'repetition_end' => 'Заканчивать повторение', + 'repetitions' => 'Повторения', + 'calendar' => 'Календарь', + 'weekend' => 'Weekend', + ]; diff --git a/resources/lang/ru_RU/import.php b/resources/lang/ru_RU/import.php index 457e18aed2..3f572ed187 100644 --- a/resources/lang/ru_RU/import.php +++ b/resources/lang/ru_RU/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -169,23 +172,23 @@ return [ // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', - 'job_config_roles_submit' => 'Continue', - 'job_config_roles_column_name' => 'Name of column', + 'job_config_roles_submit' => 'Продолжить', + 'job_config_roles_column_name' => 'Название столбца', 'job_config_roles_column_example' => 'Column example data', 'job_config_roles_column_role' => 'Column data meaning', 'job_config_roles_do_map_value' => 'Map these values', 'job_config_roles_no_example' => 'No example data available', 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', - 'job_config_roles_colum_count' => 'Column', + 'job_config_roles_colum_count' => 'Столбец', // job config for the file provider (stage: mapping): 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', - 'job_config_field_value' => 'Field value', - 'job_config_field_mapped' => 'Mapped to', + 'job_config_field_value' => 'Значение поля', + 'job_config_field_mapped' => 'Сопоставлено с', 'map_do_not_map' => '(не сопоставлено)', - 'job_config_map_submit' => 'Start the import', + 'job_config_map_submit' => 'Начать импорт', // import status page: @@ -196,11 +199,11 @@ return [ 'status_job_running' => 'Please wait, running the import...', 'status_job_storing' => 'Please wait, storing data...', 'status_job_rules' => 'Please wait, running rules...', - 'status_fatal_title' => 'Fatal error', + 'status_fatal_title' => 'Фатальная ошибка', 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', - 'status_finished_title' => 'Import finished', - 'status_finished_text' => 'The import has finished.', + 'status_finished_title' => 'Импорт завершён', + 'status_finished_text' => 'Импорт завершен!', 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', 'unknown_import_result' => 'Unknown import result', 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', diff --git a/resources/lang/ru_RU/intro.php b/resources/lang/ru_RU/intro.php index 8f21f6457e..0aaae3dcaf 100644 --- a/resources/lang/ru_RU/intro.php +++ b/resources/lang/ru_RU/intro.php @@ -111,7 +111,7 @@ return [ 'bills_create_skip_holder' => 'Если счёт выставляется каждые 2 недели, в поле "пропустить" нужно поставить "1", чтобы пропускать все прочие недели.', // rules index - 'rules_index_intro' => 'Firefly III позволяет вам использовать правилами, которые автоматически применяются к любой транзакции, которую вы создаёте или редактируете.', + 'rules_index_intro' => 'Firefly III позволяет вам использовать правила, автоматически применяющиеся к любой транзакции, которую вы создаёте или редактируете.', 'rules_index_new_rule_group' => 'Вы можете комбинировать правила в группы, чтобы упростить управление ими.', 'rules_index_new_rule' => 'Создайте столько правил, сколько захотите.', 'rules_index_prio_buttons' => 'Упорядочивайте их так, как вы считаете нужным.', diff --git a/resources/lang/ru_RU/list.php b/resources/lang/ru_RU/list.php index ad3e77cc87..3d285ed97e 100644 --- a/resources/lang/ru_RU/list.php +++ b/resources/lang/ru_RU/list.php @@ -119,8 +119,13 @@ return [ 'file_type' => 'Тип файла', 'attached_to' => 'Прикреплено к', 'file_exists' => 'Файл существует', - 'spectre_bank' => 'Bank', - 'spectre_last_use' => 'Last login', - 'spectre_status' => 'Status', - 'bunq_payment_id' => 'bunq payment ID', + 'spectre_bank' => 'Банк', + 'spectre_last_use' => 'Последний вход', + 'spectre_status' => 'Статус', + 'bunq_payment_id' => 'ID платежа bunq', + 'repetitions' => 'Повторы', + 'title' => 'Название', + 'transaction_s' => 'Транзакции', + 'field' => 'Поле', + 'value' => 'Значение', ]; diff --git a/resources/lang/ru_RU/validation.php b/resources/lang/ru_RU/validation.php index 9d551d5344..e3fd87a974 100644 --- a/resources/lang/ru_RU/validation.php +++ b/resources/lang/ru_RU/validation.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'iban' => 'Это некорректный IBAN.', - 'source_equals_destination' => 'Счёт источник и счёт назначения совпадают', + 'source_equals_destination' => 'The source account equals the destination account.', 'unique_account_number_for_user' => 'Этот номер счёта уже используется.', 'unique_iban_for_user' => 'Этот IBAN уже используется.', 'deleted_user' => 'По соображениям безопасности, вы не можете зарегистрироваться, используя этот адрес электронной почты.', @@ -34,16 +34,22 @@ return [ 'file_attached' => 'Файл ":name". успешно загружен.', 'must_exist' => 'ID в поле field :attribute не существует в базе данных.', 'all_accounts_equal' => 'Все счета в данном поле должны совпадать.', - 'invalid_selection' => 'Вы сделали неправильный выбор', + 'invalid_selection' => 'Your selection is invalid.', 'belongs_user' => 'Данное значение недопустимо для этого поля.', 'at_least_one_transaction' => 'Необходима как минимум одна транзакция.', + 'at_least_one_repetition' => 'Need at least one repetition.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'Содержимое этого поля недействительно без информации о валюте.', 'equal_description' => 'Описание транзакции не должно совпадать с глобальным описанием.', 'file_invalid_mime' => 'Файл ":name" имеет тип ":mime". Загрузка файлов такого типа невозможна.', 'file_too_large' => 'Файл ":name" слишком большой.', - 'belongs_to_user' => 'Значение :attribute неизвестно', + 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => 'Необходимо принять :attribute.', 'bic' => 'Это некорректный BIC.', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute должен быть больше нуля.', 'active_url' => ':attribute не является допустимым URL-адресом.', 'after' => ':attribute должна быть позже :date.', @@ -53,8 +59,8 @@ return [ 'array' => ':attribute должен быть массивом.', 'unique_for_user' => 'Уже существует запись с этим :attribute.', 'before' => ':attribute должна быть раньше :date.', - 'unique_object_for_user' => 'Это имя уже используется', - 'unique_account_for_user' => 'Имя аккаунта уже используется', + 'unique_object_for_user' => 'This name is already in use.', + 'unique_account_for_user' => 'This account name is already in use.', 'between.numeric' => ':attribute должен быть больше :min и меньше :max.', 'between.file' => ':attribute должен быть размером :min - :max килобайт.', 'between.string' => ':attribute должен содержать :min - :max символов.', @@ -85,6 +91,9 @@ return [ 'min.array' => 'Значение :attribute должно содержать не меньше :min элементов.', 'not_in' => 'Выбранный :attribute не верный.', 'numeric' => ':attribute должен быть числом.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'Формат :attribute некорректен.', 'required' => 'Поле :attribute является обязательным.', 'required_if' => 'Значение :attribute является обязательным, когда :other равное :value.', @@ -109,9 +118,12 @@ return [ 'file' => ':attribute должен быть файлом.', 'in_array' => 'Поле :attribute не существует в :other.', 'present' => 'Поле :attribute должно быть заполнено.', - 'amount_zero' => 'Общее количество не может быть равно нулю', - 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'amount_zero' => 'The total amount cannot be zero.', + 'unique_piggy_bank_for_user' => 'Название копилки должно быть уникальным.', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security.', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions.', + 'valid_recurrence_rep_moment' => 'Invalid repetition moment for this type of repetition.', + 'invalid_account_info' => 'Invalid account information.', 'attributes' => [ 'email' => '"Адрес электронной почты"', 'description' => '"Описание"', diff --git a/resources/lang/tr_TR/components.php b/resources/lang/tr_TR/components.php index 6299088f8e..32e959ed1d 100644 --- a/resources/lang/tr_TR/components.php +++ b/resources/lang/tr_TR/components.php @@ -24,9 +24,9 @@ declare(strict_types=1); return [ // profile - 'personal_access_tokens' => 'Personal access tokens', + 'personal_access_tokens' => 'Kişisel Erişim Anahtarı', // bills: - 'not_expected_period' => 'Not expected this period', - 'not_or_not_yet' => 'Not (yet)', + 'not_expected_period' => 'Bu periyotta beklenmiyor', + 'not_or_not_yet' => 'Henüz değil', ]; diff --git a/resources/lang/tr_TR/config.php b/resources/lang/tr_TR/config.php index 4bc8b0175d..8e98d16bf4 100644 --- a/resources/lang/tr_TR/config.php +++ b/resources/lang/tr_TR/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'tr', - 'locale' => 'tr, Turkish, tr_TR, tr_TR.utf8, tr_TR.S.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => '%W. hafta, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'MMMM Do, YYYY', - 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'tr', + 'locale' => 'tr, Turkish, tr_TR, tr_TR.utf8, tr_TR.S.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => '%W. hafta, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'MMMM Do, YYYY', + 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Pazartesi', + 'dow_2' => 'Salı', + 'dow_3' => 'Çarşamba', + 'dow_4' => 'Perşembe', + 'dow_5' => 'Cuma', + 'dow_6' => 'Cumartesi', + 'dow_7' => 'Pazar', ]; diff --git a/resources/lang/tr_TR/demo.php b/resources/lang/tr_TR/demo.php index f60723c7a3..9b4e96f0a2 100644 --- a/resources/lang/tr_TR/demo.php +++ b/resources/lang/tr_TR/demo.php @@ -28,10 +28,15 @@ return [ 'index' => 'Firefly III\'e hoş geldiniz! Bu sayfada, finansal durumunuzun özetini görebilirsiniz. Daha fazla bilgi için Hesapları → Varlık Hesaplarını ve tabii ki deBütçe ve Rapor sayfalarına göz atın. Ya da sadece bir göz gezdirin ve ne durumda olduğunuzu görün.', 'accounts-index' => 'Varlık hesapları kişisel banka hesaplarınızdır. Gider hesaplar, mağazalar ve arkadaşlar gibi para harcadığınız hesaplardır. Gelir hesapları, işiniz veya diğer gelir kaynakları gibi para aldığınız hesaplardır. Bu sayfada bunları düzenleyebilir veya silebilirsiniz.', 'budgets-index' => 'Bu sayfa bütçelerinize genel bir bakış sunmaktadır. Üstteki çubuk bütçelenebilecek miktarı gösterir. Bu sağdaki tutara tıklayarak herhangi bir dönem için özelleştirilebilir. Gerçekte harcadığınız tutar aşağıdaki barda gösterilir. Aşağıda, bütçe başına harcamalar ve bütçeniz için ne kadar para ayırdığınız gösterilir.', - 'reports-index-start' => 'Firefly III supports a number of types of reports. Read about them by clicking on the -icon in the top right corner.', + 'reports-index-start' => 'Firefly III, bir dizi rapor türünü desteklemektedir. Sağ üst köşedeki simgeyi tıklayarak onları okuyabilirsiniz.', 'reports-index-examples' => 'Şu örnekleri incelediğinizden emin olun: a aylık finansal genel bakış, a yıllık finansal genel bakış ve a bütçe genel bakışı.', 'currencies-index' => 'Firefly III, birden fazla para birimini destekliyor. Euro varsayılan olmasına rağmen, ABD Doları ve diğer birçok para birimine ayarlanabilir. Gördüğünüz gibi küçük bir para birimi seçeneği dedahil edilmiştir ancak isterseniz kendi para biriminizi ekleyebilirsiniz. Varsayılan para birimi değiştirilebilir ancak mevcut işlemlerin para birimi değiştirilemez: Firefly III, aynı anda birden çok para biriminin kullanılmasını destekler.', 'transactions-index' => 'Bu masraflar, mevduatlar ve transferler için özellikle yaratıcı değildir. Bunlar otomatik olarak oluşturuldu.', 'piggy-banks-index' => 'Gördüğünüz gibi, üç tane banka var. Her domuzcuk bankasındaki para miktarını değiştirmek için artı ve eksi düğmelerini kullanın. Her domuzcuk bankasının yönetimini görmek için domuzcuk\'un üzerine tıklayın.', - 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'import-index' => 'Herhangi bir CSV dosyası Firefly III\'e aktarılabilir. Ayrıca bunq ve Spectre\'den veri almayı da destekliyor. Diğer bankalar ve finansal toplayıcılar gelecekte uygulanacaktır. Ancak bir demo kullanıcısı olarak, yalnızca "fake" -provider etkinlikte görebilirsiniz. Sürecin nasıl çalıştığını göstermek için bazı rasgele işlemler üretecektir. +', + 'recurring-index' => ' +Lütfen bu özelliğin aktif geliştirme aşamasında olduğunu ve beklendiği gibi çalışmayabileceğini unutmayın.', + 'recurring-create' => ' +Lütfen bu özelliğin aktif geliştirme aşamasında olduğunu ve beklendiği gibi çalışmayabileceğini unutmayın.', ]; diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index 32747c7c17..e7e77d38ab 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -46,17 +46,18 @@ return [ 'Opening balance' => 'Açılış bakiyesi', 'create_new_stuff' => 'Yeni bir şey oluştur', 'new_withdrawal' => 'Yeni para çekme', - 'create_new_transaction' => 'Create new transaction', - 'go_to_asset_accounts' => 'View your asset accounts', - 'go_to_budgets' => 'Go to your budgets', - 'go_to_categories' => 'Go to your categories', - 'go_to_bills' => 'Go to your bills', - 'go_to_expense_accounts' => 'See your expense accounts', - 'go_to_revenue_accounts' => 'See your revenue accounts', - 'go_to_piggies' => 'Go to your piggy banks', + 'create_new_transaction' => 'Yeni işlem oluştur', + 'go_to_asset_accounts' => 'Varlık hesaplarınızı görüntüleyin +', + 'go_to_budgets' => 'Bütçelerine git', + 'go_to_categories' => 'Kategorilerinize gidin', + 'go_to_bills' => 'Faturalarına git', + 'go_to_expense_accounts' => 'Gider hesaplarınızı görün', + 'go_to_revenue_accounts' => 'Gelir hesaplarınızı görün', + 'go_to_piggies' => 'Kumbaranıza gidin', 'new_deposit' => 'Yeni Depozito', 'new_transfer' => 'Yeni transfer', - 'new_transfers' => 'New transfer', + 'new_transfers' => 'Yeni transfer', 'new_asset_account' => 'Yeni varlık hesabı', 'new_expense_account' => 'Yeni gider hesabı', 'new_revenue_account' => 'Yeni Gelir Hesabı', @@ -71,9 +72,9 @@ return [ 'flash_error_multiple' => 'Bir hata var|:count hata var', 'net_worth' => 'Net değer', 'route_has_no_help' => 'Bu rota için yardım yok.', - 'help_for_this_page' => 'Help for this page', - 'no_help_could_be_found' => 'No help text could be found.', - 'no_help_title' => 'Apologies, an error occurred.', + 'help_for_this_page' => 'Bu sayfa için yardım', + 'no_help_could_be_found' => 'Hiçbir yardım metni bulunamadı.', + 'no_help_title' => 'Üzgünüz, bir hata oluştu.', 'two_factor_welcome' => 'Merhaba, :user!', 'two_factor_enter_code' => 'Devam etmek için lütfen iki faktörlü kimlik doğrulama kodunuzu girin. Uygulamanız sizin için oluşturabilir.', 'two_factor_code_here' => 'Kodu buraya girin', @@ -122,14 +123,14 @@ return [ 'clone_deposit' => 'Bu depozitoyu klonla', 'clone_transfer' => 'Bu transferi kopyala', 'multi_select_no_selection' => 'Hiçbiri seçilmedi', - 'multi_select_select_all' => 'Select all', - 'multi_select_n_selected' => 'selected', + 'multi_select_select_all' => 'Tümünü seç', + 'multi_select_n_selected' => 'seçili', 'multi_select_all_selected' => 'Tümü seçildi', 'multi_select_filter_placeholder' => 'Bul..', - 'intro_next_label' => 'Next', - 'intro_prev_label' => 'Previous', - 'intro_skip_label' => 'Skip', - 'intro_done_label' => 'Done', + 'intro_next_label' => 'Sonraki', + 'intro_prev_label' => 'Önceki', + 'intro_skip_label' => 'Atla', + 'intro_done_label' => 'Bitti', 'between_dates_breadcrumb' => ':start ve :end arasında', 'all_journals_without_budget' => 'Bütçesiz tüm işlemler', 'journals_without_budget' => 'Bütçesiz İşlemler', @@ -161,42 +162,43 @@ return [ 'invalid_server_configuration' => 'Geçersiz sunucu yapılandırması', 'invalid_locale_settings' => 'Firefly III parasal tutarları biçimlendiremiyor çünkü gerekli paketler sunucunuzda yok. Bunun nasıl yapıldığıyla ilgili talimatlar var.', 'quickswitch' => 'Hızlı anahtar', - 'sign_in_to_start' => 'Sign in to start your session', - 'sign_in' => 'Sign in', - 'register_new_account' => 'Register a new account', - 'forgot_my_password' => 'I forgot my password', - 'problems_with_input' => 'There were some problems with your input.', - 'reset_password' => 'Reset your password', - 'button_reset_password' => 'Reset password', - 'reset_button' => 'Reset', - 'want_to_login' => 'I want to login', - 'button_register' => 'Register', - 'authorization' => 'Authorization', - 'active_bills_only' => 'active bills only', - 'average_per_bill' => 'average per bill', - 'expected_total' => 'expected total', + 'sign_in_to_start' => 'Oturumu başlatmak için giriş yapın', + 'sign_in' => 'Oturum aç', + 'register_new_account' => 'Yeni hesap kaydı', + 'forgot_my_password' => 'Şifremi unuttum', + 'problems_with_input' => 'Girişinizle ilgili bazı problemler var.', + 'reset_password' => 'Şifreni sıfırla', + 'button_reset_password' => 'Parolayı Sıfırla', + 'reset_button' => 'Sıfırla', + 'want_to_login' => 'Giriş yapmak istiyorum', + 'button_register' => 'Kayıt ol', + 'authorization' => 'Yetkilendirme', + 'active_bills_only' => 'sadece aktif faturalar', + 'average_per_bill' => 'fatura başına ortalama', + 'expected_total' => 'beklenen toplam', // API access - 'authorization_request' => 'Firefly III v:version Authorization Request', - 'authorization_request_intro' => ':client is requesting permission to access your financial administration. Would you like to authorize :client to access these records?', - 'scopes_will_be_able' => 'This application will be able to:', - 'button_authorize' => 'Authorize', - 'none_in_select_list' => '(none)', + 'authorization_request' => 'Firefly III v: version Yetkilendirme İsteği', + 'authorization_request_intro' => ':client finansal yönetiminize erişmek için izin istiyor. Bu kayıtlara erişmek için :client \'yi yetkilendirmek ister misiniz?', + 'scopes_will_be_able' => 'Bu uygulama şunları yapabilir:', + 'button_authorize' => 'İzin ver', + 'none_in_select_list' => '(Yok)', // check for updates: - 'update_check_title' => 'Check for updates', - 'admin_update_check_title' => 'Automatically check for update', - 'admin_update_check_explain' => 'Firefly III can check for updates automatically. When you enable this setting, it will contact Github to see if a new version of Firefly III is available. When it is, you will get a notification. You can test this notification using the button on the right. Please indicate below if you want Firefly III to check for updates.', - 'check_for_updates_permission' => 'Firefly III can check for updates, but it needs your permission to do so. Please go to the administration to indicate if you would like this feature to be enabled.', - 'updates_ask_me_later' => 'Ask me later', - 'updates_do_not_check' => 'Do not check for updates', - 'updates_enable_check' => 'Enable the check for updates', - 'admin_update_check_now_title' => 'Check for updates now', - 'admin_update_check_now_explain' => 'If you press the button, Firefly III will see if your current version is the latest.', - 'check_for_updates_button' => 'Check now!', - 'update_new_version_alert' => 'A new version of Firefly III is available. You are running v:your_version, the latest version is v:new_version which was released on :date.', - 'update_current_version_alert' => 'You are running v:version, which is the latest available release.', - 'update_newer_version_alert' => 'You are running v:your_version, which is newer than the latest release, v:new_version.', - 'update_check_error' => 'An error occurred while checking for updates. Please view the log files.', + 'update_check_title' => 'Güncellemeleri kontrol et', + 'admin_update_check_title' => 'Güncellemeleri otomatik olarak kontrol et', + 'admin_update_check_explain' => 'Firefly III güncellemeleri otomatik olarak kontrol edebilir. Bu ayarı etkinleştirdiğinizde, Firefly III\'ün yeni bir sürümünün mevcut olup olmadığını görmek için Github ile iletişime geçecektir. Bu olduğunda, bir bildirim alacaksınız. Bu bildirimi sağdaki düğmeyi kullanarak test edebilirsiniz. Firefly III\'ün güncellemeleri kontrol etmesini istiyorsanız lütfen aşağıya belirtin.', + 'check_for_updates_permission' => 'Firefly III güncellemeleri kontrol edebilir, ancak bunu yapmak için izniniz gerekir. Bu özelliğin etkinleştirilmesini isteyip istemediğinizi belirtmek için lütfen yönetim bölümüne gidin.', + 'updates_ask_me_later' => 'Daha sonra sor', + 'updates_do_not_check' => 'Güncelleştirmeleri kontrol ETME', + 'updates_enable_check' => 'Güncelleme kontrolünü etkinleştir', + 'admin_update_check_now_title' => 'Güncellemeleri kontrol et', + 'admin_update_check_now_explain' => 'Düğmeye basarsanız, Firefly III geçerli sürümünüzün en son olup olmadığını görür.', + 'check_for_updates_button' => 'Kontrol Et!', + 'update_new_version_alert' => 'Firefly III\'ün yeni bir sürümü mevcut. v:your_version çalıştırıyorsunuz, en son sürüm v:new_version :date. tarihinde yayınlandı.', + 'update_current_version_alert' => 'Mevcut en yeni sürümü v:version, kullanıyorsunuz.', + 'update_newer_version_alert' => 'En son sürüm olan v:new_version\'dan daha yeni olan v:your_version kullanıyorsunuz. +', + 'update_check_error' => 'Güncelleştirmeleri denetlerken bir hata oluştu. Lütfen günlük dosyalarını inceleyin.', // search 'search' => 'Ara', @@ -223,7 +225,7 @@ return [ // export data: 'import_and_export' => 'İçe al ve dışarı aktar', 'export_data' => 'Veriyi dışarı aktar', - 'export_and_backup_data' => 'Export data', + 'export_and_backup_data' => 'Veriyi dışarı aktar', 'export_data_intro' => 'Use the exported data to move to a new financial application. Please note that these files are not meant as a backup. They do not contain enough meta-data to fully restore a new Firefly III installation. If you want to make a backup of your data, please backup the database directly.', 'export_format' => 'Dışa Aktarma Biçimi', 'export_format_csv' => 'Virgülle ayrılmış değerler (CSV file)', @@ -269,8 +271,8 @@ return [ 'move_rule_group_down' => 'Grup kuralını aşağı taşı', 'save_rules_by_moving' => 'Bu kuralları başka bir kural grubuna taşıyarak kaydedin:', 'make_new_rule' => '":title" kural grubunda yeni kural oluşturun', - 'rule_is_strict' => 'strict rule', - 'rule_is_not_strict' => 'non-strict rule', + 'rule_is_strict' => 'sıkı kural', + 'rule_is_not_strict' => 'sıkı olmayan kural', 'rule_help_stop_processing' => 'Bu kutuyu işaretlediğinizde, bu grupta sonraki kurallar yürütülemez.', 'rule_help_strict' => 'In strict rules ALL triggers must fire for the action(s) to be executed. In non-strict rules, ANY trigger is enough for the action(s) to be executed.', 'rule_help_active' => 'Aktif olmayan kurallar asla çalışmaz.', @@ -412,7 +414,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'rule_action_clear_notes_choice' => 'Herhangi bir notu kaldır', 'rule_action_clear_notes' => 'Herhangi bir notu kaldır', 'rule_action_set_notes_choice' => 'Notları şuna ayarla..', - 'rule_action_link_to_bill_choice' => 'Link to a bill..', + 'rule_action_link_to_bill_choice' => 'Bir fatura bağlantı...', 'rule_action_link_to_bill' => 'Link to bill ":action_value"', 'rule_action_set_notes' => 'Notları ":action_value" olarak ayarla', @@ -466,6 +468,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'pref_two_factor_auth_code_help' => 'QR kodunu, telefonunuzda Authy veya Authenticator uygulamalarında bulun ve oluşturulan kodu girin.', 'pref_two_factor_auth_reset_code' => 'Doğrulama kodunu sıfırla', 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Ayarları kaydet', 'saved_preferences' => 'Tercihler kaydedildi!', 'preferences_general' => 'Genel', @@ -669,7 +672,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'bill_will_automatch' => 'Fatura uygun işlemlere otomatik olarak bağlandı', 'skips_over' => 'atla', 'bill_store_error' => 'An unexpected error occurred while storing your new bill. Please check the log files', - 'list_inactive_rule' => 'inactive rule', + 'list_inactive_rule' => 'Etkin Olmayan Kurallar', // accounts: 'details_for_asset' => '":name" Varlık hesabı ayrıntıları', @@ -793,7 +796,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'no_bulk_category' => 'Don\'t update category', 'no_bulk_budget' => 'Don\'t update budget', 'no_bulk_tags' => 'Don\'t update tag(s)', - 'bulk_edit' => 'Bulk edit', + 'bulk_edit' => 'Toplu düzenle', 'cannot_edit_other_fields' => 'Gösterecek yer olmadığı için, bu dosya dışındaki dosyaları toplu olarak düzenleyemezsiniz. Eğer o alanları düzenlemeniz gerekliyse lütfen linki takip edin ve onları teker teker düzenleyin.', 'no_budget' => 'hiçbiri', 'no_budget_squared' => '(Bütçe yok)', @@ -805,12 +808,12 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'opt_group_savingAsset' => 'Tasarruf Hesapları', 'opt_group_sharedAsset' => 'Paylaşılan varlık hesapları', 'opt_group_ccAsset' => 'Kredi Kartı', - 'opt_group_cashWalletAsset' => 'Cash wallets', - 'notes' => 'Notes', + 'opt_group_cashWalletAsset' => 'Nakit cüzdan', + 'notes' => 'Notlar', 'unknown_journal_error' => 'Could not store the transaction. Please check the log files.', // new user: - 'welcome' => 'Welcome to Firefly III!', + 'welcome' => 'Firefly III\'e hoşgeldiniz!', 'submit' => 'Gönder', 'getting_started' => 'Başla', 'to_get_started' => 'Firefly III\'ü başarılı şekilde yüklediğinizi görmek güzel. Bu aracı kullanmak için lütfen banka adınızı ve ana hesabınızın bakiyesini girin. Birden fazla hesabınız varsa endişelenmeyin. Onları daha sonra ekleyebilirsiniz. Bu adım sadece Firefly III\'ün bir yerden başlaması gerektiği içindir.', @@ -818,10 +821,10 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'finish_up_new_user' => 'İşte bu! Submit tıklayarak devam edebilirsiniz. Firefly III Anasayfasına yönlendirileceksiniz.', 'stored_new_accounts_new_user' => 'Yuppi! Yeni hesabınız kaydedildi.', 'set_preferred_language' => 'If you prefer to use Firefly III in another language, please indicate so here.', - 'language' => 'Language', - 'new_savings_account' => ':bank_name savings account', - 'cash_wallet' => 'Cash wallet', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'language' => 'Dil', + 'new_savings_account' => ':bank_name tasarruf hesabı', + 'cash_wallet' => 'Nakit cüzdan', + 'currency_not_present' => 'Normalde kullandığınız para birimi listelenmiyorsa endişelenmeyin. Seçenekler> Para Birimleri altında kendi para birimlerini oluşturabilirsiniz.', // home page: 'yourAccounts' => 'Hesaplarınız', @@ -901,7 +904,6 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'balanceEnd' => 'Dönem Sonunda Bakiye', 'splitByAccount' => 'Hesaplara göre bölünmüş', 'coveredWithTags' => 'Etiketler kapatılmıştır', - 'leftUnbalanced' => 'Sol dengesiz', 'leftInBudget' => 'Bütçede bırakıldı', 'sumOfSums' => 'Hesap toplamı', 'noCategory' => '(Kategori yok)', @@ -970,7 +972,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'account_role_sharedAsset' => 'Paylaşılan varlık hesabı', 'account_role_savingAsset' => 'Birikim hesabı', 'account_role_ccAsset' => 'Kredi Kartı', - 'account_role_cashWalletAsset' => 'Cash wallet', + 'account_role_cashWalletAsset' => 'Nakit cüzdan', 'budget_chart_click' => 'Bir grafik görmek için lütfen yukarıdaki tabloda bir bütçe adına tıklayın.', 'category_chart_click' => 'Bir grafik görmek için lütfen yukarıdaki tabloda bir kategori adına tıklayın.', 'in_out_accounts' => 'Kazanılan ve kombinasyon başına harcanan', @@ -1019,7 +1021,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'remove_money_from_piggy_title' => '":name" kumbarasından para çek', 'add' => 'Ekle', 'no_money_for_piggy' => 'Bu kumbaraya koyacak paran yok.', - 'suggested_savings_per_month' => 'Suggested per month', + 'suggested_savings_per_month' => 'Aylık önerildi', 'remove' => 'Kaldır', 'max_amount_add' => 'Ekleyebileceğiniz azami tutar', @@ -1031,7 +1033,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'events' => 'Etkinlikler', 'target_amount' => 'Hedef miktar', 'start_date' => 'Başlangıç Tarihi', - 'no_start_date' => 'No start date', + 'no_start_date' => 'başlangıç tarihi yok', 'target_date' => 'Hedeflenen tarih', 'no_target_date' => 'Hedef tarihi yok', 'table' => 'Tablo', @@ -1118,7 +1120,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'invalid_link_selection' => 'Bu işlemler bağlantılanamıyor', 'journals_linked' => 'İşlemler bağlantıları oluşturuldu.', 'journals_error_linked' => 'Bu işlemler zaten bağlantılı.', - 'journals_link_to_self' => 'You cannot link a transaction to itself', + 'journals_link_to_self' => 'Kendisi için bir ilişki ekleyemezsiniz.', 'journal_links' => 'İşlem bağlantıları', 'this_withdrawal' => 'Bu çekim', 'this_deposit' => 'Bu depozito', @@ -1135,6 +1137,8 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'is (partially) refunded by_inward' => 'tarafından (kısmen) geri ödendi', 'is (partially) paid for by_inward' => 'tarafından (kısmen) ödendi', 'is (partially) reimbursed by_inward' => 'tarafından (kısmen) iade edildi', + 'inward_transaction' => 'Içe işlem', + 'outward_transaction' => 'Dış işlem', 'relates to_outward' => 'ile ilişkili', '(partially) refunds_outward' => '(kısmen) geri ödeme', '(partially) pays for_outward' => 'için (kısmen) öder', @@ -1158,6 +1162,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', // Import page (general strings only) 'import_index_title' => 'Firefly III\'e veri aktarma', 'import_data' => 'Veriyi içe aktar', + 'import_transactions' => 'İşlemleri içe aktarma', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Bir Sandstorm.io ortamında Firefly III kullanıyorsanız, bu işlev kullanılamaz.', @@ -1207,4 +1212,68 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'no_bills_intro_default' => 'Henüz bir faturanız yok. Kira veya sigortanız gibi düzenli giderleri takip etmek için faturalar oluşturabilirsiniz.', 'no_bills_imperative_default' => 'Böyle düzenli faturalarınız var mı? Bir fatura oluşturun ve ödemelerinizi takip edin:', 'no_bills_create_default' => 'Fatura oluştur', + + // recurring transactions + 'recurrences' => 'Tekrar Eden İşlemler', + 'no_recurring_title_default' => 'Yinelenen bir işlem yapalım!', + 'no_recurring_intro_default' => 'Henüz yinelenen işleminiz yok. Firefly III\'ün sizin için otomatik olarak işlemler oluşturmasını sağlamak için bunları kullanabilirsiniz.', + 'no_recurring_imperative_default' => 'Bu oldukça gelişmiş bir özelliktir ve son derece kullanışlı olabilir. Devam etmeden önce, (?) - sağ üst köşedeki dokümanları okuduğunuzdan emin olun.', + 'no_recurring_create_default' => 'Yinelenen bir işlem oluştur', + 'make_new_recurring' => 'Yinelenen bir işlem oluştur', + 'recurring_daily' => 'Her gün', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'İlişkili işlemler', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Etiketler', + 'recurring_meta_field_notes' => 'Notlar', + 'recurring_meta_field_bill_id' => 'Fatura', + 'recurring_meta_field_piggy_bank_id' => 'Kumbara', + 'create_new_recurrence' => 'Yinelenen bir işlem oluştur', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/tr_TR/form.php b/resources/lang/tr_TR/form.php index ac3f7c5de7..afb1492e96 100644 --- a/resources/lang/tr_TR/form.php +++ b/resources/lang/tr_TR/form.php @@ -24,66 +24,66 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Banka adı', - 'bank_balance' => 'Bakiye', - 'savings_balance' => 'Tasarruf bakiyesi', - 'credit_card_limit' => 'Kredi kartı limiti', - 'automatch' => 'Otomatik olarak eşleştir', - 'skip' => 'Atla', - 'name' => 'İsim', - 'active' => 'Aktif', - 'amount_min' => 'Minimum tutar', - 'amount_max' => 'Minimum tutar', - 'match' => 'Eşleşti', - 'strict' => 'Strict mode', - 'repeat_freq' => 'Tekrarlar', - 'journal_currency_id' => 'Para birimi', - 'currency_id' => 'Para birimi', - 'transaction_currency_id' => 'Currency', - 'external_ip' => 'Your server\'s external IP', - 'attachments' => 'Ekler', - 'journal_amount' => 'Tutar', - 'journal_source_account_name' => 'Gelir hesabı (kaynak)', - 'journal_source_account_id' => 'Varlık hesabı (kaynak)', - 'BIC' => 'BIC', - 'verify_password' => 'Parola güvenliğini doğrula', - 'source_account' => 'Kaynak hesap', - 'destination_account' => 'Hedef Hesap', - 'journal_destination_account_id' => 'Öğe hesabı (Hedef)', - 'asset_destination_account' => 'Öğe hesabı (Hedef)', - 'asset_source_account' => 'Varlık Hesabı (kaynak)', - 'journal_description' => 'Tanımlama', - 'note' => 'Notlar', - 'split_journal' => 'Bu işlemi böl / Taksitlendir', - 'split_journal_explanation' => 'Bu işlemi taksitlendirin', - 'currency' => 'Para birimi', - 'account_id' => 'Varlık hesabı', - 'budget_id' => 'Bütçe', - 'openingBalance' => 'Açılış bakiyesi', - 'tagMode' => 'Etiket modu', - 'tag_position' => 'Etiket konumu', - 'virtualBalance' => 'Sanal bakiye', - 'targetamount' => 'Hedef tutar', - 'accountRole' => 'Hesap rolü', - 'openingBalanceDate' => 'Açılış bakiyesi tarihi', - 'ccType' => 'Kredi kartı ödeme planı', - 'ccMonthlyPaymentDate' => 'Kredi kartı aylık ödeme tarihi', - 'piggy_bank_id' => 'Kumbara', - 'returnHere' => 'Dön buraya', - 'returnHereExplanation' => 'Sakladıktan sonra, başka bir tane oluşturmak için buraya dön.', - 'returnHereUpdateExplanation' => 'Güncelledikten sonra buraya dönün.', - 'description' => 'Tanımlama', - 'expense_account' => 'Gider Hesabı', - 'revenue_account' => 'Gelir hesabı', - 'decimal_places' => 'Ondalık basamak', - 'exchange_rate_instruction' => 'Yabancı para birimleri', - 'source_amount' => 'Miktar (kaynak)', - 'destination_amount' => 'Miktar (Hedef)', - 'native_amount' => 'Yerli Miktar', - 'new_email_address' => 'Yeni e-posta adresi', - 'verification' => 'Doğrulama', - 'api_key' => 'API anahtarı', - 'remember_me' => 'Remember me', + 'bank_name' => 'Banka adı', + 'bank_balance' => 'Bakiye', + 'savings_balance' => 'Tasarruf bakiyesi', + 'credit_card_limit' => 'Kredi kartı limiti', + 'automatch' => 'Otomatik olarak eşleştir', + 'skip' => 'Atla', + 'name' => 'İsim', + 'active' => 'Aktif', + 'amount_min' => 'Minimum tutar', + 'amount_max' => 'Minimum tutar', + 'match' => 'Eşleşti', + 'strict' => 'Strict mode', + 'repeat_freq' => 'Tekrarlar', + 'journal_currency_id' => 'Para birimi', + 'currency_id' => 'Para birimi', + 'transaction_currency_id' => 'Currency', + 'external_ip' => 'Your server\'s external IP', + 'attachments' => 'Ekler', + 'journal_amount' => 'Tutar', + 'journal_source_name' => 'Revenue account (source)', + 'journal_source_id' => 'Asset account (source)', + 'BIC' => 'BIC', + 'verify_password' => 'Parola güvenliğini doğrula', + 'source_account' => 'Kaynak hesap', + 'destination_account' => 'Hedef Hesap', + 'journal_destination_id' => 'Asset account (destination)', + 'asset_destination_account' => 'Öğe hesabı (Hedef)', + 'asset_source_account' => 'Varlık Hesabı (kaynak)', + 'journal_description' => 'Tanımlama', + 'note' => 'Notlar', + 'split_journal' => 'Bu işlemi böl / Taksitlendir', + 'split_journal_explanation' => 'Bu işlemi taksitlendirin', + 'currency' => 'Para birimi', + 'account_id' => 'Varlık hesabı', + 'budget_id' => 'Bütçe', + 'openingBalance' => 'Açılış bakiyesi', + 'tagMode' => 'Etiket modu', + 'tag_position' => 'Etiket konumu', + 'virtualBalance' => 'Sanal bakiye', + 'targetamount' => 'Hedef tutar', + 'accountRole' => 'Hesap rolü', + 'openingBalanceDate' => 'Açılış bakiyesi tarihi', + 'ccType' => 'Kredi kartı ödeme planı', + 'ccMonthlyPaymentDate' => 'Kredi kartı aylık ödeme tarihi', + 'piggy_bank_id' => 'Kumbara', + 'returnHere' => 'Dön buraya', + 'returnHereExplanation' => 'Sakladıktan sonra, başka bir tane oluşturmak için buraya dön.', + 'returnHereUpdateExplanation' => 'Güncelledikten sonra buraya dönün.', + 'description' => 'Tanımlama', + 'expense_account' => 'Gider Hesabı', + 'revenue_account' => 'Gelir hesabı', + 'decimal_places' => 'Ondalık basamak', + 'exchange_rate_instruction' => 'Yabancı para birimleri', + 'source_amount' => 'Miktar (kaynak)', + 'destination_amount' => 'Miktar (Hedef)', + 'native_amount' => 'Yerli Miktar', + 'new_email_address' => 'Yeni e-posta adresi', + 'verification' => 'Doğrulama', + 'api_key' => 'API anahtarı', + 'remember_me' => 'Remember me', 'source_account_asset' => 'Kaynak Hesabı (varlık hesabı)', 'destination_account_expense' => 'Hedef Hesap (gider hesabı)', @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Mevduata dönüştürün', 'convert_Transfer' => 'Aktarımı dönüştür', - 'amount' => 'Tutar', - 'foreign_amount' => 'Foreign amount', - 'existing_attachments' => 'Existing attachments', - 'date' => 'Tarih', - 'interest_date' => 'Faiz tarihi', - 'book_date' => 'Kitap Tarihi', - 'process_date' => 'İşlem tarihi', - 'category' => 'Kategori', - 'tags' => 'Etiketler', - 'deletePermanently' => 'Kalıcı olarak sil', - 'cancel' => 'İptal', - 'targetdate' => 'Hedeflenen tarih', - 'startdate' => 'Başlangıç Tarihi', - 'tag' => 'Etiket', - 'under' => 'Altında', - 'symbol' => 'Sembol', - 'code' => 'Kod', - 'iban' => 'IBAN numarası', - 'accountNumber' => 'Hesap numarası', - 'creditCardNumber' => 'Kredi Kartı Numarası', - 'has_headers' => 'Başlıklar', - 'date_format' => 'Tarih formatı', - 'specifix' => 'Banka veya dosyalara özel düzeltmeler', - 'attachments[]' => 'Ekler', - 'store_new_withdrawal' => 'Yeni para çekme oluştur', - 'store_new_deposit' => 'Yeni depozito oluştur', - 'store_new_transfer' => 'Yeni transfer oluştur', - 'add_new_withdrawal' => 'Yeni para çekme ekle', - 'add_new_deposit' => 'Yeni depozito ekle', - 'add_new_transfer' => 'Yeni bir transfer ekle', - 'title' => 'Başlık', - 'notes' => 'Notlar', - 'filename' => 'Dosya adı', - 'mime' => 'MIME türü', - 'size' => 'Boyut', - 'trigger' => 'Tetikleyici', - 'stop_processing' => 'İşlemeyi durdur', - 'start_date' => 'Sayfa başlangıcı', - 'end_date' => 'Kapsama alanı dışında', - 'export_start_range' => 'İhracatın başlangıcı', - 'export_end_range' => 'İhracatın sonu', - 'export_format' => 'Dosya biçimi', - 'include_attachments' => 'Yüklenen ekleri dahil et', - 'include_old_uploads' => 'İçe aktarılan verileri dahil et', - 'accounts' => 'Bu hesaptan işlemleri çıkarın', - 'delete_account' => '":name" adlı hesabı sil', - 'delete_bill' => 'Faturayı sil ":name"', - 'delete_budget' => '":name" bütçesini sil', - 'delete_category' => '":name" kategorisini sil', - 'delete_currency' => 'Para birimini sil ":name"', - 'delete_journal' => '":description" açıklamalı işlemi sil', - 'delete_attachment' => '":name" eklentisini sil', - 'delete_rule' => '\'":title" kuralını sil', - 'delete_rule_group' => '":title" kural grubunu sil', - 'delete_link_type' => '":name" bağlantı türünü sil', - 'delete_user' => '":email" kullanıcısını sil', - 'user_areYouSure' => '":email" kullanıcısını silerseniz her şey gitmiş olacak. Geriye alma, silmeyi iptal etme veya başka bir şey yoktur. Eğer kendinizi silerseniz, bu Firefly III\'e erişiminizi kaybedersiniz.', - 'attachment_areYouSure' => '":name" isimli eklentiyi silmek istediğinizden emin misiniz?', - 'account_areYouSure' => '":name" isimli hesabı silmek istediğinizden emin misiniz?', - 'bill_areYouSure' => '":name" isimli faturayı silmek istediğinizden emin misiniz?', - 'rule_areYouSure' => '":title" başlıklı kuralı silmek istediğinizden emin misiniz?', - 'ruleGroup_areYouSure' => '":title" başlıklı kural grubunu silmek istediğinizden emin misiniz?', - 'budget_areYouSure' => '":name" isimli bütçeyi silmek istediğinizden emin misiniz?', - 'category_areYouSure' => '":name" isimli kategoriyi silmek istediğinizden emin misiniz?', - 'currency_areYouSure' => '":name" isimli para birimini silmek istediğinizden emin misiniz?', - 'piggyBank_areYouSure' => '":name" isimli kumbarayı silmek istediğinizden emin misiniz?', - 'journal_areYouSure' => ':description" açıklamalı işlemi silmek istediğinizden emin misiniz?', - 'mass_journal_are_you_sure' => 'Bu işlemi silmek istediğinizden emin misiniz?', - 'tag_areYouSure' => '":tag" etiketini silmek istediğinizden emin misiniz?', - 'journal_link_areYouSure' => ':source and :destination arasındaki bağlantıyı silmek istediğinizden emin misiniz?', - 'linkType_areYouSure' => '":name" (":inward" / ":outward") bağlantı türünü silmek istediğinizden emin misiniz?', - 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', - 'mass_make_selection' => 'Onay kutusunu kaldırarak öğelerin silinmesini engelleyebilirsiniz.', - 'delete_all_permanently' => 'Seçilenleri kalıcı olarak sil', - 'update_all_journals' => 'Bu işlemleri güncelleyin', - 'also_delete_transactions' => 'Bu hesaba bağlı olan tek işlem de silinecektir. |Bu hesaba bağlı tüm :count işlemleri de silinecektir.', - 'also_delete_connections' => 'Bu bağlantıya bağlı tek işlem bağlantısını kaybedecek.| Bu bağlantı türüne bağlı tüm :count işlemleri bağlantılarını kaybedecek.', - 'also_delete_rules' => 'Bu hesaba bağlı olan tek kural grubu da silinecektir. |Bu hesaba bağlı tüm :count kuralları da silinecektir.', - 'also_delete_piggyBanks' => 'Bu hesaba bağlı olan tek kumbara da silinecektir. |Bu hesaba bağlı olan tüm :count kumbaraları da silinecektir.', - 'bill_keep_transactions' => 'Bu hesaba bağlı olan tek işlem de silinmeyecek. |Bu hesaba bağlı tüm :count işlemleri de silinmeden muaf olacaklar.', - 'budget_keep_transactions' => 'Bu bütçeye bağlı olan tek işlem silinmeyecek. |Bu bütçeye bağlı tüm :count işlemleri de silinmeden muaf olacaklar.', - 'category_keep_transactions' => 'Bu kategoriye bağlı olan tek işlem silinmeyecek. |Bu kategoriye bağlı tüm :count işlemleri de silinmeden muaf olacaklar.', - 'tag_keep_transactions' => 'Bu etikete bağlı olan tek işlem silinmeyecek. |Bu etikete bağlı tüm :count işlemleri de silinmeden muaf olacaklar.', - 'check_for_updates' => 'Check for updates', + 'amount' => 'Tutar', + 'foreign_amount' => 'Foreign amount', + 'existing_attachments' => 'Existing attachments', + 'date' => 'Tarih', + 'interest_date' => 'Faiz tarihi', + 'book_date' => 'Kitap Tarihi', + 'process_date' => 'İşlem tarihi', + 'category' => 'Kategori', + 'tags' => 'Etiketler', + 'deletePermanently' => 'Kalıcı olarak sil', + 'cancel' => 'İptal', + 'targetdate' => 'Hedeflenen tarih', + 'startdate' => 'Başlangıç Tarihi', + 'tag' => 'Etiket', + 'under' => 'Altında', + 'symbol' => 'Sembol', + 'code' => 'Kod', + 'iban' => 'IBAN numarası', + 'accountNumber' => 'Hesap numarası', + 'creditCardNumber' => 'Kredi Kartı Numarası', + 'has_headers' => 'Başlıklar', + 'date_format' => 'Tarih formatı', + 'specifix' => 'Banka veya dosyalara özel düzeltmeler', + 'attachments[]' => 'Ekler', + 'store_new_withdrawal' => 'Yeni para çekme oluştur', + 'store_new_deposit' => 'Yeni depozito oluştur', + 'store_new_transfer' => 'Yeni transfer oluştur', + 'add_new_withdrawal' => 'Yeni para çekme ekle', + 'add_new_deposit' => 'Yeni depozito ekle', + 'add_new_transfer' => 'Yeni bir transfer ekle', + 'title' => 'Başlık', + 'notes' => 'Notlar', + 'filename' => 'Dosya adı', + 'mime' => 'MIME türü', + 'size' => 'Boyut', + 'trigger' => 'Tetikleyici', + 'stop_processing' => 'İşlemeyi durdur', + 'start_date' => 'Sayfa başlangıcı', + 'end_date' => 'Kapsama alanı dışında', + 'export_start_range' => 'İhracatın başlangıcı', + 'export_end_range' => 'İhracatın sonu', + 'export_format' => 'Dosya biçimi', + 'include_attachments' => 'Yüklenen ekleri dahil et', + 'include_old_uploads' => 'İçe aktarılan verileri dahil et', + 'accounts' => 'Bu hesaptan işlemleri çıkarın', + 'delete_account' => '":name" adlı hesabı sil', + 'delete_bill' => 'Faturayı sil ":name"', + 'delete_budget' => '":name" bütçesini sil', + 'delete_category' => '":name" kategorisini sil', + 'delete_currency' => 'Para birimini sil ":name"', + 'delete_journal' => '":description" açıklamalı işlemi sil', + 'delete_attachment' => '":name" eklentisini sil', + 'delete_rule' => '\'":title" kuralını sil', + 'delete_rule_group' => '":title" kural grubunu sil', + 'delete_link_type' => '":name" bağlantı türünü sil', + 'delete_user' => '":email" kullanıcısını sil', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => '":email" kullanıcısını silerseniz her şey gitmiş olacak. Geriye alma, silmeyi iptal etme veya başka bir şey yoktur. Eğer kendinizi silerseniz, bu Firefly III\'e erişiminizi kaybedersiniz.', + 'attachment_areYouSure' => '":name" isimli eklentiyi silmek istediğinizden emin misiniz?', + 'account_areYouSure' => '":name" isimli hesabı silmek istediğinizden emin misiniz?', + 'bill_areYouSure' => '":name" isimli faturayı silmek istediğinizden emin misiniz?', + 'rule_areYouSure' => '":title" başlıklı kuralı silmek istediğinizden emin misiniz?', + 'ruleGroup_areYouSure' => '":title" başlıklı kural grubunu silmek istediğinizden emin misiniz?', + 'budget_areYouSure' => '":name" isimli bütçeyi silmek istediğinizden emin misiniz?', + 'category_areYouSure' => '":name" isimli kategoriyi silmek istediğinizden emin misiniz?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => '":name" isimli para birimini silmek istediğinizden emin misiniz?', + 'piggyBank_areYouSure' => '":name" isimli kumbarayı silmek istediğinizden emin misiniz?', + 'journal_areYouSure' => ':description" açıklamalı işlemi silmek istediğinizden emin misiniz?', + 'mass_journal_are_you_sure' => 'Bu işlemi silmek istediğinizden emin misiniz?', + 'tag_areYouSure' => '":tag" etiketini silmek istediğinizden emin misiniz?', + 'journal_link_areYouSure' => ':source and :destination arasındaki bağlantıyı silmek istediğinizden emin misiniz?', + 'linkType_areYouSure' => '":name" (":inward" / ":outward") bağlantı türünü silmek istediğinizden emin misiniz?', + 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', + 'mass_make_selection' => 'Onay kutusunu kaldırarak öğelerin silinmesini engelleyebilirsiniz.', + 'delete_all_permanently' => 'Seçilenleri kalıcı olarak sil', + 'update_all_journals' => 'Bu işlemleri güncelleyin', + 'also_delete_transactions' => 'Bu hesaba bağlı olan tek işlem de silinecektir. |Bu hesaba bağlı tüm :count işlemleri de silinecektir.', + 'also_delete_connections' => 'Bu bağlantıya bağlı tek işlem bağlantısını kaybedecek.| Bu bağlantı türüne bağlı tüm :count işlemleri bağlantılarını kaybedecek.', + 'also_delete_rules' => 'Bu hesaba bağlı olan tek kural grubu da silinecektir. |Bu hesaba bağlı tüm :count kuralları da silinecektir.', + 'also_delete_piggyBanks' => 'Bu hesaba bağlı olan tek kumbara da silinecektir. |Bu hesaba bağlı olan tüm :count kumbaraları da silinecektir.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Check for updates', 'email' => 'E-posta adresi', 'password' => 'Şifre', @@ -216,11 +219,23 @@ return [ 'country_code' => 'Ülke kodu', 'provider_code' => 'Banka ya da veri sağlayıcı', - 'due_date' => 'Bitiş Tarihi', - 'payment_date' => 'Ödeme Tarihi', - 'invoice_date' => 'Fatura Tarihi', - 'internal_reference' => 'Dahili referans', - 'inward' => 'Dahili açıklama', - 'outward' => 'Harici açıklama', - 'rule_group_id' => 'Kural grubu', + 'due_date' => 'Bitiş Tarihi', + 'payment_date' => 'Ödeme Tarihi', + 'invoice_date' => 'Fatura Tarihi', + 'internal_reference' => 'Dahili referans', + 'inward' => 'Dahili açıklama', + 'outward' => 'Harici açıklama', + 'rule_group_id' => 'Kural grubu', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + 'weekend' => 'Weekend', + ]; diff --git a/resources/lang/tr_TR/import.php b/resources/lang/tr_TR/import.php index dd1ecba61d..d5b413b0c1 100644 --- a/resources/lang/tr_TR/import.php +++ b/resources/lang/tr_TR/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/tr_TR/list.php b/resources/lang/tr_TR/list.php index 4b2516e5f6..100e0aa33a 100644 --- a/resources/lang/tr_TR/list.php +++ b/resources/lang/tr_TR/list.php @@ -34,7 +34,8 @@ return [ 'name' => 'İsim', 'role' => 'Rol', 'currentBalance' => 'Cari bakiye', - 'linked_to_rules' => 'Relevant rules', + 'linked_to_rules' => ' +İlgili kurallar', 'active' => 'Aktif mi?', 'lastActivity' => 'Son Etkinlik', 'balanceDiff' => 'Bakiye farkı', @@ -92,7 +93,7 @@ return [ 'budget_count' => 'Bütçelerin sayısı', 'rule_and_groups_count' => 'Kuralların ve kural gruplarının sayısı', 'tags_count' => 'Etiket sayısı', - 'tags' => 'Tags', + 'tags' => 'Etiketler', 'inward' => 'Dahili açıklama', 'outward' => 'Dışa açıklama', 'number_of_transactions' => 'İşlem sayısı', @@ -103,8 +104,8 @@ return [ 'sum_deposits' => 'Toplam para yatırma', 'sum_transfers' => 'Transferlerin toplamı', 'reconcile' => 'Onaylanmış', - 'account_on_spectre' => 'Account (Spectre)', - 'do_import' => 'Import from this account', + 'account_on_spectre' => '(Spectre) Hesabı', + 'do_import' => 'Bu hesaptan içeri aktar', 'sepa-ct-id' => 'SEPA End to End Identifier', 'sepa-ct-op' => 'SEPA Opposing Account Identifier', 'sepa-db' => 'SEPA Mandate Identifier', @@ -114,13 +115,18 @@ return [ 'sepa-ci' => 'SEPA Creditor Identifier', 'external_id' => 'External ID', 'account_at_bunq' => 'Account with bunq', - 'file_name' => 'File name', - 'file_size' => 'File size', - 'file_type' => 'File type', - 'attached_to' => 'Attached to', - 'file_exists' => 'File exists', - 'spectre_bank' => 'Bank', - 'spectre_last_use' => 'Last login', - 'spectre_status' => 'Status', - 'bunq_payment_id' => 'bunq payment ID', + 'file_name' => 'Dosya adı', + 'file_size' => 'Dosya boyutu', + 'file_type' => 'Dosya tipi', + 'attached_to' => 'Şuraya Bağlı', + 'file_exists' => 'Dosya var', + 'spectre_bank' => 'Banka', + 'spectre_last_use' => 'Son giriş', + 'spectre_status' => 'Durum', + 'bunq_payment_id' => 'bunq ödeme kimliği', + 'repetitions' => 'Tekrarlar', + 'title' => 'Başlık', + 'transaction_s' => 'İşlemler', + 'field' => 'Alan', + 'value' => 'Değer', ]; diff --git a/resources/lang/tr_TR/validation.php b/resources/lang/tr_TR/validation.php index 7a4374c4d5..9fa4b2ad3b 100644 --- a/resources/lang/tr_TR/validation.php +++ b/resources/lang/tr_TR/validation.php @@ -23,27 +23,33 @@ declare(strict_types=1); return [ - 'iban' => 'Bu IBAN geçerli değilrdir.', - 'source_equals_destination' => 'The source account equals the destination account', + 'iban' => 'Bu geçerli bir IBAN değil.', + 'source_equals_destination' => 'The source account equals the destination account.', 'unique_account_number_for_user' => 'Bu hesap numarası zaten kullanılmaktadır.', - 'unique_iban_for_user' => 'It looks like this IBAN is already in use.', + 'unique_iban_for_user' => 'Bu IBAN numarası zaten kullanılmaktadır.', 'deleted_user' => 'Güvenlik kısıtlamaları nedeniyle, bu e-posta adresini kullanarak kayıt yapamazsınız.', 'rule_trigger_value' => 'Bu eylem, seçili işlem için geçersizdir.', 'rule_action_value' => 'Bu eylem seçili işlem için geçersizdir.', 'file_already_attached' => 'Yüklenen dosya ":name" zaten bu nesneye bağlı.', 'file_attached' => '":name" dosyası başarıyla yüklendi.', - 'must_exist' => 'The ID in field :attribute does not exist in the database.', - 'all_accounts_equal' => 'All accounts in this field must be equal.', - 'invalid_selection' => 'Your selection is invalid', - 'belongs_user' => 'This value is invalid for this field.', - 'at_least_one_transaction' => 'Need at least one transaction.', - 'require_currency_info' => 'The content of this field is invalid without currency information.', - 'equal_description' => 'Transaction description should not equal global description.', + 'must_exist' => 'ID alanı :attribute veritabanın içinde yok.', + 'all_accounts_equal' => 'Bu alandaki tüm hesapları eşit olmalıdır.', + 'invalid_selection' => 'Your selection is invalid.', + 'belongs_user' => 'Bu değer bu alan için geçerli değil.', + 'at_least_one_transaction' => 'En az bir işlem gerekir.', + 'at_least_one_repetition' => 'Need at least one repetition.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', + 'require_currency_info' => 'Bu alanın içeriği para birimi bilgileri geçersiz.', + 'equal_description' => 'İşlem açıklaması genel açıklama eşit değildir.', 'file_invalid_mime' => '":name" dosyası ":mime" türünde olup yeni bir yükleme olarak kabul edilemez.', 'file_too_large' => '":name" dosyası çok büyük.', - 'belongs_to_user' => ':attribute\'nin değeri bilinmiyor', + 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => ':attribute kabul edilmek zorunda.', 'bic' => 'Bu BIC geçerli değilrdir.', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', + 'base64' => 'Bu geçerli Base64 olarak kodlanmış veri değildir.', + 'model_id_invalid' => 'Verilen kimlik bu model için geçersiz görünüyor.', 'more' => ':attribute sıfırdan büyük olmak zorundadır.', 'active_url' => ':attribute geçerli bir URL değil.', 'after' => ':attribute :date tarihinden sonrası için tarihlendirilmelidir.', @@ -53,8 +59,8 @@ return [ 'array' => ':attribute bir dizi olmalıdır.', 'unique_for_user' => ':attribute\'de zaten bir girdi var.', 'before' => ':attribute :date tarihinden öncesi için tarihlendirilmelidir.', - 'unique_object_for_user' => 'Bu isim zaten kullanılıyor', - 'unique_account_for_user' => 'Bu hesap adı zaten kullanılıyor', + 'unique_object_for_user' => 'This name is already in use.', + 'unique_account_for_user' => 'This account name is already in use.', 'between.numeric' => ':attribute :min ve :max arasında olmalıdır.', 'between.file' => ':attribute, :min kilobayt ve :max kilobayt arasında olmalıdır.', 'between.string' => ':attribute :min karakter ve :max karakter olmalıdır.', @@ -85,6 +91,9 @@ return [ 'min.array' => ':attribute en az :min öğe içermelidir.', 'not_in' => 'Seçili :attribute geçersiz.', 'numeric' => ':attribute sayı olmalıdır.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => ':attribute biçimi geçersiz.', 'required' => ':attribute alanı gereklidir.', 'required_if' => ':other :value iken :attribute alanı gereklidir.', @@ -95,7 +104,7 @@ return [ 'required_without_all' => 'Hiçbir :values mevcut değilken :attribute alanı gereklidir.', 'same' => ':attribute ve :other eşleşmelidir.', 'size.numeric' => ':attribute :size olmalıdır.', - 'amount_min_over_max' => 'The minimum amount cannot be larger than the maximum amount.', + 'amount_min_over_max' => 'En az tutar en fazla tutardan büyük olamaz.', 'size.file' => ':attribute :size kilobyte olmalıdır.', 'size.string' => ':attribute :size karakter olmalıdır.', 'size.array' => ':attribute :size öğeye sahip olmalıdır.', @@ -109,43 +118,46 @@ return [ 'file' => ':attribute bir dosya olmalıdır.', 'in_array' => ':attribute alanı :other içinde olamaz.', 'present' => ':attribute alanı mevcut olmalıdır.', - 'amount_zero' => 'Toplam tutar sıfır olamaz', - 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'amount_zero' => 'The total amount cannot be zero.', + 'unique_piggy_bank_for_user' => 'Kumbara adı benzersiz olmalıdır.', + 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security.', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions.', + 'valid_recurrence_rep_moment' => 'Invalid repetition moment for this type of repetition.', + 'invalid_account_info' => 'Invalid account information.', 'attributes' => [ - 'email' => 'email address', - 'description' => 'description', - 'amount' => 'amount', - 'name' => 'name', - 'piggy_bank_id' => 'piggy bank ID', - 'targetamount' => 'target amount', - 'openingBalanceDate' => 'opening balance date', - 'openingBalance' => 'opening balance', - 'match' => 'match', - 'amount_min' => 'minimum amount', - 'amount_max' => 'maximum amount', - 'title' => 'title', - 'tag' => 'tag', - 'transaction_description' => 'transaction description', - 'rule-action-value.1' => 'rule action value #1', - 'rule-action-value.2' => 'rule action value #2', - 'rule-action-value.3' => 'rule action value #3', - 'rule-action-value.4' => 'rule action value #4', - 'rule-action-value.5' => 'rule action value #5', - 'rule-action.1' => 'rule action #1', - 'rule-action.2' => 'rule action #2', - 'rule-action.3' => 'rule action #3', - 'rule-action.4' => 'rule action #4', - 'rule-action.5' => 'rule action #5', - 'rule-trigger-value.1' => 'rule trigger value #1', - 'rule-trigger-value.2' => 'rule trigger value #2', - 'rule-trigger-value.3' => 'rule trigger value #3', - 'rule-trigger-value.4' => 'rule trigger value #4', - 'rule-trigger-value.5' => 'rule trigger value #5', - 'rule-trigger.1' => 'rule trigger #1', - 'rule-trigger.2' => 'rule trigger #2', - 'rule-trigger.3' => 'rule trigger #3', - 'rule-trigger.4' => 'rule trigger #4', - 'rule-trigger.5' => 'rule trigger #5', + 'email' => 'E-posta adresi', + 'description' => 'Açıklama', + 'amount' => 'Tutar', + 'name' => 'adı', + 'piggy_bank_id' => 'Kumbara ID', + 'targetamount' => 'Hedef tutar', + 'openingBalanceDate' => 'Açılış bakiyesi', + 'openingBalance' => 'Açılış bakiyesi', + 'match' => 'Eşleşme', + 'amount_min' => 'Minimum tutar', + 'amount_max' => 'Maksimum tutar', + 'title' => 'başlık', + 'tag' => 'etiket', + 'transaction_description' => 'İşlem Açıklaması', + 'rule-action-value.1' => 'kural eylemi değer #1', + 'rule-action-value.2' => 'kural eylemi değer #1', + 'rule-action-value.3' => 'kural eylem değeri #3', + 'rule-action-value.4' => 'kural eylem değeri #4', + 'rule-action-value.5' => 'kural eylem değeri #5', + 'rule-action.1' => 'kural eylemi #1', + 'rule-action.2' => 'kural eylemi #2', + 'rule-action.3' => 'kural eylemi #3', + 'rule-action.4' => 'kural eylemi #4', + 'rule-action.5' => 'kural eylemi #5', + 'rule-trigger-value.1' => 'kural tetikleyici değeri #1', + 'rule-trigger-value.2' => 'kural tetikleyici değeri #2', + 'rule-trigger-value.3' => 'kural tetikleyici değeri #3', + 'rule-trigger-value.4' => 'kural tetikleyici değeri #4', + 'rule-trigger-value.5' => 'kural tetikleyici değeri #5', + 'rule-trigger.1' => 'kural tetikleyici #1', + 'rule-trigger.2' => 'kural tetikleyici #2', + 'rule-trigger.3' => 'kural tetikleyici #3', + 'rule-trigger.4' => 'kural tetikleyici #4', + 'rule-trigger.5' => 'kural tetikleyici #5', ], ]; diff --git a/resources/views/accounts/edit.twig b/resources/views/accounts/edit.twig index feaba02711..827f0f4cc9 100644 --- a/resources/views/accounts/edit.twig +++ b/resources/views/accounts/edit.twig @@ -48,7 +48,9 @@ {% endif %} {{ ExpandedForm.textarea('notes',null,{helpText: trans('firefly.field_supports_markdown')}) }} - {{ ExpandedForm.checkbox('active','1', preFilled.active) }} + + {# only correct way to do active checkbox #} + {{ ExpandedForm.checkbox('active', 1) }} diff --git a/resources/views/admin/index.twig b/resources/views/admin/index.twig index 861cbce7b3..0f1274cb2d 100644 --- a/resources/views/admin/index.twig +++ b/resources/views/admin/index.twig @@ -14,7 +14,9 @@ {{ 'firefly_instance_configuration'|_ }} {{ 'journal_link_configuration'|_ }} + {% if not sandstorm %} {{ 'update_check_title'|_ }} + {% endif %} diff --git a/resources/views/admin/link/show.twig b/resources/views/admin/link/show.twig index 3d452df573..b9639b981c 100644 --- a/resources/views/admin/link/show.twig +++ b/resources/views/admin/link/show.twig @@ -15,10 +15,10 @@ - {{ trans('firefly.source_transaction') }} + {{ trans('firefly.inward_transaction') }} {{ trans('firefly.link_description') }} - {{ trans('firefly.destination_transaction') }} + {{ trans('firefly.outward_transaction') }} diff --git a/resources/views/bills/create.twig b/resources/views/bills/create.twig index 9bfde12466..2548c0ed6b 100644 --- a/resources/views/bills/create.twig +++ b/resources/views/bills/create.twig @@ -35,7 +35,6 @@ {{ ExpandedForm.textarea('notes',null,{helpText: trans('firefly.field_supports_markdown')}) }} {{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }} {{ ExpandedForm.integer('skip',0) }} - {{ ExpandedForm.checkbox('active',1,true) }} diff --git a/resources/views/bills/edit.twig b/resources/views/bills/edit.twig index 6302b36198..e45f891c5b 100644 --- a/resources/views/bills/edit.twig +++ b/resources/views/bills/edit.twig @@ -37,7 +37,8 @@ {{ ExpandedForm.textarea('notes',null,{helpText: trans('firefly.field_supports_markdown')}) }} {{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }} {{ ExpandedForm.integer('skip') }} - {{ ExpandedForm.checkbox('active',1) }} + {# only correct way to do active checkbox #} + {{ ExpandedForm.checkbox('active', 1) }} diff --git a/resources/views/bills/show.twig b/resources/views/bills/show.twig index c912bc5583..f22355adb5 100644 --- a/resources/views/bills/show.twig +++ b/resources/views/bills/show.twig @@ -66,7 +66,7 @@ @@ -91,13 +91,13 @@ {{ 'rescan_old'|_ }} - {% if object.data.notes != '' %} + {% if object.data.notes.data[0] %} {{ 'notes'|_ }} - {{ object.data.notes }} + {{ object.data.notes.data[0].markdown|raw }} {% endif %} diff --git a/resources/views/budgets/edit.twig b/resources/views/budgets/edit.twig index eeaf34244e..4c2f03c6be 100644 --- a/resources/views/budgets/edit.twig +++ b/resources/views/budgets/edit.twig @@ -14,7 +14,8 @@ {{ 'mandatoryFields'|_ }} - {{ ExpandedForm.checkbox('active') }} + {# only correct way to do active checkbox #} + {{ ExpandedForm.checkbox('active', 1) }} {{ ExpandedForm.text('name') }} diff --git a/resources/views/demo/recurring/index.twig b/resources/views/demo/recurring/index.twig new file mode 100644 index 0000000000..366736f634 --- /dev/null +++ b/resources/views/demo/recurring/index.twig @@ -0,0 +1 @@ +{{ trans('demo.recurring-index') }} diff --git a/resources/views/demo/recurring/recurring-create.twig b/resources/views/demo/recurring/recurring-create.twig new file mode 100644 index 0000000000..f97b5249ed --- /dev/null +++ b/resources/views/demo/recurring/recurring-create.twig @@ -0,0 +1 @@ +{{ trans('demo.recurring-create') }} diff --git a/resources/views/emails/report-new-journals-html.twig b/resources/views/emails/report-new-journals-html.twig new file mode 100644 index 0000000000..a3022d0775 --- /dev/null +++ b/resources/views/emails/report-new-journals-html.twig @@ -0,0 +1,34 @@ +{% include 'emails.header-html' %} + + {% if journals.count == 1 %} + Firefly III has created a transaction for you. + {% endif %} + {% if journals.count > 1 %} + Firefly III has created {{ journals.count }} transactions for you. + {% endif %} + + + +{% if journals.count == 1 %} + + You can find it in your Firefly III installation: + {% for journal in journals %} + {{ journal.description }} ({{ journal|journalTotalAmount }}) + {% endfor %} + +{% endif %} + +{% if journals.count > 1 %} + + You can find them in your Firefly III installation: + + + {% for journal in journals %} + + {{ journal.description }} ({{ journal|journalTotalAmount }}) + + {% endfor %} + +{% endif %} + +{% include 'emails.footer-html' %} diff --git a/resources/views/emails/report-new-journals-text.twig b/resources/views/emails/report-new-journals-text.twig new file mode 100644 index 0000000000..81faa232d5 --- /dev/null +++ b/resources/views/emails/report-new-journals-text.twig @@ -0,0 +1,25 @@ +{% include 'emails.header-text' %} +{% if journals.count == 1 %} +Firefly III has created a transaction for you. + +{% endif %} +{% if journals.count > 1 %} +Firefly III has created {{ journals.count }} transactions for you. +{% endif %} +{% if journals.count == 1 %} +You can find in in your Firefly III installation: + +{% for journal in journals %} +{{ journal.description }}: {{ route('transactions.show', journal.id) }} ({{ journal|journalTotalAmountPlain }}) +{% endfor %} +{% endif %} + +{% if journals.count > 1 %} +You can find them in your Firefly III installation: + +{% for journal in journals %} +- {{ journal.description }}: {{ route('transactions.show', journal.id) }} ({{ journal|journalTotalAmountPlain }}) +{% endfor %} +{% endif %} + +{% include 'emails.footer-text' %} diff --git a/resources/views/form/amount.twig b/resources/views/form/amount.twig index c28227d1f7..652f355148 100644 --- a/resources/views/form/amount.twig +++ b/resources/views/form/amount.twig @@ -28,6 +28,6 @@ {% include 'form/feedback' %} - - + + diff --git a/resources/views/form/options.twig b/resources/views/form/options.twig index 1734d8fe06..f11e49bbe9 100644 --- a/resources/views/form/options.twig +++ b/resources/views/form/options.twig @@ -24,7 +24,7 @@ - {{ Form.checkbox('return_to_edit', '1', old('return_to_edit') == '1', {'id': name ~ '_return_to_edit'}) }} + {{ Form.checkbox('return_to_edit', '1', null, {'id': name ~ '_return_to_edit'}) }} {{ trans('form.returnHereUpdateExplanation') }} diff --git a/resources/views/import/bunq/choose-accounts.twig b/resources/views/import/bunq/choose-accounts.twig index f5b4afba79..566837af83 100644 --- a/resources/views/import/bunq/choose-accounts.twig +++ b/resources/views/import/bunq/choose-accounts.twig @@ -7,6 +7,26 @@ + + + + + {{ trans('import.job_config_bunq_apply_rules') }} + + + + + + {{ trans('import.job_config_bunq_apply_rules_text') }} + + {{ ExpandedForm.checkbox('apply_rules', 1, true) }} + + + + + + + diff --git a/resources/views/import/file/configure-upload.twig b/resources/views/import/file/configure-upload.twig index 59de0c8a74..4e1e31c46c 100644 --- a/resources/views/import/file/configure-upload.twig +++ b/resources/views/import/file/configure-upload.twig @@ -38,7 +38,7 @@ {{ ExpandedForm.checkbox('has_headers',1,importJob.configuration['has-headers'],{helpText: trans('import.job_config_uc_header_help')}) }} {{ ExpandedForm.text('date_format',importJob.configuration['date-format'],{helpText: trans('import.job_config_uc_date_help', {dateExample: phpdate('Ymd')}) }) }} {{ ExpandedForm.select('csv_delimiter', data.delimiters, importJob.configuration['delimiter'], {helpText: trans('import.job_config_uc_delimiter_help') } ) }} - {{ ExpandedForm.assetAccountList('csv_import_account', importJob.configuration['import-account'], {helpText: trans('import.job_config_uc_account_help')}) }} + {{ ExpandedForm.activeAssetAccountList('csv_import_account', importJob.configuration['import-account'], {helpText: trans('import.job_config_uc_account_help')}) }} {{ 'optionalFields'|_ }} diff --git a/resources/views/import/spectre/accounts.twig b/resources/views/import/spectre/accounts.twig index c6c0eedf82..4fbf0ba248 100644 --- a/resources/views/import/spectre/accounts.twig +++ b/resources/views/import/spectre/accounts.twig @@ -7,6 +7,26 @@ + + + + + {{ trans('import.job_config_spectre_apply_rules') }} + + + + + + {{ trans('import.job_config_spectre_apply_rules_text') }} + + {{ ExpandedForm.checkbox('apply_rules', 1, true) }} + + + + + + + diff --git a/resources/views/list/piggy-bank-events.twig b/resources/views/list/piggy-bank-events.twig index 43c815b54a..d5f76e195b 100644 --- a/resources/views/list/piggy-bank-events.twig +++ b/resources/views/list/piggy-bank-events.twig @@ -23,10 +23,11 @@ + {% if event.amount < 0 %} - {{ trans('firefly.removed_amount', {amount: (event.amount)|formatAmountPlain})|raw }} + {{ trans('firefly.removed_amount', {amount: formatAmountByAccount(event.piggyBank.account, event.amount, false)})|raw }} {% else %} - {{ trans('firefly.added_amount', {amount: (event.amount)|formatAmountPlain})|raw }} + {{ trans('firefly.added_amount', {amount: formatAmountByAccount(event.piggyBank.account, event.amount, false)})|raw }} {% endif %} diff --git a/resources/views/partials/boxes.twig b/resources/views/partials/boxes.twig index 68708cbc25..95fc6ea5d8 100644 --- a/resources/views/partials/boxes.twig +++ b/resources/views/partials/boxes.twig @@ -1,7 +1,6 @@ - - + {# box for in and out#} - + @@ -21,7 +20,7 @@ {# box for bills #} - + @@ -41,7 +40,7 @@ {# available to spend total / per day #} - + @@ -60,7 +59,7 @@ {# net worth #} - + diff --git a/resources/views/partials/control-bar.twig b/resources/views/partials/control-bar.twig index aa96baa72b..e1d19a1beb 100644 --- a/resources/views/partials/control-bar.twig +++ b/resources/views/partials/control-bar.twig @@ -105,6 +105,15 @@ + + + + + + {{ 'new_recurring_transaction'|_ }} + + + diff --git a/resources/views/partials/menu-sidebar.twig b/resources/views/partials/menu-sidebar.twig index 4772d605d5..7dde1e8bcc 100644 --- a/resources/views/partials/menu-sidebar.twig +++ b/resources/views/partials/menu-sidebar.twig @@ -77,7 +77,7 @@ - + {{ 'moneyManagement'|_ }} @@ -98,6 +98,10 @@ {{ 'rules'|_ }} + + + {{ 'recurrences'|_ }} + @@ -113,7 +117,7 @@ - {{ 'import_data'|_ }} + {{ 'import_transactions'|_ }} {{ 'export_and_backup_data'|_ }} @@ -132,12 +136,11 @@ - {% if not SANDSTORM %} - - {{ 'profile'|_ }} - - - {% endif %} + + + {{ 'profile'|_ }} + + {{ 'preferences'|_ }} diff --git a/resources/views/piggy-banks/create.twig b/resources/views/piggy-banks/create.twig index adae7f9b34..332d8fc041 100644 --- a/resources/views/piggy-banks/create.twig +++ b/resources/views/piggy-banks/create.twig @@ -18,7 +18,7 @@ {{ ExpandedForm.text('name') }} - {{ ExpandedForm.assetAccountList('account_id', null, {label: 'saveOnAccount'|_ }) }} + {{ ExpandedForm.activeAssetAccountList('account_id', null, {label: 'saveOnAccount'|_ }) }} {{ ExpandedForm.amountNoCurrency('targetamount') }} diff --git a/resources/views/profile/code.twig b/resources/views/profile/code.twig index 1313697c72..a1288322bd 100644 --- a/resources/views/profile/code.twig +++ b/resources/views/profile/code.twig @@ -22,6 +22,9 @@ + + {{ trans('firefly.2fa_use_secret_instead', {secret: secret}) }} + diff --git a/resources/views/profile/index.twig b/resources/views/profile/index.twig index 4719bb4885..fa1e076841 100644 --- a/resources/views/profile/index.twig +++ b/resources/views/profile/index.twig @@ -6,10 +6,6 @@ {% block content %} - - - - @@ -20,15 +16,18 @@ {{ trans('firefly.user_id_is',{user: userId})|raw }} + {% if not SANDSTORM %} {{ 'change_your_email'|_ }} {{ 'change_your_password'|_ }} {{ 'delete_account'|_ }} + {% endif %} + {% if not SANDSTORM %} @@ -53,7 +52,9 @@ + {% endif %} + {% if not SANDSTORM %} @@ -84,6 +85,8 @@ + {% endif %} + diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig new file mode 100644 index 0000000000..1c098a5d87 --- /dev/null +++ b/resources/views/recurring/create.twig @@ -0,0 +1,215 @@ +{% extends "./layout/default" %} +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName) }} +{% endblock %} +{% block content %} + + + + {# row with recurrence information #} + + + + {# mandatory recurrence stuff #} + + + {{ 'mandatory_for_recurring'|_ }} + + + {{ ExpandedForm.text('title') }} + {{ ExpandedForm.date('first_date',null, {helpText: trans('firefly.help_first_date')}) }} + {{ ExpandedForm.select('repetition_type', [], null, {helpText: trans('firefly.change_date_other_options')}) }} + {{ ExpandedForm.number('skip', 0) }} + {{ ExpandedForm.select('weekend', weekendResponses, null, {helpText: trans('firefly.help_weekend')}) }} + {{ ExpandedForm.select('repetition_end', repetitionEnds) }} + {{ ExpandedForm.date('repeat_until',null) }} + {{ ExpandedForm.number('repetitions',null) }} + + {# calendar in popup #} + + {{ trans('form.calendar') }} + + + + {{ 'click_for_calendar'|_ }} + + + + + + + + + {# optional recurrence stuff #} + + + {{ 'optional_for_recurring'|_ }} + + + {{ ExpandedForm.textarea('recurring_description') }} + + {# only correct way to do active checkbox #} + {{ ExpandedForm.checkbox('active', 1) }} + + {{ ExpandedForm.checkbox('apply_rules',1) }} + + + + + + + + {# mandatory transaction information #} + + + {{ 'mandatory_for_transaction'|_ }} + + + {{ 'mandatory_fields_for_tranaction'|_ }} + {# three buttons to distinguish type of transaction#} + + + {{ trans('form.transaction_type') }} + + + + + {{ 'withdrawal'|_ }} + {{ 'deposit'|_ }} + {{ 'transfer'|_ }} + + + + + {# end of three buttons#} + + {{ ExpandedForm.text('transaction_description') }} + {# transaction information (mandatory) #} + {{ ExpandedForm.currencyList('transaction_currency_id', defaultCurrency.id) }} + {{ ExpandedForm.amountNoCurrency('amount', []) }} + + {# source account if withdrawal, or if transfer: #} + {{ ExpandedForm.activeAssetAccountList('source_id', null, {label: trans('form.asset_source_account')}) }} + + {# source account name for deposits: #} + {{ ExpandedForm.text('source_name', null, {label: trans('form.revenue_account')}) }} + + {# destination if deposit or transfer: #} + {{ ExpandedForm.activeAssetAccountList('destination_id', null, {label: trans('form.asset_destination_account')} ) }} + + {# destination account name for withdrawals #} + {{ ExpandedForm.text('destination_name', null, {label: trans('form.expense_account')}) }} + + + + + + {# optional transaction information #} + + + {{ 'optional_for_transaction'|_ }} + + + {# transaction information (optional) #} + {{ ExpandedForm.currencyListEmpty('foreign_currency_id', 0) }} + {{ ExpandedForm.amountNoCurrency('foreign_amount', []) }} + + {# BUDGET ONLY WHEN CREATING A WITHDRAWAL #} + {% if budgets|length > 1 %} + {{ ExpandedForm.select('budget_id', budgets, null) }} + {% else %} + {{ ExpandedForm.select('budget_id', budgets, null, {helpText: trans('firefly.no_budget_pointer')}) }} + {% endif %} + + {# CATEGORY ALWAYS #} + {{ ExpandedForm.text('category') }} + + {# TAGS #} + {{ ExpandedForm.text('tags') }} + + {# RELATE THIS TRANSFER TO A PIGGY BANK #} + {{ ExpandedForm.piggyBankList('piggy_bank_id',0) }} + + + + + + {# row with submit stuff. #} + + + + + {{ 'options'|_ }} + + + {{ ExpandedForm.optionsList('create','recurrence') }} + + + + + + {# + + + + {{ 'expected_repetitions'|_ }} + + + Here. + + + + + + #} + + + {# calendar modal #} + + + + + Calendar view yay + + + + + + + + + + + + + + + +{% endblock %} +{% block scripts %} + + + + + + + +{% endblock %} + +{% block styles %} + + + + +{% endblock %} diff --git a/resources/views/recurring/delete.twig b/resources/views/recurring/delete.twig new file mode 100644 index 0000000000..2843a5705e --- /dev/null +++ b/resources/views/recurring/delete.twig @@ -0,0 +1,41 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, recurrence) }} +{% endblock %} + +{% block content %} + + + + + + + + {{ trans('form.delete_recurring', {'title': recurrence.title}) }} + + + + {{ trans('form.permDeleteWarning') }} + + + + {{ trans('form.recurring_areYouSure', {'title': recurrence.title}) }} + + + + {% if journalsCreated > 0 %} + {{ Lang.choice('form.recurring_keep_transactions', journalsCreated, {count: journalsCreated }) }} + {% endif %} + + + + + + + + +{% endblock %} diff --git a/resources/views/recurring/edit.twig b/resources/views/recurring/edit.twig new file mode 100644 index 0000000000..03a5c0b4a3 --- /dev/null +++ b/resources/views/recurring/edit.twig @@ -0,0 +1,236 @@ +{% extends "./layout/default" %} +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, recurrence) }} +{% endblock %} +{% block content %} + + {{ Form.model(recurrence, {'class' : 'form-horizontal','enctype': 'multipart/form-data','id' : 'update','url' : route('recurring.update', recurrence.id)}) }} + + {# row with recurrence information #} + + + + {# mandatory recurrence stuff #} + + + {{ 'mandatory_for_recurring'|_ }} + + + + {{ ExpandedForm.text('title') }} + {{ ExpandedForm.date('first_date',array.first_date, {helpText: trans('firefly.help_first_date_no_past')}) }} + {{ ExpandedForm.select('repetition_type', [], null, {helpText: trans('firefly.change_date_other_options')}) }} + {{ ExpandedForm.number('skip', array.recurrence_repetitions[0].repetition_skip) }} + {{ ExpandedForm.select('weekend', weekendResponses, array.recurrence_repetitions[0].weekend, {helpText: trans('firefly.help_weekend')}) }} + {{ ExpandedForm.select('repetition_end', repetitionEnds, repetitionEnd) }} + {{ ExpandedForm.date('repeat_until',array.repeat_until) }} + {{ ExpandedForm.number('repetitions', array.repetitions) }} + + {# calendar in popup #} + + {{ trans('form.calendar') }} + + + + {{ 'click_for_calendar'|_ }} + + + + + + + + + {# optional recurrence stuff #} + + + {{ 'optional_for_recurring'|_ }} + + + {{ ExpandedForm.textarea('recurring_description',array.description) }} + + {# only correct way to do active checkbox #} + {{ ExpandedForm.checkbox('active', 1, preFilled.active) }} + {{ ExpandedForm.checkbox('apply_rules', 1, preFilled.apply_rules) }} + + + + + + + + {# mandatory transaction information #} + + + {{ 'mandatory_for_transaction'|_ }} + + + {{ 'mandatory_fields_for_tranaction'|_ }} + {# three buttons to distinguish type of transaction#} + + + {{ trans('form.transaction_type') }} + + + + + {{ 'withdrawal'|_ }} + {{ 'deposit'|_ }} + {{ 'transfer'|_ }} + + + + + {# end of three buttons#} + {{ ExpandedForm.text('transaction_description', array.transactions[0].description) }} + {# transaction information (mandatory) #} + {{ ExpandedForm.currencyList('transaction_currency_id', array.transactions[0].currency_id) }} + {{ ExpandedForm.amountNoCurrency('amount', array.transactions[0].amount) }} + + {# source account if withdrawal, or if transfer: #} + {{ ExpandedForm.assetAccountList('source_id', array.transactions[0].source_id, {label: trans('form.asset_source_account')}) }} + + {# source account name for deposits: #} + {{ ExpandedForm.text('source_name', array.transactions[0].source_name, {label: trans('form.revenue_account')}) }} + + {# destination if deposit or transfer: #} + {{ ExpandedForm.assetAccountList('destination_id', array.transactions[0].destination_id, {label: trans('form.asset_destination_account')} ) }} + + {# destination account name for withdrawals #} + {{ ExpandedForm.text('destination_name', array.transactions[0].destination_name, {label: trans('form.expense_account')}) }} + + + + + + {# optional transaction information #} + + + {{ 'optional_for_transaction'|_ }} + + + {# transaction information (optional) #} + {{ ExpandedForm.currencyListEmpty('foreign_currency_id', array.transactions[0].foreign_currency_id) }} + {{ ExpandedForm.amountNoCurrency('foreign_amount', array.transactions[0].foreign_amount) }} + + {# BUDGET ONLY WHEN CREATING A WITHDRAWAL #} + {% set budgetId = 0 %} + {% set categoryName = '' %} + {% for metaValue in array.transactions[0].meta %} + {% if metaValue.name == 'budget_id' %} + {% set budgetId = metaValue.value %} + {% endif %} + {% if metaValue.name == 'category_name' %} + {% set categoryName = metaValue.value %} + {% endif %} + {% endfor %} + + {% if budgets|length > 1 %} + {{ ExpandedForm.select('budget_id', budgets, budgetId) }} {##} + {% else %} + {{ ExpandedForm.select('budget_id', budgets, budgetId, {helpText: trans('firefly.no_budget_pointer')}) }} + {#budgets#} + {% endif %} + + {# CATEGORY ALWAYS #} + {{ ExpandedForm.text('category',categoryName) }} + + {# TAGS #} + {% set tags = '' %} + {% set piggyBankId = 0 %} + {% for metaValue in array.meta %} + {% if metaValue.name == 'tags' %} + {% set tags = metaValue.value %} + {% endif %} + {% if metaValue.name == 'piggy_bank_id' %} + {% set piggyBankId = metaValue.value %} + {% endif %} + {% endfor %} + {{ ExpandedForm.text('tags', tags) }} + + {# RELATE THIS TRANSFER TO A PIGGY BANK #} + {{ ExpandedForm.piggyBankList('piggy_bank_id',piggyBankId) }} + + + + + + {# row with submit stuff. #} + + + + + {{ 'options'|_ }} + + + {{ ExpandedForm.optionsList('update','recurrence') }} + + + + + + {# + + + + {{ 'expected_repetitions'|_ }} + + + Here. + + + + + + #} + + + {# calendar modal #} + + + + + Calendar view yay + + + + + + + + + + + + + + + +{% endblock %} +{% block scripts %} + + + + + + + +{% endblock %} + +{% block styles %} + + + + +{% endblock %} diff --git a/resources/views/recurring/index.twig b/resources/views/recurring/index.twig new file mode 100644 index 0000000000..03fa6e5476 --- /dev/null +++ b/resources/views/recurring/index.twig @@ -0,0 +1,150 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName) }} +{% endblock %} + +{% block content %} + + {% if recurring|length > 0 %} + + + + + + {{ 'recurrences'|_ }} + + + + + + {{ ('make_new_recurring')|_ }} + + + + + + + + {{ ('make_new_recurring')|_ }} + + + + + + {{ recurring.render|raw }} + + + + + + {{ trans('list.title') }} + {{ trans('list.transaction_s') }} + {{ trans('list.repetitions') }} + + + + {% for rt in recurring %} + + + + + + + + {% if rt.active == false %}{% endif %} + {{ rt.transaction_type|_ }}: + {{ rt.title }} + {% if rt.active == false %} ({{ 'inactive'|_|lower }}){% endif %} + {% if rt.description|length > 0 %} + {{ rt.description }} + {% endif %} + + + + + + {% for rtt in rt.transactions %} + + {# normal amount + comma#} + {{ formatAmountBySymbol(rtt['amount'],rtt['currency_symbol'],rtt['currency_dp']) }}{% if rtt['foreign_amount'] == null %},{% endif %} + + {# foreign amount + comma #} + {% if null != rtt['foreign_amount'] %} + ({{ formatAmountBySymbol(rtt['foreign_amount'],rtt['foreign_currency_symbol'],rtt['foreign_currency_dp']) }}), + {% endif %} + {{ rtt['source_name'] }} + → + {{ rtt['destination_name'] }} + + {% endfor %} + + + + + {% for rep in rt.recurrence_repetitions %} + {{ rep.description }} + {% if rep.repetition_skip == 1 %} + ({{ trans('firefly.recurring_skips_one')|lower }}). + {% endif %} + {% if rep.repetition_skip > 1 %} + ({{ trans('firefly.recurring_skips_more', {count: rep.repetition_skip})|lower }}). + {% endif %} + {% if rep.weekend == 3 %} + {{ 'will_jump_friday'|_ }} + {% endif %} + {% if rep.weekend == 4 %} + {{ 'will_jump_monday'|_ }} + {% endif %} + {% if rep.weekend == 2 %} + {{ 'except_weekends'|_ }} + {% endif %} + + {% endfor %} + + + {% if null == rt.repeat_until and rt.repetitions == 0 %} + {{ 'recurring_repeats_forever'|_ }}. + {% endif %} + {% if null != rt.repeat_until and rt.repetitions == 0 %} + {{ trans('firefly.recurring_repeats_until', {date: rt.repeat_until.formatLocalized(monthAndDayFormat)}) }}. + {% endif %} + {% if null == rt.repeat_until and rt.repetitions != 0 %} + {{ trans('firefly.recurring_repeats_x_times', {count: rt.repetitions}) }}. + {% endif %} + + + + + {% endfor %} + + + + {{ recurring.render|raw }} + + + + + + + {% endif %} + + + + + {% if recurring|length == 0 and page == 1 %} + {% include 'partials.empty' with {what: 'default', type: 'recurring',route: route('recurring.create')} %} + {% endif %} +{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/resources/views/recurring/show.twig b/resources/views/recurring/show.twig new file mode 100644 index 0000000000..b4cbf3875e --- /dev/null +++ b/resources/views/recurring/show.twig @@ -0,0 +1,215 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, recurrence) }} +{% endblock %} + +{% block content %} + + + + + + + {{ array.title }} + + ({{ array.transaction_type }}) + + {% if array.active == false %} + ({{ 'inactive'|_|lower }}) + {% endif %} + + + + {{ array.description }} + + {% if array.active == false %} + + {{ 'recurrence_is_inactive'|_ }} + + {% endif %} + + + {% for rep in array.repetitions %} + {{ rep.description }} + {% endfor %} + + + + + + + + + + + {{ ('expected_'~array.transaction_type~'s')|_ }} + + + + + + {% for rep in array.recurrence_repetitions %} + + {{ rep.description }} + {% if rep.repetition_skip == 1 %} + ({{ trans('firefly.recurring_skips_one')|lower }}) + {% endif %} + {% if rep.repetition_skip > 1 %} + ({{ trans('firefly.recurring_skips_more', {count: rep.repetition_skip})|lower }}) + {% endif %} + + {% for occ in rep.occurrences %} + {{ occ.formatLocalized(trans('config.month_and_date_day')) }} + {% endfor %} + + + {% endfor %} + + + + + + + + + + + + + {{ 'transaction_data'|_ }} + + + + + + {{ trans('list.description') }} + {{ trans('list.source') }} + {{ trans('list.destination') }} + {{ trans('list.amount') }} + {{ trans('list.category') }} + {{ trans('list.budget') }} + + + {% for transaction in array.transactions %} + + + {{ transaction.description }} + + + {{ transaction.source_name }} + + + {{ transaction.destination_name }} + + + {{ formatAmountBySymbol(transaction.amount,transaction.currency_symbol,transaction.currency_dp) }} + {% if null != transaction.foreign_amount %} + ({{ formatAmountBySymbol(transaction.foreign_amount,transaction.foreign_currency_symbol,transaction.foreign_currency_dp) }}) + {% endif %} + + + {% for meta in transaction.meta %} + {% if meta.name == 'category_name' %} + + {{ meta.category_name }} + + {% endif %} + {% endfor %} + + + {% for meta in transaction.meta %} + {% if meta.name == 'budget_id' %} + + {{ meta.budget_name }} + + {% endif %} + {% endfor %} + + + {% endfor %} + + + + + + + + {% if array.meta|length > 0 %} + + + + + {{ 'meta_data'|_ }} + + + + + + {{ trans('list.field') }} + {{ trans('list.value') }} + + + {% for meta in array.meta %} + + {{ trans('firefly.recurring_meta_field_'~meta.name) }} + + {% if meta.name == 'tags' %} + {% for tag in meta.tags %} + {{ tag }} + {% endfor %} + {% endif %} + {% if meta.name == 'notes' %} + {{ meta.value|markdown }} + {% endif %} + {% if meta.name == 'bill_id' %} + {{ meta.bill_name }} + {% endif %} + {% if meta.name == 'piggy_bank_id' %} + {{ meta.piggy_bank_name }} + {% endif %} + + + {% endfor %} + + + + + + + {% endif %} + + + + + + + + {{ ('created_'~array.transaction_type~'s')|_ }} + + + + {% include 'list.journals' with {sorting:false, hideBills:true, hideBudgets: true, showReconcile: false} %} + + + + +{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block scripts %} + + +{% endblock %} diff --git a/resources/views/rules/rule-group/edit.twig b/resources/views/rules/rule-group/edit.twig index 8ef2671a6b..78b4b672bc 100644 --- a/resources/views/rules/rule-group/edit.twig +++ b/resources/views/rules/rule-group/edit.twig @@ -14,7 +14,9 @@ {{ 'mandatoryFields'|_ }} - {{ ExpandedForm.checkbox('active') }} + {# only correct way to do active checkbox #} + {{ ExpandedForm.checkbox('active', 1) }} + {{ ExpandedForm.text('title') }} diff --git a/resources/views/rules/rule/edit.twig b/resources/views/rules/rule/edit.twig index dad8d5871c..d5a4100306 100644 --- a/resources/views/rules/rule/edit.twig +++ b/resources/views/rules/rule/edit.twig @@ -17,7 +17,10 @@ {{ ExpandedForm.text('title') }} {{ ExpandedForm.ruleGroupList('rule_group_id', ruleGroup.id) }} {{ ExpandedForm.select('trigger',allJournalTriggers(), primaryTrigger) }} - {{ ExpandedForm.checkbox('active',1,rule.active, {helpText: trans('firefly.rule_help_active')}) }} + + {# only correct way to do active checkbox #} + {{ ExpandedForm.checkbox('active', 1, null, {helpText: trans('firefly.rule_help_active')}) }} + {{ ExpandedForm.checkbox('stop_processing',1,rule.stop_processing, {helpText: trans('firefly.rule_help_stop_processing')}) }} {{ ExpandedForm.checkbox('strict',1,rule.strict, {helpText: trans('firefly.rule_help_strict')}) }} diff --git a/resources/views/transactions/bulk/edit.twig b/resources/views/transactions/bulk/edit.twig index ce587d65ac..53697d1f95 100644 --- a/resources/views/transactions/bulk/edit.twig +++ b/resources/views/transactions/bulk/edit.twig @@ -40,18 +40,8 @@ {{ journal.description }} {{ journal|journalTotalAmount }} {{ journal.date.formatLocalized(monthAndDayFormat) }} - - {% set cat = journal.categories.first %} - {% if cat %} - {{ cat.name }} - {% endif %} - - - {% set bud = journal.budgets.first %} - {% if bud %} - {{ bud.name }} - {% endif %} - + {{ journalCategories(journal)|raw }} + {{ journalBudgets(journal)|raw }} {% for tag in journal.tags %} diff --git a/resources/views/transactions/convert.twig b/resources/views/transactions/convert.twig index 8ab8cedb31..2406a5ebe6 100644 --- a/resources/views/transactions/convert.twig +++ b/resources/views/transactions/convert.twig @@ -92,7 +92,7 @@ - {{ ExpandedForm.assetAccountList('destination_account_asset', null) }} + {{ ExpandedForm.activeAssetAccountList('destination_account_asset', null) }} {% endif %} @@ -145,7 +145,7 @@ - {{ ExpandedForm.assetAccountList('source_account_asset', null) }} + {{ ExpandedForm.activeAssetAccountList('source_account_asset', null) }} {% endif %} {# FIVE #} diff --git a/resources/views/transactions/mass/edit.twig b/resources/views/transactions/mass/edit.twig index d6fcaff658..fa863e3847 100644 --- a/resources/views/transactions/mass/edit.twig +++ b/resources/views/transactions/mass/edit.twig @@ -70,7 +70,7 @@ {# SOURCE ACCOUNT ID FOR TRANSFER OR WITHDRAWAL #} {% if transaction.type == 'Transfer' or transaction.type == 'Withdrawal' %} - + {% for account in accounts %} {{ account.name }} @@ -79,13 +79,13 @@ {% else %} {# SOURCE ACCOUNT NAME FOR DEPOSIT #} + name="source_name[{{ transaction.journal_id }}]" type="text" value="{% if transaction.source_type != 'Cash account' %}{{ transaction.source_name }}{% endif %}"> {% endif %} {% if transaction.type == 'Transfer' or transaction.type == 'Deposit' %} {# DESTINATION ACCOUNT NAME FOR TRANSFER AND DEPOSIT #} - + {% for account in accounts %} {{ account.name }} @@ -95,7 +95,7 @@ {# DESTINATION ACCOUNT NAME FOR EXPENSE #} {% endif %} diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 439c29f036..f184c82dbe 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -238,15 +238,7 @@ {% for tag in journal.tags %} - {% if tag.tagMode == 'nothing' %} - - {% endif %} - {% if tag.tagMode == 'balancingAct' %} - - {% endif %} - {% if tag.tagMode == 'advancePayment' %} - - {% endif %} + {{ tag.tag }} {% endfor %} diff --git a/resources/views/transactions/single/create.twig b/resources/views/transactions/single/create.twig index bdad680bf9..82da51a107 100644 --- a/resources/views/transactions/single/create.twig +++ b/resources/views/transactions/single/create.twig @@ -33,16 +33,16 @@ {{ ExpandedForm.text('description') }} {# SELECTABLE SOURCE ACCOUNT ONLY FOR WITHDRAWALS AND TRANSFERS #} - {{ ExpandedForm.assetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }} + {{ ExpandedForm.activeAssetAccountList('source_id', null, {label: trans('form.asset_source_account')}) }} {# FREE FORMAT SOURCE ACCOUNT ONLY FOR DEPOSITS #} - {{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }} + {{ ExpandedForm.text('source_name', null, {label: trans('form.revenue_account')}) }} {# FREE FORMAT DESTINATION ACCOUNT ONLY FOR EXPENSES #} - {{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }} + {{ ExpandedForm.text('destination_name', null, {label: trans('form.expense_account')}) }} {# SELECTABLE DESTINATION ACCOUNT ONLY FOR TRANSFERS AND DEPOSITS #} - {{ ExpandedForm.assetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }} + {{ ExpandedForm.activeAssetAccountList('destination_id', null, {label: trans('form.asset_destination_account')} ) }} {# ALWAYS SHOW AMOUNT #} {{ ExpandedForm.amount('amount') }} @@ -60,7 +60,7 @@ {{ ExpandedForm.date('date', preFilled.date|default(phpdate('Y-m-d'))) }} @@ -86,8 +86,7 @@ {{ ExpandedForm.text('tags') }} {# RELATE THIS TRANSFER TO A PIGGY BANK #} - {{ ExpandedForm.select('piggy_bank_id', piggies, '0') }} - + {{ ExpandedForm.piggyBankList('piggy_bank_id',0) }} @@ -196,7 +195,7 @@ {{ ExpandedForm.optionsList('create','transaction') }} @@ -211,7 +210,6 @@ {% block scripts %}
- - - - + +
storage/logs
.env
MAPBOX_API_KEY=
:email
+ {% if journals.count == 1 %} + Firefly III has created a transaction for you. + {% endif %} + {% if journals.count > 1 %} + Firefly III has created {{ journals.count }} transactions for you. + {% endif %} +
+ You can find it in your Firefly III installation: + {% for journal in journals %} + {{ journal.description }} ({{ journal|journalTotalAmount }}) + {% endfor %} +
+ You can find them in your Firefly III installation: +
+ {{ trans('import.job_config_bunq_apply_rules_text') }} +
+ {{ trans('import.job_config_spectre_apply_rules_text') }} +
+ {{ trans('firefly.2fa_use_secret_instead', {secret: secret}) }} +
{{ trans('firefly.user_id_is',{user: userId})|raw }}
+ {{ 'click_for_calendar'|_ }} +
{{ 'mandatory_fields_for_tranaction'|_ }}
+ {{ trans('form.permDeleteWarning') }} +
+ {{ trans('form.recurring_areYouSure', {'title': recurrence.title}) }} +
+ {% if journalsCreated > 0 %} + {{ Lang.choice('form.recurring_keep_transactions', journalsCreated, {count: journalsCreated }) }} + {% endif %} +
+ {% if null == rt.repeat_until and rt.repetitions == 0 %} + {{ 'recurring_repeats_forever'|_ }}. + {% endif %} + {% if null != rt.repeat_until and rt.repetitions == 0 %} + {{ trans('firefly.recurring_repeats_until', {date: rt.repeat_until.formatLocalized(monthAndDayFormat)}) }}. + {% endif %} + {% if null == rt.repeat_until and rt.repetitions != 0 %} + {{ trans('firefly.recurring_repeats_x_times', {count: rt.repetitions}) }}. + {% endif %} + +
{{ array.description }}
+ {{ 'recurrence_is_inactive'|_ }} +