mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Merge branch 'release/4.6.13'
This commit is contained in:
commit
3562ec1f79
14
.htaccess
Normal file
14
.htaccess
Normal file
@ -0,0 +1,14 @@
|
||||
# Optional: force HTTPS:
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteEngine On
|
||||
# RewriteCond %{HTTPS} off
|
||||
# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
|
||||
# </IfModule>
|
||||
|
||||
# To hide directory listing
|
||||
Options All -Indexes
|
||||
|
||||
# To prevent access to .env and other files
|
||||
<Files .*>
|
||||
Deny from all
|
||||
</Files>
|
@ -1,3 +1,21 @@
|
||||
# 4.6.13
|
||||
- [Issue 1074](https://github.com/firefly-iii/firefly-iii/issues/1074), suggested by [MacPaille](https://github.com/MacPaille)
|
||||
- [Issue 1077](https://github.com/firefly-iii/firefly-iii/issues/1077), suggested by [wtercato](https://github.com/wtercato)
|
||||
- Bulk edit of transactions thanks to [vicmosin](https://github.com/vicmosin) ([issue 1078](https://github.com/firefly-iii/firefly-iii/issues/1078))
|
||||
- Support for Turkish.
|
||||
- [Issue 1090](https://github.com/firefly-iii/firefly-iii/issues/1090), suggested by [Findus23](https://github.com/Findus23)
|
||||
- [Issue 1097](https://github.com/firefly-iii/firefly-iii/issues/1097), suggested by [kelvinhammond](https://github.com/kelvinhammond)
|
||||
- [Issue 1093](https://github.com/firefly-iii/firefly-iii/issues/1093), suggested by [jinformatique](https://github.com/jinformatique)
|
||||
- [Issue 1098](https://github.com/firefly-iii/firefly-iii/issues/1098), suggested by [Nik-vr](https://github.com/Nik-vr)
|
||||
- [Issue 972](https://github.com/firefly-iii/firefly-iii/issues/972), reported by [pjotrvdh](https://github.com/pjotrvdh)
|
||||
- [Issue 1079](https://github.com/firefly-iii/firefly-iii/issues/1079), reported by [gavu](https://github.com/gavu)
|
||||
- [Issue 1080](https://github.com/firefly-iii/firefly-iii/issues/1080), reported by [zjean](https://github.com/zjean)
|
||||
- [Issue 1083](https://github.com/firefly-iii/firefly-iii/issues/1083), reported by [skuzzle](https://github.com/skuzzle)
|
||||
- [Issue 1085](https://github.com/firefly-iii/firefly-iii/issues/1085), reported by [nicoschreiner](https://github.com/nicoschreiner)
|
||||
- [Issue 1087](https://github.com/firefly-iii/firefly-iii/issues/1087), reported by [4oo4](https://github.com/4oo4)
|
||||
- [Issue 1089](https://github.com/firefly-iii/firefly-iii/issues/1089), reported by [robin5210](https://github.com/robin5210)
|
||||
- [Issue 1092](https://github.com/firefly-iii/firefly-iii/issues/1092), reported by [kelvinhammond](https://github.com/kelvinhammond)
|
||||
- [Issue 1096](https://github.com/firefly-iii/firefly-iii/issues/1096), reported by [wtercato](https://github.com/wtercato)
|
||||
|
||||
# 4.6.12
|
||||
- Support for Indonesian.
|
||||
|
@ -200,14 +200,22 @@ lib/x86_64-linux-gnu/libwrap.so.0.7.6
|
||||
lib/x86_64-linux-gnu/libz.so.1
|
||||
lib/x86_64-linux-gnu/libz.so.1.2.8
|
||||
lib64/ld-linux-x86-64.so.2
|
||||
opt/app/.dockerignore
|
||||
opt/app/.env
|
||||
opt/app/.env.docker
|
||||
opt/app/.env.example
|
||||
opt/app/.env.heroku
|
||||
opt/app/.env.sandstorm
|
||||
opt/app/.gitattributes
|
||||
opt/app/.htaccess
|
||||
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
|
||||
@ -231,9 +239,7 @@ opt/app/.sandstorm/setup.sh
|
||||
opt/app/.sandstorm/stack
|
||||
opt/app/CHANGELOG.md
|
||||
opt/app/CODE_OF_CONDUCT.md
|
||||
opt/app/Dockerfile
|
||||
opt/app/LICENSE
|
||||
opt/app/Procfile
|
||||
opt/app/README.md
|
||||
opt/app/app.json
|
||||
opt/app/app/Console/Commands/CreateExport.php
|
||||
@ -359,6 +365,7 @@ opt/app/app/Http/Controllers/Chart/ReportController.php
|
||||
opt/app/app/Http/Controllers/Chart/TagReportController.php
|
||||
opt/app/app/Http/Controllers/Controller.php
|
||||
opt/app/app/Http/Controllers/CurrencyController.php
|
||||
opt/app/app/Http/Controllers/DebugController.php
|
||||
opt/app/app/Http/Controllers/ExportController.php
|
||||
opt/app/app/Http/Controllers/HelpController.php
|
||||
opt/app/app/Http/Controllers/HomeController.php
|
||||
@ -389,6 +396,7 @@ opt/app/app/Http/Controllers/RuleController.php
|
||||
opt/app/app/Http/Controllers/RuleGroupController.php
|
||||
opt/app/app/Http/Controllers/SearchController.php
|
||||
opt/app/app/Http/Controllers/TagController.php
|
||||
opt/app/app/Http/Controllers/Transaction/BulkController.php
|
||||
opt/app/app/Http/Controllers/Transaction/ConvertController.php
|
||||
opt/app/app/Http/Controllers/Transaction/LinkController.php
|
||||
opt/app/app/Http/Controllers/Transaction/MassController.php
|
||||
@ -416,6 +424,7 @@ opt/app/app/Http/Requests/AttachmentFormRequest.php
|
||||
opt/app/app/Http/Requests/BillFormRequest.php
|
||||
opt/app/app/Http/Requests/BudgetFormRequest.php
|
||||
opt/app/app/Http/Requests/BudgetIncomeRequest.php
|
||||
opt/app/app/Http/Requests/BulkEditJournalRequest.php
|
||||
opt/app/app/Http/Requests/CategoryFormRequest.php
|
||||
opt/app/app/Http/Requests/ConfigurationRequest.php
|
||||
opt/app/app/Http/Requests/CurrencyFormRequest.php
|
||||
@ -625,10 +634,21 @@ 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/Exception/DuplicatedCustomerException.php
|
||||
opt/app/app/Services/Spectre/Exception/SpectreException.php
|
||||
opt/app/app/Services/Spectre/Object/Account.php
|
||||
opt/app/app/Services/Spectre/Object/Attempt.php
|
||||
opt/app/app/Services/Spectre/Object/Customer.php
|
||||
opt/app/app/Services/Spectre/Object/Holder.php
|
||||
opt/app/app/Services/Spectre/Object/Login.php
|
||||
opt/app/app/Services/Spectre/Object/SpectreObject.php
|
||||
opt/app/app/Services/Spectre/Object/Token.php
|
||||
opt/app/app/Services/Spectre/Object/Transaction.php
|
||||
opt/app/app/Services/Spectre/Request/CreateTokenRequest.php
|
||||
opt/app/app/Services/Spectre/Request/ListAccountsRequest.php
|
||||
opt/app/app/Services/Spectre/Request/ListCustomersRequest.php
|
||||
opt/app/app/Services/Spectre/Request/ListLoginsRequest.php
|
||||
opt/app/app/Services/Spectre/Request/ListTransactionsRequest.php
|
||||
opt/app/app/Services/Spectre/Request/NewCustomerRequest.php
|
||||
opt/app/app/Services/Spectre/Request/SpectreRequest.php
|
||||
opt/app/app/Support/Amount.php
|
||||
@ -657,7 +677,8 @@ opt/app/app/Support/Import/Configuration/ConfigurationInterface.php
|
||||
opt/app/app/Support/Import/Configuration/File/Initial.php
|
||||
opt/app/app/Support/Import/Configuration/File/Map.php
|
||||
opt/app/app/Support/Import/Configuration/File/Roles.php
|
||||
opt/app/app/Support/Import/Configuration/File/Upload.php
|
||||
opt/app/app/Support/Import/Configuration/File/UploadConfig.php
|
||||
opt/app/app/Support/Import/Configuration/Spectre/HaveAccounts.php
|
||||
opt/app/app/Support/Import/Information/BunqInformation.php
|
||||
opt/app/app/Support/Import/Information/InformationInterface.php
|
||||
opt/app/app/Support/Models/TransactionJournalTrait.php
|
||||
@ -763,7 +784,6 @@ opt/app/config/session.php
|
||||
opt/app/config/twigbridge.php
|
||||
opt/app/config/upgrade.php
|
||||
opt/app/config/view.php
|
||||
opt/app/crowdin.yml
|
||||
opt/app/database/factories/ModelFactory.php
|
||||
opt/app/database/migrations/2016_06_16_000000_create_support_tables.php
|
||||
opt/app/database/migrations/2016_06_16_000001_create_users_table.php
|
||||
@ -786,8 +806,7 @@ opt/app/database/seeds/PermissionSeeder.php
|
||||
opt/app/database/seeds/TransactionCurrencySeeder.php
|
||||
opt/app/database/seeds/TransactionTypeSeeder.php
|
||||
opt/app/docker-compose.yml
|
||||
opt/app/nginx_app.conf
|
||||
opt/app/phpunit.coverage.xml
|
||||
opt/app/index.php
|
||||
opt/app/public/.htaccess
|
||||
opt/app/public/android-chrome-192x192.png
|
||||
opt/app/public/android-chrome-512x512.png
|
||||
@ -920,6 +939,7 @@ opt/app/public/js/ff/tags/create-edit.js
|
||||
opt/app/public/js/ff/tags/index.js
|
||||
opt/app/public/js/ff/tags/show.js
|
||||
opt/app/public/js/ff/transactions/list.js
|
||||
opt/app/public/js/ff/transactions/mass/edit-bulk.js
|
||||
opt/app/public/js/ff/transactions/mass/edit.js
|
||||
opt/app/public/js/ff/transactions/show.js
|
||||
opt/app/public/js/ff/transactions/single/common.js
|
||||
@ -935,8 +955,6 @@ opt/app/public/js/lib/bootstrap-tagsinput.min.js.map
|
||||
opt/app/public/js/lib/bootstrap3-typeahead.min.js
|
||||
opt/app/public/js/lib/daterangepicker.js
|
||||
opt/app/public/js/lib/html5shiv.min.js
|
||||
opt/app/public/js/lib/jquery-3.1.1.min.js
|
||||
opt/app/public/js/lib/jquery-3.1.1.min.map
|
||||
opt/app/public/js/lib/jquery-3.2.1.min.js
|
||||
opt/app/public/js/lib/jquery-3.2.1.min.map
|
||||
opt/app/public/js/lib/jquery-ui.min.js
|
||||
@ -1074,6 +1092,20 @@ opt/app/resources/lang/pl_PL/list.php
|
||||
opt/app/resources/lang/pl_PL/pagination.php
|
||||
opt/app/resources/lang/pl_PL/passwords.php
|
||||
opt/app/resources/lang/pl_PL/validation.php
|
||||
opt/app/resources/lang/tr_TR/auth.php
|
||||
opt/app/resources/lang/tr_TR/bank.php
|
||||
opt/app/resources/lang/tr_TR/breadcrumbs.php
|
||||
opt/app/resources/lang/tr_TR/config.php
|
||||
opt/app/resources/lang/tr_TR/csv.php
|
||||
opt/app/resources/lang/tr_TR/demo.php
|
||||
opt/app/resources/lang/tr_TR/firefly.php
|
||||
opt/app/resources/lang/tr_TR/form.php
|
||||
opt/app/resources/lang/tr_TR/import.php
|
||||
opt/app/resources/lang/tr_TR/intro.php
|
||||
opt/app/resources/lang/tr_TR/list.php
|
||||
opt/app/resources/lang/tr_TR/pagination.php
|
||||
opt/app/resources/lang/tr_TR/passwords.php
|
||||
opt/app/resources/lang/tr_TR/validation.php
|
||||
opt/app/resources/stubs/binary.bin
|
||||
opt/app/resources/stubs/csv.csv
|
||||
opt/app/resources/stubs/demo-configuration.json
|
||||
@ -1194,13 +1226,11 @@ opt/app/resources/views/import/bunq/prerequisites.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/file/upload-config.twig
|
||||
opt/app/resources/views/import/index.twig
|
||||
opt/app/resources/views/import/spectre/input-fields.twig
|
||||
opt/app/resources/views/import/spectre/accounts.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
|
||||
@ -1219,6 +1249,7 @@ opt/app/resources/views/list/piggy-bank-events.twig
|
||||
opt/app/resources/views/list/piggy-banks.twig
|
||||
opt/app/resources/views/new-user/index.twig
|
||||
opt/app/resources/views/partials/boxes.twig
|
||||
opt/app/resources/views/partials/breadcrumbs.twig
|
||||
opt/app/resources/views/partials/control-bar.twig
|
||||
opt/app/resources/views/partials/empty.twig
|
||||
opt/app/resources/views/partials/favicons.twig
|
||||
@ -1297,10 +1328,11 @@ 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/bulk/edit.twig
|
||||
opt/app/resources/views/transactions/convert.twig
|
||||
opt/app/resources/views/transactions/index.twig
|
||||
opt/app/resources/views/transactions/links/delete.twig
|
||||
opt/app/resources/views/transactions/mass-delete.twig
|
||||
opt/app/resources/views/transactions/mass/delete.twig
|
||||
opt/app/resources/views/transactions/mass/edit.twig
|
||||
opt/app/resources/views/transactions/show.twig
|
||||
opt/app/resources/views/transactions/single/create.twig
|
||||
@ -2280,10 +2312,8 @@ opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/boots
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/none-stubs/app.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/Example.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/app.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/webpack.mix.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/ExampleComponent.vue
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/app.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/webpack.mix.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/ProviderMakeCommand.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/QueuedCommand.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/RequestMakeCommand.php
|
||||
@ -2801,6 +2831,7 @@ opt/app/vendor/league/commonmark/src/Block/Element/Heading.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/HtmlBlock.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/IndentedCode.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/InlineContainer.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/InlineContainerInterface.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/ListBlock.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/ListData.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/ListItem.php
|
||||
@ -2833,7 +2864,6 @@ opt/app/vendor/league/commonmark/src/Context.php
|
||||
opt/app/vendor/league/commonmark/src/ContextInterface.php
|
||||
opt/app/vendor/league/commonmark/src/Converter.php
|
||||
opt/app/vendor/league/commonmark/src/Cursor.php
|
||||
opt/app/vendor/league/commonmark/src/CursorState.php
|
||||
opt/app/vendor/league/commonmark/src/Delimiter/Delimiter.php
|
||||
opt/app/vendor/league/commonmark/src/Delimiter/DelimiterStack.php
|
||||
opt/app/vendor/league/commonmark/src/DocParser.php
|
||||
@ -3336,10 +3366,10 @@ 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/README.md
|
||||
opt/app/vendor/pragmarx/google2fa/changelog.md
|
||||
opt/app/vendor/pragmarx/google2fa/composer.json
|
||||
opt/app/vendor/pragmarx/google2fa/docs/playground.jpg
|
||||
opt/app/vendor/pragmarx/google2fa/readme.md
|
||||
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
|
||||
@ -3348,6 +3378,7 @@ 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/tests/Constants.php
|
||||
opt/app/vendor/pragmarx/google2fa/tests/Google2FATest.php
|
||||
opt/app/vendor/pragmarx/google2fa/tests/bootstrap.php
|
||||
opt/app/vendor/pragmarx/google2fa/upgrading.md
|
||||
@ -4049,6 +4080,8 @@ opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_7
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_8.txt
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_9.txt
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/TestCommand.php
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/TestTiti.php
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/TestToto.php
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/application_1.json
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/application_1.md
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/application_1.txt
|
||||
@ -4335,6 +4368,7 @@ 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/debug_class_loader.phpt
|
||||
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
|
||||
@ -4751,6 +4785,7 @@ opt/app/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/SaveSessionListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php
|
||||
@ -6161,7 +6196,6 @@ opt/app/vendor/watson/validating/src/ValidatingModel.php
|
||||
opt/app/vendor/watson/validating/src/ValidatingObserver.php
|
||||
opt/app/vendor/watson/validating/src/ValidatingTrait.php
|
||||
opt/app/vendor/watson/validating/src/ValidationException.php
|
||||
opt/app/webpack.mix.js
|
||||
proc/cpuinfo
|
||||
sandstorm-http-bridge
|
||||
sandstorm-http-bridge-config
|
||||
|
@ -15,8 +15,8 @@ const pkgdef :Spk.PackageDefinition = (
|
||||
|
||||
manifest = (
|
||||
appTitle = (defaultText = "Firefly III"),
|
||||
appVersion = 6,
|
||||
appMarketingVersion = (defaultText = "4.6.12"),
|
||||
appVersion = 7,
|
||||
appMarketingVersion = (defaultText = "4.6.13"),
|
||||
|
||||
actions = [
|
||||
# Define your "new document" handlers here.
|
||||
|
32
CHANGELOG.md
32
CHANGELOG.md
@ -2,6 +2,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [4.6.13] - 2018-01-06
|
||||
### Added
|
||||
- [Issue 1074](https://github.com/firefly-iii/firefly-iii/issues/1074), suggested by [MacPaille](https://github.com/MacPaille)
|
||||
- [Issue 1077](https://github.com/firefly-iii/firefly-iii/issues/1077), suggested by [wtercato](https://github.com/wtercato)
|
||||
- Bulk edit of transactions thanks to [vicmosin](https://github.com/vicmosin) ([issue 1078](https://github.com/firefly-iii/firefly-iii/issues/1078))
|
||||
- Support for Turkish.
|
||||
- [Issue 1090](https://github.com/firefly-iii/firefly-iii/issues/1090), suggested by [Findus23](https://github.com/Findus23)
|
||||
- [Issue 1097](https://github.com/firefly-iii/firefly-iii/issues/1097), suggested by [kelvinhammond](https://github.com/kelvinhammond)
|
||||
- [Issue 1093](https://github.com/firefly-iii/firefly-iii/issues/1093), suggested by [jinformatique](https://github.com/jinformatique)
|
||||
- [Issue 1098](https://github.com/firefly-iii/firefly-iii/issues/1098), suggested by [Nik-vr](https://github.com/Nik-vr)
|
||||
|
||||
### Fixed
|
||||
- [Issue 972](https://github.com/firefly-iii/firefly-iii/issues/972), reported by [pjotrvdh](https://github.com/pjotrvdh)
|
||||
- [Issue 1079](https://github.com/firefly-iii/firefly-iii/issues/1079), reported by [gavu](https://github.com/gavu)
|
||||
- [Issue 1080](https://github.com/firefly-iii/firefly-iii/issues/1080), reported by [zjean](https://github.com/zjean)
|
||||
- [Issue 1083](https://github.com/firefly-iii/firefly-iii/issues/1083), reported by [skuzzle](https://github.com/skuzzle)
|
||||
- [Issue 1085](https://github.com/firefly-iii/firefly-iii/issues/1085), reported by [nicoschreiner](https://github.com/nicoschreiner)
|
||||
- [Issue 1087](https://github.com/firefly-iii/firefly-iii/issues/1087), reported by [4oo4](https://github.com/4oo4)
|
||||
- [Issue 1089](https://github.com/firefly-iii/firefly-iii/issues/1089), reported by [robin5210](https://github.com/robin5210)
|
||||
- [Issue 1092](https://github.com/firefly-iii/firefly-iii/issues/1092), reported by [kelvinhammond](https://github.com/kelvinhammond)
|
||||
- [Issue 1096](https://github.com/firefly-iii/firefly-iii/issues/1096), reported by [wtercato](https://github.com/wtercato)
|
||||
|
||||
## [4.6.12] - 2017-12-31
|
||||
### Added
|
||||
- Support for Indonesian.
|
||||
@ -16,7 +38,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- [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 1015](https://github.com/firefly-iii/firefly-iii/issues/1015), as reported 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)
|
||||
@ -53,7 +75,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Reconciliation of accounts ([issue 736](https://github.com/firefly-iii/firefly-iii/issues/736)), as requested by [kristophr](https://github.com/kristophr) and several others
|
||||
|
||||
### Changed
|
||||
- Extended currency list, as suggested by @emuhendis in [issue 994](https://github.com/firefly-iii/firefly-iii/issues/994)
|
||||
- Extended currency list, as suggested by [emuhendis](https://github.com/emuhendis) in [issue 994](https://github.com/firefly-iii/firefly-iii/issues/994)
|
||||
- [Issue 996](https://github.com/firefly-iii/firefly-iii/issues/996) as suggested by [dp87](https://github.com/dp87)
|
||||
|
||||
### Removed
|
||||
@ -78,7 +100,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- [Issue 1025](https://github.com/firefly-iii/firefly-iii/issues/1025), reported by [gavu](https://github.com/gavu)
|
||||
|
||||
|
||||
## [4.6.10] - 2017-11-xx
|
||||
## [4.6.10] - 2017-11-03
|
||||
### Added
|
||||
- Greatly expanded Docker support thanks to [alazare619](https://github.com/alazare619)
|
||||
- [Issue 967](https://github.com/firefly-iii/firefly-iii/issues/967), thanks to [Aquariu](https://github.com/Aquariu)
|
||||
@ -353,8 +375,8 @@ This will be the last release to support PHP 7.0.
|
||||
- Can now make rules for attachments, see [issue 608](https://github.com/firefly-iii/firefly-iii/issues/608), as suggested by [dzaikos](https://github.com/dzaikos).
|
||||
|
||||
### Fixed
|
||||
- Fixed [issue 629](https://github.com/firefly-iii/firefly-iii/issues/629), reported by @forcaeluz
|
||||
- Fixed [issue 630](https://github.com/firefly-iii/firefly-iii/issues/630), reported by @welbert
|
||||
- Fixed [issue 629](https://github.com/firefly-iii/firefly-iii/issues/629), reported by [forcaeluz](https://github.com/forcaeluz)
|
||||
- Fixed [issue 630](https://github.com/firefly-iii/firefly-iii/issues/630), reported by [welbert](https://github.com/welbert)
|
||||
- And more various bug fixes.
|
||||
|
||||
## [4.3.8] - 2017-04-08
|
||||
|
@ -37,6 +37,10 @@ Register for a free Heroku account and instantly run Firefly III on your very ow
|
||||
|
||||
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.
|
||||
|
||||
### Other options
|
||||
|
||||
Firefly III is also available as a package on [https://softaculous.com/](Softaculous) and [AMPPS](https://www.ampps.com/).
|
||||
|
||||
## More about Firefly III
|
||||
|
||||
Personal financial management is pretty difficult, and everybody has their own approach to it.
|
||||
|
@ -146,6 +146,7 @@ class UpgradeDatabase extends Command
|
||||
|
||||
// both 0? set to default currency:
|
||||
if (0 === $accountCurrency && 0 === $obCurrency) {
|
||||
AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete();
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
|
||||
@ -218,7 +219,7 @@ class UpgradeDatabase extends Command
|
||||
}
|
||||
|
||||
// when mismatch in transaction:
|
||||
if ($transaction->transaction_currency_id !== $currency->id) {
|
||||
if (!(intval($transaction->transaction_currency_id) === intval($currency->id))) {
|
||||
$transaction->foreign_currency_id = $transaction->transaction_currency_id;
|
||||
$transaction->foreign_amount = $transaction->amount;
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
@ -401,24 +402,26 @@ class UpgradeDatabase extends Command
|
||||
|
||||
// has no currency ID? Must have, so fill in using account preference:
|
||||
if (null === $transaction->transaction_currency_id) {
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->transaction_currency_id = intval($currency->id);
|
||||
Log::debug(sprintf('Transaction #%d has no currency setting, now set to %s', $transaction->id, $currency->code));
|
||||
$transaction->save();
|
||||
}
|
||||
|
||||
// does not match the source account (see above)? Can be fixed
|
||||
// when mismatch in transaction and NO foreign amount is set:
|
||||
if ($transaction->transaction_currency_id !== $currency->id && null === $transaction->foreign_amount) {
|
||||
if (!(intval($transaction->transaction_currency_id) === intval($currency->id)) && null === $transaction->foreign_amount) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Transaction #%d has a currency setting (#%d) that should be #%d. Amount remains %s, currency is changed.',
|
||||
'Transaction #%d has a currency setting (#%d) (%s) that should be #%d (%s). Amount remains %s, currency is changed.',
|
||||
$transaction->id,
|
||||
$transaction->transaction_currency_id,
|
||||
$this->var_dump_ret(intval($transaction->transaction_currency_id)),
|
||||
$currency->id,
|
||||
$this->var_dump_ret(intval($currency->id)),
|
||||
$transaction->amount
|
||||
)
|
||||
);
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->transaction_currency_id = intval($currency->id);
|
||||
$transaction->save();
|
||||
}
|
||||
|
||||
@ -436,7 +439,7 @@ class UpgradeDatabase extends Command
|
||||
}
|
||||
|
||||
// if the destination account currency is the same, both foreign_amount and foreign_currency_id must be NULL for both transactions:
|
||||
if ($opposingCurrency->id === $currency->id) {
|
||||
if (intval($opposingCurrency->id) === intval($currency->id)) {
|
||||
// update both transactions to match:
|
||||
$transaction->foreign_amount = null;
|
||||
$transaction->foreign_currency_id = null;
|
||||
@ -450,7 +453,7 @@ class UpgradeDatabase extends Command
|
||||
return;
|
||||
}
|
||||
// if destination account currency is different, both transactions must have this currency as foreign currency id.
|
||||
if ($opposingCurrency->id !== $currency->id) {
|
||||
if (!(intval($opposingCurrency->id) === intval($currency->id))) {
|
||||
$transaction->foreign_currency_id = $opposingCurrency->id;
|
||||
$opposing->foreign_currency_id = $opposingCurrency->id;
|
||||
$transaction->save();
|
||||
@ -500,4 +503,20 @@ class UpgradeDatabase extends Command
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $mixed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function var_dump_ret($mixed = null): string
|
||||
{
|
||||
ob_start();
|
||||
var_dump($mixed);
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return trim($content);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -773,7 +773,8 @@ class JournalCollector implements JournalCollectorInterface
|
||||
|
||||
$this->query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id');
|
||||
$this->query->leftJoin('categories as transaction_categories', 'transaction_categories.id', '=', 'category_transaction.category_id');
|
||||
|
||||
$this->query->whereNull('transaction_journal_categories.deleted_at');
|
||||
$this->query->whereNull('transaction_categories.deleted_at');
|
||||
$this->fields[] = 'category_transaction_journal.category_id as transaction_journal_category_id';
|
||||
$this->fields[] = 'transaction_journal_categories.encrypted as transaction_journal_category_encrypted';
|
||||
$this->fields[] = 'transaction_journal_categories.name as transaction_journal_category_name';
|
||||
|
@ -29,6 +29,7 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Requests\AccountFormRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
@ -156,14 +157,14 @@ class AccountController extends Controller
|
||||
* @throws FireflyException
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function edit(Request $request, Account $account)
|
||||
public function edit(Request $request, Account $account, AccountRepositoryInterface $repository)
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
/** @var CurrencyRepositoryInterface $currencyRepos */
|
||||
$currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
|
||||
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||
$allCurrencies = $repository->get();
|
||||
$allCurrencies = $currencyRepos->get();
|
||||
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
|
||||
$roles = [];
|
||||
foreach (config('firefly.accountRoles') as $role) {
|
||||
@ -183,7 +184,7 @@ class AccountController extends Controller
|
||||
$openingBalanceAmount = '0' === $account->getOpeningBalanceAmount() ? '' : $openingBalanceAmount;
|
||||
$openingBalanceDate = $account->getOpeningBalanceDate();
|
||||
$openingBalanceDate = 1900 === $openingBalanceDate->year ? null : $openingBalanceDate->format('Y-m-d');
|
||||
$currency = $repository->find(intval($account->getMeta('currency_id')));
|
||||
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
|
||||
|
||||
$preFilled = [
|
||||
'accountNumber' => $account->getMeta('accountNumber'),
|
||||
@ -195,7 +196,15 @@ class AccountController extends Controller
|
||||
'openingBalance' => $openingBalanceAmount,
|
||||
'virtualBalance' => $account->virtual_balance,
|
||||
'currency_id' => $currency->id,
|
||||
'notes' => '',
|
||||
];
|
||||
/** @var Note $note */
|
||||
$note = $repository->getNote($account);
|
||||
if (null !== $note) {
|
||||
$preFilled['notes'] = $note->text;
|
||||
}
|
||||
|
||||
|
||||
$request->session()->flash('preFilled', $preFilled);
|
||||
|
||||
return view(
|
||||
|
@ -22,8 +22,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use FireflyConfig;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
|
||||
/**
|
||||
* Class ForgotPasswordController
|
||||
@ -51,4 +56,57 @@ class ForgotPasswordController extends Controller
|
||||
parent::__construct();
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a reset link to the given user.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @param UserRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function sendResetLinkEmail(Request $request, UserRepositoryInterface $repository)
|
||||
{
|
||||
$this->validateEmail($request);
|
||||
|
||||
// verify if the user is not a demo user. If so, we give him back an error.
|
||||
$user = User::where('email', $request->get('email'))->first();
|
||||
|
||||
if (!is_null($user) && $repository->hasRole($user, 'demo')) {
|
||||
return back()->withErrors(['email' => trans('firefly.cannot_reset_demo_user')]);
|
||||
}
|
||||
|
||||
// We will send the password reset link to this user. Once we have attempted
|
||||
// to send the link, we will examine the response then see the message we
|
||||
// need to show to the user. Finally, we'll send out a proper response.
|
||||
$response = $this->broker()->sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
if ($response == Password::RESET_LINK_SENT) {
|
||||
return back()->with('status', trans($response));
|
||||
}
|
||||
|
||||
return back()->withErrors(['email' => trans($response)]); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* Display the form to request a password reset link.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function showLinkRequestForm()
|
||||
{
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
$allowRegistration = true;
|
||||
if (true === $singleUserMode && $userCount > 0) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
return view('auth.passwords.email')->with(compact('allowRegistration'));
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use FireflyConfig;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
@ -52,4 +55,28 @@ class ResetPasswordController extends Controller
|
||||
parent::__construct();
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the password reset view for the given token.
|
||||
*
|
||||
* If no token is present, display the link request form.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string|null $token
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function showResetForm(Request $request, $token = null)
|
||||
{
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
$allowRegistration = true;
|
||||
if (true === $singleUserMode && $userCount > 0) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
return view('auth.passwords.reset')->with(
|
||||
['token' => $token, 'email' => $request->email, 'allowRegistration' => $allowRegistration]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
159
app/Http/Controllers/DebugController.php
Normal file
159
app/Http/Controllers/DebugController.php
Normal file
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* DebugController.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 Carbon\Carbon;
|
||||
use DB;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
|
||||
/**
|
||||
* Class DebugController
|
||||
*/
|
||||
class DebugController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$search = ['~', '#'];
|
||||
$replace = ['\~', '# '];
|
||||
|
||||
$phpVersion = str_replace($search, $replace, PHP_VERSION);
|
||||
$phpOs = str_replace($search, $replace, php_uname());
|
||||
$interface = PHP_SAPI;
|
||||
$now = Carbon::create()->format('Y-m-d H:i:s e');
|
||||
$extensions = join(', ', get_loaded_extensions());
|
||||
$drivers = join(', ', DB::availableDrivers());
|
||||
$currentDriver = DB::getDriverName();
|
||||
$userAgent = $request->header('user-agent');
|
||||
$isSandstorm = var_export(env('IS_SANDSTORM', 'unknown'), true);
|
||||
$isDocker = var_export(env('IS_DOCKER', 'unknown'), true);
|
||||
$trustedProxies = env('TRUSTED_PROXIES', '(none)');
|
||||
$displayErrors = ini_get('display_errors');
|
||||
$errorReporting = $this->errorReporting(intval(ini_get('error_reporting')));
|
||||
$appEnv = env('APP_ENV', '');
|
||||
$appDebug = var_export(env('APP_DEBUG', false), true);
|
||||
$appLog = env('APP_LOG', '');
|
||||
$appLogLevel = env('APP_LOG_LEVEL', '');
|
||||
$packages = $this->collectPackages();
|
||||
$cacheDriver = env('CACHE_DRIVER', 'unknown');
|
||||
|
||||
|
||||
// get latest log file:
|
||||
$logger = Log::getMonolog();
|
||||
$handlers = $logger->getHandlers();
|
||||
$logContent = '';
|
||||
foreach ($handlers as $handler) {
|
||||
if ($handler instanceof RotatingFileHandler) {
|
||||
$logFile = $handler->getUrl();
|
||||
if (null !== $logFile) {
|
||||
$logContent = file_get_contents($logFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
// last few lines
|
||||
$logContent = 'Truncated from this point <----|' . substr($logContent, -4096);
|
||||
|
||||
return view(
|
||||
'debug',
|
||||
compact(
|
||||
'phpVersion',
|
||||
'extensions',
|
||||
'carbon',
|
||||
'appEnv',
|
||||
'appDebug',
|
||||
'appLog',
|
||||
'appLogLevel',
|
||||
'now',
|
||||
'packages',
|
||||
'drivers',
|
||||
'currentDriver',
|
||||
'userAgent',
|
||||
'displayErrors',
|
||||
'errorReporting',
|
||||
'phpOs',
|
||||
'interface',
|
||||
'logContent',
|
||||
'cacheDriver',
|
||||
'isDocker',
|
||||
'isSandstorm',
|
||||
'trustedProxies'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some common combinations.
|
||||
*
|
||||
* @param int $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function errorReporting(int $value): string
|
||||
{
|
||||
$array = [
|
||||
-1 => 'ALL errors',
|
||||
E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED => 'E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED',
|
||||
E_ALL => 'E_ALL',
|
||||
E_ALL & ~E_DEPRECATED & ~E_STRICT => 'E_ALL & ~E_DEPRECATED & ~E_STRICT',
|
||||
E_ALL & ~E_NOTICE => 'E_ALL & ~E_NOTICE',
|
||||
E_ALL & ~E_NOTICE & ~E_STRICT => 'E_ALL & ~E_NOTICE & ~E_STRICT',
|
||||
E_COMPILE_ERROR | E_RECOVERABLE_ERROR | E_ERROR | E_CORE_ERROR => 'E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR',
|
||||
];
|
||||
if (isset($array[$value])) {
|
||||
return $array[$value];
|
||||
}
|
||||
|
||||
return strval($value); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function collectPackages(): array
|
||||
{
|
||||
$packages = [];
|
||||
$file = realpath(__DIR__ . '/../../../vendor/composer/installed.json');
|
||||
if (!($file === false) && file_exists($file)) {
|
||||
// file exists!
|
||||
$content = file_get_contents($file);
|
||||
$json = json_decode($content, true);
|
||||
foreach ($json as $package) {
|
||||
$packages[]
|
||||
= [
|
||||
'name' => $package['name'],
|
||||
'version' => $package['version'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
}
|
@ -24,7 +24,6 @@ namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Artisan;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Exception;
|
||||
use FireflyIII\Events\RequestedVersionCheckStatus;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
@ -38,7 +37,6 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Route;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
use Preferences;
|
||||
use Response;
|
||||
use Route as RouteFacade;
|
||||
@ -98,59 +96,6 @@ class HomeController extends Controller
|
||||
return Response::json(['ok' => 'ok']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function displayDebug(Request $request)
|
||||
{
|
||||
$phpVersion = str_replace('~', '\~', PHP_VERSION);
|
||||
$phpOs = php_uname();
|
||||
$interface = PHP_SAPI;
|
||||
$now = Carbon::create()->format('Y-m-d H:i:s e');
|
||||
$extensions = join(', ', get_loaded_extensions());
|
||||
$drivers = join(', ', DB::availableDrivers());
|
||||
$currentDriver = DB::getDriverName();
|
||||
$userAgent = $request->header('user-agent');
|
||||
$isSandstorm = var_export(env('IS_SANDSTORM', 'unknown'), true);
|
||||
$isDocker = var_export(env('IS_DOCKER', 'unknown'), true);
|
||||
$trustedProxies = env('TRUSTED_PROXIES', '(none)');
|
||||
|
||||
// get latest log file:
|
||||
$logger = Log::getMonolog();
|
||||
$handlers = $logger->getHandlers();
|
||||
$logContent = '';
|
||||
foreach ($handlers as $handler) {
|
||||
if ($handler instanceof RotatingFileHandler) {
|
||||
$logFile = $handler->getUrl();
|
||||
if (null !== $logFile) {
|
||||
$logContent = file_get_contents($logFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
// last few lines
|
||||
$logContent = 'Truncated from this point <----|' . substr($logContent, -4096);
|
||||
|
||||
return view(
|
||||
'debug',
|
||||
compact(
|
||||
'phpVersion',
|
||||
'extensions',
|
||||
'carbon',
|
||||
'now',
|
||||
'drivers',
|
||||
'currentDriver',
|
||||
'userAgent',
|
||||
'phpOs',
|
||||
'interface',
|
||||
'logContent',
|
||||
'isDocker',
|
||||
'isSandstorm',
|
||||
'trustedProxies'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
|
@ -78,7 +78,9 @@ class ConfigurationController extends Controller
|
||||
|
||||
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');
|
||||
@ -135,6 +137,7 @@ class ConfigurationController extends Controller
|
||||
if (null === $className || !class_exists($className)) {
|
||||
throw new FireflyException(sprintf('Cannot find configurator class for job of type "%s".', $type)); // @codeCoverageIgnore
|
||||
}
|
||||
Log::debug(sprintf('Going to create class "%s"', $className));
|
||||
/** @var ConfiguratorInterface $configurator */
|
||||
$configurator = app($className);
|
||||
$configurator->setJob($job);
|
||||
|
178
app/Http/Controllers/Transaction/BulkController.php
Normal file
178
app/Http/Controllers/Transaction/BulkController.php
Normal file
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
/**
|
||||
* BulkController.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\Transaction;
|
||||
|
||||
|
||||
use ExpandedForm;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\BulkEditJournalRequest;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Session;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class BulkController
|
||||
*/
|
||||
class BulkController extends Controller
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
app('view')->share('title', trans('firefly.transactions'));
|
||||
app('view')->share('mainTitleIcon', 'fa-repeat');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $journals
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(Request $request, Collection $journals)
|
||||
{
|
||||
|
||||
$subTitle = trans('firefly.mass_bulk_journals');
|
||||
|
||||
// skip transactions that have multiple destinations, multiple sources or are an opening balance.
|
||||
$filtered = new Collection;
|
||||
$messages = [];
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$sources = $journal->sourceAccountList();
|
||||
$destinations = $journal->destinationAccountList();
|
||||
if ($sources->count() > 1) {
|
||||
$messages[] = trans('firefly.cannot_edit_multiple_source', ['description' => $journal->description, 'id' => $journal->id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($destinations->count() > 1) {
|
||||
$messages[] = trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]);
|
||||
continue;
|
||||
}
|
||||
if (TransactionType::OPENING_BALANCE === $journal->transactionType->type) {
|
||||
$messages[] = trans('firefly.cannot_edit_opening_balance');
|
||||
continue;
|
||||
}
|
||||
|
||||
// cannot edit reconciled transactions / journals:
|
||||
if ($journal->transactions->first()->reconciled) {
|
||||
$messages[] = trans('firefly.cannot_edit_reconciled', ['description' => $journal->description, 'id' => $journal->id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$filtered->push($journal);
|
||||
}
|
||||
|
||||
if (count($messages) > 0) {
|
||||
$request->session()->flash('info', $messages);
|
||||
}
|
||||
|
||||
// put previous url in session
|
||||
$this->rememberPreviousUri('transactions.bulk-edit.uri');
|
||||
|
||||
// get list of budgets:
|
||||
/** @var BudgetRepositoryInterface $repository */
|
||||
$repository = app(BudgetRepositoryInterface::class);
|
||||
$budgetList = ExpandedForm::makeSelectListWithEmpty($repository->getActiveBudgets());
|
||||
// collect some useful meta data for the mass edit:
|
||||
$filtered->each(
|
||||
function (TransactionJournal $journal) {
|
||||
$journal->transaction_count = $journal->transactions()->count();
|
||||
}
|
||||
);
|
||||
|
||||
if (0 === $filtered->count()) {
|
||||
$request->session()->flash('error', trans('firefly.no_edit_multiple_left'));
|
||||
}
|
||||
|
||||
$journals = $filtered;
|
||||
|
||||
return view('transactions.bulk.edit', compact('journals', 'subTitle', 'budgetList'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param BulkEditJournalRequest $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function update(BulkEditJournalRequest $request, JournalRepositoryInterface $repository)
|
||||
{
|
||||
$journalIds = $request->get('journals');
|
||||
$ignoreCategory = intval($request->get('ignore_category')) === 1;
|
||||
$ignoreBudget = intval($request->get('ignore_budget')) === 1;
|
||||
$ignoreTags = intval($request->get('ignore_tags')) === 1;
|
||||
$count = 0;
|
||||
if (is_array($journalIds)) {
|
||||
foreach ($journalIds as $journalId) {
|
||||
$journal = $repository->find(intval($journalId));
|
||||
if (!is_null($journal)) {
|
||||
$count++;
|
||||
Log::debug(sprintf('Found journal #%d', $journal->id));
|
||||
// update category if not told to ignore
|
||||
if ($ignoreCategory === false) {
|
||||
Log::debug(sprintf('Set category to %s', $request->string('category')));
|
||||
$repository->updateCategory($journal, $request->string('category'));
|
||||
}
|
||||
// update budget if not told to ignore (and is withdrawal)
|
||||
if ($ignoreBudget === false) {
|
||||
Log::debug(sprintf('Set budget to %d', $request->integer('budget_id')));
|
||||
$repository->updateBudget($journal, $request->integer('budget_id'));
|
||||
}
|
||||
if ($ignoreTags === false) {
|
||||
Log::debug(sprintf('Set tags to %s', $request->string('budget_id')));
|
||||
$repository->updateTags($journal, explode(',', $request->string('tags')));
|
||||
}
|
||||
// update tags if not told to ignore (and is withdrawal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Preferences::mark();
|
||||
$request->session()->flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count]));
|
||||
|
||||
// redirect to previous URL:
|
||||
return redirect($this->getPreviousUri('transactions.bulk-edit.uri'));
|
||||
}
|
||||
|
||||
}
|
@ -26,6 +26,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\MassDeleteJournalRequest;
|
||||
use FireflyIII\Http\Requests\MassEditJournalRequest;
|
||||
use FireflyIII\Http\Requests\MassEditBulkJournalRequest;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
@ -71,7 +72,7 @@ class MassController extends Controller
|
||||
// put previous url in session
|
||||
$this->rememberPreviousUri('transactions.mass-delete.uri');
|
||||
|
||||
return view('transactions.mass-delete', compact('journals', 'subTitle'));
|
||||
return view('transactions.mass.delete', compact('journals', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,7 +132,7 @@ class MassController extends Controller
|
||||
// skip transactions that have multiple destinations, multiple sources or are an opening balance.
|
||||
$filtered = new Collection;
|
||||
$messages = [];
|
||||
// @var TransactionJournal
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$sources = $journal->sourceAccountList();
|
||||
$destinations = $journal->destinationAccountList();
|
||||
@ -213,7 +214,7 @@ class MassController extends Controller
|
||||
if (is_array($journalIds)) {
|
||||
foreach ($journalIds as $journalId) {
|
||||
$journal = $repository->find(intval($journalId));
|
||||
if ($journal) {
|
||||
if (!is_null($journal)) {
|
||||
// get optional fields:
|
||||
$what = strtolower($journal->transactionTypeStr());
|
||||
$sourceAccountId = $request->get('source_account_id')[$journal->id] ?? 0;
|
||||
@ -264,4 +265,5 @@ class MassController extends Controller
|
||||
// redirect to previous URL:
|
||||
return redirect($this->getPreviousUri('transactions.mass-edit.uri'));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ class AccountFormRequest extends Request
|
||||
'openingBalanceDate' => $this->date('openingBalanceDate'),
|
||||
'ccType' => $this->string('ccType'),
|
||||
'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'),
|
||||
'notes' => $this->string('notes'),
|
||||
];
|
||||
}
|
||||
|
||||
|
50
app/Http/Requests/BulkEditJournalRequest.php
Normal file
50
app/Http/Requests/BulkEditJournalRequest.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* BulkEditJournalRequest.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\Requests;
|
||||
|
||||
/**
|
||||
* Class MassEditBulkJournalRequest.
|
||||
*/
|
||||
class BulkEditJournalRequest extends Request
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
// Only allow logged in users
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
|
||||
// fixed
|
||||
return [
|
||||
'journals.*' => 'required|belongsToUser:transaction_journals,id',
|
||||
];
|
||||
}
|
||||
}
|
@ -91,33 +91,33 @@ class JournalFormRequest extends Request
|
||||
{
|
||||
$what = $this->get('what');
|
||||
$rules = [
|
||||
'what' => 'required|in:withdrawal,deposit,transfer',
|
||||
'date' => 'required|date',
|
||||
|
||||
'what' => 'required|in:withdrawal,deposit,transfer',
|
||||
'date' => 'required|date',
|
||||
'amount_currency_id_amount' => 'exists:transaction_currencies,id|required',
|
||||
// then, custom fields:
|
||||
'interest_date' => 'date|nullable',
|
||||
'book_date' => 'date|nullable',
|
||||
'process_date' => 'date|nullable',
|
||||
'due_date' => 'date|nullable',
|
||||
'payment_date' => 'date|nullable',
|
||||
'invoice_date' => 'date|nullable',
|
||||
'internal_reference' => 'min:1,max:255|nullable',
|
||||
'notes' => 'min:1,max:50000|nullable',
|
||||
'interest_date' => 'date|nullable',
|
||||
'book_date' => 'date|nullable',
|
||||
'process_date' => 'date|nullable',
|
||||
'due_date' => 'date|nullable',
|
||||
'payment_date' => 'date|nullable',
|
||||
'invoice_date' => 'date|nullable',
|
||||
'internal_reference' => 'min:1,max:255|nullable',
|
||||
'notes' => 'min:1,max:50000|nullable',
|
||||
// and then transaction rules:
|
||||
'description' => 'required|between:1,255',
|
||||
'amount' => 'numeric|required|more:0',
|
||||
'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable',
|
||||
'category' => 'between:1,255|nullable',
|
||||
'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
|
||||
'source_account_name' => 'between:1,255|nullable',
|
||||
'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
|
||||
'destination_account_name' => 'between:1,255|nullable',
|
||||
'piggy_bank_id' => 'between:1,255|nullable',
|
||||
'description' => 'required|between:1,255',
|
||||
'amount' => 'numeric|required|more:0',
|
||||
'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable',
|
||||
'category' => 'between:1,255|nullable',
|
||||
'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
|
||||
'source_account_name' => 'between:1,255|nullable',
|
||||
'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
|
||||
'destination_account_name' => 'between:1,255|nullable',
|
||||
'piggy_bank_id' => 'between:1,255|nullable',
|
||||
|
||||
// foreign currency amounts
|
||||
'native_amount' => 'numeric|more:0|nullable',
|
||||
'source_amount' => 'numeric|more:0|nullable',
|
||||
'destination_amount' => 'numeric|more:0|nullable',
|
||||
'native_amount' => 'numeric|more:0|nullable',
|
||||
'source_amount' => 'numeric|more:0|nullable',
|
||||
'destination_amount' => 'numeric|more:0|nullable',
|
||||
];
|
||||
|
||||
// some rules get an upgrade depending on the type of data:
|
||||
|
@ -143,7 +143,7 @@ class Request extends FormRequest
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function integer(string $field): int
|
||||
public function integer(string $field): int
|
||||
{
|
||||
return intval($this->get($field));
|
||||
}
|
||||
|
@ -24,11 +24,12 @@ namespace FireflyIII\Import\Configuration;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\Import\Configuration\ConfigurationInterface;
|
||||
use FireflyIII\Support\Import\Configuration\File\Initial;
|
||||
use FireflyIII\Support\Import\Configuration\File\Map;
|
||||
use FireflyIII\Support\Import\Configuration\File\Roles;
|
||||
use FireflyIII\Support\Import\Configuration\File\Upload;
|
||||
use FireflyIII\Support\Import\Configuration\File\UploadConfig;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
@ -36,17 +37,41 @@ use Log;
|
||||
*/
|
||||
class FileConfigurator implements ConfiguratorInterface
|
||||
{
|
||||
/** @var array */
|
||||
private $defaultConfig
|
||||
= [
|
||||
'stage' => 'initial',
|
||||
'has-headers' => false, // assume
|
||||
'date-format' => 'Ymd', // assume
|
||||
'delimiter' => ',', // assume
|
||||
'import-account' => 0, // none,
|
||||
'specifics' => [], // none
|
||||
'column-count' => 0, // unknown
|
||||
'column-roles' => [], // unknown
|
||||
'column-do-mapping' => [], // not yet set which columns must be mapped
|
||||
'column-mapping-config' => [], // no mapping made yet.
|
||||
'file-type' => 'csv', // assume
|
||||
'has-config-file' => true,
|
||||
'apply-rules' => true,
|
||||
'match-bills' => false,
|
||||
'auto-start' => false,
|
||||
];
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
|
||||
// give job default config:
|
||||
/** @var string */
|
||||
private $warning = '';
|
||||
|
||||
/**
|
||||
* ConfiguratorInterface constructor.
|
||||
* FileConfigurator constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
Log::debug('Created FileConfigurator');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,10 +85,13 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function configureJob(array $data): bool
|
||||
{
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call configureJob() without a job.');
|
||||
}
|
||||
$class = $this->getConfigurationClass();
|
||||
$job = $this->job;
|
||||
/** @var ConfigurationInterface $object */
|
||||
$object = new $class($this->job);
|
||||
$object = app($class);
|
||||
$object->setJob($job);
|
||||
$result = $object->storeConfiguration($data);
|
||||
$this->warning = $object->getWarningMessage();
|
||||
@ -80,6 +108,10 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function getNextData(): array
|
||||
{
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call getNextData() without a job.');
|
||||
}
|
||||
|
||||
$class = $this->getConfigurationClass();
|
||||
$job = $this->job;
|
||||
/** @var ConfigurationInterface $object */
|
||||
@ -96,52 +128,56 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function getNextView(): string
|
||||
{
|
||||
if (!$this->job->configuration['has-file-upload']) {
|
||||
return 'import.file.upload';
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call getNextView() without a job.');
|
||||
}
|
||||
if (!$this->job->configuration['initial-config-complete']) {
|
||||
return 'import.file.initial';
|
||||
$config = $this->getConfig();
|
||||
$stage = $config['stage'] ?? 'initial';
|
||||
switch ($stage) {
|
||||
case 'initial': // has nothing, no file upload or anything.
|
||||
return 'import.file.initial';
|
||||
case 'upload-config': // has file, needs file config.
|
||||
return 'import.file.upload-config';
|
||||
case 'roles': // has configured file, needs roles.
|
||||
return 'import.file.roles';
|
||||
case 'map': // has roles, needs mapping.
|
||||
return 'import.file.map';
|
||||
}
|
||||
if (!$this->job->configuration['column-roles-complete']) {
|
||||
return 'import.file.roles';
|
||||
}
|
||||
if (!$this->job->configuration['column-mapping-complete']) {
|
||||
return 'import.file.map';
|
||||
}
|
||||
|
||||
throw new FireflyException('No view for state');
|
||||
throw new FireflyException(sprintf('No view for stage "%s"', $stage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return possible warning to user.
|
||||
*
|
||||
* @return string
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getWarningMessage(): string
|
||||
{
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call getWarningMessage() without a job.');
|
||||
}
|
||||
|
||||
return $this->warning;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function isJobConfigured(): bool
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
$config['has-file-upload'] = $config['has-file-upload'] ?? false;
|
||||
$config['initial-config-complete'] = $config['initial-config-complete'] ?? false;
|
||||
$config['column-roles-complete'] = $config['column-roles-complete'] ?? false;
|
||||
$config['column-mapping-complete'] = $config['column-mapping-complete'] ?? false;
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call isJobConfigured() without a job.');
|
||||
}
|
||||
$config = $this->getConfig();
|
||||
$stage = $config['stage'] ?? 'initial';
|
||||
if ($stage === 'ready') {
|
||||
Log::debug('isJobConfigured returns true');
|
||||
|
||||
if ($config['initial-config-complete']
|
||||
&& $config['column-roles-complete']
|
||||
&& $config['column-mapping-complete']
|
||||
&& $config['has-file-upload']
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
Log::debug('isJobConfigured returns false');
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -151,12 +187,24 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
if (null === $this->job->configuration || 0 === count($this->job->configuration)) {
|
||||
Log::debug(sprintf('Gave import job %s initial configuration.', $this->job->key));
|
||||
$this->job->configuration = config('csv.default_config');
|
||||
$this->job->save();
|
||||
}
|
||||
Log::debug(sprintf('FileConfigurator::setJob(#%d: %s)', $job->id, $job->key));
|
||||
$this->job = $job;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
|
||||
$config = $this->getConfig();
|
||||
$newConfig = array_merge($this->defaultConfig, $config);
|
||||
$this->repository->setConfiguration($job, $newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Short hand method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getConfig(): array
|
||||
{
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,18 +214,22 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
private function getConfigurationClass(): string
|
||||
{
|
||||
$class = false;
|
||||
switch (true) {
|
||||
case !$this->job->configuration['has-file-upload']:
|
||||
$class = Upload::class;
|
||||
break;
|
||||
case !$this->job->configuration['initial-config-complete']:
|
||||
$config = $this->getConfig();
|
||||
$stage = $config['stage'] ?? 'initial';
|
||||
$class = false;
|
||||
Log::debug(sprintf('Now in getConfigurationClass() for stage "%s"', $stage));
|
||||
|
||||
switch ($stage) {
|
||||
case 'initial': // has nothing, no file upload or anything.
|
||||
$class = Initial::class;
|
||||
break;
|
||||
case !$this->job->configuration['column-roles-complete']:
|
||||
case 'upload-config': // has file, needs file config.
|
||||
$class = UploadConfig::class;
|
||||
break;
|
||||
case 'roles': // has configured file, needs roles.
|
||||
$class = Roles::class;
|
||||
break;
|
||||
case !$this->job->configuration['column-mapping-complete']:
|
||||
case 'map': // has roles, needs mapping.
|
||||
$class = Map::class;
|
||||
break;
|
||||
default:
|
||||
@ -185,11 +237,12 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
}
|
||||
|
||||
if (false === $class || 0 === strlen($class)) {
|
||||
throw new FireflyException('Cannot handle current job state in getConfigurationClass().');
|
||||
throw new FireflyException(sprintf('Cannot handle job stage "%s" in getConfigurationClass().', $stage));
|
||||
}
|
||||
if (!class_exists($class)) {
|
||||
throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class));
|
||||
throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class)); // @codeCoverageIgnore
|
||||
}
|
||||
Log::debug(sprintf('Configuration class is "%s"', $class));
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
@ -22,7 +22,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Import\Configuration;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Support\Import\Configuration\Spectre\HaveAccounts;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class SpectreConfigurator.
|
||||
@ -51,7 +54,29 @@ class SpectreConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function configureJob(array $data): bool
|
||||
{
|
||||
die('cannot store config');
|
||||
$config = $this->job->configuration;
|
||||
$stage = $config['stage'];
|
||||
$status = $this->job->status;
|
||||
Log::debug(sprintf('in getNextData(), for stage "%s".', $stage));
|
||||
switch ($stage) {
|
||||
case 'have-accounts':
|
||||
/** @var HaveAccounts $class */
|
||||
$class = app(HaveAccounts::class);
|
||||
$class->setJob($this->job);
|
||||
$class->storeConfiguration($data);
|
||||
|
||||
// update job for next step and set to "configured".
|
||||
$config = $this->job->configuration;
|
||||
$config['stage'] = 'have-account-mapping';
|
||||
$this->job->configuration = $config;
|
||||
$this->job->status = 'configured';
|
||||
$this->job->save();
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot store configuration when job is in state "%s"', $stage));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,11 +86,34 @@ class SpectreConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function getNextData(): array
|
||||
{
|
||||
// update config to tell Firefly we've redirected the user.
|
||||
$config = $this->job->configuration;
|
||||
$config['is-redirected'] = true;
|
||||
$config = $this->job->configuration;
|
||||
$stage = $config['stage'];
|
||||
$status = $this->job->status;
|
||||
Log::debug(sprintf('in getNextData(), for stage "%s".', $stage));
|
||||
switch ($stage) {
|
||||
case 'has-token':
|
||||
// simply redirect to Spectre.
|
||||
$config['is-redirected'] = true;
|
||||
$config['stage'] = 'user-logged-in';
|
||||
$status = 'configured';
|
||||
break;
|
||||
case 'have-accounts':
|
||||
// use special class:
|
||||
/** @var HaveAccounts $class */
|
||||
$class = app(HaveAccounts::class);
|
||||
$class->setJob($this->job);
|
||||
$data = $class->getData();
|
||||
|
||||
return $data;
|
||||
default:
|
||||
return [];
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// update config and status:
|
||||
$this->job->configuration = $config;
|
||||
$this->job->status = 'configured';
|
||||
$this->job->status = $status;
|
||||
$this->job->save();
|
||||
|
||||
return $this->job->configuration;
|
||||
@ -76,7 +124,22 @@ class SpectreConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function getNextView(): string
|
||||
{
|
||||
return 'import.spectre.redirect';
|
||||
$config = $this->job->configuration;
|
||||
$stage = $config['stage'];
|
||||
Log::debug(sprintf('in getNextView(), for stage "%s".', $stage));
|
||||
switch ($stage) {
|
||||
case 'has-token':
|
||||
// redirect to Spectre.
|
||||
return 'import.spectre.redirect';
|
||||
break;
|
||||
case 'have-accounts':
|
||||
return 'import.spectre.accounts';
|
||||
break;
|
||||
default:
|
||||
return '';
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,13 +157,20 @@ class SpectreConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function isJobConfigured(): bool
|
||||
{
|
||||
// job is configured (and can start) when token is empty:
|
||||
$config = $this->job->configuration;
|
||||
if ($config['has-token'] === false) {
|
||||
return true;
|
||||
}
|
||||
$stage = $config['stage'];
|
||||
Log::debug(sprintf('in isJobConfigured(), for stage "%s".', $stage));
|
||||
switch ($stage) {
|
||||
case 'has-token':
|
||||
case 'have-accounts':
|
||||
Log::debug('isJobConfigured returns false');
|
||||
|
||||
return false;
|
||||
return false;
|
||||
default:
|
||||
Log::debug('isJobConfigured returns true');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,18 +178,27 @@ class SpectreConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job)
|
||||
{
|
||||
$defaultConfig = [
|
||||
'has-token' => false,
|
||||
'token' => '',
|
||||
'token-expires' => 0,
|
||||
'token-url' => '',
|
||||
'is-redirected' => false,
|
||||
|
||||
$defaultConfig = [
|
||||
'has-token' => false,
|
||||
'token' => '',
|
||||
'token-expires' => 0,
|
||||
'token-url' => '',
|
||||
'is-redirected' => false,
|
||||
'customer' => null,
|
||||
'login' => null,
|
||||
'stage' => 'initial',
|
||||
'accounts' => '',
|
||||
'accounts-mapped' => '',
|
||||
'auto-start' => true,
|
||||
];
|
||||
$extendedStatus = $job->extended_status;
|
||||
$extendedStatus['steps'] = 100;
|
||||
|
||||
$config = $job->configuration;
|
||||
$finalConfig = array_merge($defaultConfig, $config);
|
||||
$job->configuration = $finalConfig;
|
||||
|
||||
$config = $job->configuration;
|
||||
$finalConfig = array_merge($defaultConfig, $config);
|
||||
$job->configuration = $finalConfig;
|
||||
$job->extended_status = $extendedStatus;
|
||||
$job->save();
|
||||
$this->job = $job;
|
||||
}
|
||||
|
@ -45,8 +45,10 @@ class Amount implements ConverterInterface
|
||||
if (null === $value) {
|
||||
return '0';
|
||||
}
|
||||
$value = strval($value);
|
||||
Log::debug(sprintf('Start with amount "%s"', $value));
|
||||
$original = $value;
|
||||
$value = strval($value);
|
||||
$value = $this->stripAmount($value);
|
||||
$len = strlen($value);
|
||||
$decimalPosition = $len - 3;
|
||||
$altPosition = $len - 2;
|
||||
@ -81,27 +83,43 @@ class Amount implements ConverterInterface
|
||||
// if decimal is dot, replace all comma's and spaces with nothing. then parse as float (round to 4 pos)
|
||||
if ('.' === $decimal) {
|
||||
$search = [',', ' '];
|
||||
$oldValue = $value;
|
||||
$value = str_replace($search, '', $value);
|
||||
Log::debug(sprintf('Converted amount from "%s" to "%s".', $oldValue, $value));
|
||||
Log::debug(sprintf('Converted amount from "%s" to "%s".', $original, $value));
|
||||
}
|
||||
if (',' === $decimal) {
|
||||
$search = ['.', ' '];
|
||||
$oldValue = $value;
|
||||
$value = str_replace($search, '', $value);
|
||||
$value = str_replace(',', '.', $value);
|
||||
Log::debug(sprintf('Converted amount from "%s" to "%s".', $oldValue, $value));
|
||||
Log::debug(sprintf('Converted amount from "%s" to "%s".', $original, $value));
|
||||
}
|
||||
if (null === $decimal) {
|
||||
// replace all:
|
||||
$search = ['.', ' ', ','];
|
||||
$oldValue = $value;
|
||||
$value = str_replace($search, '', $value);
|
||||
Log::debug(sprintf('No decimal character found. Converted amount from "%s" to "%s".', $oldValue, $value));
|
||||
Log::debug(sprintf('No decimal character found. Converted amount from "%s" to "%s".', $original, $value));
|
||||
}
|
||||
|
||||
$number = strval(number_format(round(floatval($value), 12), 12, '.', ''));
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function stripAmount(string $value): string
|
||||
{
|
||||
$str = preg_replace('/[^\-\(\)\.\,0-9 ]/', '', $value);
|
||||
$len = strlen($str);
|
||||
if ($str{0} === '(' && $str{$len - 1} === ')') {
|
||||
$str = '-' . substr($str, 1, ($len - 2));
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Stripped "%s" away to "%s"', $value, $str));
|
||||
|
||||
return $str;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Import\Object\ImportJournal;
|
||||
use FireflyIII\Import\Specifics\SpecificInterface;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Models\TransactionJournalMeta;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Iterator;
|
||||
use League\Csv\Reader;
|
||||
@ -43,6 +43,8 @@ class CsvProcessor implements FileProcessorInterface
|
||||
private $job;
|
||||
/** @var Collection */
|
||||
private $objects;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
/** @var array */
|
||||
private $validConverters = [];
|
||||
/** @var array */
|
||||
@ -60,9 +62,14 @@ class CsvProcessor implements FileProcessorInterface
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getObjects(): Collection
|
||||
{
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call getObjects() without a job.');
|
||||
}
|
||||
|
||||
return $this->objects;
|
||||
}
|
||||
|
||||
@ -73,9 +80,13 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*
|
||||
* @throws \League\Csv\Exception
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function run(): bool
|
||||
{
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call run() without a job.');
|
||||
}
|
||||
Log::debug('Now in CsvProcessor run(). Job is now running...');
|
||||
|
||||
$entries = new Collection($this->getImportArray());
|
||||
@ -86,8 +97,8 @@ class CsvProcessor implements FileProcessorInterface
|
||||
$row = array_values($row);
|
||||
if ($this->rowAlreadyImported($row)) {
|
||||
$message = sprintf('Row #%d has already been imported.', $index);
|
||||
$this->job->addError($index, $message);
|
||||
$this->job->addStepsDone(5); // all steps.
|
||||
$this->repository->addStepsDone($this->job, 5);
|
||||
$this->addError($index, $message);
|
||||
Log::info($message);
|
||||
|
||||
return null;
|
||||
@ -99,23 +110,34 @@ class CsvProcessor implements FileProcessorInterface
|
||||
Log::debug(sprintf('Number of entries left: %d', $notImported->count()));
|
||||
|
||||
// set (new) number of steps:
|
||||
$status = $this->job->extended_status;
|
||||
$status['steps'] = $notImported->count() * 5;
|
||||
$this->job->extended_status = $status;
|
||||
$this->job->save();
|
||||
Log::debug(sprintf('Number of steps: %d', $notImported->count() * 5));
|
||||
$extended = $this->getExtendedStatus();
|
||||
$steps = $notImported->count() * 5;
|
||||
$extended['steps'] = $steps;
|
||||
$this->setExtendedStatus($extended);
|
||||
Log::debug(sprintf('Number of steps: %d', $steps));
|
||||
|
||||
$notImported->each(
|
||||
function (array $row, int $index) {
|
||||
$journal = $this->importRow($index, $row);
|
||||
$this->objects->push($journal);
|
||||
$this->job->addStepsDone(1);
|
||||
$this->repository->addStepsDone($this->job, 1);
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* Shorthand method
|
||||
*
|
||||
* @param array $array
|
||||
*/
|
||||
public function setExtendedStatus(array $array)
|
||||
{
|
||||
$this->repository->setExtendedStatus($this->job, $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set import job for this processor.
|
||||
*
|
||||
@ -125,11 +147,30 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job): FileProcessorInterface
|
||||
{
|
||||
$this->job = $job;
|
||||
$this->job = $job;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $index
|
||||
* @param string $message
|
||||
*/
|
||||
private function addError(int $index, string $message): void
|
||||
{
|
||||
$extended = $this->getExtendedStatus();
|
||||
$extended['errors'][$index][] = $message;
|
||||
$this->setExtendedStatus($extended);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta data to the individual value and verify that it can be handled in a later stage.
|
||||
*
|
||||
@ -142,7 +183,7 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*/
|
||||
private function annotateValue(int $index, string $value)
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
$config = $this->getConfig();
|
||||
$role = $config['column-roles'][$index] ?? '_ignore';
|
||||
$mapped = $config['column-mapping-config'][$index][$value] ?? null;
|
||||
|
||||
@ -160,6 +201,29 @@ class CsvProcessor implements FileProcessorInterface
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* Shorthand method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getConfig(): array
|
||||
{
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* Shorthand method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getExtendedStatus(): array
|
||||
{
|
||||
return $this->repository->getExtendedStatus($this->job);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Iterator
|
||||
*
|
||||
@ -169,16 +233,17 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*/
|
||||
private function getImportArray(): Iterator
|
||||
{
|
||||
$content = $this->job->uploadFileContents();
|
||||
$config = $this->job->configuration;
|
||||
$reader = Reader::createFromString($content);
|
||||
$delimiter = $config['delimiter'];
|
||||
$content = $this->repository->uploadFileContents($this->job);
|
||||
$config = $this->getConfig();
|
||||
$reader = Reader::createFromString($content);
|
||||
$delimiter = $config['delimiter'] ?? ',';
|
||||
$hasHeaders = isset($config['has-headers']) ? $config['has-headers'] : false;
|
||||
if ('tab' === $delimiter) {
|
||||
$delimiter = "\t";
|
||||
$delimiter = "\t"; // @codeCoverageIgnore
|
||||
}
|
||||
$reader->setDelimiter($delimiter);
|
||||
if ($config['has-headers']) {
|
||||
$reader->setHeaderOffset(0);
|
||||
if ($hasHeaders) {
|
||||
$reader->setHeaderOffset(0); // @codeCoverageIgnore
|
||||
}
|
||||
$results = $reader->getRecords();
|
||||
Log::debug('Created a CSV reader.');
|
||||
@ -191,6 +256,7 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*
|
||||
* @param int $jsonError
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return string
|
||||
*/
|
||||
private function getJsonError(int $jsonError): string
|
||||
@ -230,7 +296,7 @@ class CsvProcessor implements FileProcessorInterface
|
||||
$jsonError = json_last_error();
|
||||
|
||||
if (false === $json) {
|
||||
throw new FireflyException(sprintf('Error while encoding JSON for CSV row: %s', $this->getJsonError($jsonError)));
|
||||
throw new FireflyException(sprintf('Error while encoding JSON for CSV row: %s', $this->getJsonError($jsonError))); // @codeCoverageIgnore
|
||||
}
|
||||
$hash = hash('sha256', $json);
|
||||
|
||||
@ -251,8 +317,9 @@ class CsvProcessor implements FileProcessorInterface
|
||||
{
|
||||
$row = array_values($row);
|
||||
Log::debug(sprintf('Now at row %d', $index));
|
||||
$row = $this->specifics($row);
|
||||
$hash = $this->getRowHash($row);
|
||||
$row = $this->specifics($row);
|
||||
$hash = $this->getRowHash($row);
|
||||
$config = $this->getConfig();
|
||||
|
||||
$journal = new ImportJournal;
|
||||
$journal->setUser($this->job->user);
|
||||
@ -271,7 +338,8 @@ class CsvProcessor implements FileProcessorInterface
|
||||
}
|
||||
}
|
||||
// set some extra info:
|
||||
$journal->asset->setDefaultAccountId($this->job->configuration['import-account']);
|
||||
$importAccount = intval($config['import-account'] ?? 0);
|
||||
$journal->asset->setDefaultAccountId($importAccount);
|
||||
|
||||
return $journal;
|
||||
}
|
||||
@ -288,12 +356,8 @@ class CsvProcessor implements FileProcessorInterface
|
||||
private function rowAlreadyImported(array $array): bool
|
||||
{
|
||||
$hash = $this->getRowHash($array);
|
||||
$json = json_encode($hash);
|
||||
$entry = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('data', $json)
|
||||
->where('name', 'importHash')
|
||||
->first();
|
||||
if (null !== $entry) {
|
||||
$count = $this->repository->countByHash($hash);
|
||||
if ($count > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ use Illuminate\Console\Command;
|
||||
use Monolog\Handler\AbstractProcessingHandler;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* Class CommandHandler.
|
||||
*/
|
||||
class CommandHandler extends AbstractProcessingHandler
|
||||
|
@ -44,18 +44,17 @@ class AssetAccountIbans implements MapperInterface
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($set as $account) {
|
||||
$iban = $account->iban ?? '';
|
||||
$iban = $account->iban ?? '';
|
||||
$accountId = intval($account->id);
|
||||
if (strlen($iban) > 0) {
|
||||
$topList[$account->id] = $account->iban . ' (' . $account->name . ')';
|
||||
$topList[$accountId] = $account->iban . ' (' . $account->name . ')';
|
||||
}
|
||||
if (0 === strlen($iban)) {
|
||||
$list[$account->id] = $account->name;
|
||||
$list[$accountId] = $account->name;
|
||||
}
|
||||
}
|
||||
asort($topList);
|
||||
asort($list);
|
||||
|
||||
$list = $topList + $list;
|
||||
asort($list);
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@ -43,16 +43,15 @@ class AssetAccounts implements MapperInterface
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($set as $account) {
|
||||
$name = $account->name;
|
||||
$iban = $account->iban ?? '';
|
||||
$accountId = intval($account->id);
|
||||
$name = $account->name;
|
||||
$iban = $account->iban ?? '';
|
||||
if (strlen($iban) > 0) {
|
||||
$name .= ' (' . $account->iban . ')';
|
||||
$name .= ' (' . $iban . ')';
|
||||
}
|
||||
$list[$account->id] = $name;
|
||||
$list[$accountId] = $name;
|
||||
}
|
||||
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@ -42,10 +42,10 @@ class Bills implements MapperInterface
|
||||
|
||||
/** @var Bill $bill */
|
||||
foreach ($result as $bill) {
|
||||
$list[$bill->id] = $bill->name . ' [' . $bill->match . ']';
|
||||
$billId = intval($bill->id);
|
||||
$list[$billId] = $bill->name . ' [' . $bill->match . ']';
|
||||
}
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@ -37,15 +37,15 @@ class Budgets implements MapperInterface
|
||||
{
|
||||
/** @var BudgetRepositoryInterface $repository */
|
||||
$repository = app(BudgetRepositoryInterface::class);
|
||||
$result = $repository->getBudgets();
|
||||
$result = $repository->getActiveBudgets();
|
||||
$list = [];
|
||||
|
||||
/** @var Budget $budget */
|
||||
foreach ($result as $budget) {
|
||||
$list[$budget->id] = $budget->name;
|
||||
$budgetId = intval($budget->id);
|
||||
$list[$budgetId] = $budget->name;
|
||||
}
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@ -42,10 +42,10 @@ class Categories implements MapperInterface
|
||||
|
||||
/** @var Category $category */
|
||||
foreach ($result as $category) {
|
||||
$list[$category->id] = $category->name;
|
||||
$categoryId = intval($category->id);
|
||||
$list[$categoryId] = $category->name;
|
||||
}
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@ -50,18 +50,17 @@ class OpposingAccountIbans implements MapperInterface
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($set as $account) {
|
||||
$iban = $account->iban ?? '';
|
||||
$iban = $account->iban ?? '';
|
||||
$accountId = intval($account->id);
|
||||
if (strlen($iban) > 0) {
|
||||
$topList[$account->id] = $account->iban . ' (' . $account->name . ')';
|
||||
$topList[$accountId] = $account->iban . ' (' . $account->name . ')';
|
||||
}
|
||||
if (0 === strlen($iban)) {
|
||||
$list[$account->id] = $account->name;
|
||||
$list[$accountId] = $account->name;
|
||||
}
|
||||
}
|
||||
asort($topList);
|
||||
asort($list);
|
||||
|
||||
$list = $topList + $list;
|
||||
asort($list);
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@ -49,16 +49,15 @@ class OpposingAccounts implements MapperInterface
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($set as $account) {
|
||||
$name = $account->name;
|
||||
$iban = $account->iban ?? '';
|
||||
$accountId = intval($account->id);
|
||||
$name = $account->name;
|
||||
$iban = $account->iban ?? '';
|
||||
if (strlen($iban) > 0) {
|
||||
$name .= ' (' . $account->iban . ')';
|
||||
$name .= ' (' . $iban . ')';
|
||||
}
|
||||
$list[$account->id] = $name;
|
||||
$list[$accountId] = $name;
|
||||
}
|
||||
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@ -42,10 +42,10 @@ class Tags implements MapperInterface
|
||||
|
||||
/** @var Tag $tag */
|
||||
foreach ($result as $tag) {
|
||||
$list[$tag->id] = $tag->tag;
|
||||
$tagId = intval($tag->id);
|
||||
$list[$tagId] = $tag->tag;
|
||||
}
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@ -22,7 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Import\Mapper;
|
||||
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
|
||||
/**
|
||||
* Class TransactionCurrencies.
|
||||
@ -34,12 +34,14 @@ class TransactionCurrencies implements MapperInterface
|
||||
*/
|
||||
public function getMap(): array
|
||||
{
|
||||
$currencies = TransactionCurrency::get();
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$currencies = $repository->get();
|
||||
$list = [];
|
||||
foreach ($currencies as $currency) {
|
||||
$list[$currency->id] = $currency->name . ' (' . $currency->code . ')';
|
||||
$currencyId = intval($currency->id);
|
||||
$list[$currencyId] = $currency->name . ' (' . $currency->code . ')';
|
||||
}
|
||||
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
@ -34,6 +34,11 @@ class TagsComma implements PreProcessorInterface
|
||||
*/
|
||||
public function run(string $value): array
|
||||
{
|
||||
return explode(',', $value);
|
||||
$set = explode(',', $value);
|
||||
$set = array_map('trim', $set);
|
||||
$set = array_filter($set, 'strlen');
|
||||
$return = array_values($set);
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,11 @@ class TagsSpace implements PreProcessorInterface
|
||||
*/
|
||||
public function run(string $value): array
|
||||
{
|
||||
return explode(' ', $value);
|
||||
$set = explode(' ', $value);
|
||||
$set = array_map('trim', $set);
|
||||
$set = array_filter($set, 'strlen');
|
||||
$return = array_values($set);
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Import\Object;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
@ -74,6 +75,7 @@ class ImportAccount
|
||||
|
||||
/**
|
||||
* @return Account
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getAccount(): Account
|
||||
{
|
||||
@ -85,6 +87,7 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* @return string
|
||||
*/
|
||||
public function getExpectedType(): string
|
||||
@ -93,6 +96,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $expectedType
|
||||
*/
|
||||
public function setExpectedType(string $expectedType)
|
||||
@ -101,6 +106,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $accountIban
|
||||
*/
|
||||
public function setAccountIban(array $accountIban)
|
||||
@ -109,6 +116,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $value
|
||||
*/
|
||||
public function setAccountId(array $value)
|
||||
@ -117,6 +126,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $accountName
|
||||
*/
|
||||
public function setAccountName(array $accountName)
|
||||
@ -125,6 +136,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $accountNumber
|
||||
*/
|
||||
public function setAccountNumber(array $accountNumber)
|
||||
@ -133,6 +146,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $defaultAccountId
|
||||
*/
|
||||
public function setDefaultAccountId(int $defaultAccountId)
|
||||
@ -141,6 +156,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $forbiddenAccountId
|
||||
*/
|
||||
public function setForbiddenAccountId(int $forbiddenAccountId)
|
||||
@ -149,6 +166,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user)
|
||||
@ -158,20 +177,21 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Account
|
||||
* @return Account|null
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*/
|
||||
private function findExistingObject(): Account
|
||||
private function findExistingObject(): ?Account
|
||||
{
|
||||
Log::debug('In findExistingObject() for Account');
|
||||
// 0: determin account type:
|
||||
/** @var AccountType $accountType */
|
||||
$accountType = AccountType::whereType($this->expectedType)->first();
|
||||
$accountType = $this->repository->getAccountType($this->expectedType);
|
||||
|
||||
// 1: find by ID, iban or name (and type)
|
||||
if (3 === count($this->accountId)) {
|
||||
Log::debug(sprintf('Finding account of type %d and ID %d', $accountType->id, $this->accountId['value']));
|
||||
/** @var Account $account */
|
||||
|
||||
$account = $this->user->accounts()->where('id', '!=', $this->forbiddenAccountId)->where('account_type_id', $accountType->id)->where(
|
||||
'id',
|
||||
$this->accountId['value']
|
||||
@ -233,13 +253,13 @@ class ImportAccount
|
||||
// 4: do not search by account number.
|
||||
Log::debug('Found NO existing accounts.');
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Account
|
||||
* @return Account|null
|
||||
*/
|
||||
private function findMappedObject(): Account
|
||||
private function findMappedObject(): ?Account
|
||||
{
|
||||
Log::debug('In findMappedObject() for Account');
|
||||
$fields = ['accountId', 'accountIban', 'accountNumber', 'accountName'];
|
||||
@ -248,7 +268,7 @@ class ImportAccount
|
||||
Log::debug(sprintf('Find mapped account based on field "%s" with value', $field), $array);
|
||||
// check if a pre-mapped object exists.
|
||||
$mapped = $this->getMappedObject($array);
|
||||
if (null !== $mapped->id) {
|
||||
if (null !== $mapped) {
|
||||
Log::debug(sprintf('Found account #%d!', $mapped->id));
|
||||
|
||||
return $mapped;
|
||||
@ -256,38 +276,38 @@ class ImportAccount
|
||||
}
|
||||
Log::debug('Found no account on mapped data or no map present.');
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
*
|
||||
* @return Account
|
||||
* @return Account|null
|
||||
*/
|
||||
private function getMappedObject(array $array): Account
|
||||
private function getMappedObject(array $array): ?Account
|
||||
{
|
||||
Log::debug('In getMappedObject() for Account');
|
||||
if (0 === count($array)) {
|
||||
Log::debug('Array is empty, nothing will come of this.');
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (array_key_exists('mapped', $array) && null === $array['mapped']) {
|
||||
Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value']));
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
|
||||
Log::debug('Finding a mapped account based on', $array);
|
||||
|
||||
$search = intval($array['mapped']);
|
||||
$search = intval($array['mapped'] ?? 0);
|
||||
$account = $this->repository->find($search);
|
||||
|
||||
if (null === $account->id) {
|
||||
Log::error(sprintf('There is no account with id #%d. Invalid mapping will be ignored!', $search));
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
// must be of the same type
|
||||
// except when mapped is an asset, then it's fair game.
|
||||
@ -302,7 +322,7 @@ class ImportAccount
|
||||
)
|
||||
);
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Found account! #%d ("%s"). Return it', $account->id, $account->name));
|
||||
@ -312,19 +332,26 @@ class ImportAccount
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function store(): bool
|
||||
{
|
||||
if (is_null($this->user)) {
|
||||
throw new FireflyException('ImportAccount cannot continue without user.');
|
||||
}
|
||||
if ((is_null($this->defaultAccountId) || intval($this->defaultAccountId) === 0) && AccountType::ASSET === $this->expectedType) {
|
||||
throw new FireflyException('ImportAccount cannot continue without a default account to fall back on.');
|
||||
}
|
||||
// 1: find mapped object:
|
||||
$mapped = $this->findMappedObject();
|
||||
if (null !== $mapped->id) {
|
||||
if (null !== $mapped) {
|
||||
$this->account = $mapped;
|
||||
|
||||
return true;
|
||||
}
|
||||
// 2: find existing by given values:
|
||||
$found = $this->findExistingObject();
|
||||
if (null !== $found->id) {
|
||||
if (null !== $found) {
|
||||
$this->account = $found;
|
||||
|
||||
return true;
|
||||
@ -335,7 +362,7 @@ class ImportAccount
|
||||
$oldExpectedType = $this->expectedType;
|
||||
$this->expectedType = AccountType::ASSET;
|
||||
$found = $this->findExistingObject();
|
||||
if (null !== $found->id) {
|
||||
if (null !== $found) {
|
||||
Log::debug('Found asset account!');
|
||||
$this->account = $found;
|
||||
|
||||
|
@ -302,6 +302,10 @@ class ImportJournal
|
||||
if (0 === count($info)) {
|
||||
throw new FireflyException('No amount information for this row.');
|
||||
}
|
||||
$class = $info['class'] ?? '';
|
||||
if (strlen($class) === 0) {
|
||||
throw new FireflyException('No amount information (conversion class) for this row.');
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Converter class is %s', $info['class']));
|
||||
/** @var ConverterInterface $amountConverter */
|
||||
|
@ -24,9 +24,18 @@ namespace FireflyIII\Import\Routine;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Services\Spectre\Exception\DuplicatedCustomerException;
|
||||
use FireflyIII\Services\Spectre\Exception\SpectreException;
|
||||
use FireflyIII\Services\Spectre\Object\Account;
|
||||
use FireflyIII\Services\Spectre\Object\Customer;
|
||||
use FireflyIII\Services\Spectre\Object\Login;
|
||||
use FireflyIII\Services\Spectre\Object\Token;
|
||||
use FireflyIII\Services\Spectre\Request\CreateTokenRequest;
|
||||
use FireflyIII\Services\Spectre\Request\ListAccountsRequest;
|
||||
use FireflyIII\Services\Spectre\Request\ListCustomersRequest;
|
||||
use FireflyIII\Services\Spectre\Request\ListLoginsRequest;
|
||||
use FireflyIII\Services\Spectre\Request\ListTransactionsRequest;
|
||||
use FireflyIII\Services\Spectre\Request\NewCustomerRequest;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
@ -46,6 +55,9 @@ class SpectreRoutine implements RoutineInterface
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* ImportRoutine constructor.
|
||||
*/
|
||||
@ -80,57 +92,61 @@ class SpectreRoutine implements RoutineInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* A Spectre job that ends up here is either "configured" or "running", and will be set to "running"
|
||||
* when it is "configured".
|
||||
*
|
||||
* Job has several stages, stored in extended status key 'stage'
|
||||
*
|
||||
* initial: just begun, nothing happened. action: get a customer and a token. Next status: has-token
|
||||
* has-token: redirect user to sandstorm, make user login. set job to: user-logged-in
|
||||
* user-logged-in: customer has an attempt. action: analyse/get attempt and go for next status.
|
||||
* if attempt failed: job status is error, save a warning somewhere?
|
||||
* if success, try to get accounts. Save in config key 'accounts'. set status: have-accounts and "configuring"
|
||||
*
|
||||
* have-accounts: make user link accounts and select accounts to import from.
|
||||
*
|
||||
* If job is "configuring" and stage "have-accounts" then present the accounts and make user link them to
|
||||
* own asset accounts. Store this mapping, set config to "have-account-mapping" and job status configured".
|
||||
*
|
||||
* have-account-mapping: start downloading transactions?
|
||||
*
|
||||
*
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
|
||||
*/
|
||||
public function run(): bool
|
||||
{
|
||||
if ('configured' !== $this->job->status) {
|
||||
Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->job->status));
|
||||
|
||||
return false;
|
||||
if ('configured' === $this->job->status) {
|
||||
$this->repository->updateStatus($this->job, 'running');
|
||||
}
|
||||
Log::info(sprintf('Start with import job %s using Spectre.', $this->job->key));
|
||||
set_time_limit(0);
|
||||
|
||||
// check if job has token first!
|
||||
$config = $this->job->configuration;
|
||||
$hasToken = $config['has-token'] ?? false;
|
||||
if ($hasToken === false) {
|
||||
Log::debug('Job has no token');
|
||||
// create customer if user does not have one:
|
||||
$customer = $this->getCustomer();
|
||||
Log::debug(sprintf('Customer ID is %s', $customer->getId()));
|
||||
// use customer to request a token:
|
||||
$uri = route('import.status', [$this->job->key]);
|
||||
$token = $this->getToken($customer, $uri);
|
||||
Log::debug(sprintf('Token is %s', $token->getToken()));
|
||||
$config = $this->job->configuration;
|
||||
$stage = $config['stage'];
|
||||
|
||||
// update job, give it the token:
|
||||
$config = $this->job->configuration;
|
||||
$config['has-token'] = true;
|
||||
$config['token'] = $token->getToken();
|
||||
$config['token-expires'] = $token->getExpiresAt()->format('U');
|
||||
$config['token-url'] = $token->getConnectUrl();
|
||||
$this->job->configuration = $config;
|
||||
|
||||
Log::debug('Job config is now', $config);
|
||||
|
||||
// update job, set status to "configuring".
|
||||
$this->job->status = 'configuring';
|
||||
$this->job->save();
|
||||
Log::debug(sprintf('Job status is now %s', $this->job->status));
|
||||
|
||||
return true;
|
||||
}
|
||||
$isRedirected = $config['is-redirected'] ?? false;
|
||||
if ($isRedirected === true) {
|
||||
// assume user has "used" the token.
|
||||
// ...
|
||||
// now what?
|
||||
throw new FireflyException('Application cannot handle this.');
|
||||
switch ($stage) {
|
||||
case 'initial':
|
||||
// get customer and token:
|
||||
$this->runStageInitial();
|
||||
break;
|
||||
case 'has-token':
|
||||
// import routine does nothing at this point:
|
||||
break;
|
||||
case 'user-logged-in':
|
||||
$this->runStageLoggedIn();
|
||||
break;
|
||||
case 'have-account-mapping':
|
||||
$this->runStageHaveMapping();
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot handle stage %s', $stage));
|
||||
}
|
||||
|
||||
var_dump($config);
|
||||
exit;
|
||||
|
||||
throw new FireflyException('Application cannot handle this.');
|
||||
}
|
||||
|
||||
@ -139,18 +155,39 @@ class SpectreRoutine implements RoutineInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
$this->job = $job;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
|
||||
*/
|
||||
protected function createCustomer(): Customer
|
||||
{
|
||||
$newCustomerRequest = new NewCustomerRequest($this->job->user);
|
||||
$newCustomerRequest->call();
|
||||
$customer = $newCustomerRequest->getCustomer();
|
||||
$customer = null;
|
||||
try {
|
||||
$newCustomerRequest->call();
|
||||
$customer = $newCustomerRequest->getCustomer();
|
||||
} catch (DuplicatedCustomerException $e) {
|
||||
// already exists, must fetch customer instead.
|
||||
Log::warning('Customer exists already for user, fetch it.');
|
||||
}
|
||||
if (is_null($customer)) {
|
||||
$getCustomerRequest = new ListCustomersRequest($this->job->user);
|
||||
$getCustomerRequest->call();
|
||||
$customers = $getCustomerRequest->getCustomers();
|
||||
/** @var Customer $current */
|
||||
foreach ($customers as $current) {
|
||||
if ($current->getIdentifier() === 'default_ff3_customer') {
|
||||
$customer = $current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Preferences::setForUser($this->job->user, 'spectre_customer', $customer->toArray());
|
||||
|
||||
@ -160,15 +197,26 @@ class SpectreRoutine implements RoutineInterface
|
||||
|
||||
/**
|
||||
* @return Customer
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws FireflyException
|
||||
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
|
||||
*/
|
||||
protected function getCustomer(): Customer
|
||||
{
|
||||
$preference = Preferences::getForUser($this->job->user, 'spectre_customer', null);
|
||||
if (is_null($preference)) {
|
||||
return $this->createCustomer();
|
||||
$config = $this->job->configuration;
|
||||
if (!is_null($config['customer'])) {
|
||||
$customer = new Customer($config['customer']);
|
||||
|
||||
return $customer;
|
||||
}
|
||||
$customer = new Customer($preference->data);
|
||||
|
||||
$customer = $this->createCustomer();
|
||||
$config['customer'] = [
|
||||
'id' => $customer->getId(),
|
||||
'identifier' => $customer->getIdentifier(),
|
||||
'secret' => $customer->getSecret(),
|
||||
];
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
|
||||
return $customer;
|
||||
}
|
||||
@ -179,6 +227,7 @@ class SpectreRoutine implements RoutineInterface
|
||||
*
|
||||
* @return Token
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
|
||||
*/
|
||||
protected function getToken(Customer $customer, string $returnUri): Token
|
||||
{
|
||||
@ -191,4 +240,124 @@ class SpectreRoutine implements RoutineInterface
|
||||
return $request->getToken();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
* @throws SpectreException
|
||||
*/
|
||||
protected function runStageInitial(): void
|
||||
{
|
||||
Log::debug('In runStageInitial()');
|
||||
|
||||
// create customer if user does not have one:
|
||||
$customer = $this->getCustomer();
|
||||
Log::debug(sprintf('Customer ID is %s', $customer->getId()));
|
||||
|
||||
// use customer to request a token:
|
||||
$uri = route('import.status', [$this->job->key]);
|
||||
$token = $this->getToken($customer, $uri);
|
||||
Log::debug(sprintf('Token is %s', $token->getToken()));
|
||||
|
||||
// update job, give it the token:
|
||||
$config = $this->job->configuration;
|
||||
$config['has-token'] = true;
|
||||
$config['token'] = $token->getToken();
|
||||
$config['token-expires'] = $token->getExpiresAt()->format('U');
|
||||
$config['token-url'] = $token->getConnectUrl();
|
||||
$config['stage'] = 'has-token';
|
||||
$this->job->configuration = $config;
|
||||
|
||||
Log::debug('Job config is now', $config);
|
||||
|
||||
// update job, set status to "configuring".
|
||||
$this->job->status = 'configuring';
|
||||
$this->job->save();
|
||||
Log::debug(sprintf('Job status is now %s', $this->job->status));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
* @throws SpectreException
|
||||
*/
|
||||
protected function runStageLoggedIn(): void
|
||||
{
|
||||
Log::debug('In runStageLoggedIn');
|
||||
// list all logins:
|
||||
$customer = $this->getCustomer();
|
||||
$request = new ListLoginsRequest($this->job->user);
|
||||
$request->setCustomer($customer);
|
||||
$request->call();
|
||||
$logins = $request->getLogins();
|
||||
/** @var Login $final */
|
||||
$final = null;
|
||||
// loop logins, find the latest with no error in it:
|
||||
$time = 0;
|
||||
/** @var Login $login */
|
||||
foreach ($logins as $login) {
|
||||
$attempt = $login->getLastAttempt();
|
||||
$attemptTime = intval($attempt->getCreatedAt()->format('U'));
|
||||
if ($attemptTime > $time && is_null($attempt->getFailErrorClass())) {
|
||||
$time = $attemptTime;
|
||||
$final = $login;
|
||||
}
|
||||
}
|
||||
if (is_null($final)) {
|
||||
throw new FireflyException('No valid login attempt found.');
|
||||
}
|
||||
|
||||
// list the users accounts using this login.
|
||||
$accountRequest = new ListAccountsRequest($this->job->user);
|
||||
$accountRequest->setLogin($login);
|
||||
$accountRequest->call();
|
||||
$accounts = $accountRequest->getAccounts();
|
||||
|
||||
// store accounts in job:
|
||||
$all = [];
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$all[] = $account->toArray();
|
||||
}
|
||||
|
||||
// update job:
|
||||
$config = $this->job->configuration;
|
||||
$config['accounts'] = $all;
|
||||
$config['login'] = $login->toArray();
|
||||
$config['stage'] = 'have-accounts';
|
||||
$this->job->configuration = $config;
|
||||
$this->job->status = 'configuring';
|
||||
$this->job->save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function runStageHaveMapping()
|
||||
{
|
||||
// for each spectre account id in 'account-mappings'.
|
||||
// find FF account
|
||||
// get transactions.
|
||||
// import?!
|
||||
$config = $this->job->configuration;
|
||||
$accounts = $config['accounts'] ?? [];
|
||||
/** @var array $accountArray */
|
||||
foreach ($accounts as $accountArray) {
|
||||
$account = new Account($accountArray);
|
||||
$importId = intval($config['accounts-mapped'][$account->getid()] ?? 0);
|
||||
$doImport = $importId !== 0 ? true : false;
|
||||
if (!$doImport) {
|
||||
continue;
|
||||
}
|
||||
// import into account
|
||||
$listTransactionsRequest = new ListTransactionsRequest($this->job->user);
|
||||
$listTransactionsRequest->setAccount($account);
|
||||
$listTransactionsRequest->call();
|
||||
$transactions = $listTransactionsRequest->getTransactions();
|
||||
var_dump($transactions);exit;
|
||||
|
||||
}
|
||||
var_dump($config);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,10 @@ class SnsDescription implements SpecificInterface
|
||||
*/
|
||||
public function run(array $row): array
|
||||
{
|
||||
$row = array_values($row);
|
||||
$row = array_values($row);
|
||||
if (!isset($row[17])) {
|
||||
return $row;
|
||||
}
|
||||
$row[17] = ltrim($row[17], "'");
|
||||
$row[17] = rtrim($row[17], "'");
|
||||
|
||||
|
@ -94,8 +94,8 @@ class ImportStorage
|
||||
$this->defaultCurrencyId = $currency->id;
|
||||
$this->transfers = $this->getTransfers();
|
||||
$config = $job->configuration;
|
||||
$this->applyRules = $config['apply_rules'] ?? false;
|
||||
$this->matchBills = $config['match_bills'] ?? false;
|
||||
$this->applyRules = $config['apply-rules'] ?? false;
|
||||
$this->matchBills = $config['match-bills'] ?? false;
|
||||
if (true === $this->applyRules) {
|
||||
Log::debug('applyRules seems to be true, get the rules.');
|
||||
$this->rules = $this->getRules();
|
||||
|
@ -345,6 +345,15 @@ class Account extends Model
|
||||
$this->attributes['iban'] = Crypt::encrypt($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* Get all of the notes.
|
||||
*/
|
||||
public function notes()
|
||||
{
|
||||
return $this->morphMany(Note::class, 'noteable');
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
|
@ -132,7 +132,7 @@ class Bill extends Model
|
||||
*/
|
||||
public function notes()
|
||||
{
|
||||
return $this->morphMany('FireflyIII\Models\Note', 'noteable');
|
||||
return $this->morphMany(Note::class, 'noteable');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -161,7 +161,7 @@ class ImportJob extends Model
|
||||
*/
|
||||
public function getExtendedStatusAttribute($value)
|
||||
{
|
||||
if (0 === strlen($value)) {
|
||||
if (0 === strlen(strval($value))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -209,6 +209,7 @@ class ImportJob extends Model
|
||||
$disk = Storage::disk('upload');
|
||||
$encryptedContent = $disk->get($fileName);
|
||||
$content = Crypt::decrypt($encryptedContent);
|
||||
$content = trim($content);
|
||||
Log::debug(sprintf('Content size is %d bytes.', strlen($content)));
|
||||
|
||||
return $content;
|
||||
|
@ -57,8 +57,8 @@ class Note extends Model
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* Get all of the owning noteable models. Currently piggy bank and
|
||||
* transaction journal.
|
||||
*
|
||||
* Get all of the owning noteable models.
|
||||
*/
|
||||
public function noteable()
|
||||
{
|
||||
|
@ -29,6 +29,7 @@ use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
@ -86,6 +87,28 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return account type by string.
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return AccountType|null
|
||||
*/
|
||||
public function getAccountType(string $type): ?AccountType
|
||||
{
|
||||
return AccountType::whereType($type)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
*
|
||||
* @return Note|null
|
||||
*/
|
||||
public function getNote(Account $account): ?Note
|
||||
{
|
||||
return $account->notes()->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date of the very last transaction in this account.
|
||||
*
|
||||
@ -176,6 +199,11 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
}
|
||||
$this->deleteInitialBalance($newAccount);
|
||||
|
||||
// update note:
|
||||
if (isset($data['notes'])) {
|
||||
$this->updateNote($newAccount, $data['notes']);
|
||||
}
|
||||
|
||||
return $newAccount;
|
||||
}
|
||||
|
||||
@ -199,6 +227,12 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
$this->updateInitialBalance($account, $data);
|
||||
}
|
||||
|
||||
// update note:
|
||||
if (isset($data['notes']) && null !== $data['notes']) {
|
||||
$this->updateNote($account, strval($data['notes']));
|
||||
}
|
||||
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
@ -364,7 +398,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
'user_id' => $this->user->id,
|
||||
'transaction_type_id' => $transactionType->id,
|
||||
'transaction_currency_id' => $currencyId,
|
||||
'description' => 'Initial balance for "' . $account->name . '"',
|
||||
'description' => strval(trans('firefly.initial_balance_description', ['account' => $account->name])),
|
||||
'completed' => true,
|
||||
'date' => $data['openingBalanceDate'],
|
||||
]
|
||||
@ -507,6 +541,33 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @param string $note
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function updateNote(Account $account, string $note): bool
|
||||
{
|
||||
if (0 === strlen($note)) {
|
||||
$dbNote = $account->notes()->first();
|
||||
if (null !== $dbNote) {
|
||||
$dbNote->delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
$dbNote = $account->notes()->first();
|
||||
if (null === $dbNote) {
|
||||
$dbNote = new Note();
|
||||
$dbNote->noteable()->associate($account);
|
||||
}
|
||||
$dbNote->text = trim($note);
|
||||
$dbNote->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @param TransactionJournal $journal
|
||||
@ -518,13 +579,15 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
*/
|
||||
protected function updateOpeningBalanceJournal(Account $account, TransactionJournal $journal, array $data): bool
|
||||
{
|
||||
$date = $data['openingBalanceDate'];
|
||||
$amount = strval($data['openingBalance']);
|
||||
$currencyId = intval($data['currency_id']);
|
||||
$date = $data['openingBalanceDate'];
|
||||
$amount = strval($data['openingBalance']);
|
||||
$negativeAmount = bcmul($amount, '-1');
|
||||
$currencyId = intval($data['currency_id']);
|
||||
|
||||
Log::debug(sprintf('Submitted amount for opening balance to update is %s', $amount));
|
||||
Log::debug(sprintf('Submitted amount for opening balance to update is "%s"', $amount));
|
||||
|
||||
if (0 === bccomp($amount, '0')) {
|
||||
Log::notice(sprintf('Amount "%s" is zero, delete opening balance.', $amount));
|
||||
$journal->delete();
|
||||
|
||||
return true;
|
||||
@ -534,18 +597,18 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
$journal->date = $date;
|
||||
$journal->transaction_currency_id = $currencyId;
|
||||
$journal->save();
|
||||
|
||||
// update transactions:
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($journal->transactions()->get() as $transaction) {
|
||||
if ($account->id === $transaction->account_id) {
|
||||
Log::debug(sprintf('Will change transaction #%d amount from %s to %s', $transaction->id, $transaction->amount, $amount));
|
||||
if (intval($account->id) === intval($transaction->account_id)) {
|
||||
Log::debug(sprintf('Will (eq) change transaction #%d amount from "%s" to "%s"', $transaction->id, $transaction->amount, $amount));
|
||||
$transaction->amount = $amount;
|
||||
$transaction->transaction_currency_id = $currencyId;
|
||||
$transaction->save();
|
||||
}
|
||||
if ($account->id !== $transaction->account_id) {
|
||||
$negativeAmount = bcmul($amount, '-1');
|
||||
Log::debug(sprintf('Will change transaction #%d amount from %s to %s', $transaction->id, $transaction->amount, $negativeAmount));
|
||||
if (!(intval($account->id) === intval($transaction->account_id))) {
|
||||
Log::debug(sprintf('Will (neq) change transaction #%d amount from "%s" to "%s"', $transaction->id, $transaction->amount, $negativeAmount));
|
||||
$transaction->amount = $negativeAmount;
|
||||
$transaction->transaction_currency_id = $currencyId;
|
||||
$transaction->save();
|
||||
|
@ -24,6 +24,8 @@ namespace FireflyIII\Repositories\Account;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
@ -83,6 +85,15 @@ interface AccountRepositoryInterface
|
||||
*/
|
||||
public function findByName(string $name, array $types): Account;
|
||||
|
||||
/**
|
||||
* Return account type by string.
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return AccountType|null
|
||||
*/
|
||||
public function getAccountType(string $type): ?AccountType;
|
||||
|
||||
/**
|
||||
* @param array $accountIds
|
||||
*
|
||||
@ -109,6 +120,13 @@ interface AccountRepositoryInterface
|
||||
*/
|
||||
public function getCashAccount(): Account;
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
*
|
||||
* @return Note|null
|
||||
*/
|
||||
public function getNote(Account $account): ?Note;
|
||||
|
||||
/**
|
||||
* Find or create the opposing reconciliation account.
|
||||
*
|
||||
|
@ -25,6 +25,7 @@ namespace FireflyIII\Repositories\ImportJob;
|
||||
use Crypt;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Models\TransactionJournalMeta;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Str;
|
||||
@ -41,6 +42,37 @@ class ImportJobRepository implements ImportJobRepositoryInterface
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
* @param int $steps
|
||||
*
|
||||
* @return ImportJob
|
||||
*/
|
||||
public function addStepsDone(ImportJob $job, int $steps = 1): ImportJob
|
||||
{
|
||||
$job->addStepsDone($steps);
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of imported rows with this hash value.
|
||||
*
|
||||
* @param string $hash
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countByHash(string $hash): int
|
||||
{
|
||||
$json = json_encode($hash);
|
||||
$count = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('data', $json)
|
||||
->where('name', 'importHash')
|
||||
->count();
|
||||
|
||||
return intval($count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*
|
||||
@ -62,7 +94,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface
|
||||
$importJob->file_type = $type;
|
||||
$importJob->key = Str::random(12);
|
||||
$importJob->status = 'new';
|
||||
$importJob->configuration = config(sprintf('import.default_config.%s', $type)) ?? [];
|
||||
$importJob->configuration = [];
|
||||
$importJob->extended_status = [
|
||||
'steps' => 0,
|
||||
'done' => 0,
|
||||
@ -86,6 +118,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface
|
||||
*/
|
||||
public function findByKey(string $key): ImportJob
|
||||
{
|
||||
/** @var ImportJob $result */
|
||||
$result = $this->user->importJobs()->where('key', $key)->first(['import_jobs.*']);
|
||||
if (null === $result) {
|
||||
return new ImportJob;
|
||||
@ -94,6 +127,40 @@ class ImportJobRepository implements ImportJobRepositoryInterface
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return configuration of job.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getConfiguration(ImportJob $job): array
|
||||
{
|
||||
$config = $job->configuration;
|
||||
if (is_array($config)) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return extended status of job.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getExtendedStatus(ImportJob $job): array
|
||||
{
|
||||
$status = $job->extended_status;
|
||||
if (is_array($status)) {
|
||||
return $status;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
* @param UploadedFile $file
|
||||
@ -186,6 +253,30 @@ class ImportJobRepository implements ImportJobRepositoryInterface
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
* @param array $array
|
||||
*
|
||||
* @return ImportJob
|
||||
*/
|
||||
public function setExtendedStatus(ImportJob $job, array $array): ImportJob
|
||||
{
|
||||
// remove 'errors' because it gets larger and larger and larger...
|
||||
$display = $array;
|
||||
unset($display['errors']);
|
||||
Log::debug(sprintf('Incoming extended status for job "%s" is (except errors): ', $job->key), $display);
|
||||
$currentStatus = $job->extended_status;
|
||||
$newStatus = array_merge($currentStatus, $array);
|
||||
$job->extended_status = $newStatus;
|
||||
$job->save();
|
||||
|
||||
// remove 'errors' because it gets larger and larger and larger...
|
||||
unset($newStatus['errors']);
|
||||
Log::debug(sprintf('Set extended status of job "%s" to (except errors): ', $job->key), $newStatus);
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
@ -207,4 +298,17 @@ class ImportJobRepository implements ImportJobRepositoryInterface
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return import file content.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return string
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
*/
|
||||
public function uploadFileContents(ImportJob $job): string
|
||||
{
|
||||
return $job->uploadFileContents();
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,23 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
*/
|
||||
interface ImportJobRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
* @param int $steps
|
||||
*
|
||||
* @return ImportJob
|
||||
*/
|
||||
public function addStepsDone(ImportJob $job, int $steps = 1): ImportJob;
|
||||
|
||||
/**
|
||||
* Return number of imported rows with this hash value.
|
||||
*
|
||||
* @param string $hash
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countByHash(string $hash): int;
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*
|
||||
@ -45,6 +62,24 @@ interface ImportJobRepositoryInterface
|
||||
*/
|
||||
public function findByKey(string $key): ImportJob;
|
||||
|
||||
/**
|
||||
* Return configuration of job.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getConfiguration(ImportJob $job): array;
|
||||
|
||||
/**
|
||||
* Return extended status of job.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getExtendedStatus(ImportJob $job): array;
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
* @param UploadedFile $file
|
||||
@ -69,6 +104,14 @@ interface ImportJobRepositoryInterface
|
||||
*/
|
||||
public function setConfiguration(ImportJob $job, array $configuration): ImportJob;
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
* @param array $array
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setExtendedStatus(ImportJob $job, array $array): ImportJob;
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
@ -81,4 +124,13 @@ interface ImportJobRepositoryInterface
|
||||
* @return ImportJob
|
||||
*/
|
||||
public function updateStatus(ImportJob $job, string $status): ImportJob;
|
||||
|
||||
/**
|
||||
* Return import file content.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function uploadFileContents(ImportJob $job): string;
|
||||
}
|
||||
|
@ -22,12 +22,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Repositories\Journal;
|
||||
|
||||
use DB;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\MessageBag;
|
||||
@ -432,6 +435,43 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
return $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param int $budgetId
|
||||
*
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
public function updateBudget(TransactionJournal $journal, int $budgetId): TransactionJournal
|
||||
{
|
||||
if ($budgetId === 0) {
|
||||
$journal->budgets()->detach();
|
||||
$journal->save();
|
||||
|
||||
return $journal;
|
||||
}
|
||||
$this->storeBudgetWithJournal($journal, $budgetId);
|
||||
|
||||
return $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param string $category
|
||||
*
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
public function updateCategory(TransactionJournal $journal, string $category): TransactionJournal
|
||||
{
|
||||
Log::debug(sprintf('In updateCategory("%s")', $category));
|
||||
$journal->categories()->detach();
|
||||
if (strlen($category) === 0) {
|
||||
return $journal;
|
||||
}
|
||||
$this->storeCategoryWithJournal($journal, $category);
|
||||
|
||||
return $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as above but for transaction journal with multiple transactions.
|
||||
*
|
||||
@ -492,4 +532,48 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
|
||||
return $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tags.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $array
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateTags(TransactionJournal $journal, array $array): bool
|
||||
{
|
||||
// create tag repository
|
||||
/** @var TagRepositoryInterface $tagRepository */
|
||||
$tagRepository = app(TagRepositoryInterface::class);
|
||||
|
||||
// find or create all tags:
|
||||
$tags = [];
|
||||
$ids = [];
|
||||
foreach ($array as $name) {
|
||||
if (strlen(trim($name)) > 0) {
|
||||
$tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
|
||||
$tags[] = $tag;
|
||||
$ids[] = $tag->id;
|
||||
}
|
||||
}
|
||||
|
||||
// delete all tags connected to journal not in this array:
|
||||
if (count($ids) > 0) {
|
||||
DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->whereNotIn('tag_id', $ids)->delete();
|
||||
}
|
||||
// if count is zero, delete them all:
|
||||
if (0 === count($ids)) {
|
||||
DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->delete();
|
||||
}
|
||||
|
||||
// connect each tag to journal (if not yet connected):
|
||||
/** @var Tag $tag */
|
||||
foreach ($tags as $tag) {
|
||||
Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id));
|
||||
$tagRepository->connect($journal, $tag);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -160,6 +160,22 @@ interface JournalRepositoryInterface
|
||||
*/
|
||||
public function update(TransactionJournal $journal, array $data): TransactionJournal;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param int $budgetId
|
||||
*
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
public function updateBudget(TransactionJournal $journal, int $budgetId): TransactionJournal;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param string $category
|
||||
*
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
public function updateCategory(TransactionJournal $journal, string $category): TransactionJournal;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $data
|
||||
@ -167,4 +183,12 @@ interface JournalRepositoryInterface
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $tags
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateTags(TransactionJournal $journal, array $tags): bool;
|
||||
}
|
||||
|
@ -100,6 +100,7 @@ trait SupportJournalsTrait
|
||||
$budget = Budget::find($budgetId);
|
||||
$journal->budgets()->save($budget);
|
||||
}
|
||||
$journal->touch();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,6 +113,7 @@ trait SupportJournalsTrait
|
||||
$category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $journal->user_id]);
|
||||
$journal->categories()->save($category);
|
||||
}
|
||||
$journal->touch();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -247,6 +249,10 @@ trait SupportJournalsTrait
|
||||
case TransactionType::WITHDRAWAL:
|
||||
// continue:
|
||||
$nativeCurrencyId = intval($accounts[$check]->getMeta('currency_id'));
|
||||
if ($nativeCurrencyId === 0) {
|
||||
// fall back to given ID (not everybody upgrades nicely).
|
||||
$nativeCurrencyId = $submittedCurrencyId;
|
||||
}
|
||||
|
||||
// does not match? Then user has submitted amount in a foreign currency:
|
||||
if ($nativeCurrencyId !== $submittedCurrencyId) {
|
||||
|
@ -37,6 +37,7 @@ use Log;
|
||||
*/
|
||||
trait UpdateJournalsTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* When the user edits a split journal, each line is missing crucial data:.
|
||||
*
|
||||
@ -121,47 +122,5 @@ trait UpdateJournalsTrait
|
||||
$transaction->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tags.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
* @param array $array
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function updateTags(TransactionJournal $journal, array $array): bool
|
||||
{
|
||||
// create tag repository
|
||||
/** @var TagRepositoryInterface $tagRepository */
|
||||
$tagRepository = app(TagRepositoryInterface::class);
|
||||
|
||||
// find or create all tags:
|
||||
$tags = [];
|
||||
$ids = [];
|
||||
foreach ($array as $name) {
|
||||
if (strlen(trim($name)) > 0) {
|
||||
$tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
|
||||
$tags[] = $tag;
|
||||
$ids[] = $tag->id;
|
||||
}
|
||||
}
|
||||
|
||||
// delete all tags connected to journal not in this array:
|
||||
if (count($ids) > 0) {
|
||||
DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->whereNotIn('tag_id', $ids)->delete();
|
||||
}
|
||||
// if count is zero, delete them all:
|
||||
if (0 === count($ids)) {
|
||||
DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->delete();
|
||||
}
|
||||
|
||||
// connect each tag to journal (if not yet connected):
|
||||
/** @var Tag $tag */
|
||||
foreach ($tags as $tag) {
|
||||
Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id));
|
||||
$tagRepository->connect($journal, $tag);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ use SimpleXMLElement;
|
||||
/**
|
||||
* Class UpdateRequest
|
||||
*/
|
||||
class UpdateRequest implements GitHubRequest
|
||||
class UpdateRequest implements GithubRequest
|
||||
{
|
||||
/** @var array */
|
||||
private $releases = [];
|
||||
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* DuplicatedCustomerException.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Exception;
|
||||
|
||||
/**
|
||||
* Class DuplicatedCustomerException
|
||||
*/
|
||||
class DuplicatedCustomerException extends SpectreException
|
||||
{
|
||||
|
||||
}
|
34
app/Services/Spectre/Exception/SpectreException.php
Normal file
34
app/Services/Spectre/Exception/SpectreException.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* SpectreException.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class SpectreException
|
||||
*/
|
||||
class SpectreException extends Exception
|
||||
{
|
||||
|
||||
}
|
101
app/Services/Spectre/Object/Account.php
Normal file
101
app/Services/Spectre/Object/Account.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* Account.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Object;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Class Account
|
||||
*/
|
||||
class Account extends SpectreObject
|
||||
{
|
||||
/** @var float */
|
||||
private $balance;
|
||||
/** @var Carbon */
|
||||
private $createdAt;
|
||||
/** @var string */
|
||||
private $currencyCode;
|
||||
/** @var array */
|
||||
private $extra = [];
|
||||
/** @var int */
|
||||
private $id;
|
||||
/** @var int */
|
||||
private $loginId;
|
||||
/** @var string */
|
||||
private $name;
|
||||
/** @var string */
|
||||
private $nature;
|
||||
/** @var Carbon */
|
||||
private $updatedAt;
|
||||
|
||||
/**
|
||||
* Account constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->id = $data['id'];
|
||||
$this->loginId = $data['login_id'];
|
||||
$this->currencyCode = $data['currency_code'];
|
||||
$this->balance = $data['balance'];
|
||||
$this->name = $data['name'];
|
||||
$this->nature = $data['nature'];
|
||||
$this->createdAt = new Carbon($data['created_at']);
|
||||
$this->updatedAt = new Carbon($data['updated_at']);
|
||||
|
||||
foreach ($data['extra'] as $key => $value) {
|
||||
$this->extra[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = [
|
||||
'balance' => $this->balance,
|
||||
'created_at' => $this->createdAt->toIso8601String(),
|
||||
'currency_code' => $this->currencyCode,
|
||||
'extra' => $this->extra,
|
||||
'id' => $this->id,
|
||||
'login_id' => $this->loginId,
|
||||
'name' => $this->name,
|
||||
'nature' => $this->nature,
|
||||
'updated_at' => $this->updatedAt->toIso8601String(),
|
||||
];
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
}
|
208
app/Services/Spectre/Object/Attempt.php
Normal file
208
app/Services/Spectre/Object/Attempt.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/**
|
||||
* Attempt.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Object;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Class Attempt
|
||||
*/
|
||||
class Attempt extends SpectreObject
|
||||
{
|
||||
/** @var string */
|
||||
private $apiMode;
|
||||
/** @var int */
|
||||
private $apiVersion;
|
||||
/** @var bool */
|
||||
private $automaticFetch;
|
||||
/** @var bool */
|
||||
private $categorize;
|
||||
/** @var Carbon */
|
||||
private $consentGivenAt;
|
||||
/** @var array */
|
||||
private $consentTypes = [];
|
||||
/** @var Carbon */
|
||||
private $createdAt;
|
||||
/** @var array */
|
||||
private $customFields = [];
|
||||
/** @var bool */
|
||||
private $dailyRefresh;
|
||||
/** @var string */
|
||||
private $deviceType;
|
||||
/** @var array */
|
||||
private $excludeAccounts = [];
|
||||
/** @var Carbon */
|
||||
private $failAt;
|
||||
/** @var string */
|
||||
private $failErrorClass;
|
||||
/** @var string */
|
||||
private $failMessage;
|
||||
/** @var string */
|
||||
private $fetchType;
|
||||
/** @var bool */
|
||||
private $finished;
|
||||
/** @var bool */
|
||||
private $finishedRecent;
|
||||
/** @var Carbon */
|
||||
private $fromDate;
|
||||
/** @var int */
|
||||
private $id;
|
||||
/** @var bool */
|
||||
private $interactive;
|
||||
/** @var string */
|
||||
private $locale;
|
||||
/** @var bool */
|
||||
private $partial;
|
||||
/** @var string */
|
||||
private $remoteIp;
|
||||
/** @var bool */
|
||||
private $showConsentInformation;
|
||||
/** @var array */
|
||||
private $stages = [];
|
||||
/** @var bool */
|
||||
private $storeCredentials;
|
||||
/** @var Carbon */
|
||||
private $successAt;
|
||||
/** @var Carbon */
|
||||
private $toDate;
|
||||
/** @var Carbon */
|
||||
private $updatedAt;
|
||||
/** @var string */
|
||||
private $userAgent; // undocumented
|
||||
|
||||
/**
|
||||
* Attempt constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->apiMode = $data['api_mode'];
|
||||
$this->apiVersion = $data['api_version'];
|
||||
$this->automaticFetch = $data['automatic_fetch'];
|
||||
$this->categorize = $data['categorize'];
|
||||
$this->createdAt = new Carbon($data['created_at']);
|
||||
$this->consentGivenAt = new Carbon($data['consent_given_at']);
|
||||
$this->consentTypes = $data['consent_types'];
|
||||
$this->customFields = $data['custom_fields'];
|
||||
$this->dailyRefresh = $data['daily_refresh'];
|
||||
$this->deviceType = $data['device_type'];
|
||||
$this->userAgent = $data['user_agent'] ?? '';
|
||||
$this->remoteIp = $data['remote_ip'];
|
||||
$this->excludeAccounts = $data['exclude_accounts'];
|
||||
$this->failAt = new Carbon($data['fail_at']);
|
||||
$this->failErrorClass = $data['fail_error_class'];
|
||||
$this->failMessage = $data['fail_message'];
|
||||
$this->fetchType = $data['fetch_type'];
|
||||
$this->finished = $data['finished'];
|
||||
$this->finishedRecent = $data['finished_recent'];
|
||||
$this->fromDate = new Carbon($data['from_date']);
|
||||
$this->id = $data['id'];
|
||||
$this->interactive = $data['interactive'];
|
||||
$this->locale = $data['locale'];
|
||||
$this->partial = $data['partial'];
|
||||
$this->showConsentInformation = $data['show_consent_confirmation'];
|
||||
$this->stages = $data['stages'] ?? [];
|
||||
$this->storeCredentials = $data['store_credentials'];
|
||||
$this->successAt = new Carbon($data['success_at']);
|
||||
$this->toDate = new Carbon($data['to_date']);
|
||||
$this->updatedAt = new Carbon($data['updated_at']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Carbon
|
||||
*/
|
||||
public function getCreatedAt(): Carbon
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Carbon
|
||||
*/
|
||||
public function getFailAt(): Carbon
|
||||
{
|
||||
return $this->failAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getFailErrorClass(): ?string
|
||||
{
|
||||
return $this->failErrorClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getFailMessage(): ?string
|
||||
{
|
||||
return $this->failMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = [
|
||||
'api_mode' => $this->apiMode,
|
||||
'api_version' => $this->apiVersion,
|
||||
'automatic_fetch' => $this->automaticFetch,
|
||||
'categorize' => $this->categorize,
|
||||
'created_at' => $this->createdAt->toIso8601String(),
|
||||
'consent_given_at' => $this->consentGivenAt->toIso8601String(),
|
||||
'consent_types' => $this->consentTypes,
|
||||
'custom_fields' => $this->customFields,
|
||||
'daily_refresh' => $this->dailyRefresh,
|
||||
'device_type' => $this->deviceType,
|
||||
'user_agent' => $this->userAgent,
|
||||
'remote_ip' => $this->remoteIp,
|
||||
'exclude_accounts' => $this->excludeAccounts,
|
||||
'fail_at' => $this->failAt->toIso8601String(),
|
||||
'fail_error_class' => $this->failErrorClass,
|
||||
'fail_message' => $this->failMessage,
|
||||
'fetch_type' => $this->fetchType,
|
||||
'finished' => $this->finished,
|
||||
'finished_recent' => $this->finishedRecent,
|
||||
'from_date' => $this->fromDate->toIso8601String(),
|
||||
'id' => $this->id,
|
||||
'interactive' => $this->interactive,
|
||||
'locale' => $this->locale,
|
||||
'partial' => $this->partial,
|
||||
'show_consent_confirmation' => $this->showConsentInformation,
|
||||
'stages' => $this->stages,
|
||||
'store_credentials' => $this->storeCredentials,
|
||||
'success_at' => $this->successAt->toIso8601String(),
|
||||
'to_date' => $this->toDate->toIso8601String(),
|
||||
'updated_at' => $this->updatedAt->toIso8601String(),
|
||||
];
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
}
|
48
app/Services/Spectre/Object/Holder.php
Normal file
48
app/Services/Spectre/Object/Holder.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Holder.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Object;
|
||||
|
||||
/**
|
||||
* Class Holder
|
||||
*/
|
||||
class Holder extends SpectreObject
|
||||
{
|
||||
/**
|
||||
* Holder constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
146
app/Services/Spectre/Object/Login.php
Normal file
146
app/Services/Spectre/Object/Login.php
Normal file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* Login.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Object;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
|
||||
/**
|
||||
* Class Login
|
||||
*/
|
||||
class Login extends SpectreObject
|
||||
{
|
||||
/** @var Carbon */
|
||||
private $consentGivenAt;
|
||||
/** @var array */
|
||||
private $consentTypes;
|
||||
/** @var string */
|
||||
private $countryCode;
|
||||
/** @var Carbon */
|
||||
private $createdAt;
|
||||
/** @var int */
|
||||
private $customerId;
|
||||
/** @var bool */
|
||||
private $dailyRefresh;
|
||||
/** @var Holder */
|
||||
private $holderInfo;
|
||||
/** @var int */
|
||||
private $id;
|
||||
/** @var Attempt */
|
||||
private $lastAttempt;
|
||||
/** @var Carbon */
|
||||
private $lastSuccessAt;
|
||||
/** @var Carbon */
|
||||
private $nextRefreshPossibleAt;
|
||||
/** @var string */
|
||||
private $providerCode;
|
||||
/** @var int */
|
||||
private $providerId;
|
||||
/** @var string */
|
||||
private $providerName;
|
||||
/** @var bool */
|
||||
private $showConsentConfirmation;
|
||||
/** @var string */
|
||||
private $status;
|
||||
/** @var bool */
|
||||
private $storeCredentials;
|
||||
/** @var Carbon */
|
||||
private $updatedAt;
|
||||
|
||||
/**
|
||||
* Login constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->consentGivenAt = new Carbon($data['consent_given_at']);
|
||||
$this->consentTypes = $data['consent_types'];
|
||||
$this->countryCode = $data['country_code'];
|
||||
$this->createdAt = new Carbon($data['created_at']);
|
||||
$this->updatedAt = new Carbon($data['updated_at']);
|
||||
$this->customerId = $data['customer_id'];
|
||||
$this->dailyRefresh = $data['daily_refresh'];
|
||||
$this->holderInfo = new Holder($data['holder_info']);
|
||||
$this->id = $data['id'];
|
||||
$this->lastAttempt = new Attempt($data['last_attempt']);
|
||||
$this->lastSuccessAt = new Carbon($data['last_success_at']);
|
||||
$this->nextRefreshPossibleAt = new Carbon($data['next_refresh_possible_at']);
|
||||
$this->providerCode = $data['provider_code'];
|
||||
$this->providerId = $data['provider_id'];
|
||||
$this->providerName = $data['provider_name'];
|
||||
$this->showConsentConfirmation = $data['show_consent_confirmation'];
|
||||
$this->status = $data['status'];
|
||||
$this->storeCredentials = $data['store_credentials'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Attempt
|
||||
*/
|
||||
public function getLastAttempt(): Attempt
|
||||
{
|
||||
return $this->lastAttempt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = [
|
||||
'consent_given_at' => $this->consentGivenAt->toIso8601String(),
|
||||
'consent_types' => $this->consentTypes,
|
||||
'country_code' => $this->countryCode,
|
||||
'created_at' => $this->createdAt->toIso8601String(),
|
||||
'updated_at' => $this->updatedAt->toIso8601String(),
|
||||
'customer_id' => $this->customerId,
|
||||
'daily_refresh' => $this->dailyRefresh,
|
||||
'holder_info' => $this->holderInfo->toArray(),
|
||||
'id' => $this->id,
|
||||
'last_attempt' => $this->lastAttempt->toArray(),
|
||||
'last_success_at' => $this->lastSuccessAt->toIso8601String(),
|
||||
'next_refresh_possible_at' => $this->nextRefreshPossibleAt,
|
||||
'provider_code' => $this->providerCode,
|
||||
'provider_id' => $this->providerId,
|
||||
'provider_name' => $this->providerName,
|
||||
'show_consent_confirmation' => $this->showConsentConfirmation,
|
||||
'status' => $this->status,
|
||||
'store_credentials' => $this->storeCredentials,
|
||||
|
||||
];
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
}
|
41
app/Services/Spectre/Object/Transaction.php
Normal file
41
app/Services/Spectre/Object/Transaction.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Transaction.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Object;
|
||||
|
||||
/**
|
||||
* Class Transaction
|
||||
*/
|
||||
class Transaction extends SpectreObject
|
||||
{
|
||||
/**
|
||||
* Transaction constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data) {
|
||||
var_dump($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
@ -44,6 +44,7 @@ class CreateTokenRequest extends SpectreRequest
|
||||
/**
|
||||
*
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
|
93
app/Services/Spectre/Request/ListAccountsRequest.php
Normal file
93
app/Services/Spectre/Request/ListAccountsRequest.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* ListAccountsRequest.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Request;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Services\Spectre\Exception\SpectreException;
|
||||
use FireflyIII\Services\Spectre\Object\Account;
|
||||
use FireflyIII\Services\Spectre\Object\Login;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class ListAccountsRequest
|
||||
*/
|
||||
class ListAccountsRequest extends SpectreRequest
|
||||
{
|
||||
/** @var array */
|
||||
private $accounts = [];
|
||||
/** @var Login */
|
||||
private $login;
|
||||
|
||||
/**
|
||||
* @throws SpectreException
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
$hasNextPage = true;
|
||||
$nextId = 0;
|
||||
while ($hasNextPage) {
|
||||
Log::debug(sprintf('Now calling ListAccountsRequest for next_id %d', $nextId));
|
||||
$parameters = ['from_id' => $nextId, 'login_id' => $this->login->getId()];
|
||||
$uri = '/api/v3/accounts?' . http_build_query($parameters);
|
||||
$response = $this->sendSignedSpectreGet($uri, []);
|
||||
|
||||
// count entries:
|
||||
Log::debug(sprintf('Found %d entries in data-array', count($response['data'])));
|
||||
|
||||
// extract next ID
|
||||
$hasNextPage = false;
|
||||
if (isset($response['meta']['next_id']) && intval($response['meta']['next_id']) > $nextId) {
|
||||
$hasNextPage = true;
|
||||
$nextId = $response['meta']['next_id'];
|
||||
Log::debug(sprintf('Next ID is now %d.', $nextId));
|
||||
} else {
|
||||
Log::debug('No next page.');
|
||||
}
|
||||
|
||||
// store customers:
|
||||
foreach ($response['data'] as $accountArray) {
|
||||
$this->accounts[] = new Account($accountArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAccounts(): array
|
||||
{
|
||||
return $this->accounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Login $login
|
||||
*/
|
||||
public function setLogin(Login $login): void
|
||||
{
|
||||
$this->login = $login;
|
||||
}
|
||||
|
||||
|
||||
}
|
82
app/Services/Spectre/Request/ListCustomersRequest.php
Normal file
82
app/Services/Spectre/Request/ListCustomersRequest.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* ListCustomersRequest.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Request;
|
||||
|
||||
use FireflyIII\Services\Spectre\Object\Customer;
|
||||
use Log;
|
||||
|
||||
|
||||
/**
|
||||
* Class ListCustomersRequest
|
||||
*/
|
||||
class ListCustomersRequest extends SpectreRequest
|
||||
{
|
||||
/** @var array */
|
||||
private $customers = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
$hasNextPage = true;
|
||||
$nextId = 0;
|
||||
while ($hasNextPage) {
|
||||
Log::debug(sprintf('Now calling ListCustomersRequest for next_id %d', $nextId));
|
||||
$parameters = ['from_id' => $nextId];
|
||||
$uri = '/api/v3/customers/?' . http_build_query($parameters);
|
||||
$response = $this->sendSignedSpectreGet($uri, []);
|
||||
|
||||
// count entries:
|
||||
Log::debug(sprintf('Found %d entries in data-array', count($response['data'])));
|
||||
|
||||
// extract next ID
|
||||
$hasNextPage = false;
|
||||
if (isset($response['meta']['next_id']) && intval($response['meta']['next_id']) > $nextId) {
|
||||
$hasNextPage = true;
|
||||
$nextId = $response['meta']['next_id'];
|
||||
Log::debug(sprintf('Next ID is now %d.', $nextId));
|
||||
} else {
|
||||
Log::debug('No next page.');
|
||||
}
|
||||
|
||||
// store customers:
|
||||
foreach ($response['data'] as $customerArray) {
|
||||
$this->customers[] = new Customer($customerArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCustomers(): array
|
||||
{
|
||||
return $this->customers;
|
||||
}
|
||||
|
||||
|
||||
}
|
91
app/Services/Spectre/Request/ListLoginsRequest.php
Normal file
91
app/Services/Spectre/Request/ListLoginsRequest.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* ListLoginsRequest.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Request;
|
||||
|
||||
use FireflyIII\Services\Spectre\Object\Customer;
|
||||
use FireflyIII\Services\Spectre\Object\Login;
|
||||
use Log;
|
||||
/**
|
||||
* Class ListLoginsRequest
|
||||
*/
|
||||
class ListLoginsRequest extends SpectreRequest
|
||||
{
|
||||
/** @var Customer */
|
||||
private $customer;
|
||||
|
||||
/** @var array */
|
||||
private $logins = [];
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getLogins(): array
|
||||
{
|
||||
return $this->logins;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
$hasNextPage = true;
|
||||
$nextId = 0;
|
||||
while ($hasNextPage) {
|
||||
Log::debug(sprintf('Now calling ListLoginsRequest for next_id %d', $nextId));
|
||||
$parameters = ['from_id' => $nextId, 'customer_id' => $this->customer->getId()];
|
||||
$uri = '/api/v3/logins/?' . http_build_query($parameters);
|
||||
$response = $this->sendSignedSpectreGet($uri, []);
|
||||
|
||||
// count entries:
|
||||
Log::debug(sprintf('Found %d entries in data-array', count($response['data'])));
|
||||
|
||||
// extract next ID
|
||||
$hasNextPage = false;
|
||||
if (isset($response['meta']['next_id']) && intval($response['meta']['next_id']) > $nextId) {
|
||||
$hasNextPage = true;
|
||||
$nextId = $response['meta']['next_id'];
|
||||
Log::debug(sprintf('Next ID is now %d.', $nextId));
|
||||
} else {
|
||||
Log::debug('No next page.');
|
||||
}
|
||||
|
||||
// store logins:
|
||||
/** @var array $loginArray */
|
||||
foreach ($response['data'] as $loginArray) {
|
||||
$this->logins[] = new Login($loginArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Customer $customer
|
||||
*/
|
||||
public function setCustomer(Customer $customer): void
|
||||
{
|
||||
$this->customer = $customer;
|
||||
}
|
||||
|
||||
|
||||
}
|
90
app/Services/Spectre/Request/ListTransactionsRequest.php
Normal file
90
app/Services/Spectre/Request/ListTransactionsRequest.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* ListTransactionsRequest.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Spectre\Request;
|
||||
|
||||
use FireflyIII\Services\Spectre\Object\Account;
|
||||
use FireflyIII\Services\Spectre\Object\Transaction;
|
||||
use Log;
|
||||
/**
|
||||
* Class ListTransactionsRequest
|
||||
*/
|
||||
class ListTransactionsRequest extends SpectreRequest
|
||||
{
|
||||
/** @var Account */
|
||||
private $account;
|
||||
/** @var array */
|
||||
private $transactions = [];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
$hasNextPage = true;
|
||||
$nextId = 0;
|
||||
while ($hasNextPage) {
|
||||
Log::debug(sprintf('Now calling ListTransactionsRequest for next_id %d', $nextId));
|
||||
$parameters = ['from_id' => $nextId,'account_id' => $this->account->getId()];
|
||||
$uri = '/api/v3/transactions?' . http_build_query($parameters);
|
||||
$response = $this->sendSignedSpectreGet($uri, []);
|
||||
|
||||
// count entries:
|
||||
Log::debug(sprintf('Found %d entries in data-array', count($response['data'])));
|
||||
|
||||
// extract next ID
|
||||
$hasNextPage = false;
|
||||
if (isset($response['meta']['next_id']) && intval($response['meta']['next_id']) > $nextId) {
|
||||
$hasNextPage = true;
|
||||
$nextId = $response['meta']['next_id'];
|
||||
Log::debug(sprintf('Next ID is now %d.', $nextId));
|
||||
} else {
|
||||
Log::debug('No next page.');
|
||||
}
|
||||
|
||||
// store customers:
|
||||
foreach ($response['data'] as $transactionArray) {
|
||||
$this->transactions[] = new Transaction($transactionArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getTransactions(): array
|
||||
{
|
||||
return $this->transactions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
*/
|
||||
public function setAccount(Account $account): void
|
||||
{
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -34,6 +34,7 @@ class NewCustomerRequest extends SpectreRequest
|
||||
|
||||
/**
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Services\Spectre\Request;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Services\Spectre\Exception\DuplicatedCustomerException;
|
||||
use FireflyIII\Services\Spectre\Exception\SpectreException;
|
||||
use FireflyIII\User;
|
||||
use Log;
|
||||
use Requests;
|
||||
@ -179,6 +181,7 @@ abstract class SpectreRequest
|
||||
* @return array
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @throws SpectreException
|
||||
*/
|
||||
protected function sendSignedSpectreGet(string $uri, array $data): array
|
||||
{
|
||||
@ -222,6 +225,7 @@ abstract class SpectreRequest
|
||||
* @return array
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @throws SpectreException
|
||||
*/
|
||||
protected function sendSignedSpectrePost(string $uri, array $data): array
|
||||
{
|
||||
@ -255,15 +259,21 @@ abstract class SpectreRequest
|
||||
* @param Requests_Response $response
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @throws SpectreException
|
||||
*/
|
||||
private function detectError(Requests_Response $response): void
|
||||
{
|
||||
$body = $response->body;
|
||||
$array = json_decode($body, true);
|
||||
if (isset($array['error_class'])) {
|
||||
$message = $array['error_message'] ?? '(no message)';
|
||||
$message = $array['error_message'] ?? '(no message)';
|
||||
$errorClass = $array['error_class'];
|
||||
$class = sprintf('\\FireflyIII\\Services\\Spectre\Exception\\%sException', $errorClass);
|
||||
if (class_exists($class)) {
|
||||
throw new $class($message);
|
||||
}
|
||||
|
||||
throw new FireflyException(sprintf('Error of class %s: %s', $array['error_class'], $message));
|
||||
throw new FireflyException(sprintf('Error of class %s: %s', $errorClass, $message));
|
||||
}
|
||||
|
||||
$statusCode = intval($response->status_code);
|
||||
|
@ -22,62 +22,48 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Import\Configuration\File;
|
||||
|
||||
use ExpandedForm;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\Import\Configuration\ConfigurationInterface;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class CsvInitial.
|
||||
* Class Initial.
|
||||
*/
|
||||
class Initial implements ConfigurationInterface
|
||||
{
|
||||
/**
|
||||
* @var ImportJob
|
||||
*/
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/** @var string */
|
||||
private $warning = '';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Log::debug('Constructed Initial.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data necessary to show the configuration screen.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$delimiters = [
|
||||
',' => trans('form.csv_comma'),
|
||||
';' => trans('form.csv_semicolon'),
|
||||
'tab' => trans('form.csv_tab'),
|
||||
];
|
||||
$importFileTypes = [];
|
||||
$defaultImportType = config('import.options.file.default_import_format');
|
||||
|
||||
// update job with default date format:
|
||||
$config = $this->job->configuration;
|
||||
if (!isset($config['date-format'])) {
|
||||
$config['date-format'] = 'Ymd';
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
}
|
||||
$specifics = [];
|
||||
|
||||
// collect specifics.
|
||||
foreach (config('csv.import_specifics') as $name => $className) {
|
||||
$specifics[$name] = [
|
||||
'name' => $className::getName(),
|
||||
'description' => $className::getDescription(),
|
||||
];
|
||||
foreach (config('import.options.file.import_formats') as $type) {
|
||||
$importFileTypes[$type] = trans('import.import_file_type_' . $type);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'accounts' => ExpandedForm::makeSelectList($accounts),
|
||||
'specifix' => [],
|
||||
'delimiters' => $delimiters,
|
||||
'specifics' => $specifics,
|
||||
return [
|
||||
'default_type' => $defaultImportType,
|
||||
'file_types' => $importFileTypes,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,7 +73,7 @@ class Initial implements ConfigurationInterface
|
||||
*/
|
||||
public function getWarningMessage(): string
|
||||
{
|
||||
return '';
|
||||
return $this->warning;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,7 +83,9 @@ class Initial implements ConfigurationInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job): ConfigurationInterface
|
||||
{
|
||||
$this->job = $job;
|
||||
$this->job = $job;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -111,59 +99,44 @@ class Initial implements ConfigurationInterface
|
||||
*/
|
||||
public function storeConfiguration(array $data): bool
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$importId = $data['csv_import_account'] ?? 0;
|
||||
$account = $repository->find(intval($importId));
|
||||
Log::debug('Now in storeConfiguration for file Upload.');
|
||||
$config = $this->getConfig();
|
||||
$type = $data['import_file_type'] ?? 'csv'; // assume it's a CSV file.
|
||||
$config['file-type'] = in_array($type, config('import.options.file.import_formats')) ? $type : 'csv';
|
||||
|
||||
$hasHeaders = isset($data['has_headers']) && 1 === intval($data['has_headers']) ? true : false;
|
||||
$config = $this->job->configuration;
|
||||
$config['initial-config-complete'] = true;
|
||||
$config['has-headers'] = $hasHeaders;
|
||||
$config['date-format'] = $data['date_format'];
|
||||
$config['delimiter'] = $data['csv_delimiter'];
|
||||
$config['delimiter'] = 'tab' === $config['delimiter'] ? "\t" : $config['delimiter'];
|
||||
$config['apply_rules'] = isset($data['apply_rules']) && 1 === intval($data['apply_rules']) ? true : false;
|
||||
$config['match_bills'] = isset($data['match_bills']) && 1 === intval($data['match_bills']) ? true : false;
|
||||
// update config:
|
||||
$this->repository->setConfiguration($this->job, $config);
|
||||
|
||||
Log::debug('Entered import account.', ['id' => $importId]);
|
||||
// make repository process file:
|
||||
$uploaded = $this->repository->processFile($this->job, $data['import_file'] ?? null);
|
||||
Log::debug(sprintf('Result of upload is %s', var_export($uploaded, true)));
|
||||
|
||||
if (null !== $account->id) {
|
||||
Log::debug('Found account.', ['id' => $account->id, 'name' => $account->name]);
|
||||
$config['import-account'] = $account->id;
|
||||
// process config, if present:
|
||||
if (isset($data['configuration_file'])) {
|
||||
$this->repository->processConfiguration($this->job, $data['configuration_file']);
|
||||
}
|
||||
|
||||
if (null === $account->id) {
|
||||
Log::error('Could not find anything for csv_import_account.', ['id' => $importId]);
|
||||
if (false === $uploaded) {
|
||||
$this->warning = 'No valid upload.';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$config = $this->storeSpecifics($data, $config);
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
// if file was upload safely, assume we can go to the next stage:
|
||||
$config = $this->getConfig();
|
||||
$config['stage'] = 'upload-config';
|
||||
$this->repository->setConfiguration($this->job, $config);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $config
|
||||
* Short hand method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function storeSpecifics(array $data, array $config): array
|
||||
private function getConfig(): array
|
||||
{
|
||||
// loop specifics.
|
||||
if (isset($data['specifics']) && is_array($data['specifics'])) {
|
||||
$names = array_keys($data['specifics']);
|
||||
foreach ($names as $name) {
|
||||
// verify their content.
|
||||
$className = sprintf('FireflyIII\Import\Specifics\%s', $name);
|
||||
if (class_exists($className)) {
|
||||
$config['specifics'][$name] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ use FireflyIII\Import\Mapper\MapperInterface;
|
||||
use FireflyIII\Import\MapperPreProcess\PreProcessorInterface;
|
||||
use FireflyIII\Import\Specifics\SpecificInterface;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\Import\Configuration\ConfigurationInterface;
|
||||
use League\Csv\Reader;
|
||||
use League\Csv\Statement;
|
||||
@ -37,12 +38,12 @@ use Log;
|
||||
*/
|
||||
class Map implements ConfigurationInterface
|
||||
{
|
||||
/** @var array */
|
||||
private $configuration = [];
|
||||
/** @var array that holds each column to be mapped by the user */
|
||||
private $data = [];
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
/** @var array */
|
||||
private $validSpecifics = [];
|
||||
|
||||
@ -55,16 +56,16 @@ class Map implements ConfigurationInterface
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
$this->configuration = $this->job->configuration;
|
||||
$this->getMappableColumns();
|
||||
|
||||
// in order to actually map we also need all possible values from the CSV file.
|
||||
$content = $this->job->uploadFileContents();
|
||||
$config = $this->getConfig();
|
||||
$content = $this->repository->uploadFileContents($this->job);
|
||||
$offset = 0;
|
||||
/** @var Reader $reader */
|
||||
$reader = Reader::createFromString($content);
|
||||
$reader->setDelimiter($this->configuration['delimiter']);
|
||||
if ($this->configuration['has-headers']) {
|
||||
$reader->setDelimiter($config['delimiter']);
|
||||
if ($config['has-headers']) {
|
||||
$offset = 1;
|
||||
}
|
||||
$stmt = (new Statement)->offset($offset);
|
||||
@ -106,6 +107,10 @@ class Map implements ConfigurationInterface
|
||||
foreach ($setIndexes as $index) {
|
||||
$this->data[$index]['values'] = array_unique($this->data[$index]['values']);
|
||||
asort($this->data[$index]['values']);
|
||||
// if the count of this array is zero, there is nothing to map.
|
||||
if (count($this->data[$index]['values']) === 0) {
|
||||
unset($this->data[$index]);
|
||||
}
|
||||
}
|
||||
unset($setIndexes);
|
||||
|
||||
@ -136,7 +141,9 @@ class Map implements ConfigurationInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job): ConfigurationInterface
|
||||
{
|
||||
$this->job = $job;
|
||||
$this->job = $job;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -150,7 +157,8 @@ class Map implements ConfigurationInterface
|
||||
*/
|
||||
public function storeConfiguration(array $data): bool
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
$config = $this->getConfig();
|
||||
|
||||
if (isset($data['mapping'])) {
|
||||
foreach ($data['mapping'] as $index => $data) {
|
||||
$config['column-mapping-config'][$index] = [];
|
||||
@ -164,9 +172,8 @@ class Map implements ConfigurationInterface
|
||||
}
|
||||
|
||||
// set thing to be completed.
|
||||
$config['column-mapping-complete'] = true;
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
$config['stage'] = 'ready';
|
||||
$this->saveConfig($config);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -181,11 +188,21 @@ class Map implements ConfigurationInterface
|
||||
$mapperClass = config('csv.import_roles.' . $column . '.mapper');
|
||||
$mapperName = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass);
|
||||
/** @var MapperInterface $mapper */
|
||||
$mapper = new $mapperName;
|
||||
$mapper = app($mapperName);
|
||||
|
||||
return $mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Short hand method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getConfig(): array
|
||||
{
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*
|
||||
@ -193,8 +210,7 @@ class Map implements ConfigurationInterface
|
||||
*/
|
||||
private function getMappableColumns(): bool
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
|
||||
$config = $this->getConfig();
|
||||
/**
|
||||
* @var int
|
||||
* @var bool $mustBeMapped
|
||||
@ -246,7 +262,9 @@ class Map implements ConfigurationInterface
|
||||
{
|
||||
// run specifics here:
|
||||
// and this is the point where the specifix go to work.
|
||||
$names = array_keys($this->job->configuration['specifics']);
|
||||
$config = $this->getConfig();
|
||||
$specifics = $config['specifics'] ?? [];
|
||||
$names = array_keys($specifics);
|
||||
foreach ($names as $name) {
|
||||
if (!in_array($name, $this->validSpecifics)) {
|
||||
throw new FireflyException(sprintf('"%s" is not a valid class name', $name));
|
||||
@ -262,6 +280,14 @@ class Map implements ConfigurationInterface
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
*/
|
||||
private function saveConfig(array $array)
|
||||
{
|
||||
$this->repository->setConfiguration($this->job, $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $column
|
||||
* @param bool $mustBeMapped
|
||||
|
@ -24,6 +24,7 @@ namespace FireflyIII\Support\Import\Configuration\File;
|
||||
|
||||
use FireflyIII\Import\Specifics\SpecificInterface;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\Import\Configuration\ConfigurationInterface;
|
||||
use League\Csv\Reader;
|
||||
use League\Csv\Statement;
|
||||
@ -40,7 +41,8 @@ class Roles implements ConfigurationInterface
|
||||
private $data = [];
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
/** @var string */
|
||||
private $warning = '';
|
||||
|
||||
@ -54,8 +56,8 @@ class Roles implements ConfigurationInterface
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
$content = $this->job->uploadFileContents();
|
||||
$content = $this->repository->uploadFileContents($this->job);
|
||||
$config = $this->getConfig();
|
||||
$headers = [];
|
||||
$offset = 0;
|
||||
// create CSV reader.
|
||||
@ -112,7 +114,9 @@ class Roles implements ConfigurationInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job): ConfigurationInterface
|
||||
{
|
||||
$this->job = $job;
|
||||
$this->job = $job;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -127,26 +131,40 @@ class Roles implements ConfigurationInterface
|
||||
public function storeConfiguration(array $data): bool
|
||||
{
|
||||
Log::debug('Now in storeConfiguration of Roles.');
|
||||
$config = $this->job->configuration;
|
||||
$config = $this->getConfig();
|
||||
$count = $config['column-count'];
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
$role = $data['role'][$i] ?? '_ignore';
|
||||
$mapping = isset($data['map'][$i]) && $data['map'][$i] === '1' ? true : false;
|
||||
$config['column-roles'][$i] = $role;
|
||||
$config['column-do-mapping'][$i] = $mapping;
|
||||
Log::debug(sprintf('Column %d has been given role %s', $i, $role));
|
||||
Log::debug(sprintf('Column %d has been given role %s (mapping: %s)', $i, $role, var_export($mapping, true)));
|
||||
}
|
||||
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
|
||||
$this->saveConfig($config);
|
||||
$this->ignoreUnmappableColumns();
|
||||
$this->setRolesComplete();
|
||||
|
||||
$config = $this->getConfig();
|
||||
$config['stage'] = 'map';
|
||||
$this->saveConfig($config);
|
||||
|
||||
$this->isMappingNecessary();
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Short hand method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getConfig(): array
|
||||
{
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@ -165,11 +183,12 @@ class Roles implements ConfigurationInterface
|
||||
*/
|
||||
private function ignoreUnmappableColumns(): bool
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
$config = $this->getConfig();
|
||||
$count = $config['column-count'];
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
$role = $config['column-roles'][$i] ?? '_ignore';
|
||||
$mapping = $config['column-do-mapping'][$i] ?? false;
|
||||
Log::debug(sprintf('Role for column %d is %s, and mapping is %s', $i, $role, var_export($mapping, true)));
|
||||
|
||||
if ('_ignore' === $role && true === $mapping) {
|
||||
$mapping = false;
|
||||
@ -177,9 +196,7 @@ class Roles implements ConfigurationInterface
|
||||
}
|
||||
$config['column-do-mapping'][$i] = $mapping;
|
||||
}
|
||||
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
$this->saveConfig($config);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -189,7 +206,7 @@ class Roles implements ConfigurationInterface
|
||||
*/
|
||||
private function isMappingNecessary()
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
$config = $this->getConfig();
|
||||
$count = $config['column-count'];
|
||||
$toBeMapped = 0;
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
@ -200,12 +217,11 @@ class Roles implements ConfigurationInterface
|
||||
}
|
||||
Log::debug(sprintf('Found %d columns that need mapping.', $toBeMapped));
|
||||
if (0 === $toBeMapped) {
|
||||
// skip setting of map, because none need to be mapped:
|
||||
$config['column-mapping-complete'] = true;
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
$config['stage'] = 'ready';
|
||||
}
|
||||
|
||||
$this->saveConfig($config);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -248,7 +264,9 @@ class Roles implements ConfigurationInterface
|
||||
*/
|
||||
private function processSpecifics(array $row): array
|
||||
{
|
||||
$names = array_keys($this->job->configuration['specifics'] ?? []);
|
||||
$config = $this->getConfig();
|
||||
$specifics = $config['specifics'] ?? [];
|
||||
$names = array_keys($specifics);
|
||||
foreach ($names as $name) {
|
||||
/** @var SpecificInterface $specific */
|
||||
$specific = app('FireflyIII\Import\Specifics\\' . $name);
|
||||
@ -258,12 +276,20 @@ class Roles implements ConfigurationInterface
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
*/
|
||||
private function saveConfig(array $array)
|
||||
{
|
||||
$this->repository->setConfiguration($this->job, $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function setRolesComplete(): bool
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
$config = $this->getConfig();
|
||||
$count = $config['column-count'];
|
||||
$assigned = 0;
|
||||
$hasAmount = false;
|
||||
@ -278,13 +304,12 @@ class Roles implements ConfigurationInterface
|
||||
}
|
||||
if ($assigned > 0 && $hasAmount) {
|
||||
$config['column-roles-complete'] = true;
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
$this->warning = '';
|
||||
$this->warning = '';
|
||||
}
|
||||
if (0 === $assigned || !$hasAmount) {
|
||||
$this->warning = strval(trans('import.roles_warning'));
|
||||
}
|
||||
$this->saveConfig($config);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -294,11 +319,10 @@ class Roles implements ConfigurationInterface
|
||||
*/
|
||||
private function updateColumCount(): bool
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
$count = $this->data['total'];
|
||||
$config['column-count'] = $count;
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
$config = $this->getConfig();
|
||||
$count = $this->data['total'];
|
||||
$config['column-count'] = $count;
|
||||
$this->saveConfig($config);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1,116 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Upload.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\Support\Import\Configuration\File;
|
||||
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\Import\Configuration\ConfigurationInterface;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class Upload.
|
||||
*/
|
||||
class Upload implements ConfigurationInterface
|
||||
{
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
|
||||
/** @var string */
|
||||
private $warning = '';
|
||||
|
||||
/**
|
||||
* Get the data necessary to show the configuration screen.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
$importFileTypes = [];
|
||||
$defaultImportType = config('import.options.file.default_import_format');
|
||||
|
||||
foreach (config('import.options.file.import_formats') as $type) {
|
||||
$importFileTypes[$type] = trans('import.import_file_type_' . $type);
|
||||
}
|
||||
|
||||
return [
|
||||
'default_type' => $defaultImportType,
|
||||
'file_types' => $importFileTypes,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return possible warning to user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWarningMessage(): string
|
||||
{
|
||||
return $this->warning;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return ConfigurationInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job): ConfigurationInterface
|
||||
{
|
||||
$this->job = $job;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the result.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function storeConfiguration(array $data): bool
|
||||
{
|
||||
Log::debug('Now in storeConfiguration for file Upload.');
|
||||
/** @var ImportJobRepositoryInterface $repository */
|
||||
$repository = app(ImportJobRepositoryInterface::class);
|
||||
$type = $data['import_file_type'] ?? 'unknown';
|
||||
$config = $this->job->configuration;
|
||||
$config['file-type'] = in_array($type, config('import.options.file.import_formats')) ? $type : 'unknown';
|
||||
$repository->setConfiguration($this->job, $config);
|
||||
$uploaded = $repository->processFile($this->job, $data['import_file'] ?? null);
|
||||
$this->job->save();
|
||||
Log::debug(sprintf('Result of upload is %s', var_export($uploaded, true)));
|
||||
// process config, if present:
|
||||
if (isset($data['configuration_file'])) {
|
||||
$repository->processConfiguration($this->job, $data['configuration_file']);
|
||||
}
|
||||
$config = $this->job->configuration;
|
||||
$config['has-file-upload'] = $uploaded;
|
||||
$repository->setConfiguration($this->job, $config);
|
||||
|
||||
if (false === $uploaded) {
|
||||
$this->warning = 'No valid upload.';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
191
app/Support/Import/Configuration/File/UploadConfig.php
Normal file
191
app/Support/Import/Configuration/File/UploadConfig.php
Normal file
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* UploadConfig.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\Support\Import\Configuration\File;
|
||||
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\Import\Configuration\ConfigurationInterface;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class UploadConfig.
|
||||
*/
|
||||
class UploadConfig implements ConfigurationInterface
|
||||
{
|
||||
/** @var AccountRepositoryInterface */
|
||||
private $accountRepository;
|
||||
/**
|
||||
* @var ImportJob
|
||||
*/
|
||||
private $job;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
$accounts = $this->accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$delimiters = [
|
||||
',' => trans('form.csv_comma'),
|
||||
';' => trans('form.csv_semicolon'),
|
||||
'tab' => trans('form.csv_tab'),
|
||||
];
|
||||
$config = $this->getConfig();
|
||||
$config['date-format'] = $config['date-format'] ?? 'Ymd';
|
||||
$specifics = [];
|
||||
$this->saveConfig($config);
|
||||
|
||||
// collect specifics.
|
||||
foreach (config('csv.import_specifics') as $name => $className) {
|
||||
$specifics[$name] = [
|
||||
'name' => $className::getName(),
|
||||
'description' => $className::getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
$data = [
|
||||
'accounts' => app('expandedform')->makeSelectList($accounts),
|
||||
'specifix' => [],
|
||||
'delimiters' => $delimiters,
|
||||
'specifics' => $specifics,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return possible warning to user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWarningMessage(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return ConfigurationInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job): ConfigurationInterface
|
||||
{
|
||||
$this->job = $job;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
$this->accountRepository->setUser($job->user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the result.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function storeConfiguration(array $data): bool
|
||||
{
|
||||
Log::debug('Now in Initial::storeConfiguration()');
|
||||
$config = $this->getConfig();
|
||||
$importId = intval($data['csv_import_account'] ?? 0);
|
||||
$account = $this->accountRepository->find($importId);
|
||||
$delimiter = strval($data['csv_delimiter']);
|
||||
|
||||
// set "headers":
|
||||
$config['has-headers'] = intval($data['has_headers'] ?? 0) === 1;
|
||||
$config['date-format'] = strval($data['date_format']);
|
||||
$config['delimiter'] = 'tab' === $delimiter ? "\t" : $config['delimiter'];
|
||||
$config['apply-rules'] = intval($data['apply_rules'] ?? 0) === 1;
|
||||
$config['match-bills'] = intval($data['match_bills'] ?? 0) === 1;
|
||||
|
||||
Log::debug('Entered import account.', ['id' => $importId]);
|
||||
|
||||
|
||||
if (null !== $account->id) {
|
||||
Log::debug('Found account.', ['id' => $account->id, 'name' => $account->name]);
|
||||
$config['import-account'] = $account->id;
|
||||
}
|
||||
|
||||
if (null === $account->id) {
|
||||
Log::error('Could not find anything for csv_import_account.', ['id' => $importId]);
|
||||
}
|
||||
|
||||
$config = $this->storeSpecifics($data, $config);
|
||||
Log::debug('Final config is ', $config);
|
||||
|
||||
// onto the next stage!
|
||||
|
||||
$config['stage'] = 'roles';
|
||||
$this->saveConfig($config);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Short hand method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getConfig(): array
|
||||
{
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
*/
|
||||
private function saveConfig(array $array)
|
||||
{
|
||||
$this->repository->setConfiguration($this->job, $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $config
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function storeSpecifics(array $data, array $config): array
|
||||
{
|
||||
// loop specifics.
|
||||
if (isset($data['specifics']) && is_array($data['specifics'])) {
|
||||
$names = array_keys($data['specifics']);
|
||||
foreach ($names as $name) {
|
||||
// verify their content.
|
||||
$className = sprintf('FireflyIII\Import\Specifics\%s', $name);
|
||||
if (class_exists($className)) {
|
||||
$config['specifics'][$name] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
157
app/Support/Import/Configuration/Spectre/HaveAccounts.php
Normal file
157
app/Support/Import/Configuration/Spectre/HaveAccounts.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* HaveAccounts.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Import\Configuration\Spectre;
|
||||
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\Import\Configuration\ConfigurationInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class HaveAccounts
|
||||
*/
|
||||
class HaveAccounts implements ConfigurationInterface
|
||||
{
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
|
||||
/**
|
||||
* Get the data necessary to show the configuration screen.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
/** @var CurrencyRepositoryInterface $currencyRepository */
|
||||
$currencyRepository = app(CurrencyRepositoryInterface::class);
|
||||
$data = [];
|
||||
$config = $this->job->configuration;
|
||||
$collection = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$defaultCurrency = app('amount')->getDefaultCurrency();
|
||||
$dbAccounts = [];
|
||||
/** @var Account $dbAccount */
|
||||
foreach ($collection as $dbAccount) {
|
||||
$id = $dbAccount->id;
|
||||
$currencyId = intval($dbAccount->getMeta('currency_id'));
|
||||
$currency = $currencyRepository->find($currencyId);
|
||||
$dbAccounts[$id] = [
|
||||
'account' => $dbAccount,
|
||||
'currency' => is_null($currency->id) ? $defaultCurrency : $currency,
|
||||
];
|
||||
}
|
||||
|
||||
// loop Spectre accounts:
|
||||
/**
|
||||
* @var int $index
|
||||
* @var array $spectreAccount
|
||||
*/
|
||||
foreach ($config['accounts'] as $index => $spectreAccount) {
|
||||
// find accounts with currency code
|
||||
$code = $spectreAccount['currency_code'];
|
||||
$selection = $this->filterAccounts($dbAccounts, $code);
|
||||
$config['accounts'][$index]['options'] = app('expandedform')->makeSelectList($selection);
|
||||
}
|
||||
|
||||
|
||||
$data = [
|
||||
'config' => $config,
|
||||
];
|
||||
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return possible warning to user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWarningMessage(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return ConfigurationInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the result.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function storeConfiguration(array $data): bool
|
||||
{
|
||||
$accounts = $data['spectre_account_id'] ?? [];
|
||||
$mapping = [];
|
||||
foreach ($accounts as $spectreId) {
|
||||
$spectreId = intval($spectreId);
|
||||
$doImport = intval($data['do_import'][$spectreId] ?? 0) === 1;
|
||||
$account = intval($data['import'][$spectreId] ?? 0);
|
||||
if ($doImport) {
|
||||
$mapping[$spectreId] = $account;
|
||||
}
|
||||
}
|
||||
$config = $this->job->configuration;
|
||||
$config['accounts-mapped'] = $mapping;
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $dbAccounts
|
||||
* @param string $code
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function filterAccounts(array $dbAccounts, string $code): Collection
|
||||
{
|
||||
$collection = new Collection;
|
||||
foreach ($dbAccounts as $accountId => $data) {
|
||||
if ($data['currency']->code === $code) {
|
||||
$collection->push($data['account']);
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@ class SingleCacheProperties extends CacheProperties
|
||||
$this->properties = new Collection;
|
||||
if (auth()->check()) {
|
||||
$this->addProperty(auth()->user()->id);
|
||||
$this->addProperty(app('preferences')->lastActivity());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ class Steam
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->where('transaction_journals.date', '<=', $date->format('Y-m-d'))
|
||||
->where('transactions.foreign_currency_id', $currencyId)
|
||||
->where('transactions.transaction_currency_id', '!=', $currencyId)
|
||||
->sum('transactions.foreign_amount')
|
||||
);
|
||||
$balance = bcadd($nativeBalance, $foreignBalance);
|
||||
@ -114,6 +115,7 @@ class Steam
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->where('transaction_journals.date', '<=', $date->format('Y-m-d'))
|
||||
->where('transactions.foreign_currency_id', $currencyId)
|
||||
->where('transactions.transaction_currency_id', '!=', $currencyId)
|
||||
->sum('transactions.foreign_amount')
|
||||
);
|
||||
$balance = bcadd($nativeBalance, $foreignBalance);
|
||||
|
@ -99,7 +99,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"pre-install-cmd": [
|
||||
"if [ -z ${DYNO+x} ]; then echo Not in Heroku environment.; else php -r \"file_exists('.env') || copy('.env.heroku', '.env');\"; fi"
|
||||
"@php -r \"if (!(getenv('DYNO'))===false){file_exists('.env') || copy('.env.heroku', '.env');}\""
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
|
206
composer.lock
generated
206
composer.lock
generated
@ -947,34 +947,34 @@
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "0.16.0",
|
||||
"version": "0.17.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "c0e41be0f80c51ad3170c9c713f74a0b8dec59ce"
|
||||
"reference": "3b4c2224524776a584de663c7a04bc8eb2e1544d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c0e41be0f80c51ad3170c9c713f74a0b8dec59ce",
|
||||
"reference": "c0e41be0f80c51ad3170c9c713f74a0b8dec59ce",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3b4c2224524776a584de663c7a04bc8eb2e1544d",
|
||||
"reference": "3b4c2224524776a584de663c7a04bc8eb2e1544d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=5.4.8"
|
||||
"php": ">=5.6.5"
|
||||
},
|
||||
"replace": {
|
||||
"colinodell/commonmark-php": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"cebe/markdown": "~1.0",
|
||||
"commonmark/commonmark.js": "0.28",
|
||||
"erusev/parsedown": "~1.0",
|
||||
"jgm/commonmark": "0.28",
|
||||
"michelf/php-markdown": "~1.4",
|
||||
"mikehaertl/php-shellcommand": "~1.2.0",
|
||||
"phpunit/phpunit": "^4.8.35|~5.7",
|
||||
"phpunit/phpunit": "~5.7|~6.5",
|
||||
"scrutinizer/ocular": "~1.1",
|
||||
"symfony/finder": "~2.3|~3.0"
|
||||
"symfony/finder": "~3.0|~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"league/commonmark-extras": "Library of useful extensions including smart punctuation"
|
||||
@ -985,7 +985,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.17-dev"
|
||||
"dev-master": "0.18-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -1012,7 +1012,7 @@
|
||||
"markdown",
|
||||
"parser"
|
||||
],
|
||||
"time": "2017-10-31T00:49:55+00:00"
|
||||
"time": "2017-12-30T22:08:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/csv",
|
||||
@ -1448,16 +1448,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
"version": "v2.0.6",
|
||||
"version": "v2.0.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/antonioribeiro/google2fa.git",
|
||||
"reference": "bc2d654305e4d09254125f8cd390a7fbc4742d46"
|
||||
"reference": "5a818bda62fab0c0a79060b06d50d50b5525d631"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/bc2d654305e4d09254125f8cd390a7fbc4742d46",
|
||||
"reference": "bc2d654305e4d09254125f8cd390a7fbc4742d46",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/5a818bda62fab0c0a79060b06d50d50b5525d631",
|
||||
"reference": "5a818bda62fab0c0a79060b06d50d50b5525d631",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1468,8 +1468,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"bacon/bacon-qr-code": "~1.0",
|
||||
"phpspec/phpspec": "~2.1",
|
||||
"phpunit/phpunit": "~4"
|
||||
"phpunit/phpunit": "~4|~5|~6"
|
||||
},
|
||||
"suggest": {
|
||||
"bacon/bacon-qr-code": "Required to generate inline QR Codes."
|
||||
@ -1483,7 +1482,8 @@
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PragmaRX\\Google2FA\\": "src/"
|
||||
"PragmaRX\\Google2FA\\": "src/",
|
||||
"PragmaRX\\Google2FA\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
@ -1505,7 +1505,7 @@
|
||||
"google2fa",
|
||||
"laravel"
|
||||
],
|
||||
"time": "2017-09-12T06:55:05+00:00"
|
||||
"time": "2018-01-06T16:21:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa-laravel",
|
||||
@ -1974,16 +1974,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v3.4.2",
|
||||
"version": "v3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "9f21adfb92a9315b73ae2ed43138988ee4913d4e"
|
||||
"reference": "8394c8ef121949e8f858f13bc1e34f05169e4e7d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/9f21adfb92a9315b73ae2ed43138988ee4913d4e",
|
||||
"reference": "9f21adfb92a9315b73ae2ed43138988ee4913d4e",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/8394c8ef121949e8f858f13bc1e34f05169e4e7d",
|
||||
"reference": "8394c8ef121949e8f858f13bc1e34f05169e4e7d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2039,20 +2039,20 @@
|
||||
],
|
||||
"description": "Symfony Console Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-12-14T19:40:10+00:00"
|
||||
"time": "2018-01-03T07:37:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v3.4.2",
|
||||
"version": "v3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
"reference": "eac760b414cf1f64362c3dd047b989e4db121332"
|
||||
"reference": "e66394bc7610e69279bfdb3ab11b4fe65403f556"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/eac760b414cf1f64362c3dd047b989e4db121332",
|
||||
"reference": "eac760b414cf1f64362c3dd047b989e4db121332",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/e66394bc7610e69279bfdb3ab11b4fe65403f556",
|
||||
"reference": "e66394bc7610e69279bfdb3ab11b4fe65403f556",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2092,20 +2092,20 @@
|
||||
],
|
||||
"description": "Symfony CssSelector Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-12-14T19:40:10+00:00"
|
||||
"time": "2018-01-03T07:37:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v3.4.2",
|
||||
"version": "v3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/debug.git",
|
||||
"reference": "543deab3ffff94402440b326fc94153bae2dfa7a"
|
||||
"reference": "603b95dda8b00020e4e6e60dc906e7b715b1c245"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/543deab3ffff94402440b326fc94153bae2dfa7a",
|
||||
"reference": "543deab3ffff94402440b326fc94153bae2dfa7a",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/603b95dda8b00020e4e6e60dc906e7b715b1c245",
|
||||
"reference": "603b95dda8b00020e4e6e60dc906e7b715b1c245",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2148,20 +2148,20 @@
|
||||
],
|
||||
"description": "Symfony Debug Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-12-12T08:27:14+00:00"
|
||||
"time": "2018-01-03T17:14:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v4.0.2",
|
||||
"version": "v4.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "d4face19ed8002eec8280bc1c5ec18130472bf43"
|
||||
"reference": "74d33aac36208c4d6757807d9f598f0133a3a4eb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d4face19ed8002eec8280bc1c5ec18130472bf43",
|
||||
"reference": "d4face19ed8002eec8280bc1c5ec18130472bf43",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/74d33aac36208c4d6757807d9f598f0133a3a4eb",
|
||||
"reference": "74d33aac36208c4d6757807d9f598f0133a3a4eb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2211,20 +2211,20 @@
|
||||
],
|
||||
"description": "Symfony EventDispatcher Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-12-14T19:48:22+00:00"
|
||||
"time": "2018-01-03T07:38:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v3.4.2",
|
||||
"version": "v3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "dac8d7db537bac7ad8143eb11360a8c2231f251a"
|
||||
"reference": "613e26310776f49a1773b6737c6bd554b8bc8c6f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/dac8d7db537bac7ad8143eb11360a8c2231f251a",
|
||||
"reference": "dac8d7db537bac7ad8143eb11360a8c2231f251a",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/613e26310776f49a1773b6737c6bd554b8bc8c6f",
|
||||
"reference": "613e26310776f49a1773b6737c6bd554b8bc8c6f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2260,20 +2260,20 @@
|
||||
],
|
||||
"description": "Symfony Finder Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-11-05T16:10:10+00:00"
|
||||
"time": "2018-01-03T07:37:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-foundation",
|
||||
"version": "v3.4.2",
|
||||
"version": "v3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-foundation.git",
|
||||
"reference": "59bf131b5460227a2f583a7dbe6b179f98f9e0a5"
|
||||
"reference": "4a213be1cc8598089b8c7451529a2927b49b5d26"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/59bf131b5460227a2f583a7dbe6b179f98f9e0a5",
|
||||
"reference": "59bf131b5460227a2f583a7dbe6b179f98f9e0a5",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/4a213be1cc8598089b8c7451529a2927b49b5d26",
|
||||
"reference": "4a213be1cc8598089b8c7451529a2927b49b5d26",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2314,20 +2314,20 @@
|
||||
],
|
||||
"description": "Symfony HttpFoundation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-12-14T19:40:10+00:00"
|
||||
"time": "2018-01-03T17:14:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-kernel",
|
||||
"version": "v3.4.2",
|
||||
"version": "v3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-kernel.git",
|
||||
"reference": "48325096bbda77b983e642d21a4dd9bdde3ab73e"
|
||||
"reference": "1c2a82d6a8ec9b354fe4ef48ad1ad3f1a4f7db0e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/48325096bbda77b983e642d21a4dd9bdde3ab73e",
|
||||
"reference": "48325096bbda77b983e642d21a4dd9bdde3ab73e",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/1c2a82d6a8ec9b354fe4ef48ad1ad3f1a4f7db0e",
|
||||
"reference": "1c2a82d6a8ec9b354fe4ef48ad1ad3f1a4f7db0e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2402,7 +2402,7 @@
|
||||
],
|
||||
"description": "Symfony HttpKernel Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-12-15T02:05:18+00:00"
|
||||
"time": "2018-01-05T08:33:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
@ -2632,16 +2632,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v3.4.2",
|
||||
"version": "v3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "bb3ef65d493a6d57297cad6c560ee04e2a8f5098"
|
||||
"reference": "ff69f110c6b33fd33cd2089ba97d6112f44ef0ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/bb3ef65d493a6d57297cad6c560ee04e2a8f5098",
|
||||
"reference": "bb3ef65d493a6d57297cad6c560ee04e2a8f5098",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/ff69f110c6b33fd33cd2089ba97d6112f44ef0ba",
|
||||
"reference": "ff69f110c6b33fd33cd2089ba97d6112f44ef0ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2677,20 +2677,20 @@
|
||||
],
|
||||
"description": "Symfony Process Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-12-14T19:40:10+00:00"
|
||||
"time": "2018-01-03T07:37:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/routing",
|
||||
"version": "v3.4.2",
|
||||
"version": "v3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/routing.git",
|
||||
"reference": "5f248dfac5e4660c74982eb3dadc71cf58595570"
|
||||
"reference": "e2b6d6fe7b090c7af720b75c7722c6dfa7a52658"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/5f248dfac5e4660c74982eb3dadc71cf58595570",
|
||||
"reference": "5f248dfac5e4660c74982eb3dadc71cf58595570",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/e2b6d6fe7b090c7af720b75c7722c6dfa7a52658",
|
||||
"reference": "e2b6d6fe7b090c7af720b75c7722c6dfa7a52658",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2755,20 +2755,20 @@
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"time": "2017-12-14T22:37:31+00:00"
|
||||
"time": "2018-01-04T15:09:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v3.4.2",
|
||||
"version": "v3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
"reference": "4c5d5582baf2829751a5207659329c1f52eedeb6"
|
||||
"reference": "17b5962d252b2d6d1d37a2485ebb7ddc5b2bef0a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/4c5d5582baf2829751a5207659329c1f52eedeb6",
|
||||
"reference": "4c5d5582baf2829751a5207659329c1f52eedeb6",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/17b5962d252b2d6d1d37a2485ebb7ddc5b2bef0a",
|
||||
"reference": "17b5962d252b2d6d1d37a2485ebb7ddc5b2bef0a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2823,20 +2823,20 @@
|
||||
],
|
||||
"description": "Symfony Translation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-12-12T08:27:14+00:00"
|
||||
"time": "2018-01-03T07:37:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v3.4.2",
|
||||
"version": "v3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
"reference": "757074cf71b952ce9e75b557538948811c2bf006"
|
||||
"reference": "545be7e78ccbec43e599f10ff7500d0b09eda9d0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/757074cf71b952ce9e75b557538948811c2bf006",
|
||||
"reference": "757074cf71b952ce9e75b557538948811c2bf006",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/545be7e78ccbec43e599f10ff7500d0b09eda9d0",
|
||||
"reference": "545be7e78ccbec43e599f10ff7500d0b09eda9d0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2892,7 +2892,7 @@
|
||||
"debug",
|
||||
"dump"
|
||||
],
|
||||
"time": "2017-12-11T22:06:16+00:00"
|
||||
"time": "2018-01-03T17:14:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tijsverkoyen/css-to-inline-styles",
|
||||
@ -4598,16 +4598,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit-mock-objects",
|
||||
"version": "5.0.5",
|
||||
"version": "5.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
|
||||
"reference": "283b9f4f670e3a6fd6c4ff95c51a952eb5c75933"
|
||||
"reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/283b9f4f670e3a6fd6c4ff95c51a952eb5c75933",
|
||||
"reference": "283b9f4f670e3a6fd6c4ff95c51a952eb5c75933",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf",
|
||||
"reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4653,7 +4653,7 @@
|
||||
"mock",
|
||||
"xunit"
|
||||
],
|
||||
"time": "2017-12-10T08:01:53+00:00"
|
||||
"time": "2018-01-06T05:45:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
@ -5266,16 +5266,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/class-loader",
|
||||
"version": "v3.4.2",
|
||||
"version": "v3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/class-loader.git",
|
||||
"reference": "e8d36a7b5568d232f5c3f8ef92665836b9f1e038"
|
||||
"reference": "e63c12699822bb3b667e7216ba07fbcc3a3e203e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/class-loader/zipball/e8d36a7b5568d232f5c3f8ef92665836b9f1e038",
|
||||
"reference": "e8d36a7b5568d232f5c3f8ef92665836b9f1e038",
|
||||
"url": "https://api.github.com/repos/symfony/class-loader/zipball/e63c12699822bb3b667e7216ba07fbcc3a3e203e",
|
||||
"reference": "e63c12699822bb3b667e7216ba07fbcc3a3e203e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -5318,20 +5318,20 @@
|
||||
],
|
||||
"description": "Symfony ClassLoader Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-11-05T16:10:10+00:00"
|
||||
"time": "2018-01-03T07:37:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/config",
|
||||
"version": "v4.0.2",
|
||||
"version": "v4.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/config.git",
|
||||
"reference": "0356e6d5298e9e72212c0bad65c2f1b49e42d622"
|
||||
"reference": "0e86d267db0851cf55f339c97df00d693fe8592f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/0356e6d5298e9e72212c0bad65c2f1b49e42d622",
|
||||
"reference": "0356e6d5298e9e72212c0bad65c2f1b49e42d622",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/0e86d267db0851cf55f339c97df00d693fe8592f",
|
||||
"reference": "0e86d267db0851cf55f339c97df00d693fe8592f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -5378,20 +5378,20 @@
|
||||
],
|
||||
"description": "Symfony Config Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-12-14T19:48:22+00:00"
|
||||
"time": "2018-01-03T07:38:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v4.0.2",
|
||||
"version": "v4.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "8c2868641d0c4885eee9c12a89c2b695eb1985cd"
|
||||
"reference": "760e47a4ee64b4c48f4b30017011e09d4c0f05ed"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/8c2868641d0c4885eee9c12a89c2b695eb1985cd",
|
||||
"reference": "8c2868641d0c4885eee9c12a89c2b695eb1985cd",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/760e47a4ee64b4c48f4b30017011e09d4c0f05ed",
|
||||
"reference": "760e47a4ee64b4c48f4b30017011e09d4c0f05ed",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -5427,20 +5427,20 @@
|
||||
],
|
||||
"description": "Symfony Filesystem Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-12-14T19:48:22+00:00"
|
||||
"time": "2018-01-03T07:38:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/stopwatch",
|
||||
"version": "v4.0.2",
|
||||
"version": "v4.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/stopwatch.git",
|
||||
"reference": "ac0e49150555c703fef6b696d8eaba1db7a3ca03"
|
||||
"reference": "d52321f0e2b596bd03b5d1dd6eebe71caa925704"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/ac0e49150555c703fef6b696d8eaba1db7a3ca03",
|
||||
"reference": "ac0e49150555c703fef6b696d8eaba1db7a3ca03",
|
||||
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/d52321f0e2b596bd03b5d1dd6eebe71caa925704",
|
||||
"reference": "d52321f0e2b596bd03b5d1dd6eebe71caa925704",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -5476,20 +5476,20 @@
|
||||
],
|
||||
"description": "Symfony Stopwatch Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-11-09T12:45:29+00:00"
|
||||
"time": "2018-01-03T07:38:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v4.0.2",
|
||||
"version": "v4.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "a5ee52d155f06ad23b19eb63c31228ff56ad1116"
|
||||
"reference": "b84f646b9490d2101e2c25ddeec77ceefbda2eee"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/a5ee52d155f06ad23b19eb63c31228ff56ad1116",
|
||||
"reference": "a5ee52d155f06ad23b19eb63c31228ff56ad1116",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/b84f646b9490d2101e2c25ddeec77ceefbda2eee",
|
||||
"reference": "b84f646b9490d2101e2c25ddeec77ceefbda2eee",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -5534,7 +5534,7 @@
|
||||
],
|
||||
"description": "Symfony Yaml Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2017-12-12T08:41:51+00:00"
|
||||
"time": "2018-01-03T07:38:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
|
@ -18,7 +18,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'view' => 'breadcrumbs::bootstrap3',
|
||||
'view' => 'partials/breadcrumbs',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -320,18 +320,4 @@ return [
|
||||
|
||||
// number of example rows:
|
||||
'example_rows' => 5,
|
||||
'default_config' => [
|
||||
'initial-config-complete' => false,
|
||||
'has-headers' => false, // assume
|
||||
'date-format' => 'Ymd', // assume
|
||||
'delimiter' => ',', // assume
|
||||
'import-account' => 0, // none,
|
||||
'specifics' => [], // none
|
||||
'column-count' => 0, // unknown
|
||||
'column-roles' => [], // unknown
|
||||
'column-do-mapping' => [], // not yet set which columns must be mapped
|
||||
'column-roles-complete' => false, // not yet configured roles for columns
|
||||
'column-mapping-config' => [], // no mapping made yet.
|
||||
'column-mapping-complete' => false, // so mapping is not complete.
|
||||
],
|
||||
];
|
||||
|
@ -33,7 +33,7 @@ return [
|
||||
'is_demo_site' => false,
|
||||
],
|
||||
'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true),
|
||||
'version' => '4.6.12',
|
||||
'version' => '4.6.13',
|
||||
'maxUploadSize' => 15242880,
|
||||
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
|
||||
'list_length' => 10,
|
||||
@ -119,6 +119,7 @@ return [
|
||||
'id_ID' => ['name_locale' => 'Bahasa Indonesia', 'name_english' => 'Indonesian'],
|
||||
'nl_NL' => ['name_locale' => 'Nederlands', 'name_english' => 'Dutch'],
|
||||
'pl_PL' => ['name_locale' => 'Polski', 'name_english' => 'Polish '],
|
||||
'tr_TR' => ['name_locale' => 'Türkçe', 'name_english' => 'Turkish'],
|
||||
|
||||
// incomplete languages:
|
||||
//'pt_BR' => ['name_locale' => 'Português do Brasil', 'name_english' => 'Portuguese (Brazil)'],
|
||||
@ -126,7 +127,7 @@ return [
|
||||
//'es_ES' => ['name_locale' => 'Spanish', 'name_english' => 'Spanish'],
|
||||
//'ru_RU' => ['name_locale' => 'Русский', 'name_english' => 'Russian'],
|
||||
//'sl_SI' => ['name_locale' => 'Slovenščina', 'name_english' => 'Slovenian'],
|
||||
//'tr_TR' => ['name_locale' => 'Türkçe', 'name_english' => 'Turkish'],
|
||||
//
|
||||
],
|
||||
'transactionTypesByWhat' => [
|
||||
'expenses' => ['Withdrawal'],
|
||||
|
54
index.php
Normal file
54
index.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* index.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
echo '<!DOCTYPE HTML>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="8; url=./public/">
|
||||
<script type="text/javascript">
|
||||
setTimeout(function() {
|
||||
window.location.href = "./public/";
|
||||
}, 8000);
|
||||
</script>
|
||||
<title>Firefly III</title>
|
||||
<style>
|
||||
p {font-family:Arial,sans-serif;font-size:18px;color:#222;text-align:center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<strong style="color:red;">Danger!</strong> This directory should not be open to the public!
|
||||
</p>
|
||||
<p>
|
||||
<span style="font-family:monospace;">/public/</span> should be the document root of your web server.
|
||||
</p>
|
||||
<p>
|
||||
Leaving your web server configured like this is a <span style="color:red;">huge</span> security risk.
|
||||
</p>
|
||||
<p>
|
||||
Please <a href="https://github.com/firefly-iii/help/wiki/Configure-your-webserver-correctly">read more on the Github help pages</a>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
';
|
1
public/js/ff/charts.defaults.js
vendored
1
public/js/ff/charts.defaults.js
vendored
@ -31,6 +31,7 @@
|
||||
*/
|
||||
function formatLabel(str, maxwidth) {
|
||||
var sections = [];
|
||||
str = String(str);
|
||||
var words = str.split(" ");
|
||||
var temp = "";
|
||||
|
||||
|
16
public/js/ff/import/status.js
vendored
16
public/js/ff/import/status.js
vendored
@ -174,14 +174,20 @@ function jobIsStalled(data) {
|
||||
|
||||
/**
|
||||
* This function tells Firefly start the job. It will also initialize a re-check in 500ms time.
|
||||
* Only when job is in "configured" state.
|
||||
*/
|
||||
function startJob() {
|
||||
// disable the button, add loading thing.
|
||||
$('.start-job').prop('disabled', true).text('...');
|
||||
$.post(jobStartUri, {_token: token}).fail(reportOnSubmitError);
|
||||
if (job.status === "configured") {
|
||||
console.log("Job auto started!");
|
||||
// disable the button, add loading thing.
|
||||
$('.start-job').prop('disabled', true).text('...');
|
||||
$.post(jobStartUri, {_token: token}).fail(reportOnSubmitError);
|
||||
|
||||
// check status, every 500 ms.
|
||||
timeOutId = setTimeout(checkJobStatus, startInterval);
|
||||
// check status, every 500 ms.
|
||||
timeOutId = setTimeout(checkJobStatus, startInterval);
|
||||
return;
|
||||
}
|
||||
console.log("Job not auto started because state is " + job.status);
|
||||
}
|
||||
|
||||
/**
|
||||
|
9
public/js/ff/reports/default/month.js
vendored
9
public/js/ff/reports/default/month.js
vendored
@ -22,17 +22,10 @@
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
drawChart();
|
||||
lineChart(accountChartUri, 'account-balances-chart');
|
||||
|
||||
loadAjaxPartial('categoryReport', categoryReportUri);
|
||||
loadAjaxPartial('budgetReport', budgetReportUri);
|
||||
loadAjaxPartial('balanceReport', balanceReportUri);
|
||||
});
|
||||
|
||||
function drawChart() {
|
||||
"use strict";
|
||||
|
||||
// month view:
|
||||
// draw account chart
|
||||
lineChart(accountChartUri, 'account-balances-chart');
|
||||
}
|
12
public/js/ff/reports/default/multi-year.js
vendored
12
public/js/ff/reports/default/multi-year.js
vendored
@ -22,18 +22,12 @@
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
drawChart();
|
||||
lineChart(netWorthUri, 'net-worth');
|
||||
columnChart(opChartUri, 'income-expenses-chart');
|
||||
columnChart(sumChartUri, 'income-expenses-sum-chart');
|
||||
|
||||
loadAjaxPartial('budgetPeriodReport', budgetPeriodReportUri);
|
||||
loadAjaxPartial('categoryExpense', categoryExpenseUri);
|
||||
loadAjaxPartial('categoryIncome', categoryIncomeUri);
|
||||
});
|
||||
|
||||
function drawChart() {
|
||||
"use strict";
|
||||
|
||||
// income and expense over multi year:
|
||||
lineChart(netWorthUri, 'net-worth');
|
||||
columnChart(opChartUri, 'income-expenses-chart');
|
||||
columnChart(sumChartUri, 'income-expenses-sum-chart');
|
||||
}
|
||||
|
13
public/js/ff/reports/default/year.js
vendored
13
public/js/ff/reports/default/year.js
vendored
@ -22,19 +22,12 @@
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
drawChart();
|
||||
lineChart(netWorthUri, 'net-worth');
|
||||
columnChart(opChartUri, 'income-expenses-chart');
|
||||
columnChart(sumChartUri, 'income-expenses-sum-chart');
|
||||
|
||||
loadAjaxPartial('budgetPeriodReport', budgetPeriodReportUri);
|
||||
loadAjaxPartial('categoryExpense', categoryExpenseUri);
|
||||
loadAjaxPartial('categoryIncome', categoryIncomeUri);
|
||||
});
|
||||
|
||||
function drawChart() {
|
||||
"use strict";
|
||||
|
||||
lineChart(netWorthUri, 'net-worth');
|
||||
columnChart(opChartUri, 'income-expenses-chart');
|
||||
columnChart(sumChartUri, 'income-expenses-sum-chart');
|
||||
|
||||
|
||||
}
|
||||
|
58
public/js/ff/transactions/list.js
vendored
58
public/js/ff/transactions/list.js
vendored
@ -18,7 +18,7 @@
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** global: edit_selected_txt, delete_selected_txt, token */
|
||||
/** global: edit_selected_txt, edit_bulk_selected_txt, delete_selected_txt, token */
|
||||
|
||||
/**
|
||||
*
|
||||
@ -43,43 +43,14 @@ $(document).ready(function () {
|
||||
countChecked();
|
||||
});
|
||||
|
||||
// click the edit button:
|
||||
// click the mass edit button:
|
||||
$('.mass_edit').click(goToMassEdit);
|
||||
// click the bulk edit button:
|
||||
$('.bulk_edit').click(goToBulkEdit);
|
||||
// click the delete button:
|
||||
$('.mass_delete').click(goToMassDelete);
|
||||
// click reconcile button
|
||||
// $('.mass_reconcile').click(goToReconcile);
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function goToReconcile() {
|
||||
|
||||
var checked = $('.select_all_single:checked');
|
||||
var ids = [];
|
||||
$.each(checked, function (i, v) {
|
||||
ids.push(parseInt($(v).data('transaction')));
|
||||
});
|
||||
|
||||
// go to specially crafted URL:
|
||||
var bases = document.getElementsByTagName('base');
|
||||
var baseHref = null;
|
||||
|
||||
if (bases.length > 0) {
|
||||
baseHref = bases[0].href;
|
||||
}
|
||||
|
||||
$.post(baseHref + 'transactions/reconcile', {transactions: ids, _token: token}).done(function () {
|
||||
window.location.reload(true);
|
||||
}).fail(function () {
|
||||
alert('Could not reconcile transactions: please check the logs and try again later.');
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {boolean}
|
||||
@ -100,6 +71,26 @@ function goToMassEdit() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function goToBulkEdit() {
|
||||
"use strict";
|
||||
var checkedArray = getCheckboxes();
|
||||
|
||||
// go to specially crafted URL:
|
||||
var bases = document.getElementsByTagName('base');
|
||||
var baseHref = null;
|
||||
|
||||
if (bases.length > 0) {
|
||||
baseHref = bases[0].href;
|
||||
}
|
||||
|
||||
window.location.href = baseHref + '/transactions/bulk/edit/' + checkedArray;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {boolean}
|
||||
@ -144,6 +135,7 @@ function countChecked() {
|
||||
var checked = $('.select_all_single:checked').length;
|
||||
if (checked > 0) {
|
||||
$('.mass_edit span').text(edit_selected_txt + ' (' + checked + ')');
|
||||
$('.bulk_edit span').text(edit_bulk_selected_txt + ' (' + checked + ')');
|
||||
$('.mass_delete span').text(delete_selected_txt + ' (' + checked + ')');
|
||||
|
||||
// get amount for the transactions:
|
||||
|
43
public/js/ff/transactions/mass/edit-bulk.js
vendored
Normal file
43
public/js/ff/transactions/mass/edit-bulk.js
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* edit.js
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** global: what */
|
||||
|
||||
$(document).ready(function () {
|
||||
"use strict";
|
||||
|
||||
$.getJSON('json/categories').done(function (data) {
|
||||
$('input[name="category"]').typeahead({source: data});
|
||||
});
|
||||
|
||||
$.getJSON('json/tags').done(function (data) {
|
||||
var opt = {
|
||||
typeahead: {
|
||||
source: data,
|
||||
afterSelect: function () {
|
||||
this.$element.val("");
|
||||
}
|
||||
}
|
||||
};
|
||||
$('input[name="tags"]').tagsinput(
|
||||
opt
|
||||
);
|
||||
});
|
||||
});
|
4
public/js/lib/Chart.bundle.min.js
vendored
4
public/js/lib/Chart.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/lib/bootstrap3-typeahead.min.js
vendored
2
public/js/lib/bootstrap3-typeahead.min.js
vendored
File diff suppressed because one or more lines are too long
154
public/js/lib/daterangepicker.js
vendored
154
public/js/lib/daterangepicker.js
vendored
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @version: 2.1.25
|
||||
* @version: 2.1.30
|
||||
* @author: Dan Grossman http://www.dangrossman.info/
|
||||
* @copyright: Copyright (c) 2012-2017 Dan Grossman. All rights reserved.
|
||||
* @license: Licensed under the MIT license. See http://www.opensource.org/licenses/mit-license.php
|
||||
@ -11,7 +11,7 @@
|
||||
// AMD. Make globaly available as well
|
||||
define(['moment', 'jquery'], function (moment, jquery) {
|
||||
if (!jquery.fn) jquery.fn = {}; // webpack server rendering
|
||||
return (root.daterangepicker = factory(moment, jquery));
|
||||
return factory(moment, jquery);
|
||||
});
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
// Node / Browserify
|
||||
@ -27,8 +27,8 @@
|
||||
// Browser globals
|
||||
root.daterangepicker = factory(root.moment, root.jQuery);
|
||||
}
|
||||
}(this, function (moment, $) {
|
||||
var DateRangePicker = function (element, options, cb) {
|
||||
}(this, function(moment, $) {
|
||||
var DateRangePicker = function(element, options, cb) {
|
||||
|
||||
//default settings for options
|
||||
this.parentEl = 'body';
|
||||
@ -78,8 +78,7 @@
|
||||
firstDay: moment.localeData().firstDayOfWeek()
|
||||
};
|
||||
|
||||
this.callback = function () {
|
||||
};
|
||||
this.callback = function() { };
|
||||
|
||||
//some state information
|
||||
this.isShowing = false;
|
||||
@ -163,7 +162,7 @@
|
||||
if (typeof options.locale.weekLabel === 'string')
|
||||
this.locale.weekLabel = options.locale.weekLabel;
|
||||
|
||||
if (typeof options.locale.customRangeLabel === 'string') {
|
||||
if (typeof options.locale.customRangeLabel === 'string'){
|
||||
//Support unicode chars in the custom range name.
|
||||
var elem = document.createElement('textarea');
|
||||
elem.innerHTML = options.locale.customRangeLabel;
|
||||
@ -288,7 +287,7 @@
|
||||
//if no start/end dates set, check if an input element contains initial values
|
||||
if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') {
|
||||
if ($(this.element).is('input[type=text]')) {
|
||||
var val = $(this.element).val(),
|
||||
var val = $(this.element).val(),
|
||||
split = val.split(this.locale.separator);
|
||||
|
||||
start = end = null;
|
||||
@ -397,7 +396,7 @@
|
||||
|
||||
//swap the position of the predefined ranges if opens right
|
||||
if (typeof options.ranges !== 'undefined' && this.opens == 'right') {
|
||||
this.container.find('.ranges').prependTo(this.container.find('.calendar.left').parent());
|
||||
this.container.find('.ranges').prependTo( this.container.find('.calendar.left').parent() );
|
||||
}
|
||||
|
||||
//apply CSS classes and labels to buttons
|
||||
@ -425,7 +424,8 @@
|
||||
.on('click.daterangepicker', '.daterangepicker_input input', $.proxy(this.showCalendars, this))
|
||||
.on('focus.daterangepicker', '.daterangepicker_input input', $.proxy(this.formInputsFocused, this))
|
||||
.on('blur.daterangepicker', '.daterangepicker_input input', $.proxy(this.formInputsBlurred, this))
|
||||
.on('change.daterangepicker', '.daterangepicker_input input', $.proxy(this.formInputsChanged, this));
|
||||
.on('change.daterangepicker', '.daterangepicker_input input', $.proxy(this.formInputsChanged, this))
|
||||
.on('keydown.daterangepicker', '.daterangepicker_input input', $.proxy(this.formInputsKeydown, this));
|
||||
|
||||
this.container.find('.ranges')
|
||||
.on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this))
|
||||
@ -439,10 +439,11 @@
|
||||
'click.daterangepicker': $.proxy(this.show, this),
|
||||
'focus.daterangepicker': $.proxy(this.show, this),
|
||||
'keyup.daterangepicker': $.proxy(this.elementChanged, this),
|
||||
'keydown.daterangepicker': $.proxy(this.keydown, this)
|
||||
'keydown.daterangepicker': $.proxy(this.keydown, this) //IE 11 compatibility
|
||||
});
|
||||
} else {
|
||||
this.element.on('click.daterangepicker', $.proxy(this.toggle, this));
|
||||
this.element.on('keydown.daterangepicker', $.proxy(this.toggle, this));
|
||||
}
|
||||
|
||||
//
|
||||
@ -463,7 +464,7 @@
|
||||
|
||||
constructor: DateRangePicker,
|
||||
|
||||
setStartDate: function (startDate) {
|
||||
setStartDate: function(startDate) {
|
||||
if (typeof startDate === 'string')
|
||||
this.startDate = moment(startDate, this.locale.format);
|
||||
|
||||
@ -494,7 +495,7 @@
|
||||
this.updateMonthsInView();
|
||||
},
|
||||
|
||||
setEndDate: function (endDate) {
|
||||
setEndDate: function(endDate) {
|
||||
if (typeof endDate === 'string')
|
||||
this.endDate = moment(endDate, this.locale.format);
|
||||
|
||||
@ -502,7 +503,7 @@
|
||||
this.endDate = moment(endDate);
|
||||
|
||||
if (!this.timePicker)
|
||||
this.endDate = this.endDate.endOf('day');
|
||||
this.endDate = this.endDate.add(1,'d').startOf('day').subtract(1,'second');
|
||||
|
||||
if (this.timePicker && this.timePickerIncrement)
|
||||
this.endDate.minute(Math.round(this.endDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
|
||||
@ -524,15 +525,15 @@
|
||||
this.updateMonthsInView();
|
||||
},
|
||||
|
||||
isInvalidDate: function () {
|
||||
isInvalidDate: function() {
|
||||
return false;
|
||||
},
|
||||
|
||||
isCustomDate: function () {
|
||||
isCustomDate: function() {
|
||||
return false;
|
||||
},
|
||||
|
||||
updateView: function () {
|
||||
updateView: function() {
|
||||
if (this.timePicker) {
|
||||
this.renderTimePicker('left');
|
||||
this.renderTimePicker('right');
|
||||
@ -554,7 +555,7 @@
|
||||
this.updateFormInputs();
|
||||
},
|
||||
|
||||
updateMonthsInView: function () {
|
||||
updateMonthsInView: function() {
|
||||
if (this.endDate) {
|
||||
|
||||
//if both dates are visible already, do nothing
|
||||
@ -585,7 +586,7 @@
|
||||
}
|
||||
},
|
||||
|
||||
updateCalendars: function () {
|
||||
updateCalendars: function() {
|
||||
|
||||
if (this.timePicker) {
|
||||
var hour, minute, second;
|
||||
@ -626,7 +627,7 @@
|
||||
this.calculateChosenLabel();
|
||||
},
|
||||
|
||||
renderCalendar: function (side) {
|
||||
renderCalendar: function(side) {
|
||||
|
||||
//
|
||||
// Build the matrix of dates that will populate the calendar
|
||||
@ -763,7 +764,7 @@
|
||||
if (this.showWeekNumbers || this.showISOWeekNumbers)
|
||||
html += '<th class="week">' + this.locale.weekLabel + '</th>';
|
||||
|
||||
$.each(this.locale.daysOfWeek, function (index, dayOfWeek) {
|
||||
$.each(this.locale.daysOfWeek, function(index, dayOfWeek) {
|
||||
html += '<th>' + dayOfWeek + '</th>';
|
||||
});
|
||||
|
||||
@ -860,7 +861,7 @@
|
||||
|
||||
},
|
||||
|
||||
renderTimePicker: function (side) {
|
||||
renderTimePicker: function(side) {
|
||||
|
||||
// Don't bother updating the time picker if it's currently disabled
|
||||
// because an end date hasn't been clicked yet
|
||||
@ -1021,7 +1022,7 @@
|
||||
|
||||
},
|
||||
|
||||
updateFormInputs: function () {
|
||||
updateFormInputs: function() {
|
||||
|
||||
//ignore mouse movements while an above-calendar text input has focus
|
||||
if (this.container.find('input[name=daterangepicker_start]').is(":focus") || this.container.find('input[name=daterangepicker_end]').is(":focus"))
|
||||
@ -1039,8 +1040,8 @@
|
||||
|
||||
},
|
||||
|
||||
move: function () {
|
||||
var parentOffset = {top: 0, left: 0},
|
||||
move: function() {
|
||||
var parentOffset = { top: 0, left: 0 },
|
||||
containerTop;
|
||||
var parentRightEdge = $(window).width();
|
||||
if (!this.parentEl.is('body')) {
|
||||
@ -1097,13 +1098,11 @@
|
||||
}
|
||||
},
|
||||
|
||||
show: function (e) {
|
||||
show: function(e) {
|
||||
if (this.isShowing) return;
|
||||
|
||||
// Create a click proxy that is private to this instance of datepicker, for unbinding
|
||||
this._outsideClickProxy = $.proxy(function (e) {
|
||||
this.outsideClick(e);
|
||||
}, this);
|
||||
this._outsideClickProxy = $.proxy(function(e) { this.outsideClick(e); }, this);
|
||||
|
||||
// Bind global datepicker mousedown for hiding and
|
||||
$(document)
|
||||
@ -1116,9 +1115,7 @@
|
||||
.on('focusin.daterangepicker', this._outsideClickProxy);
|
||||
|
||||
// Reposition the picker if the window is resized while it's open
|
||||
$(window).on('resize.daterangepicker', $.proxy(function (e) {
|
||||
this.move(e);
|
||||
}, this));
|
||||
$(window).on('resize.daterangepicker', $.proxy(function(e) { this.move(e); }, this));
|
||||
|
||||
this.oldStartDate = this.startDate.clone();
|
||||
this.oldEndDate = this.endDate.clone();
|
||||
@ -1131,7 +1128,7 @@
|
||||
this.isShowing = true;
|
||||
},
|
||||
|
||||
hide: function (e) {
|
||||
hide: function(e) {
|
||||
if (!this.isShowing) return;
|
||||
|
||||
//incomplete date selection, revert to last values
|
||||
@ -1154,7 +1151,7 @@
|
||||
this.isShowing = false;
|
||||
},
|
||||
|
||||
toggle: function (e) {
|
||||
toggle: function(e) {
|
||||
if (this.isShowing) {
|
||||
this.hide();
|
||||
} else {
|
||||
@ -1162,7 +1159,7 @@
|
||||
}
|
||||
},
|
||||
|
||||
outsideClick: function (e) {
|
||||
outsideClick: function(e) {
|
||||
var target = $(e.target);
|
||||
// if the page is clicked anywhere except within the daterangerpicker/button
|
||||
// itself then call this.hide()
|
||||
@ -1177,18 +1174,18 @@
|
||||
this.element.trigger('outsideClick.daterangepicker', this);
|
||||
},
|
||||
|
||||
showCalendars: function () {
|
||||
showCalendars: function() {
|
||||
this.container.addClass('show-calendar');
|
||||
this.move();
|
||||
this.element.trigger('showCalendar.daterangepicker', this);
|
||||
},
|
||||
|
||||
hideCalendars: function () {
|
||||
hideCalendars: function() {
|
||||
this.container.removeClass('show-calendar');
|
||||
this.element.trigger('hideCalendar.daterangepicker', this);
|
||||
},
|
||||
|
||||
hoverRange: function (e) {
|
||||
hoverRange: function(e) {
|
||||
|
||||
//ignore mouse movements while an above-calendar text input has focus
|
||||
if (this.container.find('input[name=daterangepicker_start]').is(":focus") || this.container.find('input[name=daterangepicker_end]').is(":focus"))
|
||||
@ -1206,7 +1203,7 @@
|
||||
|
||||
},
|
||||
|
||||
clickRange: function (e) {
|
||||
clickRange: function(e) {
|
||||
var label = e.target.getAttribute('data-range-key');
|
||||
this.chosenLabel = label;
|
||||
if (label == this.locale.customRangeLabel) {
|
||||
@ -1227,7 +1224,7 @@
|
||||
}
|
||||
},
|
||||
|
||||
clickPrev: function (e) {
|
||||
clickPrev: function(e) {
|
||||
var cal = $(e.target).parents('.calendar');
|
||||
if (cal.hasClass('left')) {
|
||||
this.leftCalendar.month.subtract(1, 'month');
|
||||
@ -1239,7 +1236,7 @@
|
||||
this.updateCalendars();
|
||||
},
|
||||
|
||||
clickNext: function (e) {
|
||||
clickNext: function(e) {
|
||||
var cal = $(e.target).parents('.calendar');
|
||||
if (cal.hasClass('left')) {
|
||||
this.leftCalendar.month.add(1, 'month');
|
||||
@ -1251,7 +1248,7 @@
|
||||
this.updateCalendars();
|
||||
},
|
||||
|
||||
hoverDate: function (e) {
|
||||
hoverDate: function(e) {
|
||||
|
||||
//ignore mouse movements while an above-calendar text input has focus
|
||||
//if (this.container.find('input[name=daterangepicker_start]').is(":focus") || this.container.find('input[name=daterangepicker_end]').is(":focus"))
|
||||
@ -1278,7 +1275,7 @@
|
||||
var rightCalendar = this.rightCalendar;
|
||||
var startDate = this.startDate;
|
||||
if (!this.endDate) {
|
||||
this.container.find('.calendar tbody td').each(function (index, el) {
|
||||
this.container.find('.calendar tbody td').each(function(index, el) {
|
||||
|
||||
//skip week numbers, only look at dates
|
||||
if ($(el).hasClass('week')) return;
|
||||
@ -1300,7 +1297,7 @@
|
||||
|
||||
},
|
||||
|
||||
clickDate: function (e) {
|
||||
clickDate: function(e) {
|
||||
|
||||
if (!$(e.target).hasClass('available')) return;
|
||||
|
||||
@ -1378,7 +1375,9 @@
|
||||
var i = 0;
|
||||
for (var range in this.ranges) {
|
||||
if (this.timePicker) {
|
||||
if (this.startDate.isSame(this.ranges[range][0]) && this.endDate.isSame(this.ranges[range][1])) {
|
||||
var format = this.timePickerSeconds ? "YYYY-MM-DD hh:mm:ss" : "YYYY-MM-DD hh:mm";
|
||||
//ignore times when comparing dates if time picker seconds is not enabled
|
||||
if (this.startDate.format(format) == this.ranges[range][0].format(format) && this.endDate.format(format) == this.ranges[range][1].format(format)) {
|
||||
customRange = false;
|
||||
this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')').addClass('active').html();
|
||||
break;
|
||||
@ -1403,22 +1402,22 @@
|
||||
}
|
||||
},
|
||||
|
||||
clickApply: function (e) {
|
||||
clickApply: function(e) {
|
||||
this.hide();
|
||||
this.element.trigger('apply.daterangepicker', this);
|
||||
},
|
||||
|
||||
clickCancel: function (e) {
|
||||
clickCancel: function(e) {
|
||||
this.startDate = this.oldStartDate;
|
||||
this.endDate = this.oldEndDate;
|
||||
this.hide();
|
||||
this.element.trigger('cancel.daterangepicker', this);
|
||||
},
|
||||
|
||||
monthOrYearChanged: function (e) {
|
||||
var isLeft = $(e.target).closest('.calendar').hasClass('left'),
|
||||
monthOrYearChanged: function(e) {
|
||||
var isLeft = $(e.target).closest('.calendar').hasClass('left'),
|
||||
leftOrRight = isLeft ? 'left' : 'right',
|
||||
cal = this.container.find('.calendar.' + leftOrRight);
|
||||
cal = this.container.find('.calendar.'+leftOrRight);
|
||||
|
||||
// Month must be Number for new moment versions
|
||||
var month = parseInt(cal.find('.monthselect').val(), 10);
|
||||
@ -1457,9 +1456,9 @@
|
||||
this.updateCalendars();
|
||||
},
|
||||
|
||||
timeChanged: function (e) {
|
||||
timeChanged: function(e) {
|
||||
|
||||
var cal = $(e.target).closest('.calendar'),
|
||||
var cal = $(e.target).closest('.calendar'),
|
||||
isLeft = cal.hasClass('left');
|
||||
|
||||
var hour = parseInt(cal.find('.hourselect').val(), 10);
|
||||
@ -1505,7 +1504,7 @@
|
||||
|
||||
},
|
||||
|
||||
formInputsChanged: function (e) {
|
||||
formInputsChanged: function(e) {
|
||||
var isRight = $(e.target).closest('.calendar').hasClass('right');
|
||||
var start = moment(this.container.find('input[name="daterangepicker_start"]').val(), this.locale.format);
|
||||
var end = moment(this.container.find('input[name="daterangepicker_end"]').val(), this.locale.format);
|
||||
@ -1529,13 +1528,13 @@
|
||||
this.updateView();
|
||||
},
|
||||
|
||||
formInputsFocused: function (e) {
|
||||
formInputsFocused: function(e) {
|
||||
|
||||
// Highlight the focused input
|
||||
this.container.find('input[name="daterangepicker_start"], input[name="daterangepicker_end"]').removeClass('active');
|
||||
$(e.target).addClass('active');
|
||||
|
||||
// Set the state such that if the user goes back to using a mouse,
|
||||
// Set the state such that if the user goes back to using a mouse,
|
||||
// the calendars are aware we're selecting the end of the range, not
|
||||
// the start. This allows someone to edit the end of a date range without
|
||||
// re-selecting the beginning, by clicking on the end date input then
|
||||
@ -1549,7 +1548,7 @@
|
||||
|
||||
},
|
||||
|
||||
formInputsBlurred: function (e) {
|
||||
formInputsBlurred: function(e) {
|
||||
|
||||
// this function has one purpose right now: if you tab from the first
|
||||
// text input to the second in the UI, the endDate is nulled so that
|
||||
@ -1567,14 +1566,26 @@
|
||||
|
||||
},
|
||||
|
||||
elementChanged: function () {
|
||||
formInputsKeydown: function(e) {
|
||||
// This function ensures that if the 'enter' key was pressed in the input, then the calendars
|
||||
// are updated with the startDate and endDate.
|
||||
// This behaviour is automatic in Chrome/Firefox/Edge but not in IE 11 hence why this exists.
|
||||
// Other browsers and versions of IE are untested and the behaviour is unknown.
|
||||
if (e.keyCode === 13) {
|
||||
// Prevent the calendar from being updated twice on Chrome/Firefox/Edge
|
||||
e.preventDefault();
|
||||
this.formInputsChanged(e);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
elementChanged: function() {
|
||||
if (!this.element.is('input')) return;
|
||||
if (!this.element.val().length) return;
|
||||
if (this.element.val().length < this.locale.format.length) return;
|
||||
|
||||
var dateString = this.element.val().split(this.locale.separator),
|
||||
start = null,
|
||||
end = null;
|
||||
start = null,
|
||||
end = null;
|
||||
|
||||
if (dateString.length === 2) {
|
||||
start = moment(dateString[0], this.locale.format);
|
||||
@ -1593,14 +1604,22 @@
|
||||
this.updateView();
|
||||
},
|
||||
|
||||
keydown: function (e) {
|
||||
keydown: function(e) {
|
||||
//hide on tab or enter
|
||||
if ((e.keyCode === 9) || (e.keyCode === 13)) {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
//hide on esc and prevent propagation
|
||||
if (e.keyCode === 27) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
updateElement: function () {
|
||||
updateElement: function() {
|
||||
if (this.element.is('input') && !this.singleDatePicker && this.autoUpdateInput) {
|
||||
this.element.val(this.startDate.format(this.locale.format) + this.locale.separator + this.endDate.format(this.locale.format));
|
||||
this.element.trigger('change');
|
||||
@ -1610,7 +1629,7 @@
|
||||
}
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
remove: function() {
|
||||
this.container.remove();
|
||||
this.element.off('.daterangepicker');
|
||||
this.element.removeData();
|
||||
@ -1618,16 +1637,17 @@
|
||||
|
||||
};
|
||||
|
||||
$.fn.daterangepicker = function (options, callback) {
|
||||
this.each(function () {
|
||||
$.fn.daterangepicker = function(options, callback) {
|
||||
var implementOptions = $.extend(true, {}, $.fn.daterangepicker.defaultOptions, options);
|
||||
this.each(function() {
|
||||
var el = $(this);
|
||||
if (el.data('daterangepicker'))
|
||||
el.data('daterangepicker').remove();
|
||||
el.data('daterangepicker', new DateRangePicker(el, options, callback));
|
||||
el.data('daterangepicker', new DateRangePicker(el, implementOptions, callback));
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
return DateRangePicker;
|
||||
|
||||
}));
|
||||
}));
|
4
public/js/lib/jquery-3.1.1.min.js
vendored
4
public/js/lib/jquery-3.1.1.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
public/js/lib/modernizr-custom.js
vendored
4
public/js/lib/modernizr-custom.js
vendored
@ -1,3 +1,3 @@
|
||||
/*! modernizr 3.3.1 (Custom Build) | MIT *
|
||||
/*! modernizr 3.5.0 (Custom Build) | MIT *
|
||||
* https://modernizr.com/download/?-inputtypes-setclasses !*/
|
||||
!function(e,t,n){function a(e,t){return typeof e===t}function s(){var e,t,n,s,i,o,c;for(var u in r)if(r.hasOwnProperty(u)){if(e=[],t=r[u],t.name&&(e.push(t.name.toLowerCase()),t.options&&t.options.aliases&&t.options.aliases.length))for(n=0;n<t.options.aliases.length;n++)e.push(t.options.aliases[n].toLowerCase());for(s=a(t.fn,"function")?t.fn():t.fn,i=0;i<e.length;i++)o=e[i],c=o.split("."),1===c.length?Modernizr[c[0]]=s:(!Modernizr[c[0]]||Modernizr[c[0]]instanceof Boolean||(Modernizr[c[0]]=new Boolean(Modernizr[c[0]])),Modernizr[c[0]][c[1]]=s),l.push((s?"":"no-")+c.join("-"))}}function i(e){var t=u.className,n=Modernizr._config.classPrefix||"";if(f&&(t=t.baseVal),Modernizr._config.enableJSClass){var a=new RegExp("(^|\\s)"+n+"no-js(\\s|$)");t=t.replace(a,"$1"+n+"js$2")}Modernizr._config.enableClasses&&(t+=" "+n+e.join(" "+n),f?u.className.baseVal=t:u.className=t)}function o(){return"function"!=typeof t.createElement?t.createElement(arguments[0]):f?t.createElementNS.call(t,"http://www.w3.org/2000/svg",arguments[0]):t.createElement.apply(t,arguments)}var l=[],r=[],c={_version:"3.3.1",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,t){var n=this;setTimeout(function(){t(n[e])},0)},addTest:function(e,t,n){r.push({name:e,fn:t,options:n})},addAsyncTest:function(e){r.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=c,Modernizr=new Modernizr;var u=t.documentElement,f="svg"===u.nodeName.toLowerCase(),p=o("input"),d="search tel url email datetime date month week time datetime-local number range color".split(" "),m={};Modernizr.inputtypes=function(e){for(var a,s,i,o=e.length,l="1)",r=0;o>r;r++)p.setAttribute("type",a=e[r]),i="text"!==p.type&&"style"in p,i&&(p.value=l,p.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(a)&&p.style.WebkitAppearance!==n?(u.appendChild(p),s=t.defaultView,i=s.getComputedStyle&&"textfield"!==s.getComputedStyle(p,null).WebkitAppearance&&0!==p.offsetHeight,u.removeChild(p)):/^(search|tel)$/.test(a)||(i=/^(url|email)$/.test(a)?p.checkValidity&&p.checkValidity()===!1:p.value!=l)),m[e[r]]=!!i;return m}(d),s(),i(l),delete c.addTest,delete c.addAsyncTest;for(var h=0;h<Modernizr._q.length;h++)Modernizr._q[h]();e.Modernizr=Modernizr}(window,document);
|
||||
!function(e,t,n){function a(e,t){return typeof e===t}function s(){var e,t,n,s,i,o,c;for(var u in r)if(r.hasOwnProperty(u)){if(e=[],t=r[u],t.name&&(e.push(t.name.toLowerCase()),t.options&&t.options.aliases&&t.options.aliases.length))for(n=0;n<t.options.aliases.length;n++)e.push(t.options.aliases[n].toLowerCase());for(s=a(t.fn,"function")?t.fn():t.fn,i=0;i<e.length;i++)o=e[i],c=o.split("."),1===c.length?Modernizr[c[0]]=s:(!Modernizr[c[0]]||Modernizr[c[0]]instanceof Boolean||(Modernizr[c[0]]=new Boolean(Modernizr[c[0]])),Modernizr[c[0]][c[1]]=s),l.push((s?"":"no-")+c.join("-"))}}function i(e){var t=u.className,n=Modernizr._config.classPrefix||"";if(f&&(t=t.baseVal),Modernizr._config.enableJSClass){var a=new RegExp("(^|\\s)"+n+"no-js(\\s|$)");t=t.replace(a,"$1"+n+"js$2")}Modernizr._config.enableClasses&&(t+=" "+n+e.join(" "+n),f?u.className.baseVal=t:u.className=t)}function o(){return"function"!=typeof t.createElement?t.createElement(arguments[0]):f?t.createElementNS.call(t,"http://www.w3.org/2000/svg",arguments[0]):t.createElement.apply(t,arguments)}var l=[],r=[],c={_version:"3.5.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,t){var n=this;setTimeout(function(){t(n[e])},0)},addTest:function(e,t,n){r.push({name:e,fn:t,options:n})},addAsyncTest:function(e){r.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=c,Modernizr=new Modernizr;var u=t.documentElement,f="svg"===u.nodeName.toLowerCase(),p=o("input"),d="search tel url email datetime date month week time datetime-local number range color".split(" "),m={};Modernizr.inputtypes=function(e){for(var a,s,i,o=e.length,l="1)",r=0;o>r;r++)p.setAttribute("type",a=e[r]),i="text"!==p.type&&"style"in p,i&&(p.value=l,p.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(a)&&p.style.WebkitAppearance!==n?(u.appendChild(p),s=t.defaultView,i=s.getComputedStyle&&"textfield"!==s.getComputedStyle(p,null).WebkitAppearance&&0!==p.offsetHeight,u.removeChild(p)):/^(search|tel)$/.test(a)||(i=/^(url|email)$/.test(a)?p.checkValidity&&p.checkValidity()===!1:p.value!=l)),m[e[r]]=!!i;return m}(d),s(),i(l),delete c.addTest,delete c.addAsyncTest;for(var h=0;h<Modernizr._q.length;h++)Modernizr._q[h]();e.Modernizr=Modernizr}(window,document);
|
549
public/js/lib/moment.min.js
vendored
549
public/js/lib/moment.min.js
vendored
File diff suppressed because one or more lines are too long
103
public/lib/intro/intro.min.js
vendored
103
public/lib/intro/intro.min.js
vendored
@ -1,49 +1,54 @@
|
||||
(function(C,n){"object"===typeof exports?n(exports):"function"===typeof define&&define.amd?define(["exports"],n):n(C)})(this,function(C){function n(a){this._targetElement=a;this._introItems=[];this._options={nextLabel:"Next →",prevLabel:"← Back",skipLabel:"Skip",doneLabel:"Done",hidePrev:!1,hideNext:!1,tooltipPosition:"bottom",tooltipClass:"",highlightClass:"",exitOnEsc:!0,exitOnOverlayClick:!0,showStepNumbers:!0,keyboardNavigation:!0,showButtons:!0,showBullets:!0,showProgress:!1,scrollToElement:!0,
|
||||
overlayOpacity:0.8,scrollPadding:30,positionPrecedence:["bottom","top","right","left"],disableInteraction:!1,hintPosition:"top-middle",hintButtonLabel:"Got it",hintAnimation:!0}}function V(a){var b=[],c=this;if(this._options.steps)for(var d=0,e=this._options.steps.length;d<e;d++){var f=y(this._options.steps[d]);f.step=b.length+1;"string"===typeof f.element&&(f.element=document.querySelector(f.element));if("undefined"===typeof f.element||null==f.element){var g=document.querySelector(".introjsFloatingElement");
|
||||
null==g&&(g=document.createElement("div"),g.className="introjsFloatingElement",document.body.appendChild(g));f.element=g;f.position="floating"}null!=f.element&&b.push(f)}else{e=a.querySelectorAll("*[data-intro]");if(1>e.length)return!1;d=0;for(f=e.length;d<f;d++)if(g=e[d],"none"!=g.style.display){var k=parseInt(g.getAttribute("data-step"),10);0<k&&(b[k-1]={element:g,intro:g.getAttribute("data-intro"),step:parseInt(g.getAttribute("data-step"),10),tooltipClass:g.getAttribute("data-tooltipClass"),highlightClass:g.getAttribute("data-highlightClass"),
|
||||
position:g.getAttribute("data-position")||this._options.tooltipPosition})}d=k=0;for(f=e.length;d<f;d++)if(g=e[d],null==g.getAttribute("data-step")){for(;"undefined"!=typeof b[k];)k++;b[k]={element:g,intro:g.getAttribute("data-intro"),step:k+1,tooltipClass:g.getAttribute("data-tooltipClass"),highlightClass:g.getAttribute("data-highlightClass"),position:g.getAttribute("data-position")||this._options.tooltipPosition}}}d=[];for(e=0;e<b.length;e++)b[e]&&d.push(b[e]);b=d;b.sort(function(a,b){return a.step-
|
||||
b.step});c._introItems=b;W.call(c,a)&&(x.call(c),a.querySelector(".introjs-skipbutton"),a.querySelector(".introjs-nextbutton"),c._onKeyDown=function(b){if(27===b.keyCode&&!0==c._options.exitOnEsc)z.call(c,a);else if(37===b.keyCode)E.call(c);else if(39===b.keyCode)x.call(c);else if(13===b.keyCode){var d=b.target||b.srcElement;d&&0<d.className.indexOf("introjs-prevbutton")?E.call(c):d&&0<d.className.indexOf("introjs-skipbutton")?(c._introItems.length-1==c._currentStep&&"function"===typeof c._introCompleteCallback&&
|
||||
c._introCompleteCallback.call(c),z.call(c,a)):x.call(c);b.preventDefault?b.preventDefault():b.returnValue=!1}},c._onResize=function(a){t.call(c,document.querySelector(".introjs-helperLayer"));t.call(c,document.querySelector(".introjs-tooltipReferenceLayer"))},window.addEventListener?(this._options.keyboardNavigation&&window.addEventListener("keydown",c._onKeyDown,!0),window.addEventListener("resize",c._onResize,!0)):document.attachEvent&&(this._options.keyboardNavigation&&document.attachEvent("onkeydown",
|
||||
c._onKeyDown),document.attachEvent("onresize",c._onResize)));return!1}function y(a){if(null==a||"object"!=typeof a||"undefined"!=typeof a.nodeType)return a;var b={},c;for(c in a)b[c]="undefined"!=typeof jQuery&&a[c]instanceof jQuery?a[c]:y(a[c]);return b}function x(){this._direction="forward";if("undefined"!==typeof this._currentStepNumber)for(var a=0,b=this._introItems.length;a<b;a++)this._introItems[a].step===this._currentStepNumber&&(this._currentStep=a-1,this._currentStepNumber=void 0);"undefined"===
|
||||
typeof this._currentStep?this._currentStep=0:++this._currentStep;this._introItems.length<=this._currentStep?("function"===typeof this._introCompleteCallback&&this._introCompleteCallback.call(this),z.call(this,this._targetElement)):(a=this._introItems[this._currentStep],"undefined"!==typeof this._introBeforeChangeCallback&&this._introBeforeChangeCallback.call(this,a.element),N.call(this,a))}function E(){this._direction="backward";if(0===this._currentStep)return!1;var a=this._introItems[--this._currentStep];
|
||||
"undefined"!==typeof this._introBeforeChangeCallback&&this._introBeforeChangeCallback.call(this,a.element);N.call(this,a)}function z(a){var b=a.querySelectorAll(".introjs-overlay");if(b&&0<b.length)for(var c=b.length-1;0<=c;c--){var d=b[c];d.style.opacity=0;setTimeout(function(){this.parentNode&&this.parentNode.removeChild(this)}.bind(d),500)}(c=a.querySelector(".introjs-helperLayer"))&&c.parentNode.removeChild(c);(c=a.querySelector(".introjs-tooltipReferenceLayer"))&&c.parentNode.removeChild(c);
|
||||
(a=a.querySelector(".introjs-disableInteraction"))&&a.parentNode.removeChild(a);(a=document.querySelector(".introjsFloatingElement"))&&a.parentNode.removeChild(a);O();if((a=document.querySelectorAll(".introjs-fixParent"))&&0<a.length)for(c=a.length-1;0<=c;c--)a[c].className=a[c].className.replace(/introjs-fixParent/g,"").replace(/^\s+|\s+$/g,"");window.removeEventListener?window.removeEventListener("keydown",this._onKeyDown,!0):document.detachEvent&&document.detachEvent("onkeydown",this._onKeyDown);
|
||||
void 0!=this._introExitCallback&&this._introExitCallback.call(self);this._currentStep=void 0}function F(a,b,c,d,e){var f="",g,k;e=e||!1;b.style.top=null;b.style.right=null;b.style.bottom=null;b.style.left=null;b.style.marginLeft=null;b.style.marginTop=null;c.style.display="inherit";"undefined"!=typeof d&&null!=d&&(d.style.top=null,d.style.left=null);if(this._introItems[this._currentStep]){f=this._introItems[this._currentStep];f="string"===typeof f.tooltipClass?f.tooltipClass:this._options.tooltipClass;
|
||||
b.className=("introjs-tooltip "+f).replace(/^\s+|\s+$/g,"");k=this._introItems[this._currentStep].position;if(("auto"==k||"auto"==this._options.tooltipPosition)&&"floating"!=k){f=k;g=this._options.positionPrecedence.slice();k=G();var w=u(b).height+10,m=u(b).width+20,h=u(a),l="floating";h.left+m>k.width||0>h.left+h.width/2-m?(s(g,"bottom"),s(g,"top")):(h.height+h.top+w>k.height&&s(g,"bottom"),0>h.top-w&&s(g,"top"));h.width+h.left+m>k.width&&s(g,"right");0>h.left-m&&s(g,"left");0<g.length&&(l=g[0]);
|
||||
f&&"auto"!=f&&-1<g.indexOf(f)&&(l=f);k=l}f=u(a);a=u(b);g=G();switch(k){case "top":c.className="introjs-arrow bottom";H(f,e?0:15,a,g,b);b.style.bottom=f.height+20+"px";break;case "right":b.style.left=f.width+20+"px";f.top+a.height>g.height?(c.className="introjs-arrow left-bottom",b.style.top="-"+(a.height-f.height-20)+"px"):c.className="introjs-arrow left";break;case "left":e||!0!=this._options.showStepNumbers||(b.style.top="15px");f.top+a.height>g.height?(b.style.top="-"+(a.height-f.height-20)+"px",
|
||||
c.className="introjs-arrow right-bottom"):c.className="introjs-arrow right";b.style.right=f.width+20+"px";break;case "floating":c.style.display="none";b.style.left="50%";b.style.top="50%";b.style.marginLeft="-"+a.width/2+"px";b.style.marginTop="-"+a.height/2+"px";"undefined"!=typeof d&&null!=d&&(d.style.left="-"+(a.width/2+18)+"px",d.style.top="-"+(a.height/2+18)+"px");break;case "bottom-right-aligned":c.className="introjs-arrow top-right";P(f,0,a,b);b.style.top=f.height+20+"px";break;case "bottom-middle-aligned":c.className=
|
||||
"introjs-arrow top-middle";c=f.width/2-a.width/2;e&&(c+=5);P(f,c,a,b)&&(b.style.right=null,H(f,c,a,g,b));b.style.top=f.height+20+"px";break;default:c.className="introjs-arrow top",H(f,0,a,g,b),b.style.top=f.height+20+"px"}}}function H(a,b,c,d,e){if(a.left+b+c.width>d.width)return e.style.left=d.width-c.width-a.left+"px",!1;e.style.left=b+"px";return!0}function P(a,b,c,d){if(0>a.left+a.width-b-c.width)return d.style.left=-a.left+"px",!1;d.style.right=b+"px";return!0}function s(a,b){-1<a.indexOf(b)&&
|
||||
a.splice(a.indexOf(b),1)}function t(a){if(a&&this._introItems[this._currentStep]){var b=this._introItems[this._currentStep],c=u(b.element),d=10;I(b.element)?a.className+=" introjs-fixedTooltip":a.className=a.className.replace(" introjs-fixedTooltip","");"floating"==b.position&&(d=0);a.setAttribute("style","width: "+(c.width+d)+"px; height:"+(c.height+d)+"px; top:"+(c.top-5)+"px;left: "+(c.left-5)+"px;")}}function X(){var a=document.querySelector(".introjs-disableInteraction");null===a&&(a=document.createElement("div"),
|
||||
a.className="introjs-disableInteraction",this._targetElement.appendChild(a));t.call(this,a)}function D(a){a.setAttribute("role","button");a.tabIndex=0}function N(a){"undefined"!==typeof this._introChangeCallback&&this._introChangeCallback.call(this,a.element);var b=this,c=document.querySelector(".introjs-helperLayer"),d=document.querySelector(".introjs-tooltipReferenceLayer"),e="introjs-helperLayer";u(a.element);"string"===typeof a.highlightClass&&(e+=" "+a.highlightClass);"string"===typeof this._options.highlightClass&&
|
||||
(e+=" "+this._options.highlightClass);if(null!=c){var f=d.querySelector(".introjs-helperNumberLayer"),g=d.querySelector(".introjs-tooltiptext"),k=d.querySelector(".introjs-arrow"),w=d.querySelector(".introjs-tooltip"),m=d.querySelector(".introjs-skipbutton"),h=d.querySelector(".introjs-prevbutton"),l=d.querySelector(".introjs-nextbutton");c.className=e;w.style.opacity=0;w.style.display="none";if(null!=f){var p=this._introItems[0<=a.step-2?a.step-2:0];if(null!=p&&"forward"==this._direction&&"floating"==
|
||||
p.position||"backward"==this._direction&&"floating"==a.position)f.style.opacity=0}t.call(b,c);t.call(b,d);if((p=document.querySelectorAll(".introjs-fixParent"))&&0<p.length)for(e=p.length-1;0<=e;e--)p[e].className=p[e].className.replace(/introjs-fixParent/g,"").replace(/^\s+|\s+$/g,"");O();b._lastShowElementTimer&&clearTimeout(b._lastShowElementTimer);b._lastShowElementTimer=setTimeout(function(){null!=f&&(f.innerHTML=a.step);g.innerHTML=a.intro;w.style.display="block";F.call(b,a.element,w,k,f);b._options.showBullets&&
|
||||
(d.querySelector(".introjs-bullets li > a.active").className="",d.querySelector('.introjs-bullets li > a[data-stepnumber="'+a.step+'"]').className="active");d.querySelector(".introjs-progress .introjs-progressbar").setAttribute("style","width:"+Q.call(b)+"%;");w.style.opacity=1;f&&(f.style.opacity=1);-1===l.tabIndex?m.focus():l.focus()},350)}else{var n=document.createElement("div"),h=document.createElement("div"),c=document.createElement("div"),q=document.createElement("div"),r=document.createElement("div"),
|
||||
s=document.createElement("div"),v=document.createElement("div"),A=document.createElement("div");n.className=e;h.className="introjs-tooltipReferenceLayer";t.call(b,n);t.call(b,h);this._targetElement.appendChild(n);this._targetElement.appendChild(h);c.className="introjs-arrow";r.className="introjs-tooltiptext";r.innerHTML=a.intro;s.className="introjs-bullets";!1===this._options.showBullets&&(s.style.display="none");for(var n=document.createElement("ul"),e=0,C=this._introItems.length;e<C;e++){var y=
|
||||
document.createElement("li"),B=document.createElement("a");B.onclick=function(){b.goToStep(this.getAttribute("data-stepnumber"))};e===a.step-1&&(B.className="active");D(B);B.innerHTML=" ";B.setAttribute("data-stepnumber",this._introItems[e].step);y.appendChild(B);n.appendChild(y)}s.appendChild(n);v.className="introjs-progress";!1===this._options.showProgress&&(v.style.display="none");e=document.createElement("div");e.className="introjs-progressbar";e.setAttribute("style","width:"+Q.call(this)+
|
||||
"%;");v.appendChild(e);A.className="introjs-tooltipbuttons";!1===this._options.showButtons&&(A.style.display="none");q.className="introjs-tooltip";q.appendChild(r);q.appendChild(s);q.appendChild(v);!0==this._options.showStepNumbers&&(p=document.createElement("span"),p.className="introjs-helperNumberLayer",p.innerHTML=a.step,h.appendChild(p));q.appendChild(c);h.appendChild(q);l=document.createElement("a");l.onclick=function(){b._introItems.length-1!=b._currentStep&&x.call(b)};D(l);l.innerHTML=this._options.nextLabel;
|
||||
h=document.createElement("a");h.onclick=function(){0!=b._currentStep&&E.call(b)};D(h);h.innerHTML=this._options.prevLabel;m=document.createElement("a");m.className="introjs-button introjs-skipbutton";D(m);m.innerHTML=this._options.skipLabel;m.onclick=function(){b._introItems.length-1==b._currentStep&&"function"===typeof b._introCompleteCallback&&b._introCompleteCallback.call(b);z.call(b,b._targetElement)};A.appendChild(m);1<this._introItems.length&&(A.appendChild(h),A.appendChild(l));q.appendChild(A);
|
||||
F.call(b,a.element,q,c,p)}!0===this._options.disableInteraction&&X.call(b);h.removeAttribute("tabIndex");l.removeAttribute("tabIndex");0==this._currentStep&&1<this._introItems.length?(m.className="introjs-button introjs-skipbutton",l.className="introjs-button introjs-nextbutton",!0==this._options.hidePrev?(h.className="introjs-button introjs-prevbutton introjs-hidden",l.className+=" introjs-fullbutton"):h.className="introjs-button introjs-prevbutton introjs-disabled",h.tabIndex="-1",m.innerHTML=this._options.skipLabel):
|
||||
this._introItems.length-1==this._currentStep||1==this._introItems.length?(m.innerHTML=this._options.doneLabel,m.className+=" introjs-donebutton",h.className="introjs-button introjs-prevbutton",!0==this._options.hideNext?(l.className="introjs-button introjs-nextbutton introjs-hidden",h.className+=" introjs-fullbutton"):l.className="introjs-button introjs-nextbutton introjs-disabled",l.tabIndex="-1"):(m.className="introjs-button introjs-skipbutton",h.className="introjs-button introjs-prevbutton",l.className=
|
||||
"introjs-button introjs-nextbutton",m.innerHTML=this._options.skipLabel);l.focus();Y(a);Z(a.element)||!0!==this._options.scrollToElement||(q=a.element.getBoundingClientRect(),p=G().height,c=q.bottom-(q.bottom-q.top),q=q.bottom-p,0>c||a.element.clientHeight>p?window.scrollBy(0,c-this._options.scrollPadding):window.scrollBy(0,q+70+this._options.scrollPadding));"undefined"!==typeof this._introAfterChangeCallback&&this._introAfterChangeCallback.call(this,a.element)}function O(){for(var a=document.querySelectorAll(".introjs-showElement"),
|
||||
b=0,c=a.length;b<c;b++){var d=a[b],e=/introjs-[a-zA-Z]+/g;if(d instanceof SVGElement){var f=d.getAttribute("class")||"";d.setAttribute("class",f.replace(e,"").replace(/^\s+|\s+$/g,""))}else d.className=d.className.replace(e,"").replace(/^\s+|\s+$/g,"")}}function Y(a){if(a.element instanceof SVGElement)for(var b=a.element.parentNode;null!=a.element.parentNode&&b.tagName&&"body"!==b.tagName.toLowerCase();)"svg"===b.tagName.toLowerCase()&&J(b,"introjs-showElement introjs-relativePosition"),b=b.parentNode;
|
||||
J(a.element,"introjs-showElement");b=r(a.element,"position");"absolute"!==b&&("relative"!==b&&"fixed"!==b)&&J(a.element,"introjs-relativePosition");for(b=a.element.parentNode;null!=b&&b.tagName&&"body"!==b.tagName.toLowerCase();){a=r(b,"z-index");var c=parseFloat(r(b,"opacity")),d=r(b,"transform")||r(b,"-webkit-transform")||r(b,"-moz-transform")||r(b,"-ms-transform")||r(b,"-o-transform");if(/[0-9]+/.test(a)||1>c||"none"!==d&&void 0!==d)b.className+=" introjs-fixParent";b=b.parentNode}}function J(a,
|
||||
b){if(a instanceof SVGElement){var c=a.getAttribute("class")||"";a.setAttribute("class",c+" "+b)}else a.className+=" "+b}function r(a,b){var c="";a.currentStyle?c=a.currentStyle[b]:document.defaultView&&document.defaultView.getComputedStyle&&(c=document.defaultView.getComputedStyle(a,null).getPropertyValue(b));return c&&c.toLowerCase?c.toLowerCase():c}function I(a){var b=a.parentNode;return b&&"HTML"!==b.nodeName?"fixed"==r(a,"position")?!0:I(b):!1}function G(){if(void 0!=window.innerWidth)return{width:window.innerWidth,
|
||||
height:window.innerHeight};var a=document.documentElement;return{width:a.clientWidth,height:a.clientHeight}}function Z(a){a=a.getBoundingClientRect();return 0<=a.top&&0<=a.left&&a.bottom+80<=window.innerHeight&&a.right<=window.innerWidth}function W(a){var b=document.createElement("div"),c="",d=this;b.className="introjs-overlay";if(a.tagName&&"body"!==a.tagName.toLowerCase()){var e=u(a);e&&(c+="width: "+e.width+"px; height:"+e.height+"px; top:"+e.top+"px;left: "+e.left+"px;",b.setAttribute("style",
|
||||
c))}else c+="top: 0;bottom: 0; left: 0;right: 0;position: fixed;",b.setAttribute("style",c);a.appendChild(b);b.onclick=function(){!0==d._options.exitOnOverlayClick&&z.call(d,a)};setTimeout(function(){c+="opacity: "+d._options.overlayOpacity.toString()+";";b.setAttribute("style",c)},10);return!0}function v(){var a=this._targetElement.querySelector(".introjs-hintReference");if(a){var b=a.getAttribute("data-step");a.parentNode.removeChild(a);return b}}function R(a){this._introItems=[];if(this._options.hints){a=
|
||||
0;for(var b=this._options.hints.length;a<b;a++){var c=y(this._options.hints[a]);"string"===typeof c.element&&(c.element=document.querySelector(c.element));c.hintPosition=c.hintPosition||this._options.hintPosition;c.hintAnimation=c.hintAnimation||this._options.hintAnimation;null!=c.element&&this._introItems.push(c)}}else{c=a.querySelectorAll("*[data-hint]");if(1>c.length)return!1;a=0;for(b=c.length;a<b;a++){var d=c[a],e=d.getAttribute("data-hintAnimation"),e=e?"true"==e:this._options.hintAnimation;
|
||||
this._introItems.push({element:d,hint:d.getAttribute("data-hint"),hintPosition:d.getAttribute("data-hintPosition")||this._options.hintPosition,hintAnimation:e,tooltipClass:d.getAttribute("data-tooltipClass"),position:d.getAttribute("data-position")||this._options.tooltipPosition})}}$.call(this);document.addEventListener?(document.addEventListener("click",v.bind(this),!1),window.addEventListener("resize",K.bind(this),!0)):document.attachEvent&&(document.attachEvent("onclick",v.bind(this)),document.attachEvent("onresize",
|
||||
K.bind(this)))}function K(){for(var a=0,b=this._introItems.length;a<b;a++){var c=this._introItems[a];"undefined"!=typeof c.targetElement&&S.call(this,c.hintPosition,c.element,c.targetElement)}}function L(a){v.call(this);var b=this._targetElement.querySelector('.introjs-hint[data-step="'+a+'"]');b&&(b.className+=" introjs-hidehint");"undefined"!==typeof this._hintCloseCallback&&this._hintCloseCallback.call(this,a)}function T(a){if(a=this._targetElement.querySelector('.introjs-hint[data-step="'+a+'"]'))a.className=
|
||||
a.className.replace(/introjs\-hidehint/g,"")}function U(a){(a=this._targetElement.querySelector('.introjs-hint[data-step="'+a+'"]'))&&a.parentNode.removeChild(a)}function $(){var a=this,b=document.querySelector(".introjs-hints");null==b&&(b=document.createElement("div"),b.className="introjs-hints");for(var c=0,d=this._introItems.length;c<d;c++){var e=this._introItems[c];if(!document.querySelector('.introjs-hint[data-step="'+c+'"]')){var f=document.createElement("a");D(f);(function(b,c,d){b.onclick=
|
||||
function(e){e=e?e:window.event;e.stopPropagation&&e.stopPropagation();null!=e.cancelBubble&&(e.cancelBubble=!0);aa.call(a,b,c,d)}})(f,e,c);f.className="introjs-hint";e.hintAnimation||(f.className+=" introjs-hint-no-anim");I(e.element)&&(f.className+=" introjs-fixedhint");var g=document.createElement("div");g.className="introjs-hint-dot";var k=document.createElement("div");k.className="introjs-hint-pulse";f.appendChild(g);f.appendChild(k);f.setAttribute("data-step",c);e.targetElement=e.element;e.element=
|
||||
f;S.call(this,e.hintPosition,f,e.targetElement);b.appendChild(f)}}document.body.appendChild(b);"undefined"!==typeof this._hintsAddedCallback&&this._hintsAddedCallback.call(this)}function S(a,b,c){c=u.call(this,c);switch(a){default:case "top-left":b.style.left=c.left+"px";b.style.top=c.top+"px";break;case "top-right":b.style.left=c.left+c.width-20+"px";b.style.top=c.top+"px";break;case "bottom-left":b.style.left=c.left+"px";b.style.top=c.top+c.height-20+"px";break;case "bottom-right":b.style.left=
|
||||
c.left+c.width-20+"px";b.style.top=c.top+c.height-20+"px";break;case "middle-left":b.style.left=c.left+"px";b.style.top=c.top+(c.height-20)/2+"px";break;case "middle-right":b.style.left=c.left+c.width-20+"px";b.style.top=c.top+(c.height-20)/2+"px";break;case "middle-middle":b.style.left=c.left+(c.width-20)/2+"px";b.style.top=c.top+(c.height-20)/2+"px";break;case "bottom-middle":b.style.left=c.left+(c.width-20)/2+"px";b.style.top=c.top+c.height-20+"px";break;case "top-middle":b.style.left=c.left+(c.width-
|
||||
20)/2+"px",b.style.top=c.top+"px"}}function aa(a,b,c){"undefined"!==typeof this._hintClickCallback&&this._hintClickCallback.call(this,a,b,c);var d=v.call(this);if(parseInt(d,10)!=c){var d=document.createElement("div"),e=document.createElement("div"),f=document.createElement("div"),g=document.createElement("div");d.className="introjs-tooltip";d.onclick=function(a){a.stopPropagation?a.stopPropagation():a.cancelBubble=!0};e.className="introjs-tooltiptext";var k=document.createElement("p");k.innerHTML=
|
||||
b.hint;b=document.createElement("a");b.className="introjs-button";b.innerHTML=this._options.hintButtonLabel;b.onclick=L.bind(this,c);e.appendChild(k);e.appendChild(b);f.className="introjs-arrow";d.appendChild(f);d.appendChild(e);this._currentStep=a.getAttribute("data-step");g.className="introjs-tooltipReferenceLayer introjs-hintReference";g.setAttribute("data-step",a.getAttribute("data-step"));t.call(this,g);g.appendChild(d);document.body.appendChild(g);F.call(this,a,d,f,null,!0)}}function u(a){var b=
|
||||
{},c=document.body,d=document.documentElement,e=window.pageYOffset||d.scrollTop||c.scrollTop,c=window.pageXOffset||d.scrollLeft||c.scrollLeft;if(a instanceof SVGElement)a=a.getBoundingClientRect(),b.top=a.top+e,b.width=a.width,b.height=a.height,b.left=a.left+c;else{b.width=a.offsetWidth;b.height=a.offsetHeight;for(c=e=0;a&&!isNaN(a.offsetLeft)&&!isNaN(a.offsetTop);)e+=a.offsetLeft,c+=a.offsetTop,a=a.offsetParent;b.top=c;b.left=e}return b}function Q(){return 100*(parseInt(this._currentStep+1,10)/this._introItems.length)}
|
||||
var M=function(a){if("object"===typeof a)return new n(a);if("string"===typeof a){if(a=document.querySelector(a))return new n(a);throw Error("There is no element with given selector.");}return new n(document.body)};M.version="2.5.0";M.fn=n.prototype={clone:function(){return new n(this)},setOption:function(a,b){this._options[a]=b;return this},setOptions:function(a){var b=this._options,c={},d;for(d in b)c[d]=b[d];for(d in a)c[d]=a[d];this._options=c;return this},start:function(){V.call(this,this._targetElement);
|
||||
return this},goToStep:function(a){this._currentStep=a-2;"undefined"!==typeof this._introItems&&x.call(this);return this},addStep:function(a){this._options.steps||(this._options.steps=[]);this._options.steps.push(a);return this},addSteps:function(a){if(a.length){for(var b=0;b<a.length;b++)this.addStep(a[b]);return this}},goToStepNumber:function(a){this._currentStepNumber=a;"undefined"!==typeof this._introItems&&x.call(this);return this},nextStep:function(){x.call(this);return this},previousStep:function(){E.call(this);
|
||||
return this},exit:function(){z.call(this,this._targetElement);return this},refresh:function(){t.call(this,document.querySelector(".introjs-helperLayer"));t.call(this,document.querySelector(".introjs-tooltipReferenceLayer"));K.call(this);return this},onbeforechange:function(a){if("function"===typeof a)this._introBeforeChangeCallback=a;else throw Error("Provided callback for onbeforechange was not a function");return this},onchange:function(a){if("function"===typeof a)this._introChangeCallback=a;else throw Error("Provided callback for onchange was not a function.");
|
||||
return this},onafterchange:function(a){if("function"===typeof a)this._introAfterChangeCallback=a;else throw Error("Provided callback for onafterchange was not a function");return this},oncomplete:function(a){if("function"===typeof a)this._introCompleteCallback=a;else throw Error("Provided callback for oncomplete was not a function.");return this},onhintsadded:function(a){if("function"===typeof a)this._hintsAddedCallback=a;else throw Error("Provided callback for onhintsadded was not a function.");
|
||||
return this},onhintclick:function(a){if("function"===typeof a)this._hintClickCallback=a;else throw Error("Provided callback for onhintclick was not a function.");return this},onhintclose:function(a){if("function"===typeof a)this._hintCloseCallback=a;else throw Error("Provided callback for onhintclose was not a function.");return this},onexit:function(a){if("function"===typeof a)this._introExitCallback=a;else throw Error("Provided callback for onexit was not a function.");return this},addHints:function(){R.call(this,
|
||||
this._targetElement);return this},hideHint:function(a){L.call(this,a);return this},hideHints:function(){var a=this._targetElement.querySelectorAll(".introjs-hint");if(a&&0<a.length)for(var b=0;b<a.length;b++)L.call(this,a[b].getAttribute("data-step"));return this},showHint:function(a){T.call(this,a);return this},showHints:function(){var a=this._targetElement.querySelectorAll(".introjs-hint");if(a&&0<a.length)for(var b=0;b<a.length;b++)T.call(this,a[b].getAttribute("data-step"));else R.call(this,this._targetElement);
|
||||
return this},removeHints:function(){var a=this._targetElement.querySelectorAll(".introjs-hint");if(a&&0<a.length)for(var b=0;b<a.length;b++)U.call(this,a[b].getAttribute("data-step"));return this},removeHint:function(a){U.call(this,a);return this}};return C.introJs=M});
|
||||
(function(C,n){"object"===typeof exports?n(exports):"function"===typeof define&&define.amd?define(["exports"],n):n(C)})(this,function(C){function n(a){this._targetElement=a;this._introItems=[];this._options={nextLabel:"Next \x26rarr;",prevLabel:"\x26larr; Back",skipLabel:"Skip",doneLabel:"Done",hidePrev:!1,hideNext:!1,tooltipPosition:"bottom",tooltipClass:"",highlightClass:"",exitOnEsc:!0,exitOnOverlayClick:!0,showStepNumbers:!0,keyboardNavigation:!0,showButtons:!0,showBullets:!0,showProgress:!1,
|
||||
scrollToElement:!0,scrollTo:"element",scrollPadding:30,overlayOpacity:.8,positionPrecedence:["bottom","top","right","left"],disableInteraction:!1,hintPosition:"top-middle",hintButtonLabel:"Got it",hintAnimation:!0}}function Z(a){var b,c=[],d=this;if(this._options.steps){var f=0;for(b=this._options.steps.length;f<b;f++){var e=y(this._options.steps[f]);e.step=c.length+1;"string"===typeof e.element&&(e.element=document.querySelector(e.element));if("undefined"===typeof e.element||null==e.element){var g=
|
||||
document.querySelector(".introjsFloatingElement");null==g&&(g=document.createElement("div"),g.className="introjsFloatingElement",document.body.appendChild(g));e.element=g;e.position="floating"}e.scrollTo=e.scrollTo||this._options.scrollTo;"undefined"===typeof e.disableInteraction&&(e.disableInteraction=this._options.disableInteraction);null!=e.element&&c.push(e)}}else{g=a.querySelectorAll("*[data-intro]");if(1>g.length)return!1;for(var f=0,r=g.length;f<r;f++)if(e=g[f],"none"!=e.style.display){var q=
|
||||
parseInt(e.getAttribute("data-step"),10);b=this._options.disableInteraction;"undefined"!=typeof e.getAttribute("data-disable-interaction")&&(b=!!e.getAttribute("data-disable-interaction"));0<q&&(c[q-1]={element:e,intro:e.getAttribute("data-intro"),step:parseInt(e.getAttribute("data-step"),10),tooltipClass:e.getAttribute("data-tooltipClass"),highlightClass:e.getAttribute("data-highlightClass"),position:e.getAttribute("data-position")||this._options.tooltipPosition,scrollTo:e.getAttribute("data-scrollTo")||
|
||||
this._options.scrollTo,disableInteraction:b})}f=q=0;for(r=g.length;f<r;f++)if(e=g[f],null==e.getAttribute("data-step")){for(;"undefined"!=typeof c[q];)q++;b=this._options.disableInteraction;"undefined"!=typeof e.getAttribute("data-disable-interaction")&&(b=!!e.getAttribute("data-disable-interaction"));c[q]={element:e,intro:e.getAttribute("data-intro"),step:q+1,tooltipClass:e.getAttribute("data-tooltipClass"),highlightClass:e.getAttribute("data-highlightClass"),position:e.getAttribute("data-position")||
|
||||
this._options.tooltipPosition,scrollTo:e.getAttribute("data-scrollTo")||this._options.scrollTo,disableInteraction:b}}}f=[];for(b=0;b<c.length;b++)c[b]&&f.push(c[b]);c=f;c.sort(function(a,b){return a.step-b.step});d._introItems=c;aa.call(d,a)&&(x.call(d),a.querySelector(".introjs-skipbutton"),a.querySelector(".introjs-nextbutton"),d._onKeyDown=function(b){if(27===b.keyCode&&1==d._options.exitOnEsc)z.call(d,a);else if(37===b.keyCode)E.call(d);else if(39===b.keyCode)x.call(d);else if(13===b.keyCode){var c=
|
||||
b.target||b.srcElement;c&&0<c.className.indexOf("introjs-prevbutton")?E.call(d):c&&0<c.className.indexOf("introjs-skipbutton")?(d._introItems.length-1==d._currentStep&&"function"===typeof d._introCompleteCallback&&d._introCompleteCallback.call(d),z.call(d,a)):x.call(d);b.preventDefault?b.preventDefault():b.returnValue=!1}},d._onResize=function(a){d.refresh.call(d)},window.addEventListener?(this._options.keyboardNavigation&&window.addEventListener("keydown",d._onKeyDown,!0),window.addEventListener("resize",
|
||||
d._onResize,!0)):document.attachEvent&&(this._options.keyboardNavigation&&document.attachEvent("onkeydown",d._onKeyDown),document.attachEvent("onresize",d._onResize)));return!1}function y(a){if(null==a||"object"!=typeof a||"undefined"!=typeof a.nodeType)return a;var b={},c;for(c in a)b[c]="undefined"!=typeof jQuery&&a[c]instanceof jQuery?a[c]:y(a[c]);return b}function x(){this._direction="forward";if("undefined"!==typeof this._currentStepNumber)for(var a=0,b=this._introItems.length;a<b;a++)this._introItems[a].step===
|
||||
this._currentStepNumber&&(this._currentStep=a-1,this._currentStepNumber=void 0);"undefined"===typeof this._currentStep?this._currentStep=0:++this._currentStep;this._introItems.length<=this._currentStep?("function"===typeof this._introCompleteCallback&&this._introCompleteCallback.call(this),z.call(this,this._targetElement)):(a=this._introItems[this._currentStep],"undefined"!==typeof this._introBeforeChangeCallback&&this._introBeforeChangeCallback.call(this,a.element),O.call(this,a))}function E(){this._direction=
|
||||
"backward";if(0===this._currentStep)return!1;var a=this._introItems[--this._currentStep];"undefined"!==typeof this._introBeforeChangeCallback&&this._introBeforeChangeCallback.call(this,a.element);O.call(this,a)}function z(a,b){var c=!0;void 0!=this._introBeforeExitCallback&&(c=this._introBeforeExitCallback.call(self));if(b||!1!==c){if((c=a.querySelectorAll(".introjs-overlay"))&&0<c.length)for(b=c.length-1;0<=b;b--){var d=c[b];d.style.opacity=0;setTimeout(function(){this.parentNode&&this.parentNode.removeChild(this)}.bind(d),
|
||||
500)}(b=a.querySelector(".introjs-helperLayer"))&&b.parentNode.removeChild(b);(b=a.querySelector(".introjs-tooltipReferenceLayer"))&&b.parentNode.removeChild(b);(a=a.querySelector(".introjs-disableInteraction"))&&a.parentNode.removeChild(a);(a=document.querySelector(".introjsFloatingElement"))&&a.parentNode.removeChild(a);P();if((a=document.querySelectorAll(".introjs-fixParent"))&&0<a.length)for(b=a.length-1;0<=b;b--)a[b].className=a[b].className.replace(/introjs-fixParent/g,"").replace(/^\s+|\s+$/g,
|
||||
"");window.removeEventListener?window.removeEventListener("keydown",this._onKeyDown,!0):document.detachEvent&&document.detachEvent("onkeydown",this._onKeyDown);void 0!=this._introExitCallback&&this._introExitCallback.call(self);this._currentStep=void 0}}function F(a,b,c,d,f){f=f||!1;b.style.top=null;b.style.right=null;b.style.bottom=null;b.style.left=null;b.style.marginLeft=null;b.style.marginTop=null;c.style.display="inherit";"undefined"!=typeof d&&null!=d&&(d.style.top=null,d.style.left=null);if(this._introItems[this._currentStep]){var e=
|
||||
this._introItems[this._currentStep];e="string"===typeof e.tooltipClass?e.tooltipClass:this._options.tooltipClass;b.className=("introjs-tooltip "+e).replace(/^\s+|\s+$/g,"");var g=this._introItems[this._currentStep].position;"floating"!=g&&(g="auto"===g?Q.call(this,a,b):Q.call(this,a,b,g));e=u(a);a=u(b);var r=H();switch(g){case "top":c.className="introjs-arrow bottom";I(e,f?0:15,a,r,b);b.style.bottom=e.height+20+"px";break;case "right":b.style.left=e.width+20+"px";e.top+a.height>r.height?(c.className=
|
||||
"introjs-arrow left-bottom",b.style.top="-"+(a.height-e.height-20)+"px"):c.className="introjs-arrow left";break;case "left":f||1!=this._options.showStepNumbers||(b.style.top="15px");e.top+a.height>r.height?(b.style.top="-"+(a.height-e.height-20)+"px",c.className="introjs-arrow right-bottom"):c.className="introjs-arrow right";b.style.right=e.width+20+"px";break;case "floating":c.style.display="none";b.style.left="50%";b.style.top="50%";b.style.marginLeft="-"+a.width/2+"px";b.style.marginTop="-"+a.height/
|
||||
2+"px";"undefined"!=typeof d&&null!=d&&(d.style.left="-"+(a.width/2+18)+"px",d.style.top="-"+(a.height/2+18)+"px");break;case "bottom-right-aligned":c.className="introjs-arrow top-right";R(e,0,a,b);b.style.top=e.height+20+"px";break;case "bottom-middle-aligned":c.className="introjs-arrow top-middle";c=e.width/2-a.width/2;f&&(c+=5);R(e,c,a,b)&&(b.style.right=null,I(e,c,a,r,b));b.style.top=e.height+20+"px";break;default:c.className="introjs-arrow top",I(e,0,a,r,b),b.style.top=e.height+20+"px"}}}function I(a,
|
||||
b,c,d,f){if(a.left+b+c.width>d.width)return f.style.left=d.width-c.width-a.left+"px",!1;f.style.left=b+"px";return!0}function R(a,b,c,d){if(0>a.left+a.width-b-c.width)return d.style.left=-a.left+"px",!1;d.style.right=b+"px";return!0}function Q(a,b,c){var d=this._options.positionPrecedence.slice(),f=H(),e=u(b).height+10;b=u(b).width+20;a=u(a);var g="floating";a.left+b>f.width||0>a.left+a.width/2-b?(t(d,"bottom"),t(d,"top")):(a.height+a.top+e>f.height&&t(d,"bottom"),0>a.top-e&&t(d,"top"));a.width+a.left+
|
||||
b>f.width&&t(d,"right");0>a.left-b&&t(d,"left");0<d.length&&(g=d[0]);c&&"auto"!=c&&-1<d.indexOf(c)&&(g=c);return g}function t(a,b){-1<a.indexOf(b)&&a.splice(a.indexOf(b),1)}function w(a){if(a&&this._introItems[this._currentStep]){var b=this._introItems[this._currentStep],c=u(b.element),d=10;J(b.element)?a.className+=" introjs-fixedTooltip":a.className=a.className.replace(" introjs-fixedTooltip","");"floating"==b.position&&(d=0);a.setAttribute("style","width: "+(c.width+d)+"px; height:"+(c.height+
|
||||
d)+"px; top:"+(c.top-5)+"px;left: "+(c.left-5)+"px;")}}function ba(){var a=document.querySelector(".introjs-disableInteraction");null===a&&(a=document.createElement("div"),a.className="introjs-disableInteraction",this._targetElement.appendChild(a));w.call(this,a)}function D(a){a.setAttribute("role","button");a.tabIndex=0}function O(a){"undefined"!==typeof this._introChangeCallback&&this._introChangeCallback.call(this,a.element);var b=this,c=document.querySelector(".introjs-helperLayer"),d=document.querySelector(".introjs-tooltipReferenceLayer"),
|
||||
f="introjs-helperLayer";u(a.element);"string"===typeof a.highlightClass&&(f+=" "+a.highlightClass);"string"===typeof this._options.highlightClass&&(f+=" "+this._options.highlightClass);if(null!=c){var e=d.querySelector(".introjs-helperNumberLayer"),g=d.querySelector(".introjs-tooltiptext"),r=d.querySelector(".introjs-arrow"),q=d.querySelector(".introjs-tooltip");var l=d.querySelector(".introjs-skipbutton");var h=d.querySelector(".introjs-prevbutton");var k=d.querySelector(".introjs-nextbutton");c.className=
|
||||
f;q.style.opacity=0;q.style.display="none";if(null!=e){var p=this._introItems[0<=a.step-2?a.step-2:0];if(null!=p&&"forward"==this._direction&&"floating"==p.position||"backward"==this._direction&&"floating"==a.position)e.style.opacity=0}w.call(b,c);w.call(b,d);if((p=document.querySelectorAll(".introjs-fixParent"))&&0<p.length)for(f=p.length-1;0<=f;f--)p[f].className=p[f].className.replace(/introjs-fixParent/g,"").replace(/^\s+|\s+$/g,"");P();b._lastShowElementTimer&&clearTimeout(b._lastShowElementTimer);
|
||||
b._lastShowElementTimer=setTimeout(function(){null!=e&&(e.innerHTML=a.step);g.innerHTML=a.intro;q.style.display="block";F.call(b,a.element,q,r,e);b._options.showBullets&&(d.querySelector(".introjs-bullets li \x3e a.active").className="",d.querySelector('.introjs-bullets li \x3e a[data-stepnumber\x3d"'+a.step+'"]').className="active");d.querySelector(".introjs-progress .introjs-progressbar").setAttribute("style","width:"+S.call(b)+"%;");q.style.opacity=1;e&&(e.style.opacity=1);"undefined"!==typeof l&&
|
||||
null!=l&&/introjs-donebutton/gi.test(l.className)?l.focus():"undefined"!==typeof k&&null!=k&&k.focus();T.call(b,a.scrollTo,a,g)},350)}else{var n=document.createElement("div");h=document.createElement("div");var c=document.createElement("div"),m=document.createElement("div"),t=document.createElement("div"),v=document.createElement("div"),G=document.createElement("div"),A=document.createElement("div");n.className=f;h.className="introjs-tooltipReferenceLayer";w.call(b,n);w.call(b,h);this._targetElement.appendChild(n);
|
||||
this._targetElement.appendChild(h);c.className="introjs-arrow";t.className="introjs-tooltiptext";t.innerHTML=a.intro;v.className="introjs-bullets";!1===this._options.showBullets&&(v.style.display="none");for(var n=document.createElement("ul"),f=0,C=this._introItems.length;f<C;f++){var y=document.createElement("li"),B=document.createElement("a");B.onclick=function(){b.goToStep(this.getAttribute("data-stepnumber"))};f===a.step-1&&(B.className="active");D(B);B.innerHTML="\x26nbsp;";B.setAttribute("data-stepnumber",
|
||||
this._introItems[f].step);y.appendChild(B);n.appendChild(y)}v.appendChild(n);G.className="introjs-progress";!1===this._options.showProgress&&(G.style.display="none");f=document.createElement("div");f.className="introjs-progressbar";f.setAttribute("style","width:"+S.call(this)+"%;");G.appendChild(f);A.className="introjs-tooltipbuttons";!1===this._options.showButtons&&(A.style.display="none");m.className="introjs-tooltip";m.appendChild(t);m.appendChild(v);m.appendChild(G);1==this._options.showStepNumbers&&
|
||||
(p=document.createElement("span"),p.className="introjs-helperNumberLayer",p.innerHTML=a.step,h.appendChild(p));m.appendChild(c);h.appendChild(m);k=document.createElement("a");k.onclick=function(){b._introItems.length-1!=b._currentStep&&x.call(b)};D(k);k.innerHTML=this._options.nextLabel;h=document.createElement("a");h.onclick=function(){0!=b._currentStep&&E.call(b)};D(h);h.innerHTML=this._options.prevLabel;l=document.createElement("a");l.className="introjs-button introjs-skipbutton";D(l);l.innerHTML=
|
||||
this._options.skipLabel;l.onclick=function(){b._introItems.length-1==b._currentStep&&"function"===typeof b._introCompleteCallback&&b._introCompleteCallback.call(b);z.call(b,b._targetElement)};A.appendChild(l);1<this._introItems.length&&(A.appendChild(h),A.appendChild(k));m.appendChild(A);F.call(b,a.element,m,c,p);T.call(this,a.scrollTo,a,m)}(p=b._targetElement.querySelector(".introjs-disableInteraction"))&&p.parentNode.removeChild(p);a.disableInteraction&&ba.call(b);"undefined"!==typeof k&&null!=
|
||||
k&&k.removeAttribute("tabIndex");"undefined"!==typeof h&&null!=h&&h.removeAttribute("tabIndex");0==this._currentStep&&1<this._introItems.length?("undefined"!==typeof l&&null!=l&&(l.className="introjs-button introjs-skipbutton"),"undefined"!==typeof k&&null!=k&&(k.className="introjs-button introjs-nextbutton"),1==this._options.hidePrev?("undefined"!==typeof h&&null!=h&&(h.className="introjs-button introjs-prevbutton introjs-hidden"),"undefined"!==typeof k&&null!=k&&(k.className+=" introjs-fullbutton")):
|
||||
"undefined"!==typeof h&&null!=h&&(h.className="introjs-button introjs-prevbutton introjs-disabled"),"undefined"!==typeof h&&null!=h&&(h.tabIndex="-1"),"undefined"!==typeof l&&null!=l&&(l.innerHTML=this._options.skipLabel)):this._introItems.length-1==this._currentStep||1==this._introItems.length?("undefined"!==typeof l&&null!=l&&(l.innerHTML=this._options.doneLabel,l.className+=" introjs-donebutton"),"undefined"!==typeof h&&null!=h&&(h.className="introjs-button introjs-prevbutton"),1==this._options.hideNext?
|
||||
("undefined"!==typeof k&&null!=k&&(k.className="introjs-button introjs-nextbutton introjs-hidden"),"undefined"!==typeof h&&null!=h&&(h.className+=" introjs-fullbutton")):"undefined"!==typeof k&&null!=k&&(k.className="introjs-button introjs-nextbutton introjs-disabled"),"undefined"!==typeof k&&null!=k&&(k.tabIndex="-1")):("undefined"!==typeof l&&null!=l&&(l.className="introjs-button introjs-skipbutton"),"undefined"!==typeof h&&null!=h&&(h.className="introjs-button introjs-prevbutton"),"undefined"!==
|
||||
typeof k&&null!=k&&(k.className="introjs-button introjs-nextbutton"),"undefined"!==typeof l&&null!=l&&(l.innerHTML=this._options.skipLabel));"undefined"!==typeof k&&null!=k&&k.focus();ca(a);"undefined"!==typeof this._introAfterChangeCallback&&this._introAfterChangeCallback.call(this,a.element)}function T(a,b,c){this._options.scrollToElement&&(a="tooltip"===a?c.getBoundingClientRect():b.element.getBoundingClientRect(),c=b.element.getBoundingClientRect(),0<=c.top&&0<=c.left&&c.bottom+80<=window.innerHeight&&
|
||||
c.right<=window.innerWidth||(c=H().height,0>a.bottom-(a.bottom-a.top)||b.element.clientHeight>c?window.scrollBy(0,a.top-(c/2-a.height/2)-this._options.scrollPadding):window.scrollBy(0,a.top-(c/2-a.height/2)+this._options.scrollPadding)))}function P(){for(var a=document.querySelectorAll(".introjs-showElement"),b=0,c=a.length;b<c;b++){var d=a[b],f=/introjs-[a-zA-Z]+/g;if(d instanceof SVGElement){var e=d.getAttribute("class")||"";d.setAttribute("class",e.replace(f,"").replace(/^\s+|\s+$/g,""))}else d.className=
|
||||
d.className.replace(f,"").replace(/^\s+|\s+$/g,"")}}function ca(a){var b;if(a.element instanceof SVGElement)for(b=a.element.parentNode;null!=a.element.parentNode&&b.tagName&&"body"!==b.tagName.toLowerCase();)"svg"===b.tagName.toLowerCase()&&K(b,"introjs-showElement introjs-relativePosition"),b=b.parentNode;K(a.element,"introjs-showElement");b=m(a.element,"position");"absolute"!==b&&"relative"!==b&&"fixed"!==b&&K(a.element,"introjs-relativePosition");for(b=a.element.parentNode;null!=b&&b.tagName&&
|
||||
"body"!==b.tagName.toLowerCase();){a=m(b,"z-index");var c=parseFloat(m(b,"opacity")),d=m(b,"transform")||m(b,"-webkit-transform")||m(b,"-moz-transform")||m(b,"-ms-transform")||m(b,"-o-transform");if(/[0-9]+/.test(a)||1>c||"none"!==d&&void 0!==d)b.className+=" introjs-fixParent";b=b.parentNode}}function K(a,b){if(a instanceof SVGElement){var c=a.getAttribute("class")||"";a.setAttribute("class",c+" "+b)}else a.className+=" "+b}function m(a,b){var c="";a.currentStyle?c=a.currentStyle[b]:document.defaultView&&
|
||||
document.defaultView.getComputedStyle&&(c=document.defaultView.getComputedStyle(a,null).getPropertyValue(b));return c&&c.toLowerCase?c.toLowerCase():c}function J(a){var b=a.parentNode;return b&&"HTML"!==b.nodeName?"fixed"==m(a,"position")?!0:J(b):!1}function H(){if(void 0!=window.innerWidth)return{width:window.innerWidth,height:window.innerHeight};var a=document.documentElement;return{width:a.clientWidth,height:a.clientHeight}}function aa(a){var b=document.createElement("div"),c="",d=this;b.className=
|
||||
"introjs-overlay";if(a.tagName&&"body"!==a.tagName.toLowerCase()){var f=u(a);f&&(c+="width: "+f.width+"px; height:"+f.height+"px; top:"+f.top+"px;left: "+f.left+"px;",b.setAttribute("style",c))}else c+="top: 0;bottom: 0; left: 0;right: 0;position: fixed;",b.setAttribute("style",c);a.appendChild(b);b.onclick=function(){1==d._options.exitOnOverlayClick&&z.call(d,a)};setTimeout(function(){c+="opacity: "+d._options.overlayOpacity.toString()+";";b.setAttribute("style",c)},10);return!0}function v(){var a=
|
||||
this._targetElement.querySelector(".introjs-hintReference");if(a){var b=a.getAttribute("data-step");a.parentNode.removeChild(a);return b}}function U(a){this._introItems=[];if(this._options.hints){a=0;for(var b=this._options.hints.length;a<b;a++){var c=y(this._options.hints[a]);"string"===typeof c.element&&(c.element=document.querySelector(c.element));c.hintPosition=c.hintPosition||this._options.hintPosition;c.hintAnimation=c.hintAnimation||this._options.hintAnimation;null!=c.element&&this._introItems.push(c)}}else{c=
|
||||
a.querySelectorAll("*[data-hint]");if(1>c.length)return!1;a=0;for(b=c.length;a<b;a++){var d=c[a],f=d.getAttribute("data-hintAnimation"),f=f?"true"==f:this._options.hintAnimation;this._introItems.push({element:d,hint:d.getAttribute("data-hint"),hintPosition:d.getAttribute("data-hintPosition")||this._options.hintPosition,hintAnimation:f,tooltipClass:d.getAttribute("data-tooltipClass"),position:d.getAttribute("data-position")||this._options.tooltipPosition})}}da.call(this);document.addEventListener?
|
||||
(document.addEventListener("click",v.bind(this),!1),window.addEventListener("resize",L.bind(this),!0)):document.attachEvent&&(document.attachEvent("onclick",v.bind(this)),document.attachEvent("onresize",L.bind(this)))}function L(){for(var a=0,b=this._introItems.length;a<b;a++){var c=this._introItems[a];"undefined"!=typeof c.targetElement&&V.call(this,c.hintPosition,c.element,c.targetElement)}}function M(a){v.call(this);var b=this._targetElement.querySelector('.introjs-hint[data-step\x3d"'+a+'"]');
|
||||
b&&(b.className+=" introjs-hidehint");"undefined"!==typeof this._hintCloseCallback&&this._hintCloseCallback.call(this,a)}function W(a){if(a=this._targetElement.querySelector('.introjs-hint[data-step\x3d"'+a+'"]'))a.className=a.className.replace(/introjs\-hidehint/g,"")}function X(a){(a=this._targetElement.querySelector('.introjs-hint[data-step\x3d"'+a+'"]'))&&a.parentNode.removeChild(a)}function da(){var a=this;var b=document.querySelector(".introjs-hints");null==b&&(b=document.createElement("div"),
|
||||
b.className="introjs-hints");for(var c=0,d=this._introItems.length;c<d;c++){var f=this._introItems[c];if(!document.querySelector('.introjs-hint[data-step\x3d"'+c+'"]')){var e=document.createElement("a");D(e);(function(b,c,d){b.onclick=function(b){b=b?b:window.event;b.stopPropagation&&b.stopPropagation();null!=b.cancelBubble&&(b.cancelBubble=!0);Y.call(a,d)}})(e,f,c);e.className="introjs-hint";f.hintAnimation||(e.className+=" introjs-hint-no-anim");J(f.element)&&(e.className+=" introjs-fixedhint");
|
||||
var g=document.createElement("div");g.className="introjs-hint-dot";var m=document.createElement("div");m.className="introjs-hint-pulse";e.appendChild(g);e.appendChild(m);e.setAttribute("data-step",c);f.targetElement=f.element;f.element=e;V.call(this,f.hintPosition,e,f.targetElement);b.appendChild(e)}}document.body.appendChild(b);"undefined"!==typeof this._hintsAddedCallback&&this._hintsAddedCallback.call(this)}function V(a,b,c){c=u.call(this,c);switch(a){default:case "top-left":b.style.left=c.left+
|
||||
"px";b.style.top=c.top+"px";break;case "top-right":b.style.left=c.left+c.width-20+"px";b.style.top=c.top+"px";break;case "bottom-left":b.style.left=c.left+"px";b.style.top=c.top+c.height-20+"px";break;case "bottom-right":b.style.left=c.left+c.width-20+"px";b.style.top=c.top+c.height-20+"px";break;case "middle-left":b.style.left=c.left+"px";b.style.top=c.top+(c.height-20)/2+"px";break;case "middle-right":b.style.left=c.left+c.width-20+"px";b.style.top=c.top+(c.height-20)/2+"px";break;case "middle-middle":b.style.left=
|
||||
c.left+(c.width-20)/2+"px";b.style.top=c.top+(c.height-20)/2+"px";break;case "bottom-middle":b.style.left=c.left+(c.width-20)/2+"px";b.style.top=c.top+c.height-20+"px";break;case "top-middle":b.style.left=c.left+(c.width-20)/2+"px",b.style.top=c.top+"px"}}function Y(a){var b=document.querySelector('.introjs-hint[data-step\x3d"'+a+'"]'),c=this._introItems[a];"undefined"!==typeof this._hintClickCallback&&this._hintClickCallback.call(this,b,c,a);var d=v.call(this);if(parseInt(d,10)!=a){var d=document.createElement("div"),
|
||||
f=document.createElement("div"),e=document.createElement("div"),g=document.createElement("div");d.className="introjs-tooltip";d.onclick=function(a){a.stopPropagation?a.stopPropagation():a.cancelBubble=!0};f.className="introjs-tooltiptext";var m=document.createElement("p");m.innerHTML=c.hint;c=document.createElement("a");c.className="introjs-button";c.innerHTML=this._options.hintButtonLabel;c.onclick=M.bind(this,a);f.appendChild(m);f.appendChild(c);e.className="introjs-arrow";d.appendChild(e);d.appendChild(f);
|
||||
this._currentStep=b.getAttribute("data-step");g.className="introjs-tooltipReferenceLayer introjs-hintReference";g.setAttribute("data-step",b.getAttribute("data-step"));w.call(this,g);g.appendChild(d);document.body.appendChild(g);F.call(this,b,d,e,null,!0)}}function u(a){var b={},c=document.body,d=document.documentElement,f=window.pageYOffset||d.scrollTop||c.scrollTop,c=window.pageXOffset||d.scrollLeft||c.scrollLeft;if(a instanceof SVGElement)a=a.getBoundingClientRect(),b.top=a.top+f,b.width=a.width,
|
||||
b.height=a.height,b.left=a.left+c;else{b.width=a.offsetWidth;b.height=a.offsetHeight;for(c=f=0;a&&!isNaN(a.offsetLeft)&&!isNaN(a.offsetTop);)f+=a.offsetLeft,c+=a.offsetTop,a=a.offsetParent;b.top=c;b.left=f}return b}function S(){return parseInt(this._currentStep+1,10)/this._introItems.length*100}var N=function(a){if("object"===typeof a)return new n(a);if("string"===typeof a){if(a=document.querySelector(a))return new n(a);throw Error("There is no element with given selector.");}return new n(document.body)};
|
||||
N.version="2.7.0";N.fn=n.prototype={clone:function(){return new n(this)},setOption:function(a,b){this._options[a]=b;return this},setOptions:function(a){var b=this._options,c={},d;for(d in b)c[d]=b[d];for(d in a)c[d]=a[d];this._options=c;return this},start:function(){Z.call(this,this._targetElement);return this},goToStep:function(a){this._currentStep=a-2;"undefined"!==typeof this._introItems&&x.call(this);return this},addStep:function(a){this._options.steps||(this._options.steps=[]);this._options.steps.push(a);
|
||||
return this},addSteps:function(a){if(a.length){for(var b=0;b<a.length;b++)this.addStep(a[b]);return this}},goToStepNumber:function(a){this._currentStepNumber=a;"undefined"!==typeof this._introItems&&x.call(this);return this},nextStep:function(){x.call(this);return this},previousStep:function(){E.call(this);return this},exit:function(a){z.call(this,this._targetElement,a);return this},refresh:function(){w.call(this,document.querySelector(".introjs-helperLayer"));w.call(this,document.querySelector(".introjs-tooltipReferenceLayer"));
|
||||
if(void 0!==this._currentStep&&null!==this._currentStep){var a=document.querySelector(".introjs-helperNumberLayer"),b=document.querySelector(".introjs-arrow"),c=document.querySelector(".introjs-tooltip");F.call(this,this._introItems[this._currentStep].element,c,b,a)}L.call(this);return this},onbeforechange:function(a){if("function"===typeof a)this._introBeforeChangeCallback=a;else throw Error("Provided callback for onbeforechange was not a function");return this},onchange:function(a){if("function"===
|
||||
typeof a)this._introChangeCallback=a;else throw Error("Provided callback for onchange was not a function.");return this},onafterchange:function(a){if("function"===typeof a)this._introAfterChangeCallback=a;else throw Error("Provided callback for onafterchange was not a function");return this},oncomplete:function(a){if("function"===typeof a)this._introCompleteCallback=a;else throw Error("Provided callback for oncomplete was not a function.");return this},onhintsadded:function(a){if("function"===typeof a)this._hintsAddedCallback=
|
||||
a;else throw Error("Provided callback for onhintsadded was not a function.");return this},onhintclick:function(a){if("function"===typeof a)this._hintClickCallback=a;else throw Error("Provided callback for onhintclick was not a function.");return this},onhintclose:function(a){if("function"===typeof a)this._hintCloseCallback=a;else throw Error("Provided callback for onhintclose was not a function.");return this},onexit:function(a){if("function"===typeof a)this._introExitCallback=a;else throw Error("Provided callback for onexit was not a function.");
|
||||
return this},onbeforeexit:function(a){if("function"===typeof a)this._introBeforeExitCallback=a;else throw Error("Provided callback for onbeforeexit was not a function.");return this},addHints:function(){U.call(this,this._targetElement);return this},hideHint:function(a){M.call(this,a);return this},hideHints:function(){var a=this._targetElement.querySelectorAll(".introjs-hint");if(a&&0<a.length)for(var b=0;b<a.length;b++)M.call(this,a[b].getAttribute("data-step"));return this},showHint:function(a){W.call(this,
|
||||
a);return this},showHints:function(){var a=this._targetElement.querySelectorAll(".introjs-hint");if(a&&0<a.length)for(var b=0;b<a.length;b++)W.call(this,a[b].getAttribute("data-step"));else U.call(this,this._targetElement);return this},removeHints:function(){var a=this._targetElement.querySelectorAll(".introjs-hint");if(a&&0<a.length)for(var b=0;b<a.length;b++)X.call(this,a[b].getAttribute("data-step"));return this},removeHint:function(a){X.call(this,a);return this},showHintDialog:function(a){Y.call(this,
|
||||
a);return this}};return C.introJs=N});
|
2
public/lib/intro/introjs-rtl.min.css
vendored
2
public/lib/intro/introjs-rtl.min.css
vendored
@ -1 +1 @@
|
||||
.introjs-tooltipbuttons{text-align:left}.introjs-skipbutton{margin-left:5px}.introjs-tooltip{direction:rtl}.introjs-prevbutton{border:1px solid #d4d4d4;border-left:none;-webkit-border-radius:0 .2em .2em 0;-moz-border-radius:0 .2em .2em 0;border-radius:0 .2em .2em 0}.introjs-nextbutton{border:1px solid #d4d4d4;-webkit-border-radius:.2em 0 0 .2em;-moz-border-radius:.2em 0 0 .2em;border-radius:.2em 0 0 .2em}
|
||||
.introjs-tooltipbuttons{text-align:left}.introjs-skipbutton{margin-left:5px}.introjs-tooltip{direction:rtl}.introjs-prevbutton{border:1px solid #d4d4d4;border-left:none;-webkit-border-radius:0 .2em .2em 0;-moz-border-radius:0 .2em .2em 0;border-radius:0 .2em .2em 0}.introjs-nextbutton{border:1px solid #d4d4d4;-webkit-border-radius:.2em 0 0 .2em;-moz-border-radius:.2em 0 0 .2em;border-radius:.2em 0 0 .2em}.introjs-bullets ul li{float:right}
|
2
public/lib/intro/introjs.min.css
vendored
2
public/lib/intro/introjs.min.css
vendored
File diff suppressed because one or more lines are too long
@ -21,13 +21,12 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'locale' => 'de, Deutsch, de_DE, de_DE.utf8, de_DE.UTF-8',
|
||||
'month' => '%B %Y',
|
||||
'month_and_day' => '%e. %B %Y',
|
||||
'date_time' => '%e %B %Y, @ %T',
|
||||
'specific_day' => '%e. %B %Y',
|
||||
'week_in_year' => 'Woche %W, %Y',
|
||||
'quarter_of_year' => '%B %Y',
|
||||
'year' => '%Y',
|
||||
'half_year' => '%B %Y',
|
||||
'locale' => 'de, Deutsch, de_DE, de_DE.utf8, de_DE.UTF-8',
|
||||
'month' => '%B %Y',
|
||||
'month_and_day' => '%e. %B %Y',
|
||||
'date_time' => '%e %B %Y, @ %T',
|
||||
'specific_day' => '%e. %B %Y',
|
||||
'week_in_year' => 'Woche %W, %Y',
|
||||
'year' => '%Y',
|
||||
'half_year' => '%B %Y',
|
||||
];
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user