Merge branch 'release/4.6.12'

This commit is contained in:
James Cole 2017-12-29 20:06:56 +01:00
commit 59d732cba7
989 changed files with 20722 additions and 6165 deletions

View File

@ -3,7 +3,7 @@ APP_DEBUG=false
APP_NAME=FireflyIII
APP_KEY=SomeRandomStringOf32CharsExactly
APP_LOG=daily
APP_LOG_LEVEL=warning
APP_LOG_LEVEL=notice
APP_URL=http://localhost
TRUSTED_PROXIES=

View File

@ -4,7 +4,7 @@
## Feature requests
I am always interested in expanding Firefly III's many features. If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.github.io/requested-features/).
I am always interested in expanding Firefly III's many features. If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.org/requested-features/).
## Pull requests

View File

@ -8,4 +8,4 @@ I am running Firefly III version x.x.x
#### Other important details (log files, system info):
Please visit the /debug page to get extra debug information.
Please click the version number in the right corner of any Firefly III page to get debug information.

4
.github/SUPPORT.md vendored
View File

@ -4,8 +4,8 @@
## Bugs
First of all: thank you for reporting a bug instead of ditching the tool altogether. If you find a bug, please take the time and see if the [demo site](https://firefly-iii.nder.be/) is also suffering from this bug. Include as many log files and details as you think are necessary. Bugs have a lot of priority!
First of all: thank you for reporting a bug instead of ditching the tool altogether. If you find a bug, please take the time and see if the [demo site](https://demo.firefly-iii.org/) is also suffering from this bug. Include as many log files and details as you think are necessary. Bugs have a lot of priority!
## Installation problems
Please take the time to read the [installation guide FAQ](https://firefly-iii.github.io/installation-guide-faq/) and make sure you search through closed issues for the problems other people have had. Your problem may be among them! If not, open an issue and I will help where I can.
Please take the time to read the [installation guide FAQ](https://firefly-iii.org/installation-guide-faq/) and make sure you search through closed issues for the problems other people have had. Your problem may be among them! If not, open an issue and I will help where I can.

View File

@ -1,5 +1,24 @@
# 4.6.1.1
# 4.6.12
- Support for Indonesian.
- New report, see [issue 384](https://github.com/firefly-iii/firefly-iii/issues/384)
- [Issue 964](https://github.com/firefly-iii/firefly-iii/issues/964) as suggested by [gavu](https://github.com/gavu)
- Greatly improved Docker support and documentation.
- [Issue 1046](https://github.com/firefly-iii/firefly-iii/issues/1046), as reported by [pkoziol](https://github.com/pkoziol)
- [Issue 1047](https://github.com/firefly-iii/firefly-iii/issues/1047), as reported by [pkoziol](https://github.com/pkoziol)
- [Issue 1048](https://github.com/firefly-iii/firefly-iii/issues/1048), as reported by [webence](https://github.com/webence)
- [Issue 1049](https://github.com/firefly-iii/firefly-iii/issues/1049), as reported by [nicoschreiner](https://github.com/nicoschreiner)
- [Issue 1015](https://github.com/firefly-iii/firefly-iii/issues/1015), as reporterd by a user on Tweakers.net
- [Issue 1056](https://github.com/firefly-iii/firefly-iii/issues/1056), as reported by [repercussion](https://github.com/repercussion)
- [Issue 1061](https://github.com/firefly-iii/firefly-iii/issues/1061), as reported by [Meizikyn](https://github.com/Meizikyn)
- [Issue 1045](https://github.com/firefly-iii/firefly-iii/issues/1045), as reported by [gavu](https://github.com/gavu)
- First code for [issue 1040](https://github.com/firefly-iii/firefly-iii/issues/1040) ([simonsmiley](https://github.com/simonsmiley))
- [Issue 1059](https://github.com/firefly-iii/firefly-iii/issues/1059), as reported by [4oo4](https://github.com/4oo4)
- [Issue 1063](https://github.com/firefly-iii/firefly-iii/issues/1063), as reported by [pkoziol](https://github.com/pkoziol)
- [Issue 1064](https://github.com/firefly-iii/firefly-iii/issues/1064), as reported by [pkoziol](https://github.com/pkoziol)
- [Issue 1066](https://github.com/firefly-iii/firefly-iii/issues/1066), reported by [wtercato](https://github.com/wtercato)
# 4.6.1.1
- Import routine can scan for matching bills, [issue 956](https://github.com/firefly-iii/firefly-iii/issues/956)
- Import will no longer scan for rules, this has become optional. Originally suggested in [issue 956](https://github.com/firefly-iii/firefly-iii/issues/956) by [gavu](https://github.com/gavu)
- [Issue 1033](https://github.com/firefly-iii/firefly-iii/issues/1033), as reported by [Jumanjii](https://github.com/Jumanjii)

View File

@ -208,14 +208,6 @@ opt/app/.env.heroku
opt/app/.env.sandstorm
opt/app/.gitattributes
opt/app/.sandstorm/.gitattributes
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/action_provision
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/action_set_name
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/creator_uid
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/id
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/index_uuid
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/private_key
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/synced_folders
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/vagrant_cwd
opt/app/.sandstorm/Vagrantfile
opt/app/.sandstorm/app-graphics/firefly-iii-128.png
opt/app/.sandstorm/app-graphics/firefly-iii-150.png
@ -260,6 +252,7 @@ opt/app/app/Events/AdminRequestedTestMessage.php
opt/app/app/Events/Event.php
opt/app/app/Events/RegisteredUser.php
opt/app/app/Events/RequestedNewPassword.php
opt/app/app/Events/RequestedVersionCheckStatus.php
opt/app/app/Events/StoredTransactionJournal.php
opt/app/app/Events/UpdatedTransactionJournal.php
opt/app/app/Events/UserChangedEmail.php
@ -279,6 +272,9 @@ opt/app/app/Export/Exporter/ExporterInterface.php
opt/app/app/Export/ProcessorInterface.php
opt/app/app/Generator/Chart/Basic/ChartJsGenerator.php
opt/app/app/Generator/Chart/Basic/GeneratorInterface.php
opt/app/app/Generator/Report/Account/MonthReportGenerator.php
opt/app/app/Generator/Report/Account/MultiYearReportGenerator.php
opt/app/app/Generator/Report/Account/YearReportGenerator.php
opt/app/app/Generator/Report/Audit/MonthReportGenerator.php
opt/app/app/Generator/Report/Audit/MultiYearReportGenerator.php
opt/app/app/Generator/Report/Audit/YearReportGenerator.php
@ -301,6 +297,7 @@ opt/app/app/Handlers/Events/AdminEventHandler.php
opt/app/app/Handlers/Events/StoredJournalEventHandler.php
opt/app/app/Handlers/Events/UpdatedJournalEventHandler.php
opt/app/app/Handlers/Events/UserEventHandler.php
opt/app/app/Handlers/Events/VersionCheckEventHandler.php
opt/app/app/Helpers/Attachments/AttachmentHelper.php
opt/app/app/Helpers/Attachments/AttachmentHelperInterface.php
opt/app/app/Helpers/Chart/MetaPieChart.php
@ -339,6 +336,7 @@ opt/app/app/Http/Controllers/AccountController.php
opt/app/app/Http/Controllers/Admin/ConfigurationController.php
opt/app/app/Http/Controllers/Admin/HomeController.php
opt/app/app/Http/Controllers/Admin/LinkController.php
opt/app/app/Http/Controllers/Admin/UpdateController.php
opt/app/app/Http/Controllers/Admin/UserController.php
opt/app/app/Http/Controllers/AttachmentController.php
opt/app/app/Http/Controllers/Auth/ForgotPasswordController.php
@ -355,6 +353,7 @@ opt/app/app/Http/Controllers/Chart/BudgetController.php
opt/app/app/Http/Controllers/Chart/BudgetReportController.php
opt/app/app/Http/Controllers/Chart/CategoryController.php
opt/app/app/Http/Controllers/Chart/CategoryReportController.php
opt/app/app/Http/Controllers/Chart/ExpenseReportController.php
opt/app/app/Http/Controllers/Chart/PiggyBankController.php
opt/app/app/Http/Controllers/Chart/ReportController.php
opt/app/app/Http/Controllers/Chart/TagReportController.php
@ -363,9 +362,10 @@ opt/app/app/Http/Controllers/CurrencyController.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/BankController.php
opt/app/app/Http/Controllers/Import/FileController.php
opt/app/app/Http/Controllers/ImportController.php
opt/app/app/Http/Controllers/Import/ConfigurationController.php
opt/app/app/Http/Controllers/Import/IndexController.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
@ -382,6 +382,7 @@ opt/app/app/Http/Controllers/Report/AccountController.php
opt/app/app/Http/Controllers/Report/BalanceController.php
opt/app/app/Http/Controllers/Report/BudgetController.php
opt/app/app/Http/Controllers/Report/CategoryController.php
opt/app/app/Http/Controllers/Report/ExpenseController.php
opt/app/app/Http/Controllers/Report/OperationsController.php
opt/app/app/Http/Controllers/ReportController.php
opt/app/app/Http/Controllers/RuleController.php
@ -400,7 +401,8 @@ opt/app/app/Http/Middleware/AuthenticateTwoFactor.php
opt/app/app/Http/Middleware/Binder.php
opt/app/app/Http/Middleware/EncryptCookies.php
opt/app/app/Http/Middleware/IsAdmin.php
opt/app/app/Http/Middleware/IsLimitedUser.php
opt/app/app/Http/Middleware/IsDemoUser.php
opt/app/app/Http/Middleware/IsSandStormUser.php
opt/app/app/Http/Middleware/Range.php
opt/app/app/Http/Middleware/RedirectIfAuthenticated.php
opt/app/app/Http/Middleware/RedirectIfTwoFactorAuthenticated.php
@ -420,7 +422,6 @@ opt/app/app/Http/Requests/CurrencyFormRequest.php
opt/app/app/Http/Requests/DeleteAccountFormRequest.php
opt/app/app/Http/Requests/EmailFormRequest.php
opt/app/app/Http/Requests/ExportFormRequest.php
opt/app/app/Http/Requests/ImportUploadRequest.php
opt/app/app/Http/Requests/JournalFormRequest.php
opt/app/app/Http/Requests/JournalLinkRequest.php
opt/app/app/Http/Requests/LinkTypeFormRequest.php
@ -441,15 +442,15 @@ opt/app/app/Http/Requests/TestRuleFormRequest.php
opt/app/app/Http/Requests/TokenFormRequest.php
opt/app/app/Http/Requests/UserFormRequest.php
opt/app/app/Http/Requests/UserRegistrationRequest.php
opt/app/app/Http/breadcrumbs.php
opt/app/app/Import/Configurator/ConfiguratorInterface.php
opt/app/app/Import/Configurator/CsvConfigurator.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/AmountDebet.php
opt/app/app/Import/Converter/AmountDebit.php
opt/app/app/Import/Converter/ConverterInterface.php
opt/app/app/Import/Converter/INGDebetCredit.php
opt/app/app/Import/Converter/RabobankDebetCredit.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
@ -472,7 +473,13 @@ opt/app/app/Import/Object/ImportBudget.php
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/Routine/ImportRoutine.php
opt/app/app/Import/Prerequisites/BunqPrerequisites.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/FileRoutine.php
opt/app/app/Import/Routine/RoutineInterface.php
opt/app/app/Import/Routine/SpectreRoutine.php
opt/app/app/Import/Specifics/AbnAmroDescription.php
opt/app/app/Import/Specifics/IngDescription.php
opt/app/app/Import/Specifics/PresidentsChoice.php
@ -612,8 +619,18 @@ opt/app/app/Services/Bunq/Token/InstallationToken.php
opt/app/app/Services/Bunq/Token/SessionToken.php
opt/app/app/Services/Currency/ExchangeRateInterface.php
opt/app/app/Services/Currency/FixerIO.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/Password/PwndVerifier.php
opt/app/app/Services/Password/Verifier.php
opt/app/app/Services/Spectre/Object/Customer.php
opt/app/app/Services/Spectre/Object/SpectreObject.php
opt/app/app/Services/Spectre/Object/Token.php
opt/app/app/Services/Spectre/Request/CreateTokenRequest.php
opt/app/app/Services/Spectre/Request/NewCustomerRequest.php
opt/app/app/Services/Spectre/Request/SpectreRequest.php
opt/app/app/Support/Amount.php
opt/app/app/Support/Binder/AccountList.php
opt/app/app/Support/Binder/BinderInterface.php
@ -637,13 +654,12 @@ 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/ConfigurationInterface.php
opt/app/app/Support/Import/Configuration/Csv/Initial.php
opt/app/app/Support/Import/Configuration/Csv/Map.php
opt/app/app/Support/Import/Configuration/Csv/Roles.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/Upload.php
opt/app/app/Support/Import/Information/BunqInformation.php
opt/app/app/Support/Import/Information/InformationInterface.php
opt/app/app/Support/Import/Prerequisites/BunqPrerequisites.php
opt/app/app/Support/Import/Prerequisites/PrerequisitesInterface.php
opt/app/app/Support/Models/TransactionJournalTrait.php
opt/app/app/Support/Navigation.php
opt/app/app/Support/Preferences.php
@ -730,12 +746,15 @@ opt/app/composer.lock
opt/app/composer.phar
opt/app/config/app.php
opt/app/config/auth.php
opt/app/config/breadcrumbs.php
opt/app/config/broadcasting.php
opt/app/config/cache.php
opt/app/config/csv.php
opt/app/config/database.php
opt/app/config/filesystems.php
opt/app/config/firefly.php
opt/app/config/google2fa.php
opt/app/config/import.php
opt/app/config/intro.php
opt/app/config/mail.php
opt/app/config/queue.php
@ -766,9 +785,6 @@ opt/app/database/seeds/LinkTypeSeeder.php
opt/app/database/seeds/PermissionSeeder.php
opt/app/database/seeds/TransactionCurrencySeeder.php
opt/app/database/seeds/TransactionTypeSeeder.php
opt/app/docker-compose.dockerhub.yml
opt/app/docker-compose.override.yml
opt/app/docker-compose.prod.yml
opt/app/docker-compose.yml
opt/app/nginx_app.conf
opt/app/phpunit.coverage.xml
@ -850,6 +866,10 @@ opt/app/public/images/image.png
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/file.png
opt/app/public/images/logos/plaid.png
opt/app/public/images/logos/spectre.png
opt/app/public/images/page_green.png
opt/app/public/images/page_white_acrobat.png
opt/app/public/index.php
@ -858,6 +878,7 @@ opt/app/public/js/ff/accounts/edit-reconciliation.js
opt/app/public/js/ff/accounts/edit.js
opt/app/public/js/ff/accounts/reconcile.js
opt/app/public/js/ff/accounts/show.js
opt/app/public/js/ff/admin/update/index.js
opt/app/public/js/ff/bills/create.js
opt/app/public/js/ff/bills/edit.js
opt/app/public/js/ff/bills/show.js
@ -880,17 +901,16 @@ opt/app/public/js/ff/piggy-banks/edit.js
opt/app/public/js/ff/piggy-banks/index.js
opt/app/public/js/ff/piggy-banks/show.js
opt/app/public/js/ff/preferences/index.js
opt/app/public/js/ff/reports/account/month.js
opt/app/public/js/ff/reports/all.js
opt/app/public/js/ff/reports/audit/all.js
opt/app/public/js/ff/reports/budget/all.js
opt/app/public/js/ff/reports/budget/month.js
opt/app/public/js/ff/reports/category/all.js
opt/app/public/js/ff/reports/category/month.js
opt/app/public/js/ff/reports/default/all.js
opt/app/public/js/ff/reports/default/month.js
opt/app/public/js/ff/reports/default/multi-year.js
opt/app/public/js/ff/reports/default/year.js
opt/app/public/js/ff/reports/index.js
opt/app/public/js/ff/reports/tag/all.js
opt/app/public/js/ff/reports/tag/month.js
opt/app/public/js/ff/rules/create-edit.js
opt/app/public/js/ff/rules/index.js
@ -975,6 +995,7 @@ opt/app/resources/lang/de_DE/demo.php
opt/app/resources/lang/de_DE/firefly.php
opt/app/resources/lang/de_DE/form.php
opt/app/resources/lang/de_DE/help.php
opt/app/resources/lang/de_DE/import.php
opt/app/resources/lang/de_DE/intro.php
opt/app/resources/lang/de_DE/list.php
opt/app/resources/lang/de_DE/pagination.php
@ -988,6 +1009,7 @@ opt/app/resources/lang/en_US/csv.php
opt/app/resources/lang/en_US/demo.php
opt/app/resources/lang/en_US/firefly.php
opt/app/resources/lang/en_US/form.php
opt/app/resources/lang/en_US/import.php
opt/app/resources/lang/en_US/intro.php
opt/app/resources/lang/en_US/list.php
opt/app/resources/lang/en_US/pagination.php
@ -1002,11 +1024,26 @@ opt/app/resources/lang/fr_FR/demo.php
opt/app/resources/lang/fr_FR/firefly.php
opt/app/resources/lang/fr_FR/form.php
opt/app/resources/lang/fr_FR/help.php
opt/app/resources/lang/fr_FR/import.php
opt/app/resources/lang/fr_FR/intro.php
opt/app/resources/lang/fr_FR/list.php
opt/app/resources/lang/fr_FR/pagination.php
opt/app/resources/lang/fr_FR/passwords.php
opt/app/resources/lang/fr_FR/validation.php
opt/app/resources/lang/id_ID/auth.php
opt/app/resources/lang/id_ID/bank.php
opt/app/resources/lang/id_ID/breadcrumbs.php
opt/app/resources/lang/id_ID/config.php
opt/app/resources/lang/id_ID/csv.php
opt/app/resources/lang/id_ID/demo.php
opt/app/resources/lang/id_ID/firefly.php
opt/app/resources/lang/id_ID/form.php
opt/app/resources/lang/id_ID/import.php
opt/app/resources/lang/id_ID/intro.php
opt/app/resources/lang/id_ID/list.php
opt/app/resources/lang/id_ID/pagination.php
opt/app/resources/lang/id_ID/passwords.php
opt/app/resources/lang/id_ID/validation.php
opt/app/resources/lang/nl_NL/auth.php
opt/app/resources/lang/nl_NL/bank.php
opt/app/resources/lang/nl_NL/breadcrumbs.php
@ -1016,6 +1053,7 @@ opt/app/resources/lang/nl_NL/demo.php
opt/app/resources/lang/nl_NL/firefly.php
opt/app/resources/lang/nl_NL/form.php
opt/app/resources/lang/nl_NL/help.php
opt/app/resources/lang/nl_NL/import.php
opt/app/resources/lang/nl_NL/intro.php
opt/app/resources/lang/nl_NL/list.php
opt/app/resources/lang/nl_NL/pagination.php
@ -1030,6 +1068,7 @@ opt/app/resources/lang/pl_PL/demo.php
opt/app/resources/lang/pl_PL/firefly.php
opt/app/resources/lang/pl_PL/form.php
opt/app/resources/lang/pl_PL/help.php
opt/app/resources/lang/pl_PL/import.php
opt/app/resources/lang/pl_PL/intro.php
opt/app/resources/lang/pl_PL/list.php
opt/app/resources/lang/pl_PL/pagination.php
@ -1057,6 +1096,7 @@ opt/app/resources/views/admin/link/delete.twig
opt/app/resources/views/admin/link/edit.twig
opt/app/resources/views/admin/link/index.twig
opt/app/resources/views/admin/link/show.twig
opt/app/resources/views/admin/update/index.twig
opt/app/resources/views/admin/users/delete.twig
opt/app/resources/views/admin/users/edit.twig
opt/app/resources/views/admin/users/index.twig
@ -1151,13 +1191,17 @@ 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/prerequisites.twig
opt/app/resources/views/import/csv/initial.twig
opt/app/resources/views/import/csv/map.twig
opt/app/resources/views/import/csv/roles.twig
opt/app/resources/views/import/file/finished.twig
opt/app/resources/views/import/file/index.twig
opt/app/resources/views/import/file/status.twig
opt/app/resources/views/import/file/initial.twig
opt/app/resources/views/import/file/map.twig
opt/app/resources/views/import/file/roles.twig
opt/app/resources/views/import/file/upload.twig
opt/app/resources/views/import/index.twig
opt/app/resources/views/import/spectre/input-fields.twig
opt/app/resources/views/import/spectre/prerequisites.twig
opt/app/resources/views/import/spectre/redirect.twig
opt/app/resources/views/import/spectre/select-country.twig
opt/app/resources/views/import/spectre/select-provider.twig
opt/app/resources/views/import/status.twig
opt/app/resources/views/index.twig
opt/app/resources/views/javascript/accounts.twig
opt/app/resources/views/javascript/currencies.twig
@ -1204,6 +1248,7 @@ opt/app/resources/views/profile/change-email.twig
opt/app/resources/views/profile/change-password.twig
opt/app/resources/views/profile/delete-account.twig
opt/app/resources/views/profile/index.twig
opt/app/resources/views/reports/account/report.twig
opt/app/resources/views/reports/audit/report.twig
opt/app/resources/views/reports/budget/month.twig
opt/app/resources/views/reports/category/month.twig
@ -1211,6 +1256,7 @@ opt/app/resources/views/reports/default/month.twig
opt/app/resources/views/reports/default/multi-year.twig
opt/app/resources/views/reports/default/year.twig
opt/app/resources/views/reports/index.twig
opt/app/resources/views/reports/options/account.twig
opt/app/resources/views/reports/options/budget.twig
opt/app/resources/views/reports/options/category.twig
opt/app/resources/views/reports/options/no-options.twig
@ -1222,10 +1268,14 @@ opt/app/resources/views/reports/partials/budget-period.twig
opt/app/resources/views/reports/partials/budgets.twig
opt/app/resources/views/reports/partials/categories.twig
opt/app/resources/views/reports/partials/category-period.twig
opt/app/resources/views/reports/partials/exp-budgets.twig
opt/app/resources/views/reports/partials/exp-categories.twig
opt/app/resources/views/reports/partials/exp-not-grouped.twig
opt/app/resources/views/reports/partials/income-expenses.twig
opt/app/resources/views/reports/partials/journals-audit.twig
opt/app/resources/views/reports/partials/operations.twig
opt/app/resources/views/reports/partials/tags.twig
opt/app/resources/views/reports/partials/top-transactions.twig
opt/app/resources/views/reports/tag/month.twig
opt/app/resources/views/rules/index.twig
opt/app/resources/views/rules/partials/action.twig
@ -1246,6 +1296,7 @@ opt/app/resources/views/tags/delete.twig
opt/app/resources/views/tags/edit.twig
opt/app/resources/views/tags/index.twig
opt/app/resources/views/tags/show.twig
opt/app/resources/views/test/test.twig
opt/app/resources/views/transactions/convert.twig
opt/app/resources/views/transactions/index.twig
opt/app/resources/views/transactions/links/delete.twig
@ -1257,6 +1308,7 @@ opt/app/resources/views/transactions/single/delete.twig
opt/app/resources/views/transactions/single/edit.twig
opt/app/resources/views/transactions/split/edit.twig
opt/app/routes/api.php
opt/app/routes/breadcrumbs.php
opt/app/routes/channels.php
opt/app/routes/console.php
opt/app/routes/web.php
@ -1325,14 +1377,6 @@ 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/commonmark
opt/app/vendor/bin/doctrine-dbal
opt/app/vendor/christian-riesen/base32/LICENSE
opt/app/vendor/christian-riesen/base32/README.md
opt/app/vendor/christian-riesen/base32/build.xml
opt/app/vendor/christian-riesen/base32/composer.json
opt/app/vendor/christian-riesen/base32/phpunit.xml.dist
opt/app/vendor/christian-riesen/base32/src/Base32.php
opt/app/vendor/christian-riesen/base32/tests/Base32Test.php
opt/app/vendor/christian-riesen/base32/tests/bootstrap.php
opt/app/vendor/composer/ClassLoader.php
opt/app/vendor/composer/LICENSE
opt/app/vendor/composer/autoload_classmap.php
@ -1342,37 +1386,24 @@ opt/app/vendor/composer/autoload_psr4.php
opt/app/vendor/composer/autoload_real.php
opt/app/vendor/composer/autoload_static.php
opt/app/vendor/composer/installed.json
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/.editorconfig
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/README.md
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/RELEASE-CHECKLIST.md
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/composer.json
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/config/breadcrumbs.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/scripts/test-coverage.sh
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/CurrentRoute.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/Exception.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/Facade.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/Generator.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/Manager.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/ServiceProvider.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/View.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/TestCase.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/bootstrap.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/fixtures/CustomServiceProvider.html
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/fixtures/DependantServiceProvider.html
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/fixtures/bootstrap2.html
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/fixtures/bootstrap3.html
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/fixtures/integration.html
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/integration/CustomServiceProviderTest.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/integration/DependantServiceProviderErrorTest.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/integration/DependantServiceProviderTest.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/integration/IntegrationTest.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/unit/CurrentRouteTest.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/unit/FacadeTest.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/unit/GeneratorTest.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/unit/ManagerTest.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/tests/unit/ViewTest.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/BreadcrumbsException.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/BreadcrumbsGenerator.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/BreadcrumbsManager.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/BreadcrumbsServiceProvider.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/Exceptions/DuplicateBreadcrumbException.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/Exceptions/InvalidBreadcrumbException.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/Exceptions/UnnamedRouteException.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/Exceptions/ViewNotSetException.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/src/Facades/Breadcrumbs.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/views/bootstrap2.blade.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/views/bootstrap3.blade.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/views/bootstrap4.blade.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/views/bulma.blade.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/views/foundation6.blade.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/views/json-ld.php
opt/app/vendor/davejamesmiller/laravel-breadcrumbs/views/materialize.blade.php
opt/app/vendor/doctrine/annotations/CHANGELOG.md
opt/app/vendor/doctrine/annotations/LICENSE
opt/app/vendor/doctrine/annotations/README.md
@ -2188,6 +2219,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Events/CallQueuedListener.php
opt/app/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
opt/app/vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.php
opt/app/vendor/laravel/framework/src/Illuminate/Events/composer.json
opt/app/vendor/laravel/framework/src/Illuminate/Filesystem/Cache.php
opt/app/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php
opt/app/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php
opt/app/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemManager.php
@ -2663,6 +2695,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Support/MessageBag.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/NamespacedItemResolver.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Optional.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Pluralizer.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/ProcessUtils.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/ServiceProvider.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Str.php
opt/app/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/BusFake.php
@ -2865,23 +2898,29 @@ opt/app/vendor/league/commonmark/src/Util/Html5Entities.php
opt/app/vendor/league/commonmark/src/Util/LinkParserHelper.php
opt/app/vendor/league/commonmark/src/Util/RegexHelper.php
opt/app/vendor/league/commonmark/src/Util/UrlEncoder.php
opt/app/vendor/league/commonmark/src/Util/Xml.php
opt/app/vendor/league/csv/LICENSE
opt/app/vendor/league/csv/autoload.php
opt/app/vendor/league/csv/composer.json
opt/app/vendor/league/csv/src/AbstractCsv.php
opt/app/vendor/league/csv/src/Config/Controls.php
opt/app/vendor/league/csv/src/Config/Output.php
opt/app/vendor/league/csv/src/Exception/InvalidRowException.php
opt/app/vendor/league/csv/src/Modifier/MapIterator.php
opt/app/vendor/league/csv/src/Modifier/QueryFilter.php
opt/app/vendor/league/csv/src/Modifier/RowFilter.php
opt/app/vendor/league/csv/src/Modifier/StreamFilter.php
opt/app/vendor/league/csv/src/Modifier/StreamIterator.php
opt/app/vendor/league/csv/src/Plugin/ColumnConsistencyValidator.php
opt/app/vendor/league/csv/src/Plugin/ForbiddenNullValuesValidator.php
opt/app/vendor/league/csv/src/Plugin/SkipNullValuesFormatter.php
opt/app/vendor/league/csv/src/ByteSequence.php
opt/app/vendor/league/csv/src/CannotInsertRecord.php
opt/app/vendor/league/csv/src/CharsetConverter.php
opt/app/vendor/league/csv/src/ColumnConsistency.php
opt/app/vendor/league/csv/src/EncloseField.php
opt/app/vendor/league/csv/src/EscapeFormula.php
opt/app/vendor/league/csv/src/Exception.php
opt/app/vendor/league/csv/src/HTMLConverter.php
opt/app/vendor/league/csv/src/MapIterator.php
opt/app/vendor/league/csv/src/RFC4180Field.php
opt/app/vendor/league/csv/src/Reader.php
opt/app/vendor/league/csv/src/ResultSet.php
opt/app/vendor/league/csv/src/Statement.php
opt/app/vendor/league/csv/src/Stream.php
opt/app/vendor/league/csv/src/Writer.php
opt/app/vendor/league/csv/src/XMLConverter.php
opt/app/vendor/league/csv/src/functions.php
opt/app/vendor/league/csv/src/functions_include.php
opt/app/vendor/league/flysystem/LICENSE
opt/app/vendor/league/flysystem/composer.json
opt/app/vendor/league/flysystem/docs/CNAME
@ -3229,6 +3268,31 @@ opt/app/vendor/nesbot/carbon/src/Carbon/Lang/uz.php
opt/app/vendor/nesbot/carbon/src/Carbon/Lang/vi.php
opt/app/vendor/nesbot/carbon/src/Carbon/Lang/zh.php
opt/app/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php
opt/app/vendor/paragonie/constant_time_encoding/LICENSE.txt
opt/app/vendor/paragonie/constant_time_encoding/README.md
opt/app/vendor/paragonie/constant_time_encoding/composer.json
opt/app/vendor/paragonie/constant_time_encoding/phpunit.xml.dist
opt/app/vendor/paragonie/constant_time_encoding/psalm.xml
opt/app/vendor/paragonie/constant_time_encoding/src/Base32.php
opt/app/vendor/paragonie/constant_time_encoding/src/Base32Hex.php
opt/app/vendor/paragonie/constant_time_encoding/src/Base64.php
opt/app/vendor/paragonie/constant_time_encoding/src/Base64DotSlash.php
opt/app/vendor/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php
opt/app/vendor/paragonie/constant_time_encoding/src/Base64UrlSafe.php
opt/app/vendor/paragonie/constant_time_encoding/src/Binary.php
opt/app/vendor/paragonie/constant_time_encoding/src/EncoderInterface.php
opt/app/vendor/paragonie/constant_time_encoding/src/Encoding.php
opt/app/vendor/paragonie/constant_time_encoding/src/Hex.php
opt/app/vendor/paragonie/constant_time_encoding/src/RFC4648.php
opt/app/vendor/paragonie/constant_time_encoding/tests/Base32HexTest.php
opt/app/vendor/paragonie/constant_time_encoding/tests/Base32Test.php
opt/app/vendor/paragonie/constant_time_encoding/tests/Base64DotSlashOrderedTest.php
opt/app/vendor/paragonie/constant_time_encoding/tests/Base64DotSlashTest.php
opt/app/vendor/paragonie/constant_time_encoding/tests/Base64Test.php
opt/app/vendor/paragonie/constant_time_encoding/tests/Base64UrlSafeTest.php
opt/app/vendor/paragonie/constant_time_encoding/tests/EncodingTest.php
opt/app/vendor/paragonie/constant_time_encoding/tests/HexTest.php
opt/app/vendor/paragonie/constant_time_encoding/tests/RFC4648Test.php
opt/app/vendor/paragonie/random_compat/LICENSE
opt/app/vendor/paragonie/random_compat/build-phar.sh
opt/app/vendor/paragonie/random_compat/composer.json
@ -3247,19 +3311,45 @@ opt/app/vendor/paragonie/random_compat/lib/random_int.php
opt/app/vendor/paragonie/random_compat/other/build_phar.php
opt/app/vendor/paragonie/random_compat/psalm-autoload.php
opt/app/vendor/paragonie/random_compat/psalm.xml
opt/app/vendor/pragmarx/google2fa-laravel/LICENSE
opt/app/vendor/pragmarx/google2fa-laravel/changelog.md
opt/app/vendor/pragmarx/google2fa-laravel/composer.json
opt/app/vendor/pragmarx/google2fa-laravel/docs/middleware.jpg
opt/app/vendor/pragmarx/google2fa-laravel/phpspec.yml
opt/app/vendor/pragmarx/google2fa-laravel/readme.md
opt/app/vendor/pragmarx/google2fa-laravel/src/Events/OneTimePasswordRequested.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Exceptions/InvalidOneTimePassword.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Exceptions/InvalidSecretKey.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Facade.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Middleware.php
opt/app/vendor/pragmarx/google2fa-laravel/src/ServiceProvider.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Support/Auth.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Support/Authenticator.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Support/Config.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Support/Constants.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Support/ErrorBag.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Support/Input.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Support/Request.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Support/Response.php
opt/app/vendor/pragmarx/google2fa-laravel/src/Support/Session.php
opt/app/vendor/pragmarx/google2fa-laravel/src/config/config.php
opt/app/vendor/pragmarx/google2fa-laravel/tests/spec/Support/AuthenticatorSpec.php
opt/app/vendor/pragmarx/google2fa-laravel/upgrading.md
opt/app/vendor/pragmarx/google2fa/LICENSE
opt/app/vendor/pragmarx/google2fa/changelog.md
opt/app/vendor/pragmarx/google2fa/composer.json
opt/app/vendor/pragmarx/google2fa/phpspec.yml
opt/app/vendor/pragmarx/google2fa/docs/playground.jpg
opt/app/vendor/pragmarx/google2fa/readme.md
opt/app/vendor/pragmarx/google2fa/src/Contracts/Google2FA.php
opt/app/vendor/pragmarx/google2fa/src/Exceptions/IncompatibleWithGoogleAuthenticatorException.php
opt/app/vendor/pragmarx/google2fa/src/Exceptions/InvalidCharactersException.php
opt/app/vendor/pragmarx/google2fa/src/Exceptions/SecretKeyTooShortException.php
opt/app/vendor/pragmarx/google2fa/src/Google2FA.php
opt/app/vendor/pragmarx/google2fa/src/Support/Base32.php
opt/app/vendor/pragmarx/google2fa/src/Support/Constants.php
opt/app/vendor/pragmarx/google2fa/src/Support/QRCode.php
opt/app/vendor/pragmarx/google2fa/src/Support/Url.php
opt/app/vendor/pragmarx/google2fa/src/Vendor/Laravel/Facade.php
opt/app/vendor/pragmarx/google2fa/src/Vendor/Laravel/ServiceProvider.php
opt/app/vendor/pragmarx/google2fa/tests/spec/Google2FASpec.php
opt/app/vendor/pragmarx/google2fa/tests/Google2FATest.php
opt/app/vendor/pragmarx/google2fa/tests/bootstrap.php
opt/app/vendor/pragmarx/google2fa/upgrading.md
opt/app/vendor/psr/container/LICENSE
opt/app/vendor/psr/container/README.md
@ -4245,10 +4335,11 @@ opt/app/vendor/symfony/debug/Tests/Fixtures/reallyNotPsr0.php
opt/app/vendor/symfony/debug/Tests/Fixtures2/RequiredTwice.php
opt/app/vendor/symfony/debug/Tests/HeaderMock.php
opt/app/vendor/symfony/debug/Tests/MockExceptionHandler.php
opt/app/vendor/symfony/debug/Tests/phpt/exception_rethrown.phpt
opt/app/vendor/symfony/debug/Tests/phpt/fatal_with_nested_handlers.phpt
opt/app/vendor/symfony/debug/composer.json
opt/app/vendor/symfony/debug/phpunit.xml.dist
opt/app/vendor/symfony/event-dispatcher/CHANGELOG.md
opt/app/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php
opt/app/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
opt/app/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php
opt/app/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
@ -4262,7 +4353,6 @@ opt/app/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
opt/app/vendor/symfony/event-dispatcher/LICENSE
opt/app/vendor/symfony/event-dispatcher/README.md
opt/app/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
opt/app/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php
opt/app/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
opt/app/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
opt/app/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php
@ -4453,6 +4543,8 @@ opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/st
opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php
opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected
opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php
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/MemcacheSessionHandlerTest.php
opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php
opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
@ -4529,9 +4621,12 @@ opt/app/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueRe
opt/app/vendor/symfony/http-kernel/DependencyInjection/Extension.php
opt/app/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php
opt/app/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php
opt/app/vendor/symfony/http-kernel/DependencyInjection/LoggerPass.php
opt/app/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php
opt/app/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php
opt/app/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php
opt/app/vendor/symfony/http-kernel/DependencyInjection/ResettableServicePass.php
opt/app/vendor/symfony/http-kernel/DependencyInjection/ServicesResetter.php
opt/app/vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php
opt/app/vendor/symfony/http-kernel/Event/FilterControllerEvent.php
opt/app/vendor/symfony/http-kernel/Event/FilterResponseEvent.php
@ -4600,11 +4695,14 @@ opt/app/vendor/symfony/http-kernel/KernelEvents.php
opt/app/vendor/symfony/http-kernel/KernelInterface.php
opt/app/vendor/symfony/http-kernel/LICENSE
opt/app/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php
opt/app/vendor/symfony/http-kernel/Log/Logger.php
opt/app/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php
opt/app/vendor/symfony/http-kernel/Profiler/Profile.php
opt/app/vendor/symfony/http-kernel/Profiler/Profiler.php
opt/app/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php
opt/app/vendor/symfony/http-kernel/README.md
opt/app/vendor/symfony/http-kernel/RebootableInterface.php
opt/app/vendor/symfony/http-kernel/Resources/welcome.html.php
opt/app/vendor/symfony/http-kernel/TerminableInterface.php
opt/app/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php
opt/app/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php
@ -4614,11 +4712,13 @@ 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/EnvParametersResourceTest.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/ArgumentResolverTest.php
opt/app/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php
opt/app/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php
opt/app/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php
opt/app/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php
opt/app/vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log
opt/app/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php
opt/app/vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php
opt/app/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php
@ -4634,9 +4734,13 @@ opt/app/vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClasses
opt/app/vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php
opt/app/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php
opt/app/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php
opt/app/vendor/symfony/http-kernel/Tests/DependencyInjection/LoggerPassTest.php
opt/app/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php
opt/app/vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php
opt/app/vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php
opt/app/vendor/symfony/http-kernel/Tests/DependencyInjection/ResettableServicePassTest.php
opt/app/vendor/symfony/http-kernel/Tests/DependencyInjection/ServicesResetterTest.php
opt/app/vendor/symfony/http-kernel/Tests/Event/FilterControllerArgumentsEventTest.php
opt/app/vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php
@ -4676,6 +4780,7 @@ opt/app/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/foo.txt
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/Bundle2Bundle/foo.txt
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/foo.txt
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/hide.txt
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/ClearableService.php
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php
@ -4694,6 +4799,7 @@ opt/app/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Extensi
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/ResettableService.php
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/Resources/BaseBundle/hide.txt
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt
opt/app/vendor/symfony/http-kernel/Tests/Fixtures/Resources/ChildBundle/foo.txt
@ -4716,6 +4822,7 @@ opt/app/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php
opt/app/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php
opt/app/vendor/symfony/http-kernel/Tests/HttpKernelTest.php
opt/app/vendor/symfony/http-kernel/Tests/KernelTest.php
opt/app/vendor/symfony/http-kernel/Tests/Log/LoggerTest.php
opt/app/vendor/symfony/http-kernel/Tests/Logger.php
opt/app/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php
opt/app/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php
@ -5297,6 +5404,7 @@ opt/app/vendor/twig/twig/lib/Twig/Cache/Null.php
opt/app/vendor/twig/twig/lib/Twig/CacheInterface.php
opt/app/vendor/twig/twig/lib/Twig/Compiler.php
opt/app/vendor/twig/twig/lib/Twig/CompilerInterface.php
opt/app/vendor/twig/twig/lib/Twig/ContainerRuntimeLoader.php
opt/app/vendor/twig/twig/lib/Twig/Environment.php
opt/app/vendor/twig/twig/lib/Twig/Error.php
opt/app/vendor/twig/twig/lib/Twig/Error/Loader.php
@ -5417,6 +5525,7 @@ opt/app/vendor/twig/twig/lib/Twig/Node/SetTemp.php
opt/app/vendor/twig/twig/lib/Twig/Node/Spaceless.php
opt/app/vendor/twig/twig/lib/Twig/Node/Text.php
opt/app/vendor/twig/twig/lib/Twig/Node/With.php
opt/app/vendor/twig/twig/lib/Twig/NodeCaptureInterface.php
opt/app/vendor/twig/twig/lib/Twig/NodeInterface.php
opt/app/vendor/twig/twig/lib/Twig/NodeOutputInterface.php
opt/app/vendor/twig/twig/lib/Twig/NodeTraverser.php
@ -5427,6 +5536,7 @@ opt/app/vendor/twig/twig/lib/Twig/NodeVisitor/Sandbox.php
opt/app/vendor/twig/twig/lib/Twig/NodeVisitorInterface.php
opt/app/vendor/twig/twig/lib/Twig/Parser.php
opt/app/vendor/twig/twig/lib/Twig/ParserInterface.php
opt/app/vendor/twig/twig/lib/Twig/Profiler/Dumper/Base.php
opt/app/vendor/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php
opt/app/vendor/twig/twig/lib/Twig/Profiler/Dumper/Html.php
opt/app/vendor/twig/twig/lib/Twig/Profiler/Dumper/Text.php
@ -5486,15 +5596,188 @@ opt/app/vendor/twig/twig/lib/Twig/TokenStream.php
opt/app/vendor/twig/twig/lib/Twig/Util/DeprecationCollector.php
opt/app/vendor/twig/twig/lib/Twig/Util/TemplateDirIterator.php
opt/app/vendor/twig/twig/phpunit.xml.dist
opt/app/vendor/twig/twig/src/Cache/CacheInterface.php
opt/app/vendor/twig/twig/src/Cache/FilesystemCache.php
opt/app/vendor/twig/twig/src/Cache/NullCache.php
opt/app/vendor/twig/twig/src/Compiler.php
opt/app/vendor/twig/twig/src/Environment.php
opt/app/vendor/twig/twig/src/Error/Error.php
opt/app/vendor/twig/twig/src/Error/LoaderError.php
opt/app/vendor/twig/twig/src/Error/RuntimeError.php
opt/app/vendor/twig/twig/src/Error/SyntaxError.php
opt/app/vendor/twig/twig/src/ExpressionParser.php
opt/app/vendor/twig/twig/src/Extension/AbstractExtension.php
opt/app/vendor/twig/twig/src/Extension/CoreExtension.php
opt/app/vendor/twig/twig/src/Extension/DebugExtension.php
opt/app/vendor/twig/twig/src/Extension/EscaperExtension.php
opt/app/vendor/twig/twig/src/Extension/ExtensionInterface.php
opt/app/vendor/twig/twig/src/Extension/GlobalsInterface.php
opt/app/vendor/twig/twig/src/Extension/InitRuntimeInterface.php
opt/app/vendor/twig/twig/src/Extension/OptimizerExtension.php
opt/app/vendor/twig/twig/src/Extension/ProfilerExtension.php
opt/app/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php
opt/app/vendor/twig/twig/src/Extension/SandboxExtension.php
opt/app/vendor/twig/twig/src/Extension/StagingExtension.php
opt/app/vendor/twig/twig/src/Extension/StringLoaderExtension.php
opt/app/vendor/twig/twig/src/FileExtensionEscapingStrategy.php
opt/app/vendor/twig/twig/src/Lexer.php
opt/app/vendor/twig/twig/src/Loader/ArrayLoader.php
opt/app/vendor/twig/twig/src/Loader/ChainLoader.php
opt/app/vendor/twig/twig/src/Loader/ExistsLoaderInterface.php
opt/app/vendor/twig/twig/src/Loader/FilesystemLoader.php
opt/app/vendor/twig/twig/src/Loader/LoaderInterface.php
opt/app/vendor/twig/twig/src/Loader/SourceContextLoaderInterface.php
opt/app/vendor/twig/twig/src/Markup.php
opt/app/vendor/twig/twig/src/Node/AutoEscapeNode.php
opt/app/vendor/twig/twig/src/Node/BlockNode.php
opt/app/vendor/twig/twig/src/Node/BlockReferenceNode.php
opt/app/vendor/twig/twig/src/Node/BodyNode.php
opt/app/vendor/twig/twig/src/Node/CheckSecurityNode.php
opt/app/vendor/twig/twig/src/Node/DoNode.php
opt/app/vendor/twig/twig/src/Node/EmbedNode.php
opt/app/vendor/twig/twig/src/Node/Expression/AbstractExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/ArrayExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php
opt/app/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/CallExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/ConstantExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php
opt/app/vendor/twig/twig/src/Node/Expression/FilterExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/FunctionExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/NameExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/ParentExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/TempNameExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php
opt/app/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php
opt/app/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php
opt/app/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php
opt/app/vendor/twig/twig/src/Node/Expression/Test/NullTest.php
opt/app/vendor/twig/twig/src/Node/Expression/Test/OddTest.php
opt/app/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php
opt/app/vendor/twig/twig/src/Node/Expression/TestExpression.php
opt/app/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php
opt/app/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php
opt/app/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php
opt/app/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php
opt/app/vendor/twig/twig/src/Node/FlushNode.php
opt/app/vendor/twig/twig/src/Node/ForLoopNode.php
opt/app/vendor/twig/twig/src/Node/ForNode.php
opt/app/vendor/twig/twig/src/Node/IfNode.php
opt/app/vendor/twig/twig/src/Node/ImportNode.php
opt/app/vendor/twig/twig/src/Node/IncludeNode.php
opt/app/vendor/twig/twig/src/Node/MacroNode.php
opt/app/vendor/twig/twig/src/Node/ModuleNode.php
opt/app/vendor/twig/twig/src/Node/Node.php
opt/app/vendor/twig/twig/src/Node/NodeCaptureInterface.php
opt/app/vendor/twig/twig/src/Node/NodeOutputInterface.php
opt/app/vendor/twig/twig/src/Node/PrintNode.php
opt/app/vendor/twig/twig/src/Node/SandboxNode.php
opt/app/vendor/twig/twig/src/Node/SandboxedPrintNode.php
opt/app/vendor/twig/twig/src/Node/SetNode.php
opt/app/vendor/twig/twig/src/Node/SetTempNode.php
opt/app/vendor/twig/twig/src/Node/SpacelessNode.php
opt/app/vendor/twig/twig/src/Node/TextNode.php
opt/app/vendor/twig/twig/src/Node/WithNode.php
opt/app/vendor/twig/twig/src/NodeTraverser.php
opt/app/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php
opt/app/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php
opt/app/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php
opt/app/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php
opt/app/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php
opt/app/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php
opt/app/vendor/twig/twig/src/Parser.php
opt/app/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php
opt/app/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php
opt/app/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php
opt/app/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php
opt/app/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php
opt/app/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php
opt/app/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php
opt/app/vendor/twig/twig/src/Profiler/Profile.php
opt/app/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php
opt/app/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php
opt/app/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php
opt/app/vendor/twig/twig/src/Sandbox/SecurityError.php
opt/app/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php
opt/app/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php
opt/app/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php
opt/app/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php
opt/app/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php
opt/app/vendor/twig/twig/src/Sandbox/SecurityPolicy.php
opt/app/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php
opt/app/vendor/twig/twig/src/Source.php
opt/app/vendor/twig/twig/src/Template.php
opt/app/vendor/twig/twig/src/TemplateWrapper.php
opt/app/vendor/twig/twig/src/Test/IntegrationTestCase.php
opt/app/vendor/twig/twig/src/Test/NodeTestCase.php
opt/app/vendor/twig/twig/src/Token.php
opt/app/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/BlockTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/DoTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/FilterTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/FlushTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/ForTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/FromTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/IfTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/ImportTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/MacroTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/SetTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/SpacelessTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/TokenParserInterface.php
opt/app/vendor/twig/twig/src/TokenParser/UseTokenParser.php
opt/app/vendor/twig/twig/src/TokenParser/WithTokenParser.php
opt/app/vendor/twig/twig/src/TokenStream.php
opt/app/vendor/twig/twig/src/TwigFilter.php
opt/app/vendor/twig/twig/src/TwigFunction.php
opt/app/vendor/twig/twig/src/TwigTest.php
opt/app/vendor/twig/twig/src/Util/DeprecationCollector.php
opt/app/vendor/twig/twig/src/Util/TemplateDirIterator.php
opt/app/vendor/twig/twig/test/Twig/Tests/AutoloaderTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/Cache/FilesystemTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/CompilerTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/ContainerRuntimeLoaderTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/CustomExtensionTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/EnvironmentTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/ErrorTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/ExpressionParserTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/Extension/CoreTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/Extension/SandboxTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/FactoryRuntimeLoaderTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/FileCachingTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/FileExtensionEscapingStrategyTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/FilesystemHelper.php
@ -5561,6 +5844,7 @@ opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/default.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/dynamic_filter.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape_html_attr.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape_javascript.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/escape_non_supported_charset.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/first.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/force_escape.test
@ -5751,6 +6035,7 @@ opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/multiple_aliases.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block2.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/parent_block3.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/use/use_with_parent.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/basic.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/mixed_usage_with_raw.test
opt/app/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/verbatim/whitespace_control.test
@ -5851,12 +6136,11 @@ opt/app/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/BlackfireTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/HtmlTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/TextTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/Profiler/ProfileTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/RuntimeFactoryLoaderTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/TemplateTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/TemplateWrapperTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/TokenStreamTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/Util/DeprecationCollectorTest.php
opt/app/vendor/twig/twig/test/Twig/Tests/escapingTest.php
opt/app/vendor/twig/twig/test/bootstrap.php
opt/app/vendor/vlucas/phpdotenv/LICENSE.txt
opt/app/vendor/vlucas/phpdotenv/composer.json
opt/app/vendor/vlucas/phpdotenv/src/Dotenv.php
@ -6010,6 +6294,8 @@ usr/lib/x86_64-linux-gnu/libxml2.so.2
usr/lib/x86_64-linux-gnu/libxml2.so.2.9.1
usr/lib/x86_64-linux-gnu/libxslt.so.1
usr/lib/x86_64-linux-gnu/libxslt.so.1.1.28
usr/lib/x86_64-linux-gnu/libzip.so.5
usr/lib/x86_64-linux-gnu/libzip.so.5.0.0
usr/sbin/mysqld
usr/sbin/nginx
usr/sbin/php-fpm7.1

View File

@ -15,8 +15,8 @@ const pkgdef :Spk.PackageDefinition = (
manifest = (
appTitle = (defaultText = "Firefly III"),
appVersion = 5,
appMarketingVersion = (defaultText = "4.6.11.1"),
appVersion = 6,
appMarketingVersion = (defaultText = "4.6.12"),
actions = [
# Define your "new document" handlers here.
@ -41,7 +41,7 @@ const pkgdef :Spk.PackageDefinition = (
market = (png = (dpi1x = embed "app-graphics/firefly-iii-150.png"))
),
website = "https://firefly-iii.github.io/",
website = "https://firefly-iii.org/",
codeUrl = "https://github.com/firefly-iii/firefly-iii",
license = (openSource = gpl3),
# The license this package is distributed under. See

View File

@ -1,6 +1,7 @@
language: php
php:
- 7.1
- 7.2
cache:
directories:
@ -22,7 +23,7 @@ script:
- phpunit -c phpunit.coverage.xml
after_success:
- travis_retry php vendor/bin/coveralls -x storage/build/clover-all.xml
- travis_retry php vendor/bin/php-coveralls -x storage/build/clover-all.xml
# safelist
branches:

View File

@ -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.6.12] - 2017-12-31
### Added
- Support for Indonesian.
- New report, see [issue 384](https://github.com/firefly-iii/firefly-iii/issues/384)
- [Issue 964](https://github.com/firefly-iii/firefly-iii/issues/964) as suggested by [gavu](https://github.com/gavu)
### Changed
- Greatly improved Docker support and documentation.
### Fixed
- [Issue 1046](https://github.com/firefly-iii/firefly-iii/issues/1046), as reported by [pkoziol](https://github.com/pkoziol)
- [Issue 1047](https://github.com/firefly-iii/firefly-iii/issues/1047), as reported by [pkoziol](https://github.com/pkoziol)
- [Issue 1048](https://github.com/firefly-iii/firefly-iii/issues/1048), as reported by [webence](https://github.com/webence)
- [Issue 1049](https://github.com/firefly-iii/firefly-iii/issues/1049), as reported by [nicoschreiner](https://github.com/nicoschreiner)
- [Issue 1015](https://github.com/firefly-iii/firefly-iii/issues/1015), as reporterd by a user on Tweakers.net
- [Issue 1056](https://github.com/firefly-iii/firefly-iii/issues/1056), as reported by [repercussion](https://github.com/repercussion)
- [Issue 1061](https://github.com/firefly-iii/firefly-iii/issues/1061), as reported by [Meizikyn](https://github.com/Meizikyn)
- [Issue 1045](https://github.com/firefly-iii/firefly-iii/issues/1045), as reported by [gavu](https://github.com/gavu)
- First code for [issue 1040](https://github.com/firefly-iii/firefly-iii/issues/1040) ([simonsmiley](https://github.com/simonsmiley))
- [Issue 1059](https://github.com/firefly-iii/firefly-iii/issues/1059), as reported by [4oo4](https://github.com/4oo4)
- [Issue 1063](https://github.com/firefly-iii/firefly-iii/issues/1063), as reported by [pkoziol](https://github.com/pkoziol)
- [Issue 1064](https://github.com/firefly-iii/firefly-iii/issues/1064), as reported by [pkoziol](https://github.com/pkoziol)
- [Issue 1066](https://github.com/firefly-iii/firefly-iii/issues/1066), reported by [wtercato](https://github.com/wtercato)
## [4.6.11.1] - 2017-12-08
### Added
- Import routine can scan for matching bills, [issue 956](https://github.com/firefly-iii/firefly-iii/issues/956)

View File

@ -1,5 +1,12 @@
# use PHP 7.1 and Apache as a base.
FROM php:7.1-apache
# set working dir
ENV FIREFLY_PATH /var/www/firefly-iii
WORKDIR $FIREFLY_PATH
ADD . $FIREFLY_PATH
# install packages
RUN apt-get update -y && \
apt-get install -y --no-install-recommends libcurl4-openssl-dev \
zlib1g-dev \
@ -17,11 +24,13 @@ RUN apt-get update -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 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
# Generate locales supported by firefly
RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
# Generate locales supported by Firefly III
RUN echo "de_DE.UTF-8 UTF-8\nen_US.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nid_ID.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
# copy Apache config to correct spot.
COPY ./docker/apache2.conf /etc/apache2/apache2.conf
# Enable apache mod rewrite..
@ -30,23 +39,23 @@ RUN a2enmod rewrite
# Enable apache mod ssl..
RUN a2enmod ssl
# Create volumes for several directories:
VOLUME $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload
# Setup the Composer installer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Copy Apache Configs
# Enable default site (Firefly III)
COPY ./docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf
ENV FIREFLY_PATH /var/www/firefly-iii
WORKDIR $FIREFLY_PATH
# The working directory
COPY . $FIREFLY_PATH
# Make sure we own Firefly III directory
RUN chown -R www-data:www-data /var/www && chmod -R 775 $FIREFLY_PATH/storage
RUN composer install --prefer-dist --no-dev --no-scripts
# Run composer
RUN composer install --prefer-dist --no-dev --no-scripts --no-suggest
# Expose port 80
EXPOSE 80
# Run entrypoint thing
ENTRYPOINT ["docker/entrypoint.sh"]

View File

@ -2,9 +2,11 @@
[![Requires PHP7.1](https://img.shields.io/badge/php-7.1-red.svg)](https://secure.php.net/downloads.php) [![Latest Stable Version](https://poser.pugx.org/grumpydictator/firefly-iii/v/stable)](https://packagist.org/packages/grumpydictator/firefly-iii) [![License](https://img.shields.io/badge/license-GPL-lightgrey.svg)](https://www.gnu.org/licenses/gpl.html) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
[![The index of Firefly III](https://i.nder.be/ccynyxy0/400)](https://i.nder.be/h327vx3y) [![The account overview of Firefly III](https://i.nder.be/g8v86y2g/400)](https://i.nder.be/hkpynqr9)
[![The index of Firefly III](https://firefly-iii.org/static/screenshots/4.6.12/tiny/index.png)](https://firefly-iii.org/static/screenshots/4.6.12/index.png) [![The account overview of Firefly III](https://firefly-iii.org/static/screenshots/4.6.12/tiny/account.png)](https://firefly-iii.org/static/screenshots/4.6.12/account.png)
[![The useful financial reports of Firefly III](https://i.nder.be/cs3qx4f3/400)](https://i.nder.be/cwznmryd) [![Saving money is easy!](https://i.nder.be/gpq6ykym/400)](https://i.nder.be/gum2qf8z)
[![Overview of all budgets](https://firefly-iii.org/static/screenshots/4.6.12/tiny/budget.png)](https://firefly-iii.org/static/screenshots/4.6.12/budget.png) [![Overview of a category](https://firefly-iii.org/static/screenshots/4.6.12/tiny/category.png)](https://firefly-iii.org/static/screenshots/4.6.12/category.png)
[![View of a report](https://firefly-iii.org/static/screenshots/4.6.12/tiny/report1.png)](https://firefly-iii.org/static/screenshots/4.6.12/report1.png) [![View of another report](https://firefly-iii.org/static/screenshots/4.6.12/tiny/report2.png)](https://firefly-iii.org/static/screenshots/4.6.12/report2.png)
"Firefly III" is a financial manager for your personal finances. It can help you keep track of your expenses and income.
Firefly III supports the use of budgets. You can categorize and tag your transactions.
@ -13,17 +15,17 @@ There are many financial reports available.
## Want to try Firefly III?
There is a **[demo site](https://firefly-iii.nder.be)** with an example financial administration already present. You can use Docker, Heroku or Sandstorm.io (see below) to quickly setup your own instance.
There is a **[demo site](https://demo.firefly-iii.org)** with an example financial administration already present. You can use Docker, Heroku or Sandstorm.io (see below) to quickly setup your own instance.
## Install Firefly III
### Using docker
You can use docker-compose to [set up your personal secure](https://firefly-iii.github.io/using-docker.html) Firefly III environment. Advanced users can use the Dockerfile directly.
You can use docker-compose to [set up your personal secure](https://firefly-iii.org/using-docker.html) Firefly III environment. Advanced users can use the Dockerfile directly.
### Using vagrant (or other VMs)
You can install Firefly III on any Linux or Windows machine. You'll need a web server (preferrably on Linux) and access to the command line. Please read the [installation guide](https://firefly-iii.github.io/using-installing.html).
You can install Firefly III on any Linux or Windows machine. You'll need a web server (preferrably on Linux) and access to the command line. Please read the [installation guide](https://firefly-iii.org/using-installing.html).
### Using Heroku
@ -31,8 +33,6 @@ You can install Firefly III on any Linux or Windows machine. You'll need a web s
Register for a free Heroku account and instantly run Firefly III on your very own cloud instance.
_My Heroku configuration is currently broken, but I'm trying to fix it._
### Using Sandstorm.io
You can find Firefly III in [the Sandstorm.io marketplace](https://apps.sandstorm.io/app/uws252ya9mep4t77tevn85333xzsgrpgth8q4y1rhknn1hammw70). You can run it on your own installation or on Oasis.
@ -53,7 +53,7 @@ Firefly works on the principle that if you know where you're money is going, you
- If you feel you're missing something you can just ask me and I'll add it!
Firefly III has become pretty awesome over the years! (but please excuse me for bragging, it's just that I'm proud of it).
[You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/).
[You can read more about Firefly III, and its features, on the website](https://firefly-iii.org/).
### Contributing
@ -74,8 +74,12 @@ This work [is licensed](https://github.com/firefly-iii/firefly-iii/blob/master/L
### Other stuff
If you like Firefly and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!)
If you like Firefly III and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!)
If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com).
### Alternatives
If you are looking for alternatives, check out [Kickball's Awesome-Selfhosted list](https://github.com/Kickball/awesome-selfhosted) which features not only Firefly III but also noteworthy alternatives such as [Silverstrike](https://github.com/agstrike/silverstrike).
[![Build Status](https://travis-ci.org/firefly-iii/firefly-iii.svg?branch=master)](https://travis-ci.org/firefly-iii/firefly-iii) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [![Coverage Status](https://coveralls.io/repos/github/firefly-iii/firefly-iii/badge.svg?branch=master)](https://coveralls.io/github/firefly-iii/firefly-iii?branch=master)

View File

@ -2,7 +2,7 @@
"name": "Firefly III",
"description": "A free and open source personal finances manager",
"repository": "https://github.com/firefly-iii/firefly-iii",
"website": "https://firefly-iii.github.io/",
"website": "https://firefly-iii.org/",
"logo": "https://raw.githubusercontent.com/firefly-iii/firefly-iii/master/public/mstile-150x150.png",
"keywords": [
"finance",

View File

@ -23,8 +23,9 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands;
use Artisan;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Import\Logging\CommandHandler;
use FireflyIII\Import\Routine\ImportRoutine;
use FireflyIII\Import\Routine\RoutineInterface;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Console\Command;
@ -54,7 +55,7 @@ class CreateImport extends Command
protected $signature
= 'firefly:create-import
{file : The file to import.}
{configuration : The configuration file to use for the import/}
{configuration : The configuration file to use for the import.}
{--type=csv : The file type of the import.}
{--user= : The user ID that the import should import for.}
{--token= : The user\'s access token.}
@ -73,6 +74,8 @@ class CreateImport extends Command
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // cannot be helped
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five exactly.
*
* @throws FireflyException
*/
public function handle()
{
@ -93,7 +96,7 @@ class CreateImport extends Command
return;
}
$configurationData = json_decode(file_get_contents($configuration));
$configurationData = json_decode(file_get_contents($configuration), true);
if (null === $configurationData) {
$this->error(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
@ -114,9 +117,8 @@ class CreateImport extends Command
Artisan::call('firefly:encrypt-file', ['file' => $file, 'key' => $job->key]);
$this->line('Stored import data...');
$job->configuration = $configurationData;
$job->status = 'configured';
$job->save();
$jobRepository->setConfiguration($job, $configurationData);
$jobRepository->updateStatus($job, 'configured');
$this->line('Stored configuration...');
if (true === $this->option('start')) {
@ -131,18 +133,26 @@ class CreateImport extends Command
$monolog->pushHandler($handler);
// start the actual routine:
/** @var ImportRoutine $routine */
$routine = app(ImportRoutine::class);
$type = 'csv' === $job->file_type ? 'file' : $job->file_type;
$key = sprintf('import.routine.%s', $type);
$className = config($key);
if (null === $className || !class_exists($className)) {
throw new FireflyException(sprintf('Cannot find import routine class for job of type "%s".', $type)); // @codeCoverageIgnore
}
/** @var RoutineInterface $routine */
$routine = app($className);
$routine->setJob($job);
$routine->run();
// give feedback.
/** @var MessageBag $error */
foreach ($routine->errors as $index => $error) {
foreach ($routine->getErrors() as $index => $error) {
$this->error(sprintf('Error importing line #%d: %s', $index, $error));
}
$this->line(
sprintf('The import has finished. %d transactions have been imported out of %d records.', $routine->journals->count(), $routine->lines)
sprintf(
'The import has finished. %d transactions have been imported out of %d records.', $routine->getJournals()->count(), $routine->getLines()
)
);
}
@ -166,7 +176,7 @@ class CreateImport extends Command
$configuration = $this->argument('configuration');
$user = $userRepository->find(intval($this->option('user')));
$cwd = getcwd();
$validTypes = array_keys(config('firefly.import_formats'));
$validTypes = config('import.options.file.import_formats');
$type = strtolower($this->option('type'));
if (null === $user->id) {
$this->error(sprintf('There is no user with ID %d.', $this->option('user')));

View File

@ -22,8 +22,9 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Import\Logging\CommandHandler;
use FireflyIII\Import\Routine\ImportRoutine;
use FireflyIII\Import\Routine\RoutineInterface;
use FireflyIII\Models\ImportJob;
use Illuminate\Console\Command;
use Illuminate\Support\MessageBag;
@ -58,6 +59,8 @@ class Import extends Command
/**
* Run the import routine.
*
* @throws FireflyException
*/
public function handle()
{
@ -81,17 +84,27 @@ class Import extends Command
$handler = new CommandHandler($this);
$monolog->pushHandler($handler);
/** @var ImportRoutine $routine */
$routine = app(ImportRoutine::class);
// actually start job:
$type = 'csv' === $job->file_type ? 'file' : $job->file_type;
$key = sprintf('import.routine.%s', $type);
$className = config($key);
if (null === $className || !class_exists($className)) {
throw new FireflyException(sprintf('Cannot find import routine class for job of type "%s".', $type)); // @codeCoverageIgnore
}
/** @var RoutineInterface $routine */
$routine = app($className);
$routine->setJob($job);
$routine->run();
/** @var MessageBag $error */
foreach ($routine->errors as $index => $error) {
foreach ($routine->getErrors() as $index => $error) {
$this->error(sprintf('Error importing line #%d: %s', $index, $error));
}
$this->line(sprintf('The import has finished. %d transactions have been imported out of %d records.', $routine->journals->count(), $routine->lines));
$this->line(
sprintf('The import has finished. %d transactions have been imported out of %d records.', $routine->getJournals()->count(), $routine->getLines())
);
return;
}

View File

@ -26,8 +26,6 @@ use DB;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\LimitRepetition;
use FireflyIII\Models\Note;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
@ -75,6 +73,8 @@ class UpgradeDatabase extends Command
/**
* Execute the console command.
*
* @throws \Exception
*/
public function handle()
{
@ -282,6 +282,8 @@ class UpgradeDatabase extends Command
/**
* Move all the journal_meta notes to their note object counter parts.
*
* @throws \Exception
*/
private function migrateNotes(): void
{

View File

@ -21,14 +21,6 @@
*/
declare(strict_types=1);
/**
* UseEncryption.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
namespace FireflyIII\Console\Commands;
use Illuminate\Console\Command;
@ -65,6 +57,12 @@ class UseEncryption extends Command
*/
public function handle()
{
if (config('firefly.encryption') === true) {
$this->info('Firefly III configuration calls for encrypted data.');
}
if (config('firefly.encryption') === false) {
$this->info('Firefly III configuration calls for unencrypted data.');
}
$this->handleObjects('Account', 'name', 'encrypted');
$this->handleObjects('Bill', 'name', 'name_encrypted');
$this->handleObjects('Bill', 'match', 'match_encrypted');
@ -84,7 +82,7 @@ class UseEncryption extends Command
public function handleObjects(string $class, string $field, string $indicator)
{
$fqn = sprintf('FireflyIII\Models\%s', $class);
$encrypt = config('firefly.encryption') ? 0 : 1;
$encrypt = config('firefly.encryption') === true ? 0 : 1;
$set = $fqn::where($indicator, $encrypt)->get();
foreach ($set as $entry) {

View File

@ -68,6 +68,7 @@ trait VerifiesAccessToken
}
if (!($accessToken->data === $token)) {
Log::error(sprintf('Invalid access token for user #%d.', $userId));
Log::error(sprintf('Token given is "%s", expected "%s".', $token, $accessToken->data));
return false;
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands;
use Crypt;
use DB;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
@ -93,6 +94,7 @@ class VerifyDatabase extends Command
$this->repairPiggyBanks();
$this->createLinkTypes();
$this->createAccessTokens();
$this->fixDoubleAmounts();
}
/**
@ -100,6 +102,7 @@ class VerifyDatabase extends Command
*/
private function createAccessTokens()
{
$count = 0;
$users = User::get();
/** @var User $user */
foreach ($users as $user) {
@ -108,8 +111,12 @@ class VerifyDatabase extends Command
$token = $user->generateAccessToken();
Preferences::setForUser($user, 'access_token', $token);
$this->line(sprintf('Generated access token for user %s', $user->email));
++$count;
}
}
if (0 === $count) {
$this->info('All access tokens OK!');
}
}
/**
@ -117,6 +124,7 @@ class VerifyDatabase extends Command
*/
private function createLinkTypes()
{
$count = 0;
$set = [
'Related' => ['relates to', 'relates to'],
'Refund' => ['(partially) refunds', 'is (partially) refunded by'],
@ -130,10 +138,64 @@ class VerifyDatabase extends Command
$link->name = $name;
$link->outward = $values[0];
$link->inward = $values[1];
++$count;
}
$link->editable = false;
$link->save();
}
if (0 === $count) {
$this->info('All link types OK!');
}
}
private function fixDoubleAmounts()
{
$count = 0;
// get invalid journals
$errored = [];
$journals = DB::table('transactions')
->groupBy('transaction_journal_id')
->get(['transaction_journal_id', DB::raw('SUM(amount) AS the_sum')]);
/** @var stdClass $entry */
foreach ($journals as $entry) {
if (0 !== bccomp(strval($entry->the_sum), '0')) {
$errored[] = $entry->transaction_journal_id;
}
}
foreach ($errored as $journalId) {
// select and update:
$res = Transaction::whereNull('deleted_at')->where('transaction_journal_id', $journalId)->groupBy('amount')->get([DB::raw('MIN(id) as first_id')]);
$ids = $res->pluck('first_id')->toArray();
DB::table('transactions')->whereIn('id', $ids)->update(['amount' => DB::raw('amount * -1')]);
++$count;
// report about it
/** @var TransactionJournal $journal */
$journal = TransactionJournal::find($journalId);
if (is_null($journal)) {
continue;
}
if (TransactionType::OPENING_BALANCE === $journal->transactionType->type) {
$this->error(
sprintf(
'Transaction #%d was stored incorrectly. One of your asset accounts may show the wrong balance. Please visit /transactions/show/%d to verify the opening balance.',
$journalId, $journalId
)
);
}
if (TransactionType::OPENING_BALANCE !== $journal->transactionType->type) {
$this->error(
sprintf(
'Transaction #%d was stored incorrectly. Could be that the transaction shows the wrong amount. Please visit /transactions/show/%d to verify the opening balance.',
$journalId, $journalId
)
);
}
}
if (0 === $count) {
$this->info('Amount integrity OK!');
}
return;
}
/**
@ -290,6 +352,7 @@ class VerifyDatabase extends Command
*/
private function reportJournals()
{
$count = 0;
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->whereNotNull('transaction_journals.deleted_at')// USE THIS
->whereNull('transactions.deleted_at')
@ -308,6 +371,10 @@ class VerifyDatabase extends Command
'Error: Transaction #' . $entry->transaction_id . ' should have been deleted, but has not.' .
' Find it in the table called "transactions" and change the "deleted_at" field to: "' . $entry->journal_deleted . '"'
);
++$count;
}
if (0 === $count) {
$this->info('No orphaned transactions!');
}
}
@ -316,6 +383,7 @@ class VerifyDatabase extends Command
*/
private function reportNoTransactions()
{
$count = 0;
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->groupBy('transaction_journals.id')
->whereNull('transactions.transaction_journal_id')
@ -325,6 +393,10 @@ class VerifyDatabase extends Command
$this->error(
'Error: Journal #' . $entry->id . ' has zero transactions. Open table "transaction_journals" and delete the entry with id #' . $entry->id
);
++$count;
}
if (0 === $count) {
$this->info('No orphaned journals!');
}
}
@ -379,6 +451,8 @@ class VerifyDatabase extends Command
$sum = strval($user->transactions()->sum('amount'));
if (0 !== bccomp($sum, '0')) {
$this->error('Error: Transactions for user #' . $user->id . ' (' . $user->email . ') are off by ' . $sum . '!');
} else {
$this->info(sprintf('Amount integrity OK for user #%d', $user->id));
}
}
}

View File

@ -20,15 +20,6 @@
*/
declare(strict_types=1);
/**
* Kernel.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
namespace FireflyIII\Console;
use Illuminate\Console\Scheduling\Schedule;

View File

@ -33,7 +33,13 @@ class AdminRequestedTestMessage extends Event
{
use SerializesModels;
/**
* @var string
*/
public $ipAddress;
/**
* @var User
*/
public $user;
/**

View File

@ -32,7 +32,13 @@ class RegisteredUser extends Event
{
use SerializesModels;
/**
* @var string
*/
public $ipAddress;
/**
* @var User
*/
public $user;
/**

View File

@ -32,8 +32,17 @@ class RequestedNewPassword extends Event
{
use SerializesModels;
/**
* @var string
*/
public $ipAddress;
/**
* @var string
*/
public $token;
/**
* @var User
*/
public $user;
/**

View File

@ -0,0 +1,52 @@
<?php
/**
* RequestedVersionCheckStatus.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
/**
* Class RequestedVersionCheckStatus
*/
class RequestedVersionCheckStatus extends Event
{
use SerializesModels;
/**
* @var User
*/
public $user;
/**
* Create a new event instance. This event is triggered when Firefly III wants to know
* what the deal is with the version checker.
*
* @param User $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
}

View File

@ -20,15 +20,6 @@
*/
declare(strict_types=1);
/**
* Handler.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
namespace FireflyIII\Exceptions;
use ErrorException;
@ -37,6 +28,9 @@ use FireflyIII\Jobs\MailError;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Request;
/**
* Class Handler
*/
class Handler extends ExceptionHandler
{
/**
@ -87,6 +81,8 @@ class Handler extends ExceptionHandler
* @param \Exception $exception
*
* @return mixed|void
*
* @throws Exception
*/
public function report(Exception $exception)
{

View File

@ -45,44 +45,131 @@ use FireflyIII\Models\Transaction;
final class Entry
{
// @formatter:off
/**
* @var int
*/
public $journal_id;
/**
* @var int
*/
public $transaction_id = 0;
/**
* @var string
*/
public $date;
/**
* @var string
*/
public $description;
/**
* @var string
*/
public $currency_code;
/**
* @var string
*/
public $amount;
/**
* @var string
*/
public $foreign_currency_code = '';
/**
* @var string
*/
public $foreign_amount = '0';
/**
* @var string
*/
public $transaction_type;
/**
* @var string
*/
public $asset_account_id;
/**
* @var string
*/
public $asset_account_name;
/**
* @var string
*/
public $asset_account_iban;
/**
* @var string
*/
public $asset_account_bic;
/**
* @var string
*/
public $asset_account_number;
/**
* @var string
*/
public $asset_currency_code;
/**
* @var string
*/
public $opposing_account_id;
/**
* @var string
*/
public $opposing_account_name;
/**
* @var string
*/
public $opposing_account_iban;
/**
* @var string
*/
public $opposing_account_bic;
/**
* @var string
*/
public $opposing_account_number;
/**
* @var string
*/
public $opposing_currency_code;
/**
* @var string
*/
public $budget_id;
/**
* @var string
*/
public $budget_name;
/**
* @var string
*/
public $category_id;
/**
* @var string
*/
public $category_name;
/**
* @var string
*/
public $bill_id;
/**
* @var string
*/
public $bill_name;
/**
* @var string
*/
public $notes;
/**
* @var string
*/
public $tags;
@ -116,7 +203,7 @@ final class Entry
$entry->description = $transaction->transaction_description . '(' . $transaction->description . ')';
}
$entry->currency_code = $transaction->transactionCurrency->code;
$entry->amount = round($transaction->transaction_amount, $transaction->transactionCurrency->decimal_places);
$entry->amount = strval(round($transaction->transaction_amount, $transaction->transactionCurrency->decimal_places));
$entry->foreign_currency_code = null === $transaction->foreign_currency_id ? null : $transaction->foreignCurrency->code;
$entry->foreign_amount = null === $transaction->foreign_currency_id
@ -129,14 +216,14 @@ final class Entry
);
$entry->transaction_type = $transaction->transaction_type_type;
$entry->asset_account_id = $transaction->account_id;
$entry->asset_account_id = strval($transaction->account_id);
$entry->asset_account_name = app('steam')->tryDecrypt($transaction->account_name);
$entry->asset_account_iban = $transaction->account_iban;
$entry->asset_account_number = $transaction->account_number;
$entry->asset_account_bic = $transaction->account_bic;
$entry->asset_currency_code = $transaction->account_currency_code;
$entry->opposing_account_id = $transaction->opposing_account_id;
$entry->opposing_account_id = strval($transaction->opposing_account_id);
$entry->opposing_account_name = app('steam')->tryDecrypt($transaction->opposing_account_name);
$entry->opposing_account_iban = $transaction->opposing_account_iban;
$entry->opposing_account_number = $transaction->opposing_account_number;
@ -144,7 +231,7 @@ final class Entry
$entry->opposing_currency_code = $transaction->opposing_currency_code;
// budget
$entry->budget_id = $transaction->transaction_budget_id;
$entry->budget_id = strval($transaction->transaction_budget_id);
$entry->budget_name = app('steam')->tryDecrypt($transaction->transaction_budget_name);
if (null === $transaction->transaction_budget_id) {
$entry->budget_id = $transaction->transaction_journal_budget_id;
@ -152,7 +239,7 @@ final class Entry
}
// category
$entry->category_id = $transaction->transaction_category_id;
$entry->category_id = strval($transaction->transaction_category_id);
$entry->category_name = app('steam')->tryDecrypt($transaction->transaction_category_name);
if (null === $transaction->transaction_category_id) {
$entry->category_id = $transaction->transaction_journal_category_id;
@ -160,7 +247,7 @@ final class Entry
}
// budget
$entry->bill_id = $transaction->bill_id;
$entry->bill_id = strval($transaction->bill_id);
$entry->bill_name = app('steam')->tryDecrypt($transaction->bill_name);
$entry->tags = $transaction->tags;

View File

@ -32,8 +32,8 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\ExportJob;
use FireflyIII\Models\Note;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournalMeta;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Support\Collection;
use Log;
@ -114,7 +114,7 @@ class ExpandedProcessor implements ProcessorInterface
$notes = $this->getNotes($ids);
$tags = $this->getTags($ids);
/** @var array $ibans */
$ibans = $this->getIbans($assetIds) + $this->getIbans($opposingIds);
$ibans = array_merge($this->getIbans($assetIds), $this->getIbans($opposingIds));
$currencies = $this->getAccountCurrencies($ibans);
$transactions->each(
function (Transaction $transaction) use ($notes, $tags, $ibans, $currencies) {
@ -173,6 +173,7 @@ class ExpandedProcessor implements ProcessorInterface
* @return bool
*
* @throws FireflyException
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function createZipFile(): bool
{
@ -309,17 +310,16 @@ class ExpandedProcessor implements ProcessorInterface
private function getNotes(array $array): array
{
$array = array_unique($array);
$set = TransactionJournalMeta::whereIn('journal_meta.transaction_journal_id', $array)
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
->where('transaction_journals.user_id', $this->job->user_id)
->where('journal_meta.name', 'notes')->get(
['journal_meta.transaction_journal_id', 'journal_meta.data', 'journal_meta.id']
);
$notes = Note::where('notes.noteable_type', 'FireflyIII\\Models\\TransactionJournal')
->whereIn('notes.noteable_id', $array)
->get(['notes.*']);
$return = [];
/** @var TransactionJournalMeta $meta */
foreach ($set as $meta) {
$id = intval($meta->transaction_journal_id);
$return[$id] = $meta->data;
/** @var Note $note */
foreach ($notes as $note) {
if (strlen(trim(strval($note->text))) > 0) {
$id = intval($note->noteable_id);
$return[$id] = $note->text;
}
}
return $return;

View File

@ -24,7 +24,7 @@ namespace FireflyIII\Export\Exporter;
use FireflyIII\Export\Entry\Entry;
use League\Csv\Writer;
use SplFileObject;
use Storage;
/**
* Class CsvExporter.
@ -52,6 +52,8 @@ class CsvExporter extends BasicExporter implements ExporterInterface
/**
* @return bool
*
* @throws \TypeError
*/
public function run(): bool
{
@ -60,7 +62,10 @@ class CsvExporter extends BasicExporter implements ExporterInterface
// necessary for CSV writer:
$fullPath = storage_path('export') . DIRECTORY_SEPARATOR . $this->fileName;
$writer = Writer::createFromPath(new SplFileObject($fullPath, 'a+'), 'w');
//we create the CSV into memory
$writer = Writer::createFromPath($fullPath);
$rows = [];
// get field names for header row:
@ -88,5 +93,8 @@ class CsvExporter extends BasicExporter implements ExporterInterface
private function tempFile()
{
$this->fileName = $this->job->key . '-records.csv';
// touch file in export directory:
$disk = Storage::disk('export');
$disk->put($this->fileName, '');
}
}

View File

@ -0,0 +1,146 @@
<?php
/**
* MonthReportGenerator.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Generator\Report\Account;
use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface;
use Illuminate\Support\Collection;
/**
* Class MonthReportGenerator.
*/
class MonthReportGenerator implements ReportGeneratorInterface
{
/** @var Collection */
private $accounts;
/** @var Carbon */
private $end;
/** @var Collection */
private $expense;
/** @var Carbon */
private $start;
/**
* @return string
*
* @throws \Throwable
*/
public function generate(): string
{
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
$expenseIds = join(',', $this->expense->pluck('id')->toArray());
$reportType = 'account';
$preferredPeriod = $this->preferredPeriod();
return view(
'reports.account.report',
compact('accountIds', 'reportType', 'expenseIds', 'preferredPeriod')
)->with('start', $this->start)->with('end', $this->end)->render();
}
/**
* @param Collection $accounts
*
* @return ReportGeneratorInterface
*/
public function setAccounts(Collection $accounts): ReportGeneratorInterface
{
$this->accounts = $accounts;
return $this;
}
/**
* @param Collection $budgets
*
* @return ReportGeneratorInterface
*/
public function setBudgets(Collection $budgets): ReportGeneratorInterface
{
return $this;
}
/**
* @param Collection $categories
*
* @return ReportGeneratorInterface
*/
public function setCategories(Collection $categories): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setEndDate(Carbon $date): ReportGeneratorInterface
{
$this->end = $date;
return $this;
}
/**
* @param Collection $expense
*
* @return ReportGeneratorInterface
*/
public function setExpense(Collection $expense): ReportGeneratorInterface
{
$this->expense = $expense;
return $this;
}
/**
* @param Carbon $date
*
* @return ReportGeneratorInterface
*/
public function setStartDate(Carbon $date): ReportGeneratorInterface
{
$this->start = $date;
return $this;
}
/**
* @param Collection $tags
*
* @return ReportGeneratorInterface
*/
public function setTags(Collection $tags): ReportGeneratorInterface
{
return $this;
}
/**
* @return string
*/
protected function preferredPeriod(): string
{
return 'day';
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* MultiYearReportGenerator.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Generator\Report\Account;
/**
* Class MultiYearReportGenerator.
*/
class MultiYearReportGenerator extends MonthReportGenerator
{
// Doesn't do anything different.
/**
* @return string
*/
protected function preferredPeriod(): string
{
return 'year';
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* YearReportGenerator.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Generator\Report\Account;
/**
* Class YearReportGenerator.
*/
class YearReportGenerator extends MonthReportGenerator
{
// Doesn't do anything different.
/**
* @return string
*/
protected function preferredPeriod(): string
{
return 'month';
}
}

View File

@ -119,6 +119,16 @@ class MonthReportGenerator implements ReportGeneratorInterface
return $this;
}
/**
* @param Collection $expense
*
* @return ReportGeneratorInterface
*/
public function setExpense(Collection $expense): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*

View File

@ -128,6 +128,16 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
return $this;
}
/**
* @param Collection $expense
*
* @return ReportGeneratorInterface
*/
public function setExpense(Collection $expense): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*

View File

@ -146,6 +146,16 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
return $this;
}
/**
* @param Collection $expense
*
* @return ReportGeneratorInterface
*/
public function setExpense(Collection $expense): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*

View File

@ -63,6 +63,13 @@ interface ReportGeneratorInterface
*/
public function setEndDate(Carbon $date): ReportGeneratorInterface;
/**
* @param Collection $expense
*
* @return ReportGeneratorInterface
*/
public function setExpense(Collection $expense): ReportGeneratorInterface;
/**
* @param Carbon $date
*

View File

@ -41,6 +41,8 @@ class MonthReportGenerator implements ReportGeneratorInterface
/**
* @return string
*
* @throws \Throwable
*/
public function generate(): string
{
@ -101,6 +103,16 @@ class MonthReportGenerator implements ReportGeneratorInterface
return $this;
}
/**
* @param Collection $expense
*
* @return ReportGeneratorInterface
*/
public function setExpense(Collection $expense): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*

View File

@ -40,6 +40,8 @@ class MultiYearReportGenerator implements ReportGeneratorInterface
/**
* @return string
*
* @throws \Throwable
*/
public function generate(): string
{
@ -98,6 +100,16 @@ class MultiYearReportGenerator implements ReportGeneratorInterface
return $this;
}
/**
* @param Collection $expense
*
* @return ReportGeneratorInterface
*/
public function setExpense(Collection $expense): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*

View File

@ -40,6 +40,8 @@ class YearReportGenerator implements ReportGeneratorInterface
/**
* @return string
*
* @throws \Throwable
*/
public function generate(): string
{
@ -98,6 +100,16 @@ class YearReportGenerator implements ReportGeneratorInterface
return $this;
}
/**
* @param Collection $expense
*
* @return ReportGeneratorInterface
*/
public function setExpense(Collection $expense): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*

View File

@ -65,6 +65,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
/**
* @return string
* @throws \Throwable
*/
public function generate(): string
{
@ -141,6 +142,16 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
return $this;
}
/**
* @param Collection $expense
*
* @return ReportGeneratorInterface
*/
public function setExpense(Collection $expense): ReportGeneratorInterface
{
return $this;
}
/**
* @param Carbon $date
*

View File

@ -76,6 +76,7 @@ class UserEventHandler
{
Log::debug('In checkSingleUserIsAdmin');
/** @var User $user */
$user = $event->user;
$count = User::count();
@ -86,7 +87,7 @@ class UserEventHandler
return true;
}
// user is only user but has admin role
if ($count === 1 && $user->hasRole('owner')) {
if (1 === $count && $user->hasRole('owner')) {
Log::debug(sprintf('User #%d is only user but has role owner so all is well.', $user->id));
return true;

View File

@ -0,0 +1,78 @@
<?php
/**
* VersionCheckEventHandler.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use FireflyConfig;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\User;
use Log;
/**
* Class VersionCheckEventHandler
*/
class VersionCheckEventHandler
{
/**
* @param RequestedVersionCheckStatus $event
*/
public function checkForUpdates(RequestedVersionCheckStatus $event)
{
// in Sandstorm, cannot check for updates:
$sandstorm = 1 === intval(getenv('SANDSTORM'));
if ($sandstorm === true) {
return;
}
/** @var User $user */
$user = $event->user;
if (!$user->hasRole('owner')) {
return;
}
$permission = FireflyConfig::get('permission_update_check', -1);
$lastCheckTime = FireflyConfig::get('last_update_check', time());
$now = time();
if ($now - $lastCheckTime->data < 604800) {
Log::debug('Checked for updates less than a week ago.');
return;
}
// last check time was more than a week ago.
Log::debug('Have not checked for a new version in a week!');
// have actual permission?
if ($permission->data === -1) {
// never asked before.
session()->flash('info', strval(trans('firefly.check_for_updates_permission', ['link' => route('admin.update-check')])));
return;
}
// actually check for update and inform the user.
}
}

View File

@ -32,8 +32,17 @@ use Illuminate\Support\Collection;
*/
class BalanceLine
{
/**
*
*/
const ROLE_DEFAULTROLE = 1;
/**
*
*/
const ROLE_TAGROLE = 2;
/**
*
*/
const ROLE_DIFFROLE = 3;
/** @var Collection */

View File

@ -41,40 +41,14 @@ class BillLine
/** @var string */
protected $min;
/** @var Carbon */
private $endOfPayDate;
/** @var Carbon */
private $lastHitDate;
/** @var Carbon */
private $payDate;
/** @var Carbon */
private $endOfPayDate;
/** @var int */
private $transactionJournalId;
/**
* @return Carbon
*/
public function getPayDate(): Carbon
{
return $this->payDate;
}
/**
* @return Carbon
*/
public function getEndOfPayDate(): Carbon
{
return $this->endOfPayDate;
}
/**
* @param Carbon $endOfPayDate
*/
public function setEndOfPayDate(Carbon $endOfPayDate): void
{
$this->endOfPayDate = $endOfPayDate;
}
/**
* BillLine constructor.
*/
@ -115,6 +89,22 @@ class BillLine
$this->bill = $bill;
}
/**
* @return Carbon
*/
public function getEndOfPayDate(): Carbon
{
return $this->endOfPayDate;
}
/**
* @param Carbon $endOfPayDate
*/
public function setEndOfPayDate(Carbon $endOfPayDate): void
{
$this->endOfPayDate = $endOfPayDate;
}
/**
* @return Carbon
*/
@ -163,6 +153,22 @@ class BillLine
$this->min = $min;
}
/**
* @return Carbon
*/
public function getPayDate(): Carbon
{
return $this->payDate;
}
/**
* @param Carbon $payDate
*/
public function setPayDate(Carbon $payDate): void
{
$this->payDate = $payDate;
}
/**
* @return int
*/
@ -202,12 +208,4 @@ class BillLine
{
$this->hit = $hit;
}
/**
* @param Carbon $payDate
*/
public function setPayDate(Carbon $payDate): void
{
$this->payDate = $payDate;
}
}

View File

@ -497,6 +497,20 @@ class JournalCollector implements JournalCollectorInterface
return $this;
}
/**
* @param Collection $accounts
*
* @return JournalCollectorInterface
*/
public function setOpposingAccounts(Collection $accounts): JournalCollectorInterface
{
$this->withOpposingAccount();
$this->query->whereIn('opposing.account_id', $accounts->pluck('id')->toArray());
return $this;
}
/**
* @param int $page
*

View File

@ -160,6 +160,13 @@ interface JournalCollectorInterface
*/
public function setOffset(int $offset): JournalCollectorInterface;
/**
* @param Collection $accounts
*
* @return JournalCollectorInterface
*/
public function setOpposingAccounts(Collection $accounts): JournalCollectorInterface;
/**
* @param int $page
*
@ -196,6 +203,11 @@ interface JournalCollectorInterface
*/
public function setTypes(array $types): JournalCollectorInterface;
/**
* @param User $user
*
* @return mixed
*/
public function setUser(User $user);
/**

View File

@ -37,6 +37,11 @@ class AmountFilter implements FilterInterface
/** @var int */
private $modifier = 0;
/**
* AmountFilter constructor.
*
* @param int $modifier
*/
public function __construct(int $modifier)
{
$this->modifier = $modifier;

View File

@ -24,6 +24,9 @@ namespace FireflyIII\Helpers\Filter;
use Illuminate\Support\Collection;
/**
* Interface FilterInterface
*/
interface FilterInterface
{
/**

View File

@ -34,6 +34,9 @@ use Route;
*/
class Help implements HelpInterface
{
/**
*
*/
const CACHEKEY = 'help_%s_%s';
/** @var string */
protected $userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36';

View File

@ -79,7 +79,7 @@ class ReportHelper implements ReportHelperInterface
/** @var Bill $bill */
foreach ($bills as $bill) {
$expectedDates = $repository->getPayDatesInRange($bill, $start, $end);
foreach($expectedDates as $payDate) {
foreach ($expectedDates as $payDate) {
$endOfPayPeriod = app('navigation')->endOfX($payDate, $bill->repeat_freq, null);
$collector = app(JournalCollectorInterface::class);

View File

@ -37,14 +37,13 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Navigation;
use Preferences;
use Response;
use Session;
use View;
/**
* Class ReconcileController.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ReconcileController extends Controller
@ -59,8 +58,8 @@ class ReconcileController extends Controller
// translations:
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-credit-card');
View::share('title', trans('firefly.accounts'));
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', trans('firefly.accounts'));
return $next($request);
}
@ -74,7 +73,7 @@ class ReconcileController extends Controller
*/
public function edit(TransactionJournal $journal)
{
if ($journal->transactionType->type !== TransactionType::RECONCILIATION) {
if (TransactionType::RECONCILIATION !== $journal->transactionType->type) {
return redirect(route('transactions.edit', [$journal->id]));
}
// view related code
@ -90,8 +89,6 @@ class ReconcileController extends Controller
];
Session::flash('preFilled', $preFilled);
Session::flash('gaEventCategory', 'transactions');
Session::flash('gaEventAction', 'edit-reconciliation');
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('reconcile.edit.fromUpdate')) {
@ -103,7 +100,6 @@ class ReconcileController extends Controller
'accounts.reconcile.edit',
compact('journal', 'subTitle')
)->with('data', $preFilled);
}
/**
@ -115,6 +111,7 @@ class ReconcileController extends Controller
* @return \Illuminate\Http\JsonResponse
*
* @throws FireflyException
* @throws \Throwable
*/
public function overview(Request $request, Account $account, Carbon $start, Carbon $end)
{
@ -151,7 +148,13 @@ class ReconcileController extends Controller
$diffCompare = bccomp($difference, '0');
$return = [
'post_uri' => $route,
'html' => view('accounts.reconcile.overview', compact('account', 'start', 'diffCompare', 'difference', 'end', 'clearedIds', 'transactionIds', 'clearedAmount', 'startBalance', 'endBalance', 'amount', 'route', 'countCleared'))->render(),
'html' => view(
'accounts.reconcile.overview', compact(
'account', 'start', 'diffCompare', 'difference', 'end', 'clearedIds', 'transactionIds', 'clearedAmount',
'startBalance', 'endBalance', 'amount',
'route', 'countCleared'
)
)->render(),
];
return Response::json($return);
@ -163,6 +166,8 @@ class ReconcileController extends Controller
* @param Carbon|null $end
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
*
* @throws FireflyException
*/
public function reconcile(Account $account, Carbon $start = null, Carbon $end = null)
{
@ -179,7 +184,7 @@ class ReconcileController extends Controller
$currencyId = intval($account->getMeta('currency_id'));
$currency = $currencyRepos->find($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency();
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
}
// no start or end:
@ -187,11 +192,11 @@ class ReconcileController extends Controller
// get start and end
if (null === $start && null === $end) {
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
}
if (null === $end) {
$end = Navigation::endOfPeriod($start, $range);
$end = app('navigation')->endOfPeriod($start, $range);
}
$startDate = clone $start;
@ -206,7 +211,12 @@ class ReconcileController extends Controller
$overviewUri = route('accounts.reconcile.overview', [$account->id, '%start%', '%end%']);
$indexUri = route('accounts.reconcile', [$account->id, '%start%', '%end%']);
return view('accounts.reconcile.index', compact('account', 'currency', 'subTitleIcon', 'start', 'end', 'subTitle', 'startBalance', 'endBalance', 'transactionsUri', 'overviewUri', 'indexUri'));
return view(
'accounts.reconcile.index', compact(
'account', 'currency', 'subTitleIcon', 'start', 'end', 'subTitle', 'startBalance', 'endBalance', 'transactionsUri',
'overviewUri', 'indexUri'
)
);
}
/**
@ -217,16 +227,16 @@ class ReconcileController extends Controller
*/
public function show(JournalRepositoryInterface $repository, TransactionJournal $journal)
{
if ($journal->transactionType->type !== TransactionType::RECONCILIATION) {
if (TransactionType::RECONCILIATION !== $journal->transactionType->type) {
return redirect(route('transactions.show', [$journal->id]));
}
$subTitle = trans('firefly.reconciliation') . ' "' . $journal->description . '"';
// get main transaction:
$transaction = $repository->getAssetTransaction($journal);
$account = $transaction->account;
return view('accounts.reconcile.show', compact('journal', 'subTitle', 'transaction'));
return view('accounts.reconcile.show', compact('journal', 'subTitle', 'transaction', 'account'));
}
/**
@ -241,7 +251,7 @@ class ReconcileController extends Controller
{
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$transactions = $repository->getTransactionsById($request->get('transactions'));
$transactions = $repository->getTransactionsById($request->get('transactions') ?? []);
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$repository->reconcile($transaction); // mark as reconciled.
@ -287,6 +297,9 @@ class ReconcileController extends Controller
* @param Carbon $end
*
* @return mixed
*
* @throws \Throwable
* @throws FireflyException
*/
public function transactions(Account $account, Carbon $start, Carbon $end)
{
@ -302,7 +315,7 @@ class ReconcileController extends Controller
$currencyId = intval($account->getMeta('currency_id'));
$currency = $currencyRepos->find($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency();
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
}
$startBalance = round(app('steam')->balance($account, $startDate), $currency->decimal_places);
@ -334,10 +347,10 @@ class ReconcileController extends Controller
*/
public function update(ReconciliationFormRequest $request, AccountRepositoryInterface $repository, TransactionJournal $journal)
{
if ($journal->transactionType->type !== TransactionType::RECONCILIATION) {
if (TransactionType::RECONCILIATION !== $journal->transactionType->type) {
return redirect(route('transactions.show', [$journal->id]));
}
if (bccomp('0', $request->get('amount')) === 0) {
if (0 === bccomp('0', $request->get('amount'))) {
Session::flash('error', trans('firefly.amount_cannot_be_zero'));
return redirect(route('accounts.reconcile.edit', [$journal->id]))->withInput();
@ -356,10 +369,8 @@ class ReconcileController extends Controller
// redirect to previous URL.
return redirect($this->getPreviousUri('reconcile.edit.uri'));
}
/**
* @param Account $account
*
@ -372,7 +383,7 @@ class ReconcileController extends Controller
/** @var Transaction $transaction */
$transaction = $account->transactions()->first();
if (null === $transaction) {
throw new FireflyException('Expected a transaction. This account has none. BEEP, error.');
throw new FireflyException(sprintf('Expected a transaction. Account #%d has none. BEEP, error.', $account->id)); // @codeCoverageIgnore
}
$journal = $transaction->transactionJournal;

View File

@ -36,9 +36,9 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
use Navigation;
use Preferences;
use Steam;
use View;
@ -60,8 +60,8 @@ class AccountController extends Controller
// translations:
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-credit-card');
View::share('title', trans('firefly.accounts'));
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', trans('firefly.accounts'));
return $next($request);
}
@ -96,20 +96,17 @@ class AccountController extends Controller
$this->rememberPreviousUri('accounts.create.uri');
}
$request->session()->forget('accounts.create.fromStore');
$request->session()->flash('gaEventCategory', 'accounts');
$request->session()->flash('gaEventAction', 'create-' . $what);
return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'currencySelectList', 'allCurrencies', 'roles'));
}
/**
* @param Request $request
* @param AccountRepositoryInterface $repository
* @param Account $account
*
* @return View
*/
public function delete(Request $request, AccountRepositoryInterface $repository, Account $account)
public function delete(AccountRepositoryInterface $repository, Account $account)
{
$typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$subTitle = trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]);
@ -118,8 +115,6 @@ class AccountController extends Controller
// put previous url in session
$this->rememberPreviousUri('accounts.delete.uri');
$request->session()->flash('gaEventCategory', 'accounts');
$request->session()->flash('gaEventAction', 'delete-' . $typeName);
return view('accounts.delete', compact('account', 'subTitle', 'accountList'));
}
@ -156,6 +151,10 @@ class AccountController extends Controller
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*
* @return View
*
* @throws FireflyException
* @throws FireflyException
* @throws FireflyException
*/
public function edit(Request $request, Account $account)
{
@ -198,8 +197,6 @@ class AccountController extends Controller
'currency_id' => $currency->id,
];
$request->session()->flash('preFilled', $preFilled);
$request->session()->flash('gaEventCategory', 'accounts');
$request->session()->flash('gaEventAction', 'edit-' . $what);
return view(
'accounts.edit',
@ -218,18 +215,24 @@ class AccountController extends Controller
}
/**
* @param Request $request
* @param AccountRepositoryInterface $repository
* @param string $what
*
* @return View
*/
public function index(AccountRepositoryInterface $repository, string $what)
public function index(Request $request, AccountRepositoryInterface $repository, string $what)
{
$what = $what ?? 'asset';
$subTitle = trans('firefly.' . $what . '_accounts');
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$types = config('firefly.accountTypesByIdentifier.' . $what);
$accounts = $repository->getAccountsByType($types);
$collection = $repository->getAccountsByType($types);
$total = $collection->count();
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$accounts = $collection->slice(($page - 1) * $pageSize, $pageSize);
unset($collection);
/** @var Carbon $start */
$start = clone session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
@ -250,7 +253,11 @@ class AccountController extends Controller
}
);
return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts'));
// make paginator:
$accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page);
$accounts->setPath(route('accounts.index', [$what]));
return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'page', 'accounts'));
}
/**
@ -265,6 +272,8 @@ class AccountController extends Controller
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // long and complex but not that excessively so.
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*
* @throws FireflyException
*/
public function show(Request $request, JournalRepositoryInterface $repository, Account $account, string $moment = '')
{
@ -276,7 +285,7 @@ class AccountController extends Controller
$range = Preferences::get('viewRange', '1M')->data;
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$chartUri = route('chart.account.single', [$account->id]);
$start = null;
$end = null;
@ -299,7 +308,7 @@ class AccountController extends Controller
// prep for "specific date" view.
if (strlen($moment) > 0 && 'all' !== $moment) {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
$end = app('navigation')->endOfPeriod($start, $range);
$fStart = $start->formatLocalized($this->monthAndDayFormat);
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
$subTitle = trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
@ -309,8 +318,8 @@ class AccountController extends Controller
// prep for current period view
if (0 === strlen($moment)) {
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$fStart = $start->formatLocalized($this->monthAndDayFormat);
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
$subTitle = trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
@ -423,8 +432,8 @@ class AccountController extends Controller
$repository = app(AccountRepositoryInterface::class);
$start = $repository->oldestJournalDate($account);
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfX(new Carbon, $range, null);
$start = app('navigation')->startOfPeriod($start, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
$count = 0;
// properties for cache
@ -440,8 +449,8 @@ class AccountController extends Controller
Log::debug('Going to get period expenses and incomes.');
while ($end >= $start && $count < 90) {
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
// try a collector for income:
/** @var JournalCollectorInterface $collector */
@ -455,7 +464,7 @@ class AccountController extends Controller
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)->setTypes([TransactionType::WITHDRAWAL])->withOpposingAccount();
$spent = strval($collector->getJournals()->sum('transaction_amount'));
$dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range);
$dateName = app('navigation')->periodShow($end, $range);
$entries->push(
[
'string' => $dateStr,
@ -464,7 +473,7 @@ class AccountController extends Controller
'earned' => $earned,
'date' => clone $end,]
);
$end = Navigation::subtractPeriod($end, $range, 1);
$end = app('navigation')->subtractPeriod($end, $range, 1);
++$count;
}
$cache->store($entries);

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Middleware\IsSandStormUser;
use FireflyIII\Http\Requests\ConfigurationRequest;
use FireflyIII\Support\Facades\FireflyConfig;
use Preferences;
@ -44,12 +46,14 @@ class ConfigurationController extends Controller
$this->middleware(
function ($request, $next) {
View::share('title', strval(trans('firefly.administration')));
View::share('mainTitleIcon', 'fa-hand-spock-o');
app('view')->share('title', strval(trans('firefly.administration')));
app('view')->share('mainTitleIcon', 'fa-hand-spock-o');
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['index']);
$this->middleware(IsSandStormUser::class);
}
/**

View File

@ -24,6 +24,8 @@ namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Events\AdminRequestedTestMessage;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Middleware\IsSandStormUser;
use Illuminate\Http\Request;
use Log;
use Session;
@ -33,6 +35,16 @@ use Session;
*/
class HomeController extends Controller
{
/**
* ConfigurationController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(IsDemoUser::class)->except(['index']);
$this->middleware(IsSandStormUser::class)->except(['index']);
}
/**
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Requests\LinkTypeFormRequest;
use FireflyIII\Models\LinkType;
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
@ -44,12 +45,13 @@ class LinkController extends Controller
$this->middleware(
function ($request, $next) {
View::share('title', strval(trans('firefly.administration')));
View::share('mainTitleIcon', 'fa-hand-spock-o');
app('view')->share('title', strval(trans('firefly.administration')));
app('view')->share('mainTitleIcon', 'fa-hand-spock-o');
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['index', 'show']);
}
/**
@ -204,6 +206,13 @@ class LinkController extends Controller
return redirect($this->getPreviousUri('link_types.create.uri'));
}
/**
* @param LinkTypeFormRequest $request
* @param LinkTypeRepositoryInterface $repository
* @param LinkType $linkType
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function update(LinkTypeFormRequest $request, LinkTypeRepositoryInterface $repository, LinkType $linkType)
{
if (!$linkType->editable) {

View File

@ -0,0 +1,142 @@
<?php
/**
* UpdateController.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyConfig;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Middleware\IsSandStormUser;
use FireflyIII\Services\Github\Object\Release;
use FireflyIII\Services\Github\Request\UpdateRequest;
use Illuminate\Http\Request;
use Log;
use Response;
use Session;
/**
* Class HomeController.
*/
class UpdateController extends Controller
{
/**
* ConfigurationController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
app('view')->share('title', strval(trans('firefly.administration')));
app('view')->share('mainTitleIcon', 'fa-hand-spock-o');
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['index']);
$this->middleware(IsSandStormUser::class)->except(['index']);
}
/**
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Container\EntryNotFoundException
*/
public function index()
{
$subTitle = trans('firefly.update_check_title');
$subTitleIcon = 'fa-star';
$permission = app('fireflyconfig')->get('permission_update_check', -1);
$selected = $permission->data;
$options = [
'-1' => trans('firefly.updates_ask_me_later'),
'0' => trans('firefly.updates_do_not_check'),
'1' => trans('firefly.updates_enable_check'),
];
return view('admin.update.index', compact('subTitle', 'subTitleIcon', 'selected', 'options'));
}
/**
* @param Request $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function post(Request $request)
{
$checkForUpdates = intval($request->get('check_for_updates'));
FireflyConfig::set('permission_update_check', $checkForUpdates);
FireflyConfig::set('last_update_check', time());
Session::flash('success', strval(trans('firefly.configuration_updated')));
return redirect(route('admin.update-check'));
}
/**
* Does a manual update check.
*/
public function updateCheck()
{
$current = config('firefly.version');
/** @var UpdateRequest $request */
$request = app(UpdateRequest::class);
$check = -2;
$first = new Release(['id' => '0', 'title' => '0', 'updated' => '2017-01-01', 'content' => '']);
$string = '';
try {
$request->call();
$releases = $request->getReleases();
// first entry should be the latest entry:
/** @var Release $first */
$first = reset($releases);
$check = version_compare($current, $first->getTitle());
FireflyConfig::set('last_update_check', time());
} catch (FireflyException $e) {
Log::error(sprintf('Could not check for updates: %s', $e->getMessage()));
}
if ($check === -2) {
$string = strval(trans('firefly.update_check_error'));
}
if ($check === -1) {
// there is a new FF version!
$string = strval(
trans(
'firefly.update_new_version_alert',
['your_version' => $current, 'new_version' => $first->getTitle(), 'date' => $first->getUpdated()->formatLocalized($this->monthAndDayFormat)]
)
);
}
if ($check === 0) {
// you are running the current version!
$string = strval(trans('firefly.update_current_version_alert', ['version' => $current]));
}
if ($check === 1) {
// you are running a newer version!
$string = strval(trans('firefly.update_newer_version_alert', ['your_version' => $current, 'new_version' => $first->getTitle()]));
}
return Response::json(['result' => $string]);
}
}

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Middleware\IsSandStormUser;
use FireflyIII\Http\Requests\UserFormRequest;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
@ -45,12 +47,14 @@ class UserController extends Controller
$this->middleware(
function ($request, $next) {
View::share('title', strval(trans('firefly.administration')));
View::share('mainTitleIcon', 'fa-hand-spock-o');
app('view')->share('title', strval(trans('firefly.administration')));
app('view')->share('mainTitleIcon', 'fa-hand-spock-o');
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['index', 'show']);
$this->middleware(IsSandStormUser::class);
}
/**

View File

@ -50,8 +50,8 @@ class AttachmentController extends Controller
// translations:
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-paperclip');
View::share('title', trans('firefly.attachments'));
app('view')->share('mainTitleIcon', 'fa-paperclip');
app('view')->share('title', trans('firefly.attachments'));
return $next($request);
}
@ -59,19 +59,16 @@ class AttachmentController extends Controller
}
/**
* @param Request $request
* @param Attachment $attachment
*
* @return View
*/
public function delete(Request $request, Attachment $attachment)
public function delete(Attachment $attachment)
{
$subTitle = trans('firefly.delete_attachment', ['name' => $attachment->filename]);
// put previous url in session
$this->rememberPreviousUri('attachments.delete.uri');
$request->session()->flash('gaEventCategory', 'attachments');
$request->session()->flash('gaEventAction', 'delete-attachment');
return view('attachments.delete', compact('attachment', 'subTitle'));
}
@ -151,6 +148,8 @@ class AttachmentController extends Controller
* @param Attachment $attachment
*
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function preview(Attachment $attachment)
{

View File

@ -20,20 +20,14 @@
*/
declare(strict_types=1);
/**
* ForgotPasswordController.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
namespace FireflyIII\Http\Controllers\Auth;
use FireflyIII\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
/**
* Class ForgotPasswordController
*/
class ForgotPasswordController extends Controller
{
/*

View File

@ -20,15 +20,6 @@
*/
declare(strict_types=1);
/**
* LoginController.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
namespace FireflyIII\Http\Controllers\Auth;
use FireflyConfig;
@ -40,19 +31,16 @@ use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Schema;
/**
* @codeCoverageIgnore
* Class LoginController
*
* This controller handles authenticating users for the application and
* redirecting them to your home screen. The controller uses a trait
* to conveniently provide its functionality to your applications.
*/
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
@ -76,7 +64,9 @@ class LoginController extends Controller
*
* @param Request $request
*
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response|void
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response
*
* @throws \Illuminate\Validation\ValidationException
*/
public function login(Request $request)
{
@ -112,7 +102,7 @@ class LoginController extends Controller
* @param Request $request
* @param CookieJar $cookieJar
*
* @return $this
* @return $this|\Illuminate\Http\RedirectResponse
*/
public function logout(Request $request, CookieJar $cookieJar)
{

View File

@ -20,15 +20,6 @@
*/
declare(strict_types=1);
/**
* RegisterController.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
namespace FireflyIII\Http\Controllers\Auth;
use FireflyConfig;
@ -40,19 +31,16 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Session;
/**
* @codeCoverageIgnore
* Class RegisterController
*
* This controller handles the registration of new users as well as their
* validation and creation. By default this controller uses a trait to
* provide this functionality without requiring any additional code.
*/
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**

View File

@ -20,33 +20,21 @@
*/
declare(strict_types=1);
/**
* ResetPasswordController.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
namespace FireflyIII\Http\Controllers\Auth;
use FireflyIII\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
/**
* @codeCoverageIgnore
* Class ResetPasswordController
*
* This controller is responsible for handling password reset requests
* and uses a simple trait to include this behavior. You're free to
* explore this trait and override any methods you wish to tweak.
*/
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**

View File

@ -69,8 +69,6 @@ class TwoFactorController extends Controller
/**
* @return mixed
*
* @throws FireflyException
*/
public function lostTwoFactor()
{

View File

@ -31,6 +31,7 @@ use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Preferences;
use URL;
@ -58,8 +59,8 @@ class BillController extends Controller
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.bills'));
View::share('mainTitleIcon', 'fa-calendar-o');
app('view')->share('title', trans('firefly.bills'));
app('view')->share('mainTitleIcon', 'fa-calendar-o');
$this->attachments = app(AttachmentHelperInterface::class);
return $next($request);
@ -85,24 +86,19 @@ class BillController extends Controller
$this->rememberPreviousUri('bills.create.uri');
}
$request->session()->forget('bills.create.fromStore');
$request->session()->flash('gaEventCategory', 'bills');
$request->session()->flash('gaEventAction', 'create');
return view('bills.create', compact('periods', 'subTitle'));
}
/**
* @param Request $request
* @param Bill $bill
*
* @return View
*/
public function delete(Request $request, Bill $bill)
public function delete(Bill $bill)
{
// put previous url in session
$this->rememberPreviousUri('bills.delete.uri');
$request->session()->flash('gaEventCategory', 'bills');
$request->session()->flash('gaEventAction', 'delete');
$subTitle = trans('firefly.delete_bill', ['name' => $bill->name]);
return view('bills.delete', compact('bill', 'subTitle'));
@ -162,8 +158,6 @@ class BillController extends Controller
$request->session()->flash('preFilled', $preFilled);
$request->session()->forget('bills.edit.fromUpdate');
$request->session()->flash('gaEventCategory', 'bills');
$request->session()->flash('gaEventAction', 'edit');
return view('bills.edit', compact('subTitle', 'periods', 'bill'));
}
@ -173,15 +167,19 @@ class BillController extends Controller
*
* @return View
*/
public function index(BillRepositoryInterface $repository)
public function index(Request $request, BillRepositoryInterface $repository)
{
/** @var Carbon $start */
$start = session('start');
/** @var Carbon $end */
$end = session('end');
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$collection = $repository->getBills();
$total = $collection->count();
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
$bills = $repository->getBills();
$bills->each(
$collection->each(
function (Bill $bill) use ($repository, $start, $end) {
// paid in this period?
$bill->paidDates = $repository->getPaidDatesInRange($bill, $start, $end);
@ -194,6 +192,9 @@ class BillController extends Controller
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, $lastPaidDate);
}
);
// paginate bills
$bills = new LengthAwarePaginator($collection, $total, $pageSize, $page);
$bills->setPath(route('bills.index'));
return view('bills.index', compact('bills'));
}
@ -240,7 +241,7 @@ class BillController extends Controller
$end = session('end');
$year = $date->year;
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$yearAverage = $repository->getYearAverage($bill, $date);
$overallAverage = $repository->getOverallAverage($bill);
@ -252,7 +253,6 @@ class BillController extends Controller
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('bills.show', [$bill->id]));
$bill->paidDates = $repository->getPaidDatesInRange($bill, $date, $end);
$bill->payDates = $repository->getPayDatesInRange($bill, $date, $end);
$lastPaidDate = $this->lastPaidDate($repository->getPaidDatesInRange($bill, $date, $end), $date);
@ -280,14 +280,13 @@ class BillController extends Controller
$request->session()->flash('success', strval(trans('firefly.stored_new_bill', ['name' => $bill->name])));
Preferences::mark();
/** @var array $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null;
$this->attachments->saveAttachmentsForModel($bill, $files);
// flash messages
if (count($this->attachments->getMessages()->get('attachments')) > 0) {
$request->session()->flash('info', $this->attachments->getMessages()->get('attachments'));
$request->session()->flash('info', $this->attachments->getMessages()->get('attachments')); // @codeCoverageIgnore
}
if (1 === intval($request->get('create_another'))) {
@ -323,7 +322,7 @@ class BillController extends Controller
// flash messages
if (count($this->attachments->getMessages()->get('attachments')) > 0) {
$request->session()->flash('info', $this->attachments->getMessages()->get('attachments'));
$request->session()->flash('info', $this->attachments->getMessages()->get('attachments')); // @codeCoverageIgnore
}
if (1 === intval($request->get('return_to_edit'))) {
@ -347,8 +346,8 @@ class BillController extends Controller
*/
private function lastPaidDate(Collection $dates, Carbon $default): Carbon
{
if ($dates->count() === 0) {
return $default;
if (0 === $dates->count()) {
return $default; // @codeCoverageIgnore
}
$latest = $dates->first();
/** @var Carbon $date */
@ -359,6 +358,5 @@ class BillController extends Controller
}
return $latest;
}
}

View File

@ -35,9 +35,9 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
use Navigation;
use Preferences;
use Response;
use View;
@ -64,8 +64,8 @@ class BudgetController extends Controller
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.budgets'));
View::share('mainTitleIcon', 'fa-tasks');
app('view')->share('title', trans('firefly.budgets'));
app('view')->share('mainTitleIcon', 'fa-tasks');
$this->repository = app(BudgetRepositoryInterface::class);
return $next($request);
@ -75,6 +75,7 @@ class BudgetController extends Controller
/**
* @param Request $request
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
*
* @return \Illuminate\Http\JsonResponse
@ -85,7 +86,7 @@ class BudgetController extends Controller
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
$budgetLimit = $this->repository->updateLimitAmount($budget, $start, $end, $amount);
if (bccomp($amount,'0') === 0) {
if (0 === bccomp($amount, '0')) {
$budgetLimit = null;
}
@ -111,27 +112,22 @@ class BudgetController extends Controller
$this->rememberPreviousUri('budgets.create.uri');
}
$request->session()->forget('budgets.create.fromStore');
$request->session()->flash('gaEventCategory', 'budgets');
$request->session()->flash('gaEventAction', 'create');
$subTitle = (string)trans('firefly.create_new_budget');
return view('budgets.create', compact('subTitle'));
}
/**
* @param Request $request
* @param Budget $budget
*
* @return View
*/
public function delete(Request $request, Budget $budget)
public function delete(Budget $budget)
{
$subTitle = trans('firefly.delete_budget', ['name' => $budget->name]);
// put previous url in session
$this->rememberPreviousUri('budgets.delete.uri');
$request->session()->flash('gaEventCategory', 'budgets');
$request->session()->flash('gaEventAction', 'delete');
return view('budgets.delete', compact('budget', 'subTitle'));
}
@ -167,8 +163,6 @@ class BudgetController extends Controller
$this->rememberPreviousUri('budgets.edit.uri');
}
$request->session()->forget('budgets.edit.fromUpdate');
$request->session()->flash('gaEventCategory', 'budgets');
$request->session()->flash('gaEventAction', 'edit');
return view('budgets.edit', compact('budget', 'subTitle'));
}
@ -181,17 +175,19 @@ class BudgetController extends Controller
* @SuppressWarnings(PHPMD.CyclomaticComplexity) complex because of while loop
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function index(string $moment = null)
public function index(Request $request, string $moment = null)
{
$range = Preferences::get('viewRange', '1M')->data;
$start = session('start', new Carbon);
$end = session('end', new Carbon);
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
// make date if present:
if (null !== $moment || 0 !== strlen(strval($moment))) {
try {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
$end = app('navigation')->endOfPeriod($start, $range);
} catch (Exception $e) {
// start and end are already defined.
}
@ -200,9 +196,11 @@ class BudgetController extends Controller
$next->addDay();
$prev = clone $start;
$prev->subDay();
$prev = Navigation::startOfPeriod($prev, $range);
$prev = app('navigation')->startOfPeriod($prev, $range);
$this->repository->cleanupBudgets();
$budgets = $this->repository->getActiveBudgets();
$total = $budgets->count();
$budgets = $budgets->slice(($page - 1) * $pageSize, $pageSize);
$inactive = $this->repository->getInactiveBudgets();
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
@ -212,15 +210,19 @@ class BudgetController extends Controller
$spent = array_sum(array_column($budgetInformation, 'spent'));
$budgeted = array_sum(array_column($budgetInformation, 'budgeted'));
// paginate budgets
$budgets = new LengthAwarePaginator($budgets, $total, $pageSize, $page);
$budgets->setPath(route('budgets.index'));
// select thing for last 12 periods:
$previousLoop = [];
$previousDate = clone $start;
$count = 0;
while ($count < 12) {
$previousDate->subDay();
$previousDate = Navigation::startOfPeriod($previousDate, $range);
$previousDate = app('navigation')->startOfPeriod($previousDate, $range);
$format = $previousDate->format('Y-m-d');
$previousLoop[$format] = Navigation::periodShow($previousDate, $range);
$previousLoop[$format] = app('navigation')->periodShow($previousDate, $range);
++$count;
}
@ -232,16 +234,16 @@ class BudgetController extends Controller
while ($count < 12) {
$format = $nextDate->format('Y-m-d');
$nextLoop[$format] = Navigation::periodShow($nextDate, $range);
$nextDate = Navigation::endOfPeriod($nextDate, $range);
$nextLoop[$format] = app('navigation')->periodShow($nextDate, $range);
$nextDate = app('navigation')->endOfPeriod($nextDate, $range);
++$count;
$nextDate->addDay();
}
// display info
$currentMonth = Navigation::periodShow($start, $range);
$nextText = Navigation::periodShow($next, $range);
$prevText = Navigation::periodShow($prev, $range);
$currentMonth = app('navigation')->periodShow($start, $range);
$nextText = app('navigation')->periodShow($next, $range);
$prevText = app('navigation')->periodShow($prev, $range);
return view(
'budgets.index',
@ -254,6 +256,7 @@ class BudgetController extends Controller
'prevText',
'periodStart',
'periodEnd',
'page',
'budgetInformation',
'inactive',
'budgets',
@ -282,10 +285,15 @@ class BudgetController extends Controller
$cache->addProperty($end);
$cache->addProperty('info-income');
Log::debug(sprintf('infoIncome start is %s', $start->format('Y-m-d')));
Log::debug(sprintf('infoIncome end is %s', $end->format('Y-m-d')));
if ($cache->has()) {
$result = $cache->get(); // @codeCoverageIgnore
// @codeCoverageIgnoreStart
$result = $cache->get();
return view('budgets.info', compact('result', 'begin', 'currentEnd'));
// @codeCoverageIgnoreEnd
}
$result = [
'available' => '0',
@ -294,18 +302,27 @@ class BudgetController extends Controller
];
$currency = app('amount')->getDefaultCurrency();
$range = Preferences::get('viewRange', '1M')->data;
$begin = Navigation::subtractPeriod($start, $range, 3);
$begin = app('navigation')->subtractPeriod($start, $range, 3);
Log::debug(sprintf('Range is %s', $range));
Log::debug(sprintf('infoIncome begin is %s', $begin->format('Y-m-d')));
// get average amount available.
$total = '0';
$count = 0;
$currentStart = clone $begin;
while ($currentStart < $start) {
$currentEnd = Navigation::endOfPeriod($currentStart, $range);
Log::debug(sprintf('Loop: currentStart is %s', $currentStart->format('Y-m-d')));
$currentEnd = app('navigation')->endOfPeriod($currentStart, $range);
$total = bcadd($total, $this->repository->getAvailableBudget($currency, $currentStart, $currentEnd));
$currentStart = Navigation::addPeriod($currentStart, $range, 0);
$currentStart = app('navigation')->addPeriod($currentStart, $range, 0);
++$count;
}
Log::debug('Loop end');
if (0 === $count) {
$count = 1;
}
$result['available'] = bcdiv($total, strval($count));
// amount earned in this period:
@ -360,7 +377,7 @@ class BudgetController extends Controller
// prep for "specific date" view.
if (strlen($moment) > 0 && 'all' !== $moment) {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
$end = app('navigation')->endOfPeriod($start, $range);
$subTitle = trans(
'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@ -370,8 +387,8 @@ class BudgetController extends Controller
// prep for current period
if (0 === strlen($moment)) {
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview();
$subTitle = trans(
'firefly.without_budget_between',
@ -380,7 +397,7 @@ class BudgetController extends Controller
}
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
@ -422,7 +439,7 @@ class BudgetController extends Controller
$start = session('first', Carbon::create()->startOfYear());
$end = new Carbon;
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$limits = $this->getLimits($budget, $start, $end);
$repetition = null;
// collector:
@ -453,7 +470,7 @@ class BudgetController extends Controller
}
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$subTitle = trans(
'firefly.budget_in_period',
[
@ -583,8 +600,8 @@ class BudgetController extends Controller
$first = $repository->first();
$start = $first->date ?? new Carbon;
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfX(new Carbon, $range, null);
$start = app('navigation')->startOfPeriod($start, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
$cache = new CacheProperties;
$cache->addProperty($start);
@ -597,8 +614,8 @@ class BudgetController extends Controller
Log::debug('Going to get period expenses and incomes.');
while ($end >= $start) {
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutBudget()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
@ -606,9 +623,9 @@ class BudgetController extends Controller
$sum = strval($set->sum('transaction_amount') ?? '0');
$journals = $set->count();
$dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range);
$dateName = app('navigation')->periodShow($end, $range);
$entries->push(['string' => $dateStr, 'name' => $dateName, 'count' => $journals, 'sum' => $sum, 'date' => clone $end]);
$end = Navigation::subtractPeriod($end, $range, 1);
$end = app('navigation')->subtractPeriod($end, $range, 1);
}
$cache->store($entries);

View File

@ -34,9 +34,9 @@ use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
use Navigation;
use Preferences;
use Steam;
use View;
@ -55,8 +55,8 @@ class CategoryController extends Controller
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.categories'));
View::share('mainTitleIcon', 'fa-bar-chart');
app('view')->share('title', trans('firefly.categories'));
app('view')->share('mainTitleIcon', 'fa-bar-chart');
return $next($request);
}
@ -74,27 +74,22 @@ class CategoryController extends Controller
$this->rememberPreviousUri('categories.create.uri');
}
$request->session()->forget('categories.create.fromStore');
$request->session()->flash('gaEventCategory', 'categories');
$request->session()->flash('gaEventAction', 'create');
$subTitle = trans('firefly.create_new_category');
return view('categories.create', compact('subTitle'));
}
/**
* @param Request $request
* @param Category $category
*
* @return View
*/
public function delete(Request $request, Category $category)
public function delete(Category $category)
{
$subTitle = trans('firefly.delete_category', ['name' => $category->name]);
// put previous url in session
$this->rememberPreviousUri('categories.delete.uri');
$request->session()->flash('gaEventCategory', 'categories');
$request->session()->flash('gaEventAction', 'delete');
return view('categories.delete', compact('category', 'subTitle'));
}
@ -132,8 +127,6 @@ class CategoryController extends Controller
$this->rememberPreviousUri('categories.edit.uri');
}
$request->session()->forget('categories.edit.fromUpdate');
$request->session()->flash('gaEventCategory', 'categories');
$request->session()->flash('gaEventAction', 'edit');
return view('categories.edit', compact('category', 'subTitle'));
}
@ -143,16 +136,24 @@ class CategoryController extends Controller
*
* @return View
*/
public function index(CategoryRepositoryInterface $repository)
public function index(Request $request, CategoryRepositoryInterface $repository)
{
$categories = $repository->getCategories();
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$collection = $repository->getCategories();
$total = $collection->count();
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
$categories->each(
$collection->each(
function (Category $category) use ($repository) {
$category->lastActivity = $repository->lastUseDate($category, new Collection);
}
);
// paginate categories
$categories = new LengthAwarePaginator($collection, $total, $pageSize, $page);
$categories->setPath(route('categories.index'));
return view('categories.index', compact('categories'));
}
@ -171,7 +172,7 @@ class CategoryController extends Controller
$end = null;
$periods = new Collection;
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
// prep for "all" view.
if ('all' === $moment) {
@ -184,7 +185,7 @@ class CategoryController extends Controller
// prep for "specific date" view.
if (strlen($moment) > 0 && 'all' !== $moment) {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
$end = app('navigation')->endOfPeriod($start, $range);
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@ -194,8 +195,8 @@ class CategoryController extends Controller
// prep for current period
if (0 === strlen($moment)) {
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$periods = $this->getNoCategoryPeriodOverview();
$subTitle = trans(
'firefly.without_category_between',
@ -228,11 +229,12 @@ class CategoryController extends Controller
$subTitle = $category->name;
$subTitleIcon = 'fa-bar-chart';
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$range = Preferences::get('viewRange', '1M')->data;
$start = null;
$end = null;
$periods = new Collection;
$path = route('categories.show', [$category->id]);
// prep for "all" view.
if ('all' === $moment) {
@ -241,26 +243,28 @@ class CategoryController extends Controller
/** @var Carbon $start */
$start = null === $first ? new Carbon : $first;
$end = new Carbon;
$path = route('categories.show', [$category->id, 'all']);
}
// prep for "specific date" view.
if (strlen($moment) > 0 && 'all' !== $moment) {
$start = new Carbon($moment);
$end = Navigation::endOfPeriod($start, $range);
$end = app('navigation')->endOfPeriod($start, $range);
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name,
'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat),]
);
$periods = $this->getPeriodOverview($category);
$path = route('categories.show', [$category->id, $moment]);
}
// prep for current period
if (0 === strlen($moment)) {
/** @var Carbon $start */
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
/** @var Carbon $end */
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview($category);
$subTitle = trans(
'firefly.journals_in_period_for_category',
@ -275,7 +279,7 @@ class CategoryController extends Controller
->setCategory($category)->withBudgetInformation()->withCategoryInformation();
$collector->removeFilter(InternalTransferFilter::class);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('categories.show', [$category->id]));
$transactions->setPath($path);
return view('categories.show', compact('category', 'moment', 'transactions', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end'));
}
@ -340,8 +344,8 @@ class CategoryController extends Controller
$first = $repository->first();
$start = $first->date ?? new Carbon;
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($start, $range);
$end = Navigation::endOfX(new Carbon, $range, null);
$start = app('navigation')->startOfPeriod($start, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
// properties for cache
@ -357,8 +361,8 @@ class CategoryController extends Controller
Log::debug(sprintf('Going to get period expenses and incomes between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d')));
while ($end >= $start) {
Log::debug('Loop!');
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
// count journals without category in this period:
/** @var JournalCollectorInterface $collector */
@ -389,7 +393,7 @@ class CategoryController extends Controller
$earned = $collector->getJournals()->sum('transaction_amount');
$dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range);
$dateName = app('navigation')->periodShow($end, $range);
$entries->push(
[
'string' => $dateStr,
@ -401,7 +405,7 @@ class CategoryController extends Controller
'date' => clone $end,
]
);
$end = Navigation::subtractPeriod($end, $range, 1);
$end = app('navigation')->subtractPeriod($end, $range, 1);
}
Log::debug('End of loops');
$cache->store($entries);
@ -423,11 +427,11 @@ class CategoryController extends Controller
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$first = $repository->firstUseDate($category);
if (null === $first) {
$first = new Carbon;
$first = new Carbon; // @codeCoverageIgnore
}
$range = Preferences::get('viewRange', '1M')->data;
$first = Navigation::startOfPeriod($first, $range);
$end = Navigation::endOfX(new Carbon, $range, null);
$first = app('navigation')->startOfPeriod($first, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
$count = 0;
@ -442,12 +446,12 @@ class CategoryController extends Controller
return $cache->get(); // @codeCoverageIgnore
}
while ($end >= $first && $count < 90) {
$end = Navigation::startOfPeriod($end, $range);
$currentEnd = Navigation::endOfPeriod($end, $range);
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
$dateStr = $end->format('Y-m-d');
$dateName = Navigation::periodShow($end, $range);
$dateName = app('navigation')->periodShow($end, $range);
// amount transferred
/** @var JournalCollectorInterface $collector */
@ -468,7 +472,7 @@ class CategoryController extends Controller
'date' => clone $end,
]
);
$end = Navigation::subtractPeriod($end, $range, 1);
$end = app('navigation')->subtractPeriod($end, $range, 1);
++$count;
}
$cache->store($entries);

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
@ -38,7 +37,6 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Log;
use Navigation;
use Preferences;
use Response;
use Steam;
@ -342,13 +340,11 @@ class AccountController extends Controller
* @param Carbon $start
*
* @return \Illuminate\Http\JsonResponse
*
* @throws FireflyException
*/
public function period(Account $account, Carbon $start)
{
$range = Preferences::get('viewRange', '1M')->data;
$end = Navigation::endOfPeriod($start, $range);
$end = app('navigation')->endOfPeriod($start, $range);
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);

View File

@ -37,7 +37,6 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Navigation;
use Preferences;
use Response;
use Steam;
@ -81,7 +80,7 @@ class BudgetController extends Controller
{
$first = $this->repository->firstUseDate($budget);
$range = Preferences::get('viewRange', '1M')->data;
$currentStart = Navigation::startOfPeriod($first, $range);
$currentStart = app('navigation')->startOfPeriod($first, $range);
$last = session('end', new Carbon);
$cache = new CacheProperties();
$cache->addProperty($first);
@ -95,15 +94,15 @@ class BudgetController extends Controller
$final = clone $last;
$final->addYears(2);
$budgetCollection = new Collection([$budget]);
$last = Navigation::endOfX($last, $range, $final); // not to overshoot.
$last = app('navigation')->endOfX($last, $range, $final); // not to overshoot.
$entries = [];
while ($currentStart < $last) {
// periodspecific dates:
$currentEnd = Navigation::endOfPeriod($currentStart, $range);
$currentEnd = app('navigation')->endOfPeriod($currentStart, $range);
// sub another day because reasons.
$currentEnd->subDay();
$spent = $this->repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd);
$format = Navigation::periodShow($currentStart, $range);
$format = app('navigation')->periodShow($currentStart, $range);
$entries[$format] = bcmul($spent, '-1');
$currentStart = clone $currentEnd;
$currentStart->addDays(2);
@ -374,7 +373,7 @@ class BudgetController extends Controller
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$periods = Navigation::listOfPeriods($start, $end);
$periods = app('navigation')->listOfPeriods($start, $end);
$entries = $this->repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end); // get the expenses
$budgeted = $this->getBudgetedInPeriod($budget, $start, $end);
@ -417,7 +416,7 @@ class BudgetController extends Controller
}
// the expenses:
$periods = Navigation::listOfPeriods($start, $end);
$periods = app('navigation')->listOfPeriods($start, $end);
$entries = $this->repository->getNoBudgetPeriodReport($accounts, $start, $end);
$chartData = [];
@ -464,13 +463,13 @@ class BudgetController extends Controller
*/
private function getBudgetedInPeriod(Budget $budget, Carbon $start, Carbon $end): array
{
$key = Navigation::preferredCarbonFormat($start, $end);
$range = Navigation::preferredRangeFormat($start, $end);
$key = app('navigation')->preferredCarbonFormat($start, $end);
$range = app('navigation')->preferredRangeFormat($start, $end);
$current = clone $start;
$budgeted = [];
while ($current < $end) {
$currentStart = Navigation::startOfPeriod($current, $range);
$currentEnd = Navigation::endOfPeriod($current, $range);
$currentStart = app('navigation')->startOfPeriod($current, $range);
$currentEnd = app('navigation')->endOfPeriod($current, $range);
$budgetLimits = $this->repository->getBudgetLimits($budget, $currentStart, $currentEnd);
$index = $currentStart->format($key);
$budgeted[$index] = $budgetLimits->sum('amount');

View File

@ -37,7 +37,6 @@ use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Navigation;
use Response;
/**
@ -135,8 +134,8 @@ class BudgetReportController extends Controller
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
$function = Navigation::preferredEndOfPeriod($start, $end);
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
$function = app('navigation')->preferredEndOfPeriod($start, $end);
$chartData = [];
$currentStart = clone $start;

View File

@ -31,7 +31,6 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Navigation;
use Preferences;
use Response;
@ -74,11 +73,11 @@ class CategoryController extends Controller
$start = $repository->firstUseDate($category);
if (null === $start) {
$start = new Carbon;
$start = new Carbon; // @codeCoverageIgnore
}
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($start, $range);
$start = app('navigation')->startOfPeriod($start, $range);
$end = new Carbon;
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$chartData = [
@ -101,15 +100,15 @@ class CategoryController extends Controller
];
while ($start <= $end) {
$currentEnd = Navigation::endOfPeriod($start, $range);
$currentEnd = app('navigation')->endOfPeriod($start, $range);
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
$sum = bcadd($spent, $earned);
$label = Navigation::periodShow($start, $range);
$label = app('navigation')->periodShow($start, $range);
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
$chartData[1]['entries'][$label] = round($earned, 12);
$chartData[2]['entries'][$label] = round($sum, 12);
$start = Navigation::addPeriod($start, $range, 0);
$start = app('navigation')->addPeriod($start, $range, 0);
}
$data = $this->generator->multiSet($chartData);
@ -180,7 +179,7 @@ class CategoryController extends Controller
}
$expenses = $repository->periodExpenses(new Collection([$category]), $accounts, $start, $end);
$income = $repository->periodIncome(new Collection([$category]), $accounts, $start, $end);
$periods = Navigation::listOfPeriods($start, $end);
$periods = app('navigation')->listOfPeriods($start, $end);
$chartData = [
[
'label' => strval(trans('firefly.spent')),
@ -236,7 +235,7 @@ class CategoryController extends Controller
}
$expenses = $repository->periodExpensesNoCategory($accounts, $start, $end);
$income = $repository->periodIncomeNoCategory($accounts, $start, $end);
$periods = Navigation::listOfPeriods($start, $end);
$periods = app('navigation')->listOfPeriods($start, $end);
$chartData = [
[
'label' => strval(trans('firefly.spent')),
@ -281,8 +280,8 @@ class CategoryController extends Controller
public function specificPeriod(CategoryRepositoryInterface $repository, Category $category, Carbon $date)
{
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($date, $range);
$end = Navigation::endOfPeriod($date, $range);
$start = app('navigation')->startOfPeriod($date, $range);
$end = app('navigation')->endOfPeriod($date, $range);
$data = $this->makePeriodChart($repository, $category, $start, $end);
return Response::json($data);
@ -336,7 +335,7 @@ class CategoryController extends Controller
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start);
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start);
$sum = bcadd($spent, $earned);
$label = trim(Navigation::periodShow($start, '1D'));
$label = trim(app('navigation')->periodShow($start, '1D'));
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
$chartData[1]['entries'][$label] = round($earned, 12);

View File

@ -36,7 +36,6 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Navigation;
use Response;
/**
@ -177,8 +176,8 @@ class CategoryReportController extends Controller
return Response::json($cache->get()); // @codeCoverageIgnore
}
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
$function = Navigation::preferredEndOfPeriod($start, $end);
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
$function = app('navigation')->preferredEndOfPeriod($start, $end);
$chartData = [];
$currentStart = clone $start;

View File

@ -0,0 +1,266 @@
<?php
/**
* ExpenseReportController.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Response;
/**
* Separate controller because many helper functions are shared.
*
* Class ExpenseReportController
*/
class ExpenseReportController extends Controller
{
/** @var AccountRepositoryInterface */
protected $accountRepository;
/** @var GeneratorInterface */
protected $generator;
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->generator = app(GeneratorInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
return $next($request);
}
);
}
/**
* @param Collection $accounts
* @param Collection $expense
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Http\JsonResponse
*/
public function mainChart(Collection $accounts, Collection $expense, Carbon $start, Carbon $end)
{
$cache = new CacheProperties;
$cache->addProperty('chart.expense.report.main');
$cache->addProperty($accounts);
$cache->addProperty($expense);
$cache->addProperty($start);
$cache->addProperty($end);
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
$function = app('navigation')->preferredEndOfPeriod($start, $end);
$chartData = [];
$currentStart = clone $start;
$combined = $this->combineAccounts($expense);
// make "all" set:
$all = new Collection;
foreach ($combined as $name => $combi) {
$all = $all->merge($combi);
}
// prep chart data:
foreach ($combined as $name => $combi) {
// first is always expense account:
/** @var Account $exp */
$exp = $combi->first();
$chartData[$exp->id . '-in'] = [
'label' => $name . ' (' . strtolower(strval(trans('firefly.income'))) . ')',
'type' => 'bar',
'yAxisID' => 'y-axis-0',
'entries' => [],
];
$chartData[$exp->id . '-out'] = [
'label' => $name . ' (' . strtolower(strval(trans('firefly.expenses'))) . ')',
'type' => 'bar',
'yAxisID' => 'y-axis-0',
'entries' => [],
];
// total in, total out:
$chartData[$exp->id . '-total-in'] = [
'label' => $name . ' (' . strtolower(strval(trans('firefly.sum_of_income'))) . ')',
'type' => 'line',
'fill' => false,
'yAxisID' => 'y-axis-1',
'entries' => [],
];
$chartData[$exp->id . '-total-out'] = [
'label' => $name . ' (' . strtolower(strval(trans('firefly.sum_of_expenses'))) . ')',
'type' => 'line',
'fill' => false,
'yAxisID' => 'y-axis-1',
'entries' => [],
];
}
$sumOfIncome = [];
$sumOfExpense = [];
while ($currentStart < $end) {
$currentEnd = clone $currentStart;
$currentEnd = $currentEnd->$function();
// get expenses grouped by opposing name:
$expenses = $this->groupByName($this->getExpenses($accounts, $all, $currentStart, $currentEnd));
$income = $this->groupByName($this->getIncome($accounts, $all, $currentStart, $currentEnd));
$label = $currentStart->formatLocalized($format);
foreach ($combined as $name => $combi) {
// first is always expense account:
/** @var Account $exp */
$exp = $combi->first();
$labelIn = $exp->id . '-in';
$labelOut = $exp->id . '-out';
$labelSumIn = $exp->id . '-total-in';
$labelSumOut = $exp->id . '-total-out';
$currentIncome = $income[$name] ?? '0';
$currentExpense = $expenses[$name] ?? '0';
// add to sum:
$sumOfIncome[$exp->id] = $sumOfIncome[$exp->id] ?? '0';
$sumOfExpense[$exp->id] = $sumOfExpense[$exp->id] ?? '0';
$sumOfIncome[$exp->id] = bcadd($sumOfIncome[$exp->id], $currentIncome);
$sumOfExpense[$exp->id] = bcadd($sumOfExpense[$exp->id], $currentExpense);
// add to chart:
$chartData[$labelIn]['entries'][$label] = $currentIncome;
$chartData[$labelOut]['entries'][$label] = $currentExpense;
$chartData[$labelSumIn]['entries'][$label] = $sumOfIncome[$exp->id];
$chartData[$labelSumOut]['entries'][$label] = $sumOfExpense[$exp->id];
}
$currentStart = clone $currentEnd;
$currentStart->addDay();
}
// remove all empty entries to prevent cluttering:
$newSet = [];
foreach ($chartData as $key => $entry) {
if (0 === !array_sum($entry['entries'])) {
$newSet[$key] = $chartData[$key];
}
}
if (0 === count($newSet)) {
$newSet = $chartData; // @codeCoverageIgnore
}
$data = $this->generator->multiSet($newSet);
$cache->store($data);
return Response::json($data);
}
/**
* @param Collection $accounts
*
* @return array
*/
protected function combineAccounts(Collection $accounts): array
{
$combined = [];
/** @var Account $expenseAccount */
foreach ($accounts as $expenseAccount) {
$collection = new Collection;
$collection->push($expenseAccount);
$revenue = $this->accountRepository->findByName($expenseAccount->name, [AccountType::REVENUE]);
if (!is_null($revenue->id)) {
$collection->push($revenue);
}
$combined[$expenseAccount->name] = $collection;
}
return $combined;
}
/**
* @param Collection $accounts
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
private function getExpenses(Collection $accounts, Collection $opposing, Carbon $start, Carbon $end): Collection
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setOpposingAccounts($opposing);
$transactions = $collector->getJournals();
return $transactions;
}
/**
* @param Collection $accounts
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
private function getIncome(Collection $accounts, Collection $opposing, Carbon $start, Carbon $end): Collection
{
/** @var JournalCollectorInterface $collector */
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setOpposingAccounts($opposing);
$transactions = $collector->getJournals();
return $transactions;
}
/**
* @param Collection $set
*
* @return array
*/
private function groupByName(Collection $set): array
{
// group by opposing account name.
$grouped = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$name = $transaction->opposing_account_name;
$grouped[$name] = $grouped[$name] ?? '0';
$grouped[$name] = bcadd($transaction->transaction_amount, $grouped[$name]);
}
return $grouped;
}
}

View File

@ -29,7 +29,6 @@ use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Log;
use Navigation;
use Response;
use Steam;
@ -89,7 +88,7 @@ class ReportController extends Controller
}
/**
* Shows income and expense, debet/credit: operations.
* Shows income and expense, debit/credit: operations.
*
* @param Collection $accounts
* @param Carbon $start
@ -109,7 +108,7 @@ class ReportController extends Controller
return Response::json($cache->get()); // @codeCoverageIgnore
}
Log::debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
$source = $this->getChartData($accounts, $start, $end);
$chartData = [
[
@ -144,7 +143,7 @@ class ReportController extends Controller
}
/**
* Shows sum income and expense, debet/credit: operations.
* Shows sum income and expense, debit/credit: operations.
*
* @param Carbon $start
* @param Carbon $end
@ -256,7 +255,7 @@ class ReportController extends Controller
$tasker = app(AccountTaskerInterface::class);
while ($currentStart <= $end) {
$currentEnd = Navigation::endOfPeriod($currentStart, '1M');
$currentEnd = app('navigation')->endOfPeriod($currentStart, '1M');
$earned = strval(
array_sum(
array_map(
@ -282,7 +281,7 @@ class ReportController extends Controller
$label = $currentStart->format('Y-m') . '-01';
$spentArray[$label] = bcmul($spent, '-1');
$earnedArray[$label] = $earned;
$currentStart = Navigation::addPeriod($currentStart, '1M', 0);
$currentStart = app('navigation')->addPeriod($currentStart, '1M', 0);
}
$result = [
'spent' => $spentArray,

View File

@ -36,9 +36,11 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Navigation;
use Response;
/**
* Class TagReportController
*/
class TagReportController extends Controller
{
/** @var GeneratorInterface */
@ -168,8 +170,8 @@ class TagReportController extends Controller
return Response::json($cache->get()); // @codeCoverageIgnore
}
$format = Navigation::preferredCarbonLocalizedFormat($start, $end);
$function = Navigation::preferredEndOfPeriod($start, $end);
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
$function = app('navigation')->preferredEndOfPeriod($start, $end);
$chartData = [];
$currentStart = clone $start;

View File

@ -126,7 +126,7 @@ class Controller extends BaseController
$uri = $this->redirectUri;
}
if (!(false === strpos($uri, 'jscript'))) {
$uri = $this->redirectUri;
$uri = $this->redirectUri; // @codeCoverageIgnore
}
return $uri;

View File

@ -28,6 +28,7 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Log;
use Preferences;
use View;
@ -52,8 +53,8 @@ class CurrencyController extends Controller
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.currencies'));
View::share('mainTitleIcon', 'fa-usd');
app('view')->share('title', trans('firefly.currencies'));
app('view')->share('mainTitleIcon', 'fa-usd');
$this->repository = app(CurrencyRepositoryInterface::class);
$this->userRepository = app(UserRepositoryInterface::class);
@ -83,8 +84,6 @@ class CurrencyController extends Controller
$this->rememberPreviousUri('currencies.create.uri');
}
$request->session()->forget('currencies.create.fromStore');
$request->session()->flash('gaEventCategory', 'currency');
$request->session()->flash('gaEventAction', 'create');
return view('currencies.create', compact('subTitleIcon', 'subTitle'));
}
@ -131,8 +130,6 @@ class CurrencyController extends Controller
// put previous url in session
$this->rememberPreviousUri('currencies.delete.uri');
$request->session()->flash('gaEventCategory', 'currency');
$request->session()->flash('gaEventAction', 'delete');
$subTitle = trans('form.delete_currency', ['name' => $currency->name]);
return view('currencies.delete', compact('currency', 'subTitle'));
@ -191,8 +188,6 @@ class CurrencyController extends Controller
$this->rememberPreviousUri('currencies.edit.uri');
}
$request->session()->forget('currencies.edit.fromUpdate');
$request->session()->flash('gaEventCategory', 'currency');
$request->session()->flash('gaEventAction', 'edit');
return view('currencies.edit', compact('currency', 'subTitle', 'subTitleIcon'));
}
@ -204,12 +199,19 @@ class CurrencyController extends Controller
*/
public function index(Request $request)
{
$currencies = $this->repository->get();
$currencies = $currencies->sortBy(
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$collection = $this->repository->get();
$total = $collection->count();
$collection = $collection->sortBy(
function (TransactionCurrency $currency) {
return $currency->name;
}
);
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
$currencies = new LengthAwarePaginator($collection, $total, $pageSize, $page);
$currencies->setPath(route('currencies.index'));
$defaultCurrency = $this->repository->getCurrencyByPreference(Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR')));
$isOwner = true;
if (!$this->userRepository->hasRole(auth()->user(), 'owner')) {

View File

@ -26,6 +26,7 @@ use Carbon\Carbon;
use ExpandedForm;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Export\ProcessorInterface;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Requests\ExportFormRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\ExportJob;
@ -50,12 +51,13 @@ class ExportController extends Controller
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-file-archive-o');
View::share('title', trans('firefly.export_and_backup_data'));
app('view')->share('mainTitleIcon', 'fa-file-archive-o');
app('view')->share('title', trans('firefly.export_and_backup_data'));
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['index']);
}
/**

View File

@ -25,8 +25,12 @@ namespace FireflyIII\Http\Controllers;
use Artisan;
use Carbon\Carbon;
use DB;
use Exception;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Middleware\IsSandStormUser;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
@ -36,8 +40,8 @@ use Illuminate\Support\Collection;
use Log;
use Monolog\Handler\RotatingFileHandler;
use Preferences;
use Response;
use Route as RouteFacade;
use Session;
use View;
/**
@ -51,12 +55,16 @@ class HomeController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', 'Firefly III');
View::share('mainTitleIcon', 'fa-fire');
app('view')->share('title', 'Firefly III');
app('view')->share('mainTitleIcon', 'fa-fire');
$this->middleware(IsDemoUser::class)->except(['dateRange', 'index']);
$this->middleware(IsSandStormUser::class)->only('routes');
}
/**
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function dateRange(Request $request)
{
@ -77,12 +85,17 @@ class HomeController extends Controller
$diff = $start->diffInDays($end);
if ($diff > 50) {
Session::flash('warning', strval(trans('firefly.warning_much_data', ['days' => $diff])));
$request->session()->flash('warning', strval(trans('firefly.warning_much_data', ['days' => $diff])));
}
Session::put('is_custom_range', $isCustomRange);
Session::put('start', $start);
Session::put('end', $end);
$request->session()->put('is_custom_range', $isCustomRange);
Log::debug(sprintf('Set is_custom_range to %s', var_export($isCustomRange, true)));
$request->session()->put('start', $start);
Log::debug(sprintf('Set start to %s', $start->format('Y-m-d H:i:s')));
$request->session()->put('end', $end);
Log::debug(sprintf('Set end to %s', $end->format('Y-m-d H:i:s')));
return Response::json(['ok' => 'ok']);
}
/**
@ -92,7 +105,7 @@ class HomeController extends Controller
*/
public function displayDebug(Request $request)
{
$phpVersion = PHP_VERSION;
$phpVersion = str_replace('~', '\~', PHP_VERSION);
$phpOs = php_uname();
$interface = PHP_SAPI;
$now = Carbon::create()->format('Y-m-d H:i:s e');
@ -164,7 +177,21 @@ class HomeController extends Controller
{
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');
} catch (Exception $e) {
// dont care
}
Log::debug('Call view:clear...');
Artisan::call('view:clear');
Log::debug('Done! Redirecting...');
return redirect(route('index'));
}
@ -182,7 +209,6 @@ class HomeController extends Controller
if (0 === $count) {
return redirect(route('new-user.index'));
}
$subTitle = trans('firefly.welcomeBack');
$transactions = [];
$frontPage = Preferences::get(
@ -209,12 +235,18 @@ class HomeController extends Controller
$transactions[] = [$set, $account];
}
// fire check update event:
event(new RequestedVersionCheckStatus(auth()->user()));
return view(
'index',
compact('count', 'subTitle', 'transactions', 'showDeps', 'billCount', 'start', 'end', 'today')
);
}
/**
* @return string
*/
public function routes()
{
$set = RouteFacade::getRoutes();
@ -225,7 +257,7 @@ class HomeController extends Controller
'rules.select', 'search.search', 'test-flash', 'transactions.link.delete', 'transactions.link.switch',
'two-factor.lost', 'report.options',
];
$return = '&nbsp;';
/** @var Route $route */
foreach ($set as $route) {
$name = $route->getName();
@ -237,23 +269,25 @@ class HomeController extends Controller
}
}
if (!$found) {
echo 'touch ' . $route->getName() . '.md;';
$return .= 'touch ' . $route->getName() . '.md;';
}
}
}
return '&nbsp;';
return $return;
}
/**
* @param Request $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function testFlash()
public function testFlash(Request $request)
{
Session::flash('success', 'This is a success message.');
Session::flash('info', 'This is an info message.');
Session::flash('warning', 'This is a warning.');
Session::flash('error', 'This is an error!');
$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'));
}

View File

@ -1,158 +0,0 @@
<?php
/**
* BankController.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Import;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Support\Import\Information\InformationInterface;
use FireflyIII\Support\Import\Prerequisites\PrerequisitesInterface;
use Illuminate\Http\Request;
use Log;
use Session;
class BankController extends Controller
{
/**
* This method must ask the user all parameters necessary to start importing data. This may not be enough
* to finish the import itself (ie. mapping) but it should be enough to begin: accounts to import from,
* accounts to import into, data ranges, etc.
*
* @param string $bank
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
*/
public function form(string $bank)
{
$class = config(sprintf('firefly.import_pre.%s', $bank));
/** @var PrerequisitesInterface $object */
$object = app($class);
$object->setUser(auth()->user());
if ($object->hasPrerequisites()) {
return redirect(route('import.bank.prerequisites', [$bank]));
}
$class = config(sprintf('firefly.import_info.%s', $bank));
/** @var InformationInterface $object */
$object = app($class);
$object->setUser(auth()->user());
$remoteAccounts = $object->getAccounts();
return view('import.bank.form', compact('remoteAccounts', 'bank'));
}
/**
* With the information given in the submitted form Firefly III will call upon the bank's classes to return transaction
* information as requested. The user will be able to map unknown data and continue. Or maybe, it's put into some kind of
* fake CSV file and forwarded to the import routine.
*
* @param Request $request
* @param string $bank
*
* @return \Illuminate\Http\RedirectResponse|null
*/
public function postForm(Request $request, string $bank)
{
$class = config(sprintf('firefly.import_pre.%s', $bank));
/** @var PrerequisitesInterface $object */
$object = app($class);
$object->setUser(auth()->user());
if ($object->hasPrerequisites()) {
return redirect(route('import.bank.prerequisites', [$bank]));
}
$remoteAccounts = $request->get('do_import');
if (!is_array($remoteAccounts) || 0 === count($remoteAccounts)) {
Session::flash('error', 'Must select accounts');
return redirect(route('import.bank.form', [$bank]));
}
$remoteAccounts = array_keys($remoteAccounts);
$class = config(sprintf('firefly.import_pre.%s', $bank));
// get import file
unset($remoteAccounts, $class);
// get import config
}
/**
* This method processes the prerequisites the user has entered in the previous step.
*
* Whatever storePrerequisites does, it should make sure that the system is ready to continue immediately. So
* no extra calls or stuff, except maybe to open a session
*
* @see PrerequisitesInterface::storePrerequisites
*
* @param Request $request
* @param string $bank
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function postPrerequisites(Request $request, string $bank)
{
Log::debug(sprintf('Now in postPrerequisites for %s', $bank));
$class = config(sprintf('firefly.import_pre.%s', $bank));
/** @var PrerequisitesInterface $object */
$object = app($class);
$object->setUser(auth()->user());
if (!$object->hasPrerequisites()) {
Log::debug(sprintf('No more prerequisites for %s, move to form.', $bank));
return redirect(route('import.bank.form', [$bank]));
}
Log::debug('Going to store entered preprerequisites.');
// store post data
$result = $object->storePrerequisites($request);
if ($result->count() > 0) {
Session::flash('error', $result->first());
return redirect(route('import.bank.prerequisites', [$bank]));
}
return redirect(route('import.bank.form', [$bank]));
}
/**
* This method shows you, if necessary, a form that allows you to enter any required values, such as API keys,
* login passwords or other values.
*
* @param string $bank
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
*/
public function prerequisites(string $bank)
{
$class = config(sprintf('firefly.import_pre.%s', $bank));
/** @var PrerequisitesInterface $object */
$object = app($class);
$object->setUser(auth()->user());
if ($object->hasPrerequisites()) {
$view = $object->getView();
$parameters = $object->getViewParameters();
return view($view, $parameters);
}
return redirect(route('import.bank.form', [$bank]));
}
}

View File

@ -0,0 +1,144 @@
<?php
/**
* ConfigurationController.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Import;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Import\Configuration\ConfiguratorInterface;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use Illuminate\Http\Request;
use Log;
/**
* Class ConfigurationController
*/
class ConfigurationController extends Controller
{
/** @var ImportJobRepositoryInterface */
public $repository;
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-archive');
app('view')->share('title', trans('firefly.import_index_title'));
$this->repository = app(ImportJobRepositoryInterface::class);
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['index']);
}
/**
* Configure the job. This method is returned to until job is deemed "configured".
*
* @param ImportJob $job
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
*
* @throws FireflyException
*/
public function index(ImportJob $job)
{
// create configuration class:
$configurator = $this->makeConfigurator($job);
// is the job already configured?
if ($configurator->isJobConfigured()) {
$this->repository->updateStatus($job, 'configured');
return redirect(route('import.status', [$job->key]));
}
$this->repository->updateStatus($job, 'configuring');
$view = $configurator->getNextView();
$data = $configurator->getNextData();
$subTitle = trans('firefly.import_config_bread_crumb');
$subTitleIcon = 'fa-wrench';
return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
}
/**
* Store the configuration. Returns to "configure" method until job is configured.
*
* @param Request $request
* @param ImportJob $job
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*
* @throws FireflyException
*/
public function post(Request $request, ImportJob $job)
{
Log::debug('Now in postConfigure()', ['job' => $job->key]);
$configurator = $this->makeConfigurator($job);
// is the job already configured?
if ($configurator->isJobConfigured()) {
return redirect(route('import.status', [$job->key]));
}
$data = $request->all();
$configurator->configureJob($data);
// get possible warning from configurator:
$warning = $configurator->getWarningMessage();
if (strlen($warning) > 0) {
$request->session()->flash('warning', $warning);
}
// return to configure
return redirect(route('import.configure', [$job->key]));
}
/**
* @param ImportJob $job
*
* @return ConfiguratorInterface
*
* @throws FireflyException
*/
private function makeConfigurator(ImportJob $job): ConfiguratorInterface
{
$type = $job->file_type;
$key = sprintf('import.configuration.%s', $type);
$className = config($key);
if (null === $className || !class_exists($className)) {
throw new FireflyException(sprintf('Cannot find configurator class for job of type "%s".', $type)); // @codeCoverageIgnore
}
/** @var ConfiguratorInterface $configurator */
$configurator = app($className);
$configurator->setJob($job);
return $configurator;
}
}

View File

@ -1,311 +0,0 @@
<?php
/**
* FileController.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Import;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\ImportUploadRequest;
use FireflyIII\Import\Configurator\ConfiguratorInterface;
use FireflyIII\Import\Routine\ImportRoutine;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Http\Response as LaravelResponse;
use Log;
use Response;
use Session;
use View;
/**
* Class FileController.
*/
class FileController extends Controller
{
/** @var ImportJobRepositoryInterface */
public $repository;
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-archive');
View::share('title', trans('firefly.import_index_title'));
$this->repository = app(ImportJobRepositoryInterface::class);
return $next($request);
}
);
}
/**
* This is step 3. This repeats until the job is configured.
*
* @param ImportJob $job
*
* @return View
*
* @throws FireflyException
*/
public function configure(ImportJob $job)
{
// create configuration class:
$configurator = $this->makeConfigurator($job);
// is the job already configured?
if ($configurator->isJobConfigured()) {
$this->repository->updateStatus($job, 'configured');
return redirect(route('import.file.status', [$job->key]));
}
$view = $configurator->getNextView();
$data = $configurator->getNextData();
$subTitle = trans('firefly.import_config_bread_crumb');
$subTitleIcon = 'fa-wrench';
return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
}
/**
* Generate a JSON file of the job's configuration and send it to the user.
*
* @param ImportJob $job
*
* @return LaravelResponse
*/
public function download(ImportJob $job)
{
Log::debug('Now in download()', ['job' => $job->key]);
$config = $job->configuration;
// This is CSV import specific:
$config['column-roles-complete'] = false;
$config['column-mapping-complete'] = false;
$config['initial-config-complete'] = false;
$config['delimiter'] = "\t" === $config['delimiter'] ? 'tab' : $config['delimiter'];
$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)
->header('Content-Type', 'application/json')
->header('Content-Description', 'File Transfer')
->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($result));
return $response;
}
/**
* This is step 1. Upload a file.
*
* @return View
*/
public function index()
{
$subTitle = trans('firefly.import_index_sub_title');
$subTitleIcon = 'fa-home';
$importFileTypes = [];
$defaultImportType = config('firefly.default_import_format');
foreach (array_keys(config('firefly.import_formats')) as $type) {
$importFileTypes[$type] = trans('firefly.import_file_type_' . $type);
}
return view('import.file.index', compact('subTitle', 'subTitleIcon', 'importFileTypes', 'defaultImportType'));
}
/**
* This is step 2. It creates an Import Job. Stores the import.
*
* @param ImportUploadRequest $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function initialize(ImportUploadRequest $request)
{
Log::debug('Now in initialize()');
// create import job:
$type = $request->get('import_file_type');
$job = $this->repository->create($type);
Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]);
// process file:
$this->repository->processFile($job, $request->files->get('import_file'));
// process config, if present:
if ($request->files->has('configuration_file')) {
$this->repository->processConfiguration($job, $request->files->get('configuration_file'));
}
$this->repository->updateStatus($job, 'initialized');
return redirect(route('import.file.configure', [$job->key]));
}
/**
* Show status of import job in JSON.
*
* @param ImportJob $job
*
* @return \Illuminate\Http\JsonResponse
*/
public function json(ImportJob $job)
{
$result = [
'started' => false,
'finished' => false,
'running' => false,
'errors' => array_values($job->extended_status['errors']),
'percentage' => 0,
'show_percentage' => false,
'steps' => $job->extended_status['steps'],
'done' => $job->extended_status['done'],
'statusText' => trans('firefly.import_status_job_' . $job->status),
'status' => $job->status,
'finishedText' => '',
];
if (0 !== $job->extended_status['steps']) {
$result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
$result['show_percentage'] = true;
}
if ('finished' === $job->status) {
$tagId = $job->extended_status['tag'];
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$tag = $repository->find($tagId);
$result['finished'] = true;
$result['finishedText'] = trans('firefly.import_status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
}
if ('running' === $job->status) {
$result['started'] = true;
$result['running'] = true;
}
return Response::json($result);
}
/**
* Step 4. Save the configuration.
*
* @param Request $request
* @param ImportJob $job
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function postConfigure(Request $request, ImportJob $job)
{
Log::debug('Now in postConfigure()', ['job' => $job->key]);
$configurator = $this->makeConfigurator($job);
// is the job already configured?
if ($configurator->isJobConfigured()) {
return redirect(route('import.file.status', [$job->key]));
}
$data = $request->all();
$configurator->configureJob($data);
// get possible warning from configurator:
$warning = $configurator->getWarningMessage();
if (strlen($warning) > 0) {
Session::flash('warning', $warning);
}
// return to configure
return redirect(route('import.file.configure', [$job->key]));
}
/**
* @param ImportJob $job
*
* @return \Illuminate\Http\JsonResponse
*
* @throws FireflyException
*/
public function start(ImportJob $job)
{
/** @var ImportRoutine $routine */
$routine = app(ImportRoutine::class);
$routine->setJob($job);
$result = $routine->run();
if ($result) {
return Response::json(['run' => 'ok']);
}
throw new FireflyException('Job did not complete succesfully.');
}
/**
* @param ImportJob $job
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/
public function status(ImportJob $job)
{
$statuses = ['configured', 'running', 'finished'];
if (!in_array($job->status, $statuses)) {
return redirect(route('import.file.configure', [$job->key]));
}
$subTitle = trans('firefly.import_status_sub_title');
$subTitleIcon = 'fa-star';
return view('import.file.status', compact('job', 'subTitle', 'subTitleIcon'));
}
/**
* @param ImportJob $job
*
* @return ConfiguratorInterface
*
* @throws FireflyException
*/
private function makeConfigurator(ImportJob $job): ConfiguratorInterface
{
$type = $job->file_type;
$key = sprintf('firefly.import_configurators.%s', $type);
$className = config($key);
if (null === $className) {
throw new FireflyException('Cannot find configurator class for this job.'); // @codeCoverageIgnore
}
/** @var ConfiguratorInterface $configurator */
$configurator = app($className);
$configurator->setJob($job);
return $configurator;
}
}

View File

@ -0,0 +1,162 @@
<?php
/**
* IndexController.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Import;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Import\Routine\RoutineInterface;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use Illuminate\Http\Response as LaravelResponse;
use Log;
use Response;
use View;
/**
* Class FileController.
*/
class IndexController extends Controller
{
/** @var ImportJobRepositoryInterface */
public $repository;
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-archive');
app('view')->share('title', trans('firefly.import_index_title'));
$this->repository = app(ImportJobRepositoryInterface::class);
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['create', 'index']);
}
/**
* Creates a new import job for $bank with the default (global) job configuration.
*
* @param string $bank
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*
* @throws FireflyException
*/
public function create(string $bank)
{
if (true === !(config(sprintf('import.enabled.%s', $bank)))) {
throw new FireflyException(sprintf('Cannot import from "%s" at this time.', $bank)); // @codeCoverageIgnore
}
$importJob = $this->repository->create($bank);
// from here, always go to configure step.
return redirect(route('import.configure', [$importJob->key]));
}
/**
* Generate a JSON file of the job's configuration and send it to the user.
*
* @param ImportJob $job
*
* @return LaravelResponse
*/
public function download(ImportJob $job)
{
Log::debug('Now in download()', ['job' => $job->key]);
$config = $job->configuration;
// This is CSV import specific:
$config['column-roles-complete'] = false;
$config['column-mapping-complete'] = false;
$config['initial-config-complete'] = false;
$config['has-file-upload'] = false;
$config['delimiter'] = "\t" === $config['delimiter'] ? 'tab' : $config['delimiter'];
$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)
->header('Content-Type', 'application/json')
->header('Content-Description', 'File Transfer')
->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($result));
return $response;
}
/**
* General import index.
*
* @return View
*/
public function index()
{
$subTitle = trans('firefly.import_index_sub_title');
$subTitleIcon = 'fa-home';
$routines = config('import.enabled');
return view('import.index', compact('subTitle', 'subTitleIcon', 'routines'));
}
/**
* @param ImportJob $job
*
* @return \Illuminate\Http\JsonResponse
*
* @throws FireflyException
*/
public function start(ImportJob $job)
{
$type = $job->file_type;
$key = sprintf('import.routine.%s', $type);
$className = config($key);
if (null === $className || !class_exists($className)) {
throw new FireflyException(sprintf('Cannot find import routine class for job of type "%s".', $type)); // @codeCoverageIgnore
}
/** @var RoutineInterface $routine */
$routine = app($className);
$routine->setJob($job);
$result = $routine->run();
if ($result) {
return Response::json(['run' => 'ok']);
}
throw new FireflyException('Job did not complete successfully. Please review the log files.');
}
}

View File

@ -0,0 +1,138 @@
<?php
/**
* PrerequisitesController.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Import;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Import\Prerequisites\PrerequisitesInterface;
use Illuminate\Http\Request;
use Log;
/**
* Class PrerequisitesController
*/
class PrerequisitesController extends Controller
{
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-archive');
app('view')->share('title', trans('firefly.import_index_title'));
return $next($request);
}
);
$this->middleware(IsDemoUser::class);
}
/**
* Once there are no prerequisites, this method will create an importjob object and
* redirect the user to a view where this object can be used by a bank specific
* class to process.
*
* @param string $bank
*
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse
*
* @throws FireflyException
*/
public function index(string $bank)
{
if (true === !(config(sprintf('import.enabled.%s', $bank)))) {
throw new FireflyException(sprintf('Cannot import from "%s" at this time.', $bank)); // @codeCoverageIgnore
}
$class = strval(config(sprintf('import.prerequisites.%s', $bank)));
if (!class_exists($class)) {
throw new FireflyException(sprintf('No class to handle "%s".', $bank)); // @codeCoverageIgnore
}
/** @var PrerequisitesInterface $object */
$object = app($class);
$object->setUser(auth()->user());
if ($object->hasPrerequisites()) {
$view = $object->getView();
$parameters = ['title' => strval(trans('firefly.import_index_title')), 'mainTitleIcon' => 'fa-archive'];
$parameters = array_merge($object->getViewParameters(), $parameters);
return view($view, $parameters);
}
// if no (more) prerequisites, return to create a job:
return redirect(route('import.create-job', [$bank]));
}
/**
* This method processes the prerequisites the user has entered in the previous step.
*
* Whatever storePrerequisites does, it should make sure that the system is ready to continue immediately. So
* no extra calls or stuff, except maybe to open a session
*
* @see PrerequisitesInterface::storePrerequisites
*
* @param Request $request
* @param string $bank
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*
* @throws FireflyException
*/
public function post(Request $request, string $bank)
{
Log::debug(sprintf('Now in postPrerequisites for %s', $bank));
if (true === !(config(sprintf('import.enabled.%s', $bank)))) {
throw new FireflyException(sprintf('Cannot import from "%s" at this time.', $bank)); // @codeCoverageIgnore
}
$class = strval(config(sprintf('import.prerequisites.%s', $bank)));
if (!class_exists($class)) {
throw new FireflyException(sprintf('Cannot find class %s', $class)); // @codeCoverageIgnore
}
/** @var PrerequisitesInterface $object */
$object = app($class);
$object->setUser(auth()->user());
if (!$object->hasPrerequisites()) {
Log::debug(sprintf('No more prerequisites for %s, move to form.', $bank));
return redirect(route('import.create-job', [$bank]));
}
Log::debug('Going to store entered prerequisites.');
// store post data
$result = $object->storePrerequisites($request);
if ($result->count() > 0) {
$request->session()->flash('error', $result->first());
}
return redirect(route('import.prerequisites', [$bank]));
}
}

View File

@ -0,0 +1,113 @@
<?php
/**
* StatusController.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Import;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Response;
/**
* Class StatusController
*/
class StatusController extends Controller
{
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-archive');
app('view')->share('title', trans('firefly.import_index_title'));
return $next($request);
}
);
}
/**
* @param ImportJob $job
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
*/
public function index(ImportJob $job)
{
$statuses = ['configured', 'running', 'finished', 'error'];
if (!in_array($job->status, $statuses)) {
return redirect(route('import.configure', [$job->key]));
}
$subTitle = trans('import.status_sub_title');
$subTitleIcon = 'fa-star';
return view('import.status', compact('job', 'subTitle', 'subTitleIcon'));
}
/**
* Show status of import job in JSON.
*
* @param ImportJob $job
*
* @return \Illuminate\Http\JsonResponse
*/
public function json(ImportJob $job)
{
$result = [
'started' => false,
'finished' => false,
'running' => false,
'errors' => array_values($job->extended_status['errors']),
'percentage' => 0,
'show_percentage' => false,
'steps' => $job->extended_status['steps'],
'done' => $job->extended_status['done'],
'statusText' => trans('import.status_job_' . $job->status),
'status' => $job->status,
'finishedText' => '',
];
if (0 !== $job->extended_status['steps']) {
$result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
$result['show_percentage'] = true;
}
if ('finished' === $job->status) {
$tagId = $job->extended_status['tag'];
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$tag = $repository->find($tagId);
$result['finished'] = true;
$result['finishedText'] = trans('import.status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
}
if ('running' === $job->status) {
$result['started'] = true;
$result['running'] = true;
}
return Response::json($result);
}
}

View File

@ -1,72 +0,0 @@
<?php
/**
* ImportController.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use View;
/**
* Class ImportController.
*/
class ImportController extends Controller
{
/** @var ImportJobRepositoryInterface */
public $repository;
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-archive');
View::share('title', trans('firefly.import_index_title'));
$this->repository = app(ImportJobRepositoryInterface::class);
return $next($request);
}
);
}
/**
* General import index.
*
* @return View
*/
public function index()
{
$subTitle = trans('firefly.import_index_sub_title');
$subTitleIcon = 'fa-home';
$importFileTypes = [];
$defaultImportType = config('firefly.default_import_format');
foreach (array_keys(config('firefly.import_formats')) as $type) {
$importFileTypes[$type] = trans('firefly.import_file_type_' . $type);
}
return view('import.index', compact('subTitle', 'subTitleIcon', 'importFileTypes', 'defaultImportType'));
}
}

View File

@ -30,7 +30,6 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Http\Request;
use Log;
use Navigation;
use Preferences;
/**
@ -89,6 +88,8 @@ class JavascriptController extends Controller
/**
* @param Request $request
* @param AccountRepositoryInterface $repository
* @param CurrencyRepositoryInterface $currencyRepository
*
* @return \Illuminate\Http\Response
*/
@ -139,34 +140,43 @@ class JavascriptController extends Controller
$end = session('end');
$first = session('first');
$title = sprintf('%s - %s', $start->formatLocalized($this->monthAndDayFormat), $end->formatLocalized($this->monthAndDayFormat));
$isCustom = session('is_custom_range');
$isCustom = true === session('is_custom_range', false);
$today = new Carbon;
$ranges = [
// first range is the current range:
$title => [$start, $end],
];
Log::debug(sprintf('viewRange is %s', $viewRange));
Log::debug(sprintf('isCustom is %s', var_export($isCustom, true)));
// when current range is a custom range, add the current period as the next range.
if ($isCustom) {
Log::debug('Custom is true.');
$index = Navigation::periodShow($start, $viewRange);
$customPeriodStart = Navigation::startOfPeriod($start, $viewRange);
$customPeriodEnd = Navigation::endOfPeriod($customPeriodStart, $viewRange);
$index = app('navigation')->periodShow($start, $viewRange);
$customPeriodStart = app('navigation')->startOfPeriod($start, $viewRange);
$customPeriodEnd = app('navigation')->endOfPeriod($customPeriodStart, $viewRange);
$ranges[$index] = [$customPeriodStart, $customPeriodEnd];
}
// then add previous range and next range
$previousDate = Navigation::subtractPeriod($start, $viewRange);
$index = Navigation::periodShow($previousDate, $viewRange);
$previousStart = Navigation::startOfPeriod($previousDate, $viewRange);
$previousEnd = Navigation::endOfPeriod($previousStart, $viewRange);
$previousDate = app('navigation')->subtractPeriod($start, $viewRange);
$index = app('navigation')->periodShow($previousDate, $viewRange);
$previousStart = app('navigation')->startOfPeriod($previousDate, $viewRange);
$previousEnd = app('navigation')->endOfPeriod($previousStart, $viewRange);
$ranges[$index] = [$previousStart, $previousEnd];
$nextDate = Navigation::addPeriod($start, $viewRange, 0);
$index = Navigation::periodShow($nextDate, $viewRange);
$nextStart = Navigation::startOfPeriod($nextDate, $viewRange);
$nextEnd = Navigation::endOfPeriod($nextStart, $viewRange);
$nextDate = app('navigation')->addPeriod($start, $viewRange, 0);
$index = app('navigation')->periodShow($nextDate, $viewRange);
$nextStart = app('navigation')->startOfPeriod($nextDate, $viewRange);
$nextEnd = app('navigation')->endOfPeriod($nextStart, $viewRange);
$ranges[$index] = [$nextStart, $nextEnd];
// today:
$todayStart = app('navigation')->startOfPeriod($today, $viewRange);
$todayEnd = app('navigation')->endOfPeriod($todayStart, $viewRange);
if ($todayStart->ne($start) || $todayEnd->ne($end)) {
$ranges[ucfirst(strval(trans('firefly.today')))] = [$todayStart, $todayEnd];
}
// everything
$index = strval(trans('firefly.everything'));
$ranges[$index] = [$first, new Carbon];

View File

@ -81,7 +81,7 @@ class AutoCompleteController extends Controller
$set = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
$filtered = $set->filter(
function (Account $account) {
if ($account->active) {
if ($account->active === true) {
return $account;
}
@ -138,7 +138,7 @@ class AutoCompleteController extends Controller
$set = $repository->getAccountsByType([AccountType::REVENUE]);
$filtered = $set->filter(
function (Account $account) {
if ($account->active) {
if ($account->active === true) {
return $account;
}

View File

@ -36,6 +36,8 @@ class FrontpageController extends Controller
* @param PiggyBankRepositoryInterface $repository
*
* @return \Illuminate\Http\JsonResponse
*
* @throws \Throwable
*/
public function piggyBanks(PiggyBankRepositoryInterface $repository)
{

View File

@ -32,6 +32,8 @@ use Response;
class IntroController
{
/**
* Get the intro steps. There are currently no specific routes with an outro step.
*
* @param string $route
* @param string $specificPage
*
@ -39,12 +41,16 @@ class IntroController
*/
public function getIntroSteps(string $route, string $specificPage = '')
{
Log::debug(sprintf('getIntroSteps for route "%s" and page "%s"', $route, $specificPage));
$steps = $this->getBasicSteps($route);
$specificSteps = $this->getSpecificSteps($route, $specificPage);
if (0 === count($specificSteps)) {
Log::debug(sprintf('No specific steps for route "%s" and page "%s"', $route, $specificPage));
return Response::json($steps);
}
if ($this->hasOutroStep($route)) {
// @codeCoverageIgnoreStart
// save last step:
$lastStep = $steps[count($steps) - 1];
// remove last step:
@ -52,6 +58,7 @@ class IntroController
// merge arrays and add last step again
$steps = array_merge($steps, $specificSteps);
$steps[] = $lastStep;
// @codeCoverageIgnoreEnd
}
if (!$this->hasOutroStep($route)) {
$steps = array_merge($steps, $specificSteps);
@ -68,13 +75,16 @@ class IntroController
public function hasOutroStep(string $route): bool
{
$routeKey = str_replace('.', '_', $route);
Log::debug(sprintf('Has outro step for route %s', $routeKey));
$elements = config(sprintf('intro.%s', $routeKey));
if (!is_array($elements)) {
return false;
}
$keys = array_keys($elements);
Log::debug('Elements is array', $elements);
Log::debug('Keys is', array_keys($elements));
Log::debug(sprintf('Keys has "outro": %s', var_export(in_array('outro', array_keys($elements)), true)));
return in_array('outro', $keys);
return in_array('outro', array_keys($elements));
}
/**
@ -135,6 +145,7 @@ class IntroController
$steps[] = $currentStep;
}
}
Log::debug(sprintf('Total basic steps for %s is %d', $routeKey, count($steps)));
return $steps;
}
@ -148,6 +159,7 @@ class IntroController
private function getSpecificSteps(string $route, string $specificPage): array
{
$steps = [];
$routeKey = '';
// user is on page with specific instructions:
if (strlen($specificPage) > 0) {
@ -165,6 +177,7 @@ class IntroController
}
}
}
Log::debug(sprintf('Total specific steps for route "%s" and page "%s" (routeKey is "%s") is %d', $route, $specificPage, $routeKey, count($steps)));
return $steps;
}

View File

@ -46,6 +46,8 @@ class JsonController extends Controller
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
*
* @throws \Throwable
*/
public function action(Request $request)
{
@ -120,6 +122,8 @@ class JsonController extends Controller
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
*
* @throws \Throwable
*/
public function trigger(Request $request)
{

View File

@ -56,8 +56,8 @@ class NewUserController extends Controller
*/
public function index(AccountRepositoryInterface $repository)
{
View::share('title', trans('firefly.welcome'));
View::share('mainTitleIcon', 'fa-fire');
app('view')->share('title', trans('firefly.welcome'));
app('view')->share('mainTitleIcon', 'fa-fire');
$types = config('firefly.accountTypesByIdentifier.asset');
$count = $repository->count($types);
@ -114,7 +114,7 @@ class NewUserController extends Controller
'virtualBalance' => 0,
'active' => true,
'accountRole' => 'defaultAsset',
'openingBalance' => round($request->input('bank_balance'), 12),
'openingBalance' => $request->input('bank_balance'),
'openingBalanceDate' => new Carbon,
'currency_id' => intval($request->input('amount_currency_id_bank_balance')),
];
@ -139,7 +139,7 @@ class NewUserController extends Controller
'virtualBalance' => 0,
'active' => true,
'accountRole' => 'savingAsset',
'openingBalance' => round($request->input('savings_balance'), 12),
'openingBalance' => $request->input('savings_balance'),
'openingBalanceDate' => new Carbon,
'currency_id' => intval($request->input('amount_currency_id_bank_balance')),
];

View File

@ -30,7 +30,7 @@ use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Log;
use Preferences;
use Response;
@ -52,8 +52,8 @@ class PiggyBankController extends Controller
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.piggyBanks'));
View::share('mainTitleIcon', 'fa-sort-amount-asc');
app('view')->share('title', trans('firefly.piggyBanks'));
app('view')->share('mainTitleIcon', 'fa-sort-amount-asc');
return $next($request);
}
@ -120,8 +120,6 @@ class PiggyBankController extends Controller
$this->rememberPreviousUri('piggy-banks.create.uri');
}
Session::forget('piggy-banks.create.fromStore');
Session::flash('gaEventCategory', 'piggy-banks');
Session::flash('gaEventAction', 'create');
return view('piggy-banks.create', compact('accounts', 'subTitle', 'subTitleIcon'));
}
@ -137,8 +135,6 @@ class PiggyBankController extends Controller
// put previous url in session
$this->rememberPreviousUri('piggy-banks.delete.uri');
Session::flash('gaEventCategory', 'piggy-banks');
Session::flash('gaEventAction', 'delete');
return view('piggy-banks.delete', compact('piggyBank', 'subTitle'));
}
@ -188,8 +184,6 @@ class PiggyBankController extends Controller
'note' => null === $note ? '' : $note->text,
];
Session::flash('preFilled', $preFilled);
Session::flash('gaEventCategory', 'piggy-banks');
Session::flash('gaEventAction', 'edit');
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('piggy-banks.edit.fromUpdate')) {
@ -205,17 +199,19 @@ class PiggyBankController extends Controller
*
* @return View
*/
public function index(PiggyBankRepositoryInterface $piggyRepository)
public function index(Request $request, PiggyBankRepositoryInterface $piggyRepository)
{
/** @var Collection $piggyBanks */
$piggyBanks = $piggyRepository->getPiggyBanks();
$collection = $piggyRepository->getPiggyBanks();
$total = $collection->count();
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
/** @var Carbon $end */
$end = session('end', Carbon::now()->endOfMonth());
$accounts = [];
Log::debug('Looping piggues');
/** @var PiggyBank $piggyBank */
foreach ($piggyBanks as $piggyBank) {
foreach ($collection as $piggyBank) {
$piggyBank->savedSoFar = $piggyBank->currentRelevantRep()->currentamount ?? '0';
$piggyBank->percentage = 0 !== bccomp('0', $piggyBank->savedSoFar) ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0;
$piggyBank->leftToSave = bcsub($piggyBank->targetamount, strval($piggyBank->savedSoFar));
@ -242,6 +238,11 @@ class PiggyBankController extends Controller
}
}
// paginate piggy banks
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
$piggyBanks = new LengthAwarePaginator($collection, $total, $pageSize, $page);
$piggyBanks->setPath(route('piggy-banks.index'));
return view('piggy-banks.index', compact('piggyBanks', 'accounts'));
}
@ -401,7 +402,6 @@ class PiggyBankController extends Controller
}
$piggyBank = $repository->store($data);
Session::flash('success', strval(trans('firefly.stored_piggy_bank', ['name' => $piggyBank->name])));
Preferences::mark();

View File

@ -32,6 +32,7 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\Binder\AccountList;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use InvalidArgumentException;
use Response;
use View;
@ -81,6 +82,11 @@ class ReportController extends Controller
* @return \Illuminate\Http\JsonResponse
*
* @throws FireflyException
* @throws \Throwable
* @throws \Throwable
* @throws \Throwable
* @throws \Throwable
* @throws \Throwable
*/
public function general(Request $request)
{
@ -119,6 +125,7 @@ class ReportController extends Controller
* @return string
*
* @throws FireflyException
* @throws \Throwable
*/
private function balanceAmount(array $attributes): string
{
@ -156,7 +163,7 @@ class ReportController extends Controller
*
* @return string
*
* @throws FireflyException
* @throws \Throwable
*/
private function budgetSpentAmount(array $attributes): string
{
@ -170,11 +177,11 @@ class ReportController extends Controller
/**
* Returns all expenses in category in range.
*
* @param $attributes
* @param array $attributes
*
* @return string
*
* @throws FireflyException
* @throws \Throwable
*/
private function categoryEntry(array $attributes): string
{
@ -188,11 +195,11 @@ class ReportController extends Controller
/**
* Returns all the expenses that went to the given expense account.
*
* @param $attributes
* @param array $attributes
*
* @return string
*
* @throws FireflyException
* @throws \Throwable
*/
private function expenseEntry(array $attributes): string
{
@ -206,11 +213,11 @@ class ReportController extends Controller
/**
* Returns all the incomes that went to the given asset account.
*
* @param $attributes
* @param array $attributes
*
* @return string
*
* @throws FireflyException
* @throws \Throwable
*/
private function incomeEntry(array $attributes): string
{
@ -231,7 +238,7 @@ class ReportController extends Controller
private function parseAttributes(array $attributes): array
{
$attributes['location'] = $attributes['location'] ?? '';
$attributes['accounts'] = AccountList::routeBinder($attributes['accounts'] ?? '', '');
$attributes['accounts'] = AccountList::routeBinder($attributes['accounts'] ?? '', new Route('get', '', []));
try {
$attributes['startDate'] = Carbon::createFromFormat('Ymd', $attributes['startDate']);
} catch (InvalidArgumentException $e) {

View File

@ -26,8 +26,8 @@ use FireflyIII\Http\Requests\TokenFormRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Google2FA;
use Illuminate\Http\Request;
use PragmaRX\Google2FA\Contracts\Google2FA;
use Preferences;
use Session;
use View;
@ -46,8 +46,8 @@ class PreferencesController extends Controller
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.preferences'));
View::share('mainTitleIcon', 'fa-gear');
app('view')->share('title', trans('firefly.preferences'));
app('view')->share('mainTitleIcon', 'fa-gear');
return $next($request);
}
@ -55,22 +55,23 @@ class PreferencesController extends Controller
}
/**
* @param Google2FA $google2fa
*
* @return View
*/
public function code(Google2FA $google2fa)
public function code()
{
$domain = $this->getDomain();
$secret = $google2fa->generateSecretKey();
$secret = Google2FA::generateSecretKey();
Session::flash('two-factor-secret', $secret);
$image = $google2fa->getQRCodeInline($domain, auth()->user()->email, $secret, 200);
$image = Google2FA::getQRCodeInline($domain, auth()->user()->email, $secret, 200);
return view('preferences.code', compact('image'));
}
/**
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*
* @throws \Exception
* @throws \Exception
*/
public function deleteCode()
{
@ -94,7 +95,7 @@ class PreferencesController extends Controller
$viewRange = $viewRangePref->data;
$frontPageAccounts = Preferences::get('frontPageAccounts', []);
$language = Preferences::get('language', config('firefly.default_language', 'en_US'))->data;
$transactionPageSize = Preferences::get('transactionPageSize', 50)->data;
$listPageSize = Preferences::get('listPageSize', 50)->data;
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
$showDeps = Preferences::get('showDepositsFrontpage', false)->data;
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
@ -112,7 +113,7 @@ class PreferencesController extends Controller
'tjOptionalFields',
'viewRange',
'customFiscalYear',
'transactionPageSize',
'listPageSize',
'fiscalYearStart',
'is2faEnabled',
'has2faSecret',
@ -173,10 +174,10 @@ class PreferencesController extends Controller
Preferences::set('showDepositsFrontpage', $showDepositsFrontpage);
// save page size:
Preferences::set('transactionPageSize', 50);
$transactionPageSize = intval($request->get('transactionPageSize'));
if ($transactionPageSize > 0 && $transactionPageSize < 1337) {
Preferences::set('transactionPageSize', $transactionPageSize);
Preferences::set('listPageSize', 50);
$listPageSize = intval($request->get('listPageSize'));
if ($listPageSize > 0 && $listPageSize < 1337) {
Preferences::set('listPageSize', $listPageSize);
}
$twoFactorAuthEnabled = false;

View File

@ -26,7 +26,8 @@ use Auth;
use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Http\Middleware\IsLimitedUser;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Middleware\IsSandStormUser;
use FireflyIII\Http\Requests\DeleteAccountFormRequest;
use FireflyIII\Http\Requests\EmailFormRequest;
use FireflyIII\Http\Requests\ProfileFormRequest;
@ -56,13 +57,14 @@ class ProfileController extends Controller
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.profile'));
View::share('mainTitleIcon', 'fa-user');
app('view')->share('title', trans('firefly.profile'));
app('view')->share('mainTitleIcon', 'fa-user');
return $next($request);
}
);
$this->middleware(IsLimitedUser::class)->except(['confirmEmailChange', 'undoEmailChange']);
$this->middleware(IsDemoUser::class)->except(['index']);
$this->middleware(IsSandStormUser::class)->except('index');
}
/**
@ -91,30 +93,33 @@ class ProfileController extends Controller
}
/**
* @param UserRepositoryInterface $repository
* @param string $token
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*
* @throws FireflyException
*/
public function confirmEmailChange(string $token)
public function confirmEmailChange(UserRepositoryInterface $repository, string $token)
{
// find preference with this token value.
$set = Preferences::findByName('email_change_confirm_token');
$user = null;
Log::debug(sprintf('Found %d preferences', $set->count()));
/** @var Preference $preference */
foreach ($set as $preference) {
if ($preference->data === $token) {
Log::debug('Found user');
$user = $preference->user;
}
}
// update user to clear blocked and blocked_code.
if (null === $user) {
Log::debug('Found no user');
throw new FireflyException('Invalid token.');
}
$user->blocked = 0;
$user->blocked_code = '';
$user->save();
Log::debug('Will unblock user.');
$repository->unblockUser($user);
// return to login.
Session::flash('success', strval(trans('firefly.login_with_new_email')));
@ -172,7 +177,7 @@ class ProfileController extends Controller
$existing = $repository->findByEmail($newEmail);
if (null !== $existing) {
// force user logout.
$this->guard()->logout();
Auth::guard()->logout();
$request->session()->invalidate();
Session::flash('success', strval(trans('firefly.email_changed')));
@ -241,14 +246,11 @@ class ProfileController extends Controller
Session::flush();
$repository->destroy($user);
Session::flash('gaEventCategory', 'user');
Session::flash('gaEventAction', 'delete-account');
return redirect(route('index'));
}
/**
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function regenerate()
{
@ -260,6 +262,7 @@ class ProfileController extends Controller
}
/**
* @param UserRepositoryInterface $repository
* @param string $token
* @param string $hash
*
@ -267,7 +270,7 @@ class ProfileController extends Controller
*
* @throws FireflyException
*/
public function undoEmailChange(string $token, string $hash)
public function undoEmailChange(UserRepositoryInterface $repository, string $token, string $hash)
{
// find preference with this token value.
$set = Preferences::findByName('email_change_undo_token');
@ -285,6 +288,7 @@ class ProfileController extends Controller
// found user.
// which email address to return to?
$set = Preferences::beginsWith($user, 'previous_email_');
/** @var string $match */
$match = null;
foreach ($set as $entry) {
$hashed = hash('sha256', $entry->data);
@ -297,10 +301,9 @@ class ProfileController extends Controller
throw new FireflyException('Invalid token.');
}
// change user back
$user->email = $match;
$user->blocked = 0;
$user->blocked_code = '';
$user->save();
// now actually update user:
$repository->changeEmail($user, $match);
$repository->unblockUser($user);
// return to login.
Session::flash('success', strval(trans('firefly.login_with_old_email')));

View File

@ -39,6 +39,8 @@ class AccountController extends Controller
* @param Carbon $end
*
* @return mixed|string
*
* @throws \Throwable
*/
public function general(Collection $accounts, Carbon $start, Carbon $end)
{

View File

@ -40,6 +40,8 @@ class BalanceController extends Controller
* @param Carbon $end
*
* @return mixed|string
*
* @throws \Throwable
*/
public function general(BalanceReportHelperInterface $helper, Collection $accounts, Carbon $start, Carbon $end)
{

View File

@ -28,7 +28,6 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Navigation;
/**
* Class BudgetController.
@ -42,6 +41,8 @@ class BudgetController extends Controller
* @param Carbon $end
*
* @return mixed|string
*
* @throws \Throwable
*/
public function general(BudgetReportHelperInterface $helper, Collection $accounts, Carbon $start, Carbon $end)
{
@ -69,6 +70,8 @@ class BudgetController extends Controller
* @param Carbon $end
*
* @return mixed|string
*
* @throws \Throwable
*/
public function period(Collection $accounts, Carbon $start, Carbon $end)
{
@ -88,7 +91,7 @@ class BudgetController extends Controller
$data = $repository->getBudgetPeriodReport($budgets, $accounts, $start, $end);
$data[0] = $repository->getNoBudgetPeriodReport($accounts, $start, $end); // append report data for "no budget"
$report = $this->filterBudgetPeriodReport($data);
$periods = Navigation::listOfPeriods($start, $end);
$periods = app('navigation')->listOfPeriods($start, $end);
$result = view('reports.partials.budget-period', compact('report', 'periods'))->render();
$cache->store($result);

View File

@ -28,7 +28,6 @@ use FireflyIII\Models\Category;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Navigation;
/**
* Class CategoryController.
@ -41,6 +40,8 @@ class CategoryController extends Controller
* @param Carbon $end
*
* @return mixed|string
*
* @throws \Throwable
*/
public function expenses(Collection $accounts, Carbon $start, Carbon $end)
{
@ -58,7 +59,7 @@ class CategoryController extends Controller
$data = $repository->periodExpenses($categories, $accounts, $start, $end);
$data[0] = $repository->periodExpensesNoCategory($accounts, $start, $end);
$report = $this->filterReport($data);
$periods = Navigation::listOfPeriods($start, $end);
$periods = app('navigation')->listOfPeriods($start, $end);
$result = view('reports.partials.category-period', compact('report', 'periods'))->render();
$cache->store($result);
@ -72,6 +73,8 @@ class CategoryController extends Controller
* @param Collection $accounts
*
* @return string
*
* @throws \Throwable
*/
public function income(Collection $accounts, Carbon $start, Carbon $end)
{
@ -89,7 +92,7 @@ class CategoryController extends Controller
$data = $repository->periodIncome($categories, $accounts, $start, $end);
$data[0] = $repository->periodIncomeNoCategory($accounts, $start, $end);
$report = $this->filterReport($data);
$periods = Navigation::listOfPeriods($start, $end);
$periods = app('navigation')->listOfPeriods($start, $end);
$result = view('reports.partials.category-period', compact('report', 'periods'))->render();
$cache->store($result);
@ -105,6 +108,8 @@ class CategoryController extends Controller
* @return mixed|string
*
* @internal param ReportHelperInterface $helper
*
* @throws \Throwable
*/
public function operations(Collection $accounts, Carbon $start, Carbon $end)
{

View File

@ -0,0 +1,591 @@
<?php
/**
* ExpenseController.php
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
/**
* Class ExpenseController
*/
class ExpenseController extends Controller
{
/** @var AccountRepositoryInterface */
protected $accountRepository;
/**
* Constructor for ExpenseController
*/
public function __construct()
{
parent::__construct();
// translations:
$this->middleware(
function ($request, $next) {
$this->accountRepository = app(AccountRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Generates the overview per budget.
*
* @param Collection $accounts
* @param Collection $expense
* @param Carbon $start
* @param Carbon $end
*
* @return string
*
* @throws \Throwable
*/
public function budget(Collection $accounts, Collection $expense, Carbon $start, Carbon $end)
{
// Properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('expense-budget');
$cache->addProperty($accounts->pluck('id')->toArray());
$cache->addProperty($expense->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$combined = $this->combineAccounts($expense);
$all = new Collection;
foreach ($combined as $name => $combi) {
$all = $all->merge($combi);
}
// now find spent / earned:
$spent = $this->spentByBudget($accounts, $all, $start, $end);
// join arrays somehow:
$together = [];
foreach ($spent as $categoryId => $spentInfo) {
if (!isset($together[$categoryId])) {
$together[$categoryId]['spent'] = $spentInfo;
$together[$categoryId]['budget'] = $spentInfo['name'];
$together[$categoryId]['grand_total'] = '0';
}
$together[$categoryId]['grand_total'] = bcadd($spentInfo['grand_total'], $together[$categoryId]['grand_total']);
}
unset($spentInfo);
$result = view('reports.partials.exp-budgets', compact('together'))->render();
$cache->store($result);
return $result;
}
/**
* Generates the overview per category (spent and earned).
*
* @param Collection $accounts
* @param Collection $expense
* @param Carbon $start
* @param Carbon $end
*
* @return string
*
* @throws \Throwable
*/
public function category(Collection $accounts, Collection $expense, Carbon $start, Carbon $end)
{
// Properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('expense-category');
$cache->addProperty($accounts->pluck('id')->toArray());
$cache->addProperty($expense->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$combined = $this->combineAccounts($expense);
$all = new Collection;
foreach ($combined as $name => $combi) {
$all = $all->merge($combi);
}
// now find spent / earned:
$spent = $this->spentByCategory($accounts, $all, $start, $end);
$earned = $this->earnedByCategory($accounts, $all, $start, $end);
// join arrays somehow:
$together = [];
foreach ($spent as $categoryId => $spentInfo) {
if (!isset($together[$categoryId])) {
$together[$categoryId]['spent'] = $spentInfo;
$together[$categoryId]['category'] = $spentInfo['name'];
$together[$categoryId]['grand_total'] = '0';
}
$together[$categoryId]['grand_total'] = bcadd($spentInfo['grand_total'], $together[$categoryId]['grand_total']);
}
unset($spentInfo);
foreach ($earned as $categoryId => $earnedInfo) {
if (!isset($together[$categoryId])) {
$together[$categoryId]['earned'] = $earnedInfo;
$together[$categoryId]['category'] = $earnedInfo['name'];
$together[$categoryId]['grand_total'] = '0';
}
$together[$categoryId]['grand_total'] = bcadd($earnedInfo['grand_total'], $together[$categoryId]['grand_total']);
}
$result = view('reports.partials.exp-categories', compact('together'))->render();
$cache->store($result);
return $result;
}
/**
* Overview of spending
*
* @param Collection $accounts
* @param Collection $expense
* @param Carbon $start
* @param Carbon $end
*
* @return array|mixed|string
*
* @throws \Throwable
*/
public function spent(Collection $accounts, Collection $expense, Carbon $start, Carbon $end)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('expense-spent');
$cache->addProperty($accounts->pluck('id')->toArray());
$cache->addProperty($expense->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$combined = $this->combineAccounts($expense);
$result = [];
foreach ($combined as $name => $combi) {
/**
* @var string
* @var Collection $combi
*/
$spent = $this->spentInPeriod($accounts, $combi, $start, $end);
$earned = $this->earnedInPeriod($accounts, $combi, $start, $end);
$result[$name] = [
'spent' => $spent,
'earned' => $earned,
];
}
$result = view('reports.partials.exp-not-grouped', compact('result'))->render();
$cache->store($result);
return $result;
// for period, get spent and earned for each account (by name)
}
/**
* @param Collection $accounts
* @param Collection $expense
* @param Carbon $start
* @param Carbon $end
*
* @return string
*
* @throws \Throwable
*/
public function topExpense(Collection $accounts, Collection $expense, Carbon $start, Carbon $end)
{
// Properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('expense-budget');
$cache->addProperty($accounts->pluck('id')->toArray());
$cache->addProperty($expense->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$combined = $this->combineAccounts($expense);
$all = new Collection;
foreach ($combined as $name => $combi) {
$all = $all->merge($combi);
}
// get all expenses in period:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($accounts);
$collector->setOpposingAccounts($all);
$set = $collector->getJournals();
$sorted = $set->sortBy(
function (Transaction $transaction) {
return floatval($transaction->transaction_amount);
}
);
$result = view('reports.partials.top-transactions', compact('sorted'))->render();
$cache->store($result);
return $result;
}
/**
* @param Collection $accounts
* @param Collection $expense
* @param Carbon $start
* @param Carbon $end
*
* @return mixed|string
*
* @throws \Throwable
*/
public function topIncome(Collection $accounts, Collection $expense, Carbon $start, Carbon $end)
{
// Properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('expense-budget');
$cache->addProperty($accounts->pluck('id')->toArray());
$cache->addProperty($expense->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
$combined = $this->combineAccounts($expense);
$all = new Collection;
foreach ($combined as $name => $combi) {
$all = $all->merge($combi);
}
// get all expenses in period:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($accounts);
$collector->setOpposingAccounts($all);
$set = $collector->getJournals();
$sorted = $set->sortByDesc(
function (Transaction $transaction) {
return floatval($transaction->transaction_amount);
}
);
$result = view('reports.partials.top-transactions', compact('sorted'))->render();
$cache->store($result);
return $result;
}
/**
* @param Collection $accounts
*
* @return array
*/
protected function combineAccounts(Collection $accounts): array
{
$combined = [];
/** @var Account $expenseAccount */
foreach ($accounts as $expenseAccount) {
$collection = new Collection;
$collection->push($expenseAccount);
$revenue = $this->accountRepository->findByName($expenseAccount->name, [AccountType::REVENUE]);
if (!is_null($revenue->id)) {
$collection->push($revenue);
}
$combined[$expenseAccount->name] = $collection;
}
return $combined;
}
/**
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function earnedByCategory(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withCategoryInformation();
$set = $collector->getJournals();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$categoryName = $transaction->transaction_category_name;
$categoryId = intval($transaction->transaction_category_id);
// if null, grab from journal:
if (0 === $categoryId) {
$categoryName = $transaction->transaction_journal_category_name;
$categoryId = intval($transaction->transaction_journal_category_id);
}
if (0 !== $categoryId) {
$categoryName = app('steam')->tryDecrypt($categoryName);
}
// if not set, set to zero:
if (!isset($sum[$categoryId][$currencyId])) {
$sum[$categoryId] = [
'grand_total' => '0',
'name' => $categoryName,
'per_currency' => [
$currencyId => [
'sum' => '0',
'category' => [
'id' => $categoryId,
'name' => $categoryName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
],
],
];
}
// add amount
$sum[$categoryId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$categoryId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
);
$sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $transaction->transaction_amount);
}
return $sum;
}
/**
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function earnedInPeriod(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($assets);
$collector->setOpposingAccounts($opposing);
$set = $collector->getJournals();
$sum = [
'grand_sum' => '0',
'per_currency' => [],
];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
// if not set, set to zero:
if (!isset($sum['per_currency'][$currencyId])) {
$sum['per_currency'][$currencyId] = [
'sum' => '0',
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
];
}
// add amount
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $transaction->transaction_amount);
$sum['grand_sum'] = bcadd($sum['grand_sum'], $transaction->transaction_amount);
}
return $sum;
}
/**
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function spentByBudget(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withBudgetInformation();
$set = $collector->getJournals();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$budgetName = $transaction->transaction_budget_name;
$budgetId = intval($transaction->transaction_budget_id);
// if null, grab from journal:
if (0 === $budgetId) {
$budgetName = $transaction->transaction_journal_budget_name;
$budgetId = intval($transaction->transaction_journal_budget_id);
}
if (0 !== $budgetId) {
$budgetName = app('steam')->tryDecrypt($budgetName);
}
// if not set, set to zero:
if (!isset($sum[$budgetId][$currencyId])) {
$sum[$budgetId] = [
'grand_total' => '0',
'name' => $budgetName,
'per_currency' => [
$currencyId => [
'sum' => '0',
'budget' => [
'id' => $budgetId,
'name' => $budgetName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
],
],
];
}
// add amount
$sum[$budgetId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$budgetId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
);
$sum[$budgetId]['grand_total'] = bcadd($sum[$budgetId]['grand_total'], $transaction->transaction_amount);
}
return $sum;
}
/**
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function spentByCategory(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withCategoryInformation();
$set = $collector->getJournals();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$categoryName = $transaction->transaction_category_name;
$categoryId = intval($transaction->transaction_category_id);
// if null, grab from journal:
if (0 === $categoryId) {
$categoryName = $transaction->transaction_journal_category_name;
$categoryId = intval($transaction->transaction_journal_category_id);
}
if (0 !== $categoryId) {
$categoryName = app('steam')->tryDecrypt($categoryName);
}
// if not set, set to zero:
if (!isset($sum[$categoryId][$currencyId])) {
$sum[$categoryId] = [
'grand_total' => '0',
'name' => $categoryName,
'per_currency' => [
$currencyId => [
'sum' => '0',
'category' => [
'id' => $categoryId,
'name' => $categoryName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
],
],
];
}
// add amount
$sum[$categoryId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$categoryId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
);
$sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $transaction->transaction_amount);
}
return $sum;
}
/**
* @param Collection $assets
* @param Collection $opposing
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
protected function spentInPeriod(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array
{
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing);
$set = $collector->getJournals();
$sum = [
'grand_sum' => '0',
'per_currency' => [],
];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
// if not set, set to zero:
if (!isset($sum['per_currency'][$currencyId])) {
$sum['per_currency'][$currencyId] = [
'sum' => '0',
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
],
];
}
// add amount
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $transaction->transaction_amount);
$sum['grand_sum'] = bcadd($sum['grand_sum'], $transaction->transaction_amount);
}
return $sum;
}
}

View File

@ -40,6 +40,8 @@ class OperationsController extends Controller
* @param Carbon $end
*
* @return mixed|string
*
* @throws \Throwable
*/
public function expenses(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end)
{
@ -67,6 +69,8 @@ class OperationsController extends Controller
* @param Carbon $end
*
* @return string
*
* @throws \Throwable
*/
public function income(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end)
{
@ -95,6 +99,8 @@ class OperationsController extends Controller
* @param Carbon $end
*
* @return mixed|string
*
* @throws \Throwable
*/
public function operations(AccountTaskerInterface $tasker, Collection $accounts, Carbon $start, Carbon $end)
{

View File

@ -59,8 +59,8 @@ class ReportController extends Controller
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.reports'));
View::share('mainTitleIcon', 'fa-line-chart');
app('view')->share('title', trans('firefly.reports'));
app('view')->share('mainTitleIcon', 'fa-line-chart');
View::share('subTitleIcon', 'fa-calendar');
return $next($request);
@ -70,10 +70,47 @@ class ReportController extends Controller
/**
* @param Collection $accounts
* @param Collection $expense
* @param Carbon $start
* @param Carbon $end
*
* @return string
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function accountReport(Collection $accounts, Collection $expense, Carbon $start, Carbon $end)
{
if ($end < $start) {
return view('error')->with('message', trans('firefly.end_after_start_date')); // @codeCoverageIgnore
}
if ($start < session('first')) {
$start = session('first');
}
View::share(
'subTitle', trans(
'firefly.report_account',
['start' => $start->formatLocalized($this->monthFormat), 'end' => $end->formatLocalized($this->monthFormat)]
)
);
$generator = ReportGeneratorFactory::reportGenerator('Account', $start, $end);
$generator->setAccounts($accounts);
$generator->setExpense($expense);
$result = $generator->generate();
return $result;
}
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function auditReport(Collection $accounts, Carbon $start, Carbon $end)
{
@ -109,6 +146,8 @@ class ReportController extends Controller
* @param Carbon $end
*
* @return string
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function budgetReport(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end)
{
@ -145,6 +184,8 @@ class ReportController extends Controller
* @param Carbon $end
*
* @return string
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function categoryReport(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
{
@ -180,6 +221,8 @@ class ReportController extends Controller
* @param Carbon $end
*
* @return string
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function defaultReport(Collection $accounts, Carbon $start, Carbon $end)
{
@ -230,6 +273,8 @@ class ReportController extends Controller
* @param string $reportType
*
* @return mixed
*
* @throws \Throwable
*/
public function options(string $reportType)
{
@ -246,6 +291,9 @@ class ReportController extends Controller
case 'tag':
$result = $this->tagReportOptions();
break;
case 'account':
$result = $this->accountReportOptions();
break;
}
return Response::json(['html' => $result]);
@ -255,6 +303,8 @@ class ReportController extends Controller
* @param ReportFormRequest $request
*
* @return RedirectResponse|\Illuminate\Routing\Redirector
*
* @throws \FireflyIII\Exceptions\FireflyException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function postIndex(ReportFormRequest $request)
@ -267,6 +317,7 @@ class ReportController extends Controller
$categories = join(',', $request->getCategoryList()->pluck('id')->toArray());
$budgets = join(',', $request->getBudgetList()->pluck('id')->toArray());
$tags = join(',', $request->getTagList()->pluck('tag')->toArray());
$expense = join(',', $request->getExpenseList()->pluck('id')->toArray());
$uri = route('reports.index');
if (0 === $request->getAccountList()->count()) {
@ -314,6 +365,9 @@ class ReportController extends Controller
case 'tag':
$uri = route('reports.report.tag', [$accounts, $tags, $start, $end]);
break;
case 'account':
$uri = route('reports.report.account', [$accounts, $expense, $start, $end]);
break;
}
return redirect($uri);
@ -326,6 +380,8 @@ class ReportController extends Controller
* @param Carbon $end
*
* @return string
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function tagReport(Collection $accounts, Collection $tags, Carbon $start, Carbon $end)
{
@ -357,6 +413,32 @@ class ReportController extends Controller
/**
* @return string
*
* @throws \Throwable
*/
private function accountReportOptions(): string
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$expense = $repository->getActiveAccountsByType([AccountType::EXPENSE]);
$revenue = $repository->getActiveAccountsByType([AccountType::REVENUE]);
$set = new Collection;
$names = $revenue->pluck('name')->toArray();
foreach ($expense as $exp) {
if (in_array($exp->name, $names)) {
$set->push($exp);
}
}
$result = view('reports.options.account', compact('set'))->render();
return $result;
}
/**
* @return string
*
* @throws \Throwable
*/
private function budgetReportOptions(): string
{
@ -370,6 +452,8 @@ class ReportController extends Controller
/**
* @return string
*
* @throws \Throwable
*/
private function categoryReportOptions(): string
{
@ -383,6 +467,8 @@ class ReportController extends Controller
/**
* @return string
*
* @throws \Throwable
*/
private function noReportOptions(): string
{
@ -391,6 +477,8 @@ class ReportController extends Controller
/**
* @return string
*
* @throws \Throwable
*/
private function tagReportOptions(): string
{

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