mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Merge branch 'release/4.7.8'
This commit is contained in:
commit
cb9aefc489
@ -1,12 +0,0 @@
|
||||
---
|
||||
exclude_patterns:
|
||||
- public/lib/
|
||||
- public/js/lib/
|
||||
- public/fonts/
|
||||
- public/css/jquery-ui/
|
||||
- public/css/bootstrap-multiselect.css
|
||||
- public/css/bootstrap-sortable.css
|
||||
- public/css/bootstrap-tagsinput.css
|
||||
- public/css/daterangepicker.css
|
||||
- public/css/google-fonts.css
|
||||
- .sandstorm/
|
3314
.deploy/docker/cacert.pem
Normal file
3314
.deploy/docker/cacert.pem
Normal file
File diff suppressed because it is too large
Load Diff
64
.env.docker
64
.env.docker
@ -43,12 +43,25 @@ DB_HOST=${FF_DB_HOST}
|
||||
DB_PORT=${FF_DB_PORT}
|
||||
DB_DATABASE=${FF_DB_NAME}
|
||||
DB_USERNAME=${FF_DB_USER}
|
||||
DB_PASSWORD=${FF_DB_PASSWORD}
|
||||
DB_PASSWORD="${FF_DB_PASSWORD}"
|
||||
|
||||
# If you're looking for performance improvements, you could install memcached.
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
|
||||
# You can configure another file storage backend if you cannot use the local storage option.
|
||||
# To set this up, fill in the following variables. The upload path is used to store uploaded
|
||||
# files and the export path is to store exported data (before download).
|
||||
SFTP_HOST=${SFTP_HOST}
|
||||
SFTP_PORT=${SFTP_PORT}
|
||||
SFTP_UPLOAD_PATH=${SFTP_UPLOAD_PATH}
|
||||
SFTP_EXPORT_PATH=${SFTP_EXPORT_PATH}
|
||||
|
||||
# SFTP uses either the username/password combination or the private key to authenticate.
|
||||
SFTP_USERNAME=${SFTP_USERNAME}
|
||||
SFTP_PASSWORD="${SFTP_PASSWORD}"
|
||||
SFTP_PRIV_KEY=${SFTP_PRIV_KEY}
|
||||
|
||||
# Cookie settings. Should not be necessary to change these.
|
||||
COOKIE_PATH="/"
|
||||
COOKIE_DOMAIN=
|
||||
@ -61,7 +74,7 @@ MAIL_HOST=${MAIL_HOST}
|
||||
MAIL_PORT=${MAIL_PORT}
|
||||
MAIL_FROM=${MAIL_FROM}
|
||||
MAIL_USERNAME=${MAIL_USERNAME}
|
||||
MAIL_PASSWORD=${MAIL_PASSWORD}
|
||||
MAIL_PASSWORD="${MAIL_PASSWORD}"
|
||||
MAIL_ENCRYPTION=${MAIL_ENCRYPTION}
|
||||
|
||||
# Other mail drivers:
|
||||
@ -74,6 +87,9 @@ SPARKPOST_SECRET=${SPARKPOST_SECRET}
|
||||
SEND_REGISTRATION_MAIL=true
|
||||
SEND_ERROR_MESSAGE=false
|
||||
|
||||
# These messages contain (sensitive) transaction information:
|
||||
SEND_REPORT_JOURNALS=${SEND_REPORT_JOURNALS}
|
||||
|
||||
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
|
||||
MAPBOX_API_KEY=${MAPBOX_API_KEY}
|
||||
|
||||
@ -89,9 +105,51 @@ ANALYTICS_ID=${ANALYTICS_ID}
|
||||
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
|
||||
USE_ENCRYPTION=true
|
||||
|
||||
# Firefly III has two options for user authentication. "eloquent" is the default,
|
||||
# and "adldap" for LDAP servers.
|
||||
# For full instructions on these settings please visit:
|
||||
# https://firefly-iii.readthedocs.io/en/latest/installation/authentication.html
|
||||
LOGIN_PROVIDER=${LOGIN_PROVIDER}
|
||||
|
||||
# LDAP connection configuration
|
||||
ADLDAP_CONNECTION_SCHEME=${ADLDAP_CONNECTION_SCHEME}
|
||||
ADLDAP_AUTO_CONNECT=${ADLDAP_AUTO_CONNECT}
|
||||
|
||||
# LDAP connection settings
|
||||
ADLDAP_CONTROLLERS=${ADLDAP_CONTROLLERS}
|
||||
ADLDAP_PORT=${ADLDAP_PORT}
|
||||
ADLDAP_TIMEOUT=${ADLDAP_TIMEOUT}
|
||||
ADLDAP_BASEDN="${ADLDAP_BASEDN}"
|
||||
ADLDAP_FOLLOW_REFFERALS=${ADLDAP_FOLLOW_REFFERALS}
|
||||
ADLDAP_USE_SSL=${ADLDAP_USE_SSL}
|
||||
ADLDAP_USE_TLS=${ADLDAP_USE_TLS}
|
||||
|
||||
ADLDAP_ADMIN_USERNAME=${ADLDAP_ADMIN_USERNAME}
|
||||
ADLDAP_ADMIN_PASSWORD="${ADLDAP_ADMIN_PASSWORD}"
|
||||
|
||||
ADLDAP_ACCOUNT_PREFIX="${ADLDAP_ACCOUNT_PREFIX}"
|
||||
ADLDAP_ACCOUNT_SUFFIX="${ADLDAP_ACCOUNT_SUFFIX}"
|
||||
ADLDAP_ADMIN_ACCOUNT_PREFIX="${ADLDAP_ADMIN_ACCOUNT_PREFIX}"
|
||||
ADLDAP_ADMIN_ACCOUNT_SUFFIX="${ADLDAP_ADMIN_ACCOUNT_SUFFIX}"
|
||||
|
||||
# LDAP authentication settings.
|
||||
ADLDAP_PASSWORD_SYNC=${ADLDAP_PASSWORD_SYNC}
|
||||
ADLDAP_LOGIN_FALLBACK=${ADLDAP_LOGIN_FALLBACK}
|
||||
|
||||
ADLDAP_DISCOVER_FIELD=${ADLDAP_DISCOVER_FIELD}
|
||||
ADLDAP_AUTH_FIELD=${ADLDAP_AUTH_FIELD}
|
||||
|
||||
# Will allow SSO if your server provides an AUTH_USER field.
|
||||
WINDOWS_SSO_DISCOVER=${WINDOWS_SSO_DISCOVER}
|
||||
WINDOWS_SSO_KEY=${WINDOWS_SSO_KEY}
|
||||
|
||||
# field to sync as local username.
|
||||
ADLDAP_SYNC_FIELD=${ADLDAP_SYNC_FIELD}
|
||||
|
||||
# Leave the following configuration vars as is.
|
||||
# Unless you like to tinker and know what you're doing.
|
||||
APP_NAME=FireflyIII
|
||||
ADLDAP_CONNECTION=default
|
||||
BROADCAST_DRIVER=log
|
||||
QUEUE_DRIVER=sync
|
||||
REDIS_HOST=127.0.0.1
|
||||
@ -108,5 +166,3 @@ IS_DOCKER=true
|
||||
IS_SANDSTORM=false
|
||||
IS_HEROKU=false
|
||||
BUNQ_USE_SANDBOX=false
|
||||
MAILGUN_DOMAIN=
|
||||
MAILGUN_SECRET=
|
||||
|
61
.env.example
61
.env.example
@ -49,6 +49,19 @@ DB_PASSWORD=secret
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
|
||||
# You can configure another file storage backend if you cannot use the local storage option.
|
||||
# To set this up, fill in the following variables. The upload path is used to store uploaded
|
||||
# files and the export path is to store exported data (before download).
|
||||
SFTP_HOST=
|
||||
SFTP_PORT=
|
||||
SFTP_UPLOAD_PATH=
|
||||
SFTP_EXPORT_PATH=
|
||||
|
||||
# SFTP uses either the username/password combination or the private key to authenticate.
|
||||
SFTP_USERNAME=
|
||||
SFTP_PASSWORD=
|
||||
SFTP_PRIV_KEY=
|
||||
|
||||
# Cookie settings. Should not be necessary to change these.
|
||||
COOKIE_PATH="/"
|
||||
COOKIE_DOMAIN=
|
||||
@ -74,6 +87,9 @@ SPARKPOST_SECRET=
|
||||
SEND_REGISTRATION_MAIL=true
|
||||
SEND_ERROR_MESSAGE=true
|
||||
|
||||
# These messages contain (sensitive) transaction information:
|
||||
SEND_REPORT_JOURNALS=true
|
||||
|
||||
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
|
||||
MAPBOX_API_KEY=
|
||||
|
||||
@ -89,9 +105,52 @@ ANALYTICS_ID=
|
||||
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
|
||||
USE_ENCRYPTION=true
|
||||
|
||||
# Firefly III has two options for user authentication. "eloquent" is the default,
|
||||
# and "adldap" for LDAP servers.
|
||||
# For full instructions on these settings please visit:
|
||||
# https://firefly-iii.readthedocs.io/en/latest/installation/authentication.html
|
||||
LOGIN_PROVIDER=eloquent
|
||||
|
||||
# LDAP connection configuration
|
||||
# OpenLDAP, FreeIPA or ActiveDirectory
|
||||
ADLDAP_CONNECTION_SCHEME=OpenLDAP
|
||||
ADLDAP_AUTO_CONNECT=true
|
||||
|
||||
# LDAP connection settings
|
||||
ADLDAP_CONTROLLERS=
|
||||
ADLDAP_PORT=389
|
||||
ADLDAP_TIMEOUT=5
|
||||
ADLDAP_BASEDN=""
|
||||
ADLDAP_FOLLOW_REFFERALS=false
|
||||
ADLDAP_USE_SSL=false
|
||||
ADLDAP_USE_TLS=false
|
||||
|
||||
ADLDAP_ADMIN_USERNAME=
|
||||
ADLDAP_ADMIN_PASSWORD=
|
||||
|
||||
ADLDAP_ACCOUNT_PREFIX=
|
||||
ADLDAP_ACCOUNT_SUFFIX=
|
||||
ADLDAP_ADMIN_ACCOUNT_PREFIX=
|
||||
ADLDAP_ADMIN_ACCOUNT_SUFFIX=
|
||||
|
||||
# LDAP authentication settings.
|
||||
ADLDAP_PASSWORD_SYNC=false
|
||||
ADLDAP_LOGIN_FALLBACK=false
|
||||
|
||||
ADLDAP_DISCOVER_FIELD=distinguishedname
|
||||
ADLDAP_AUTH_FIELD=distinguishedname
|
||||
|
||||
# Will allow SSO if your server provides an AUTH_USER field.
|
||||
WINDOWS_SSO_DISCOVER=samaccountname
|
||||
WINDOWS_SSO_KEY=AUTH_USER
|
||||
|
||||
# field to sync as local username.
|
||||
ADLDAP_SYNC_FIELD=userprincipalname
|
||||
|
||||
# Leave the following configuration vars as is.
|
||||
# Unless you like to tinker and know what you're doing.
|
||||
APP_NAME=FireflyIII
|
||||
ADLDAP_CONNECTION=default
|
||||
BROADCAST_DRIVER=log
|
||||
QUEUE_DRIVER=sync
|
||||
REDIS_HOST=127.0.0.1
|
||||
@ -108,5 +167,3 @@ IS_DOCKER=false
|
||||
IS_SANDSTORM=false
|
||||
IS_HEROKU=false
|
||||
BUNQ_USE_SANDBOX=false
|
||||
MAILGUN_DOMAIN=
|
||||
MAILGUN_SECRET=
|
||||
|
61
.env.heroku
61
.env.heroku
@ -49,6 +49,19 @@ DB_CONNECTION=pgsql
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
|
||||
# You can configure another file storage backend if you cannot use the local storage option.
|
||||
# To set this up, fill in the following variables. The upload path is used to store uploaded
|
||||
# files and the export path is to store exported data (before download).
|
||||
SFTP_HOST=
|
||||
SFTP_PORT=
|
||||
SFTP_UPLOAD_PATH=
|
||||
SFTP_EXPORT_PATH=
|
||||
|
||||
# SFTP uses either the username/password combination or the private key to authenticate.
|
||||
SFTP_USERNAME=
|
||||
SFTP_PASSWORD=
|
||||
SFTP_PRIV_KEY=
|
||||
|
||||
# Cookie settings. Should not be necessary to change these.
|
||||
COOKIE_PATH="/"
|
||||
COOKIE_DOMAIN=
|
||||
@ -74,6 +87,9 @@ SPARKPOST_SECRET=
|
||||
SEND_REGISTRATION_MAIL=true
|
||||
SEND_ERROR_MESSAGE=true
|
||||
|
||||
# These messages contain (sensitive) transaction information:
|
||||
SEND_REPORT_JOURNALS=true
|
||||
|
||||
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
|
||||
MAPBOX_API_KEY=
|
||||
|
||||
@ -89,9 +105,52 @@ ANALYTICS_ID=
|
||||
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
|
||||
USE_ENCRYPTION=true
|
||||
|
||||
# Firefly III has two options for user authentication. "eloquent" is the default,
|
||||
# and "adldap" for LDAP servers.
|
||||
# For full instructions on these settings please visit:
|
||||
# https://firefly-iii.readthedocs.io/en/latest/installation/authentication.html
|
||||
LOGIN_PROVIDER=eloquent
|
||||
|
||||
# LDAP connection configuration
|
||||
# OpenLDAP, FreeIPA or ActiveDirectory
|
||||
ADLDAP_CONNECTION_SCHEME=OpenLDAP
|
||||
ADLDAP_AUTO_CONNECT=true
|
||||
|
||||
# LDAP connection settings
|
||||
ADLDAP_CONTROLLERS=
|
||||
ADLDAP_PORT=389
|
||||
ADLDAP_TIMEOUT=5
|
||||
ADLDAP_BASEDN=""
|
||||
ADLDAP_FOLLOW_REFFERALS=false
|
||||
ADLDAP_USE_SSL=false
|
||||
ADLDAP_USE_TLS=false
|
||||
|
||||
ADLDAP_ADMIN_USERNAME=
|
||||
ADLDAP_ADMIN_PASSWORD=
|
||||
|
||||
ADLDAP_ACCOUNT_PREFIX=
|
||||
ADLDAP_ACCOUNT_SUFFIX=
|
||||
ADLDAP_ADMIN_ACCOUNT_PREFIX=
|
||||
ADLDAP_ADMIN_ACCOUNT_SUFFIX=
|
||||
|
||||
# LDAP authentication settings.
|
||||
ADLDAP_PASSWORD_SYNC=false
|
||||
ADLDAP_LOGIN_FALLBACK=false
|
||||
|
||||
ADLDAP_DISCOVER_FIELD=distinguishedname
|
||||
ADLDAP_AUTH_FIELD=distinguishedname
|
||||
|
||||
# Will allow SSO if your server provides an AUTH_USER field.
|
||||
WINDOWS_SSO_DISCOVER=samaccountname
|
||||
WINDOWS_SSO_KEY=AUTH_USER
|
||||
|
||||
# field to sync as local username.
|
||||
ADLDAP_SYNC_FIELD=userprincipalname
|
||||
|
||||
# Leave the following configuration vars as is.
|
||||
# Unless you like to tinker and know what you're doing.
|
||||
APP_NAME=FireflyIII
|
||||
ADLDAP_CONNECTION=default
|
||||
BROADCAST_DRIVER=log
|
||||
QUEUE_DRIVER=sync
|
||||
REDIS_HOST=127.0.0.1
|
||||
@ -108,5 +167,3 @@ IS_DOCKER=false
|
||||
IS_SANDSTORM=false
|
||||
IS_HEROKU=true
|
||||
BUNQ_USE_SANDBOX=false
|
||||
MAILGUN_DOMAIN=
|
||||
MAILGUN_SECRET=
|
||||
|
@ -49,6 +49,19 @@ DB_PASSWORD=firefly
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
|
||||
# You can configure another file storage backend if you cannot use the local storage option.
|
||||
# To set this up, fill in the following variables. The upload path is used to store uploaded
|
||||
# files and the export path is to store exported data (before download).
|
||||
SFTP_HOST=
|
||||
SFTP_PORT=
|
||||
SFTP_UPLOAD_PATH=
|
||||
SFTP_EXPORT_PATH=
|
||||
|
||||
# SFTP uses either the username/password combination or the private key to authenticate.
|
||||
SFTP_USERNAME=
|
||||
SFTP_PASSWORD=
|
||||
SFTP_PRIV_KEY=
|
||||
|
||||
# Cookie settings. Should not be necessary to change these.
|
||||
COOKIE_PATH="/"
|
||||
COOKIE_DOMAIN=
|
||||
@ -74,6 +87,9 @@ SPARKPOST_SECRET=
|
||||
SEND_REGISTRATION_MAIL=true
|
||||
SEND_ERROR_MESSAGE=true
|
||||
|
||||
# These messages contain (sensitive) transaction information:
|
||||
SEND_REPORT_JOURNALS=true
|
||||
|
||||
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
|
||||
MAPBOX_API_KEY=
|
||||
|
||||
@ -89,9 +105,52 @@ ANALYTICS_ID=
|
||||
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
|
||||
USE_ENCRYPTION=true
|
||||
|
||||
# Firefly III has two options for user authentication. "eloquent" is the default,
|
||||
# and "adldap" for LDAP servers.
|
||||
# For full instructions on these settings please visit:
|
||||
# https://firefly-iii.readthedocs.io/en/latest/installation/authentication.html
|
||||
LOGIN_PROVIDER=eloquent
|
||||
|
||||
# LDAP connection configuration
|
||||
# or FreeIPA or ActiveDirectory
|
||||
ADLDAP_CONNECTION_SCHEME=OpenLDAP
|
||||
ADLDAP_AUTO_CONNECT=true
|
||||
|
||||
# LDAP connection settings
|
||||
ADLDAP_CONTROLLERS=
|
||||
ADLDAP_PORT=389
|
||||
ADLDAP_TIMEOUT=5
|
||||
ADLDAP_BASEDN=""
|
||||
ADLDAP_FOLLOW_REFFERALS=false
|
||||
ADLDAP_USE_SSL=false
|
||||
ADLDAP_USE_TLS=false
|
||||
|
||||
ADLDAP_ADMIN_USERNAME=
|
||||
ADLDAP_ADMIN_PASSWORD=
|
||||
|
||||
ADLDAP_ACCOUNT_PREFIX=
|
||||
ADLDAP_ACCOUNT_SUFFIX=
|
||||
ADLDAP_ADMIN_ACCOUNT_PREFIX=
|
||||
ADLDAP_ADMIN_ACCOUNT_SUFFIX=
|
||||
|
||||
# LDAP authentication settings.
|
||||
ADLDAP_PASSWORD_SYNC=false
|
||||
ADLDAP_LOGIN_FALLBACK=false
|
||||
|
||||
ADLDAP_DISCOVER_FIELD=distinguishedname
|
||||
ADLDAP_AUTH_FIELD=distinguishedname
|
||||
|
||||
# Will allow SSO if your server provides an AUTH_USER field.
|
||||
WINDOWS_SSO_DISCOVER=samaccountname
|
||||
WINDOWS_SSO_KEY=AUTH_USER
|
||||
|
||||
# field to sync as local username.
|
||||
ADLDAP_SYNC_FIELD=userprincipalname
|
||||
|
||||
# Leave the following configuration vars as is.
|
||||
# Unless you like to tinker and know what you're doing.
|
||||
APP_NAME=FireflyIII
|
||||
ADLDAP_CONNECTION=default
|
||||
BROADCAST_DRIVER=log
|
||||
QUEUE_DRIVER=sync
|
||||
REDIS_HOST=127.0.0.1
|
||||
@ -108,5 +167,3 @@ IS_DOCKER=false
|
||||
IS_SANDSTORM=true
|
||||
IS_HEROKU=false
|
||||
BUNQ_USE_SANDBOX=false
|
||||
MAILGUN_DOMAIN=
|
||||
MAILGUN_SECRET=
|
||||
|
61
.env.testing
61
.env.testing
@ -49,6 +49,19 @@ DB_CONNECTION=sqlite
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
|
||||
# You can configure another file storage backend if you cannot use the local storage option.
|
||||
# To set this up, fill in the following variables. The upload path is used to store uploaded
|
||||
# files and the export path is to store exported data (before download).
|
||||
SFTP_HOST=
|
||||
SFTP_PORT=
|
||||
SFTP_UPLOAD_PATH=
|
||||
SFTP_EXPORT_PATH=
|
||||
|
||||
# SFTP uses either the username/password combination or the private key to authenticate.
|
||||
SFTP_USERNAME=
|
||||
SFTP_PASSWORD=
|
||||
SFTP_PRIV_KEY=
|
||||
|
||||
# Cookie settings. Should not be necessary to change these.
|
||||
COOKIE_PATH="/"
|
||||
COOKIE_DOMAIN=
|
||||
@ -74,6 +87,9 @@ SPARKPOST_SECRET=
|
||||
SEND_REGISTRATION_MAIL=true
|
||||
SEND_ERROR_MESSAGE=false
|
||||
|
||||
# These messages contain (sensitive) transaction information:
|
||||
SEND_REPORT_JOURNALS=true
|
||||
|
||||
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
|
||||
MAPBOX_API_KEY=
|
||||
|
||||
@ -89,9 +105,52 @@ ANALYTICS_ID=
|
||||
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
|
||||
USE_ENCRYPTION=false
|
||||
|
||||
# Firefly III has two options for user authentication. "eloquent" is the default,
|
||||
# and "adldap" for LDAP servers.
|
||||
# For full instructions on these settings please visit:
|
||||
# https://firefly-iii.readthedocs.io/en/latest/installation/authentication.html
|
||||
LOGIN_PROVIDER=eloquent
|
||||
|
||||
# LDAP connection configuration
|
||||
# or FreeIPA or ActiveDirectory
|
||||
ADLDAP_CONNECTION_SCHEME=OpenLDAP
|
||||
ADLDAP_AUTO_CONNECT=true
|
||||
|
||||
# LDAP connection settings
|
||||
ADLDAP_CONTROLLERS=
|
||||
ADLDAP_PORT=389
|
||||
ADLDAP_TIMEOUT=5
|
||||
ADLDAP_BASEDN=""
|
||||
ADLDAP_FOLLOW_REFFERALS=false
|
||||
ADLDAP_USE_SSL=false
|
||||
ADLDAP_USE_TLS=false
|
||||
|
||||
ADLDAP_ADMIN_USERNAME=
|
||||
ADLDAP_ADMIN_PASSWORD=
|
||||
|
||||
ADLDAP_ACCOUNT_PREFIX=
|
||||
ADLDAP_ACCOUNT_SUFFIX=
|
||||
ADLDAP_ADMIN_ACCOUNT_PREFIX=
|
||||
ADLDAP_ADMIN_ACCOUNT_SUFFIX=
|
||||
|
||||
# LDAP authentication settings.
|
||||
ADLDAP_PASSWORD_SYNC=false
|
||||
ADLDAP_LOGIN_FALLBACK=false
|
||||
|
||||
ADLDAP_DISCOVER_FIELD=distinguishedname
|
||||
ADLDAP_AUTH_FIELD=distinguishedname
|
||||
|
||||
# Will allow SSO if your server provides an AUTH_USER field.
|
||||
WINDOWS_SSO_DISCOVER=samaccountname
|
||||
WINDOWS_SSO_KEY=AUTH_USER
|
||||
|
||||
# field to sync as local username.
|
||||
ADLDAP_SYNC_FIELD=userprincipalname
|
||||
|
||||
# Leave the following configuration vars as is.
|
||||
# Unless you like to tinker and know what you're doing.
|
||||
APP_NAME=FireflyIII
|
||||
ADLDAP_CONNECTION=default
|
||||
BROADCAST_DRIVER=log
|
||||
QUEUE_DRIVER=sync
|
||||
REDIS_HOST=127.0.0.1
|
||||
@ -108,5 +167,3 @@ IS_DOCKER=false
|
||||
IS_SANDSTORM=false
|
||||
IS_HEROKU=false
|
||||
BUNQ_USE_SANDBOX=true
|
||||
MAILGUN_DOMAIN=
|
||||
MAILGUN_SECRET=
|
||||
|
9
.locales
Normal file
9
.locales
Normal file
@ -0,0 +1,9 @@
|
||||
en_US
|
||||
de_DE
|
||||
fr_FR
|
||||
it_IT
|
||||
nl_NL
|
||||
pl_PL
|
||||
pt_BR
|
||||
ru_RU
|
||||
tr_TR
|
@ -1,3 +1,37 @@
|
||||
$ 4.7.8
|
||||
|
||||
- [Issue 1005](https://github.com/firefly-iii/firefly-iii/issues/1005) You can now configure Firefly III to use LDAP.
|
||||
- [Issue 1071](https://github.com/firefly-iii/firefly-iii/issues/1071) You can execute transaction rules using the command line (so you can cronjob it)
|
||||
- [Issue 1108](https://github.com/firefly-iii/firefly-iii/issues/1108) You can now reorder budgets.
|
||||
- [Issue 1159](https://github.com/firefly-iii/firefly-iii/issues/1159) The ability to import transactions from FinTS-enabled banks.
|
||||
- [Issue 1727](https://github.com/firefly-iii/firefly-iii/issues/1727) You can now use SFTP as storage for uploads and exports.
|
||||
- [Issue 1733](https://github.com/firefly-iii/firefly-iii/issues/1733) You can configure Firefly III not to send emails with transaction information in them.
|
||||
- [Issue 1040](https://github.com/firefly-iii/firefly-iii/issues/1040) Fixed various things that would not scale properly in the past.
|
||||
- [Issue 1771](https://github.com/firefly-iii/firefly-iii/issues/1771) A link to the transaction that fits the bill.
|
||||
- [Issue 1800](https://github.com/firefly-iii/firefly-iii/issues/1800) Icon updated to match others.
|
||||
- MySQL database connection now forces the InnoDB to be used.
|
||||
- [Issue 1583](https://github.com/firefly-iii/firefly-iii/issues/1583) Some times recurring transactions would not fire.
|
||||
- [Issue 1607](https://github.com/firefly-iii/firefly-iii/issues/1607) Problems with the bunq API, finally solved?! (I feel like a clickbait YouTube video now)
|
||||
- [Issue 1698](https://github.com/firefly-iii/firefly-iii/issues/1698) Certificate problems in the Docker container
|
||||
- [Issue 1751](https://github.com/firefly-iii/firefly-iii/issues/1751) Bug in autocomplete
|
||||
- [Issue 1760](https://github.com/firefly-iii/firefly-iii/issues/1760) Tag report bad math
|
||||
- [Issue 1765](https://github.com/firefly-iii/firefly-iii/issues/1765) API inconsistencies for piggy banks.
|
||||
- [Issue 1774](https://github.com/firefly-iii/firefly-iii/issues/1774) Integer exception in SQLite databases
|
||||
- [Issue 1775](https://github.com/firefly-iii/firefly-iii/issues/1775) Heroku now supports all locales
|
||||
- [Issue 1778](https://github.com/firefly-iii/firefly-iii/issues/1778) More autocomplete problems fixed
|
||||
- [Issue 1747](https://github.com/firefly-iii/firefly-iii/issues/1747) Rules now stop at the right moment.
|
||||
- [Issue 1781](https://github.com/firefly-iii/firefly-iii/issues/1781) Problems when creating new rules.
|
||||
- [Issue 1784](https://github.com/firefly-iii/firefly-iii/issues/1784) Can now create a liability with an empty balance.
|
||||
- [Issue 1785](https://github.com/firefly-iii/firefly-iii/issues/1785) Redirect error
|
||||
- [Issue 1790](https://github.com/firefly-iii/firefly-iii/issues/1790) Show attachments for bills.
|
||||
- [Issue 1792](https://github.com/firefly-iii/firefly-iii/issues/1792) Mention excluded accounts.
|
||||
- [Issue 1798](https://github.com/firefly-iii/firefly-iii/issues/1798) Could not recreate deleted piggy banks
|
||||
- [Issue 1805](https://github.com/firefly-iii/firefly-iii/issues/1805) Fixes when handling foreign currencies
|
||||
- [Issue 1807](https://github.com/firefly-iii/firefly-iii/issues/1807) Also decrypt deleted records.
|
||||
- [Issue 1812](https://github.com/firefly-iii/firefly-iii/issues/1812) Fix in transactions API
|
||||
- [Issue 1815](https://github.com/firefly-iii/firefly-iii/issues/1815) Opening balance account name can now be translated.
|
||||
- [Issue 1830](https://github.com/firefly-iii/firefly-iii/issues/1830) Multi-user in a single browser could leak autocomplete data.
|
||||
|
||||
# 4.7.7
|
||||
- [Issue 954](https://github.com/firefly-iii/firefly-iii/issues/954) Some additional view chart ranges
|
||||
- [Issue 1710](https://github.com/firefly-iii/firefly-iii/issues/1710) Added a new currency ([hamuz](https://github.com/hamuz))
|
||||
|
@ -553,6 +553,8 @@ opt/app/app/Import/Converter/RabobankDebitCredit.php
|
||||
opt/app/app/Import/JobConfiguration/BunqJobConfiguration.php
|
||||
opt/app/app/Import/JobConfiguration/FakeJobConfiguration.php
|
||||
opt/app/app/Import/JobConfiguration/FileJobConfiguration.php
|
||||
opt/app/app/Import/JobConfiguration/FinTSConfigurationSteps.php
|
||||
opt/app/app/Import/JobConfiguration/FinTSJobConfiguration.php
|
||||
opt/app/app/Import/JobConfiguration/JobConfigurationInterface.php
|
||||
opt/app/app/Import/JobConfiguration/SpectreJobConfiguration.php
|
||||
opt/app/app/Import/JobConfiguration/YnabJobConfiguration.php
|
||||
@ -578,6 +580,7 @@ opt/app/app/Import/Prerequisites/YnabPrerequisites.php
|
||||
opt/app/app/Import/Routine/BunqRoutine.php
|
||||
opt/app/app/Import/Routine/FakeRoutine.php
|
||||
opt/app/app/Import/Routine/FileRoutine.php
|
||||
opt/app/app/Import/Routine/FinTSRoutine.php
|
||||
opt/app/app/Import/Routine/RoutineInterface.php
|
||||
opt/app/app/Import/Routine/SpectreRoutine.php
|
||||
opt/app/app/Import/Routine/YnabRoutine.php
|
||||
@ -782,6 +785,7 @@ opt/app/app/Support/Facades/FireflyConfig.php
|
||||
opt/app/app/Support/Facades/Navigation.php
|
||||
opt/app/app/Support/Facades/Preferences.php
|
||||
opt/app/app/Support/Facades/Steam.php
|
||||
opt/app/app/Support/FinTS/FinTS.php
|
||||
opt/app/app/Support/FireflyConfig.php
|
||||
opt/app/app/Support/Http/Controllers/AugumentData.php
|
||||
opt/app/app/Support/Http/Controllers/BasicDataSupport.php
|
||||
@ -805,6 +809,9 @@ opt/app/app/Support/Import/JobConfiguration/File/ConfigureRolesHandler.php
|
||||
opt/app/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php
|
||||
opt/app/app/Support/Import/JobConfiguration/File/FileConfigurationInterface.php
|
||||
opt/app/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php
|
||||
opt/app/app/Support/Import/JobConfiguration/FinTS/ChooseAccountHandler.php
|
||||
opt/app/app/Support/Import/JobConfiguration/FinTS/FinTSConfigurationInterface.php
|
||||
opt/app/app/Support/Import/JobConfiguration/FinTS/NewFinTSJobHandler.php
|
||||
opt/app/app/Support/Import/JobConfiguration/Spectre/AuthenticatedHandler.php
|
||||
opt/app/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php
|
||||
opt/app/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php
|
||||
@ -833,6 +840,7 @@ opt/app/app/Support/Import/Routine/File/MappedValuesValidator.php
|
||||
opt/app/app/Support/Import/Routine/File/MappingConverger.php
|
||||
opt/app/app/Support/Import/Routine/File/OFXProcessor.php
|
||||
opt/app/app/Support/Import/Routine/File/OpposingAccountMapper.php
|
||||
opt/app/app/Support/Import/Routine/FinTS/StageImportDataHandler.php
|
||||
opt/app/app/Support/Import/Routine/Spectre/StageAuthenticatedHandler.php
|
||||
opt/app/app/Support/Import/Routine/Spectre/StageImportDataHandler.php
|
||||
opt/app/app/Support/Import/Routine/Spectre/StageNewHandler.php
|
||||
@ -1201,6 +1209,7 @@ opt/app/public/images/logos/bunq.png
|
||||
opt/app/public/images/logos/csv.png
|
||||
opt/app/public/images/logos/fake.png
|
||||
opt/app/public/images/logos/file.png
|
||||
opt/app/public/images/logos/fints.png
|
||||
opt/app/public/images/logos/plaid.png
|
||||
opt/app/public/images/logos/quovo.png
|
||||
opt/app/public/images/logos/spectre.png
|
||||
@ -1718,6 +1727,8 @@ opt/app/resources/views/import/file/configure-upload.twig
|
||||
opt/app/resources/views/import/file/map.twig
|
||||
opt/app/resources/views/import/file/new.twig
|
||||
opt/app/resources/views/import/file/roles.twig
|
||||
opt/app/resources/views/import/fints/choose_account.twig
|
||||
opt/app/resources/views/import/fints/new.twig
|
||||
opt/app/resources/views/import/index.twig
|
||||
opt/app/resources/views/import/spectre/accounts.twig
|
||||
opt/app/resources/views/import/spectre/choose-login.twig
|
||||
@ -3030,6 +3041,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Support/MessageBag.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Support/MessageProvider.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Support/Renderable.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Support/Responsable.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Translation/HasLocalePreference.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Translation/Loader.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Translation/Translator.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Validation/Factory.php
|
||||
@ -3099,6 +3111,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/QueueEntityRes
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/RelationNotFoundException.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Concerns/SupportsDefaultModels.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php
|
||||
@ -3485,6 +3498,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/PipelineServiceProvider.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Pipeline/composer.json
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/BeanstalkdQueue.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedClosure.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/Capsule/Manager.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/Connectors/BeanstalkdConnector.php
|
||||
@ -3537,6 +3551,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Queue/QueueManager.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/QueueServiceProvider.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/README.md
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/SerializableClosure.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/SerializesModels.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Queue/SqsQueue.php
|
||||
@ -4373,6 +4388,94 @@ opt/app/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php
|
||||
opt/app/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php
|
||||
opt/app/vendor/monolog/monolog/tests/Monolog/RegistryTest.php
|
||||
opt/app/vendor/monolog/monolog/tests/Monolog/TestCase.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/COMPATIBILITY.md
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/LICENSE
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/README.md
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/Samples/saldo.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/Samples/statement_of_account.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/composer.json
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/composer.lock
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Adapter/AdapterInterface.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Adapter/Curl.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Adapter/Debug.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Adapter/Exception/AdapterException.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Adapter/Exception/CurlException.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Connection.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataElementGroups/EncryptionAlgorithm.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataElementGroups/HashAlgorithm.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataElementGroups/KeyName.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataElementGroups/SecurityDateTime.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataElementGroups/SecurityIdentificationDetails.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataElementGroups/SecurityProfile.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataElementGroups/SignatureAlgorithm.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataTypes/Bin.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataTypes/Dat.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataTypes/Kik.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataTypes/Kti.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/DataTypes/Ktv.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Deg.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Dialog/Dialog.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Dialog/Exception/FailedRequestException.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/FinTs.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Message/AbstractMessage.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Message/Message.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Model/Account.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Model/SEPAAccount.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Model/Saldo.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Model/StatementOfAccount/Statement.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Model/StatementOfAccount/StatementOfAccount.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Model/StatementOfAccount/Transaction.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Parser/Exception/MT940Exception.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Parser/MT940.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Response/GetAccounts.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Response/GetSEPAAccounts.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Response/GetSaldo.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Response/GetStatementOfAccount.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Response/Initialization.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Response/Response.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/AbstractSegment.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HKEND.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HKIDN.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HKKAZ.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HKSAL.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HKSPA.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HKSYN.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HKVVB.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HNHBK.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HNHBS.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HNSHA.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HNSHK.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HNVSD.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/HNVSK.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/NameMapping.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/Segment.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Fhp/Segment/SegmentInterface.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/ConnectionTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataElementGroups/EncryptionAlgorithmTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataElementGroups/HashAlgorithmTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataElementGroups/KeyNameTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataElementGroups/SecurityDateTimeTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataElementGroups/SecurityIdentificationDetailsTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataElementGroups/SecurityProfileTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataElementGroups/SignatureAlgorithmTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataTypes/BinTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataTypes/DatTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataTypes/KikTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataTypes/KtiTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DataTypes/KtvTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/DegTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/FinTsTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/Message/MessageTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/Model/AccountTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/Model/SEPAAccountTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/Model/SaldoTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/Model/StatementOfAccount/StatementOfAccountTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/Model/StatementOfAccount/StatementTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/Model/StatementOfAccount/TransactionTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/Fhp/ResponseTest/ResponseTest.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/lib/Tests/TestInit.php
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/phplint.sh
|
||||
opt/app/vendor/mschindler83/fints-hbci-php/phpunit.xml.dist
|
||||
opt/app/vendor/nesbot/carbon/LICENSE
|
||||
opt/app/vendor/nesbot/carbon/composer.json
|
||||
opt/app/vendor/nesbot/carbon/readme.md
|
||||
@ -4456,6 +4559,23 @@ opt/app/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php
|
||||
opt/app/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php
|
||||
opt/app/vendor/nesbot/carbon/src/Carbon/Translator.php
|
||||
opt/app/vendor/nesbot/carbon/src/JsonSerializable.php
|
||||
opt/app/vendor/opis/closure/CHANGELOG.md
|
||||
opt/app/vendor/opis/closure/LICENSE
|
||||
opt/app/vendor/opis/closure/NOTICE
|
||||
opt/app/vendor/opis/closure/README.md
|
||||
opt/app/vendor/opis/closure/autoload.php
|
||||
opt/app/vendor/opis/closure/composer.json
|
||||
opt/app/vendor/opis/closure/functions.php
|
||||
opt/app/vendor/opis/closure/src/Analyzer.php
|
||||
opt/app/vendor/opis/closure/src/ClosureContext.php
|
||||
opt/app/vendor/opis/closure/src/ClosureScope.php
|
||||
opt/app/vendor/opis/closure/src/ClosureStream.php
|
||||
opt/app/vendor/opis/closure/src/ISecurityProvider.php
|
||||
opt/app/vendor/opis/closure/src/ReflectionClosure.php
|
||||
opt/app/vendor/opis/closure/src/SecurityException.php
|
||||
opt/app/vendor/opis/closure/src/SecurityProvider.php
|
||||
opt/app/vendor/opis/closure/src/SelfReference.php
|
||||
opt/app/vendor/opis/closure/src/SerializableClosure.php
|
||||
opt/app/vendor/paragonie/constant_time_encoding/LICENSE.txt
|
||||
opt/app/vendor/paragonie/constant_time_encoding/README.md
|
||||
opt/app/vendor/paragonie/constant_time_encoding/composer.json
|
||||
@ -5458,6 +5578,7 @@ opt/app/vendor/symfony/debug/Tests/Fixtures/DeprecatedInterface.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/ExtendedFinalMethod.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/FinalClass.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/FinalMethod.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/FinalMethod2Trait.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/InternalClass.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/InternalInterface.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/InternalTrait.php
|
||||
@ -5466,6 +5587,7 @@ opt/app/vendor/symfony/debug/Tests/Fixtures/NonDeprecatedInterface.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/PEARClass.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/Throwing.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/ToStringThrower.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/TraitWithInternalMethod.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/casemismatch.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/notPsr0Bis.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures/psr4/Psr4CaseMismatch.php
|
||||
@ -6539,6 +6661,7 @@ opt/app/vendor/symfony/var-dumper/Tests/Caster/XmlReaderCasterTest.php
|
||||
opt/app/vendor/symfony/var-dumper/Tests/Cloner/DataTest.php
|
||||
opt/app/vendor/symfony/var-dumper/Tests/Cloner/VarClonerTest.php
|
||||
opt/app/vendor/symfony/var-dumper/Tests/Dumper/CliDumperTest.php
|
||||
opt/app/vendor/symfony/var-dumper/Tests/Dumper/FunctionsTest.php
|
||||
opt/app/vendor/symfony/var-dumper/Tests/Dumper/HtmlDumperTest.php
|
||||
opt/app/vendor/symfony/var-dumper/Tests/Dumper/ServerDumperTest.php
|
||||
opt/app/vendor/symfony/var-dumper/Tests/Fixtures/FooInterface.php
|
||||
|
@ -15,8 +15,8 @@ const pkgdef :Spk.PackageDefinition = (
|
||||
|
||||
manifest = (
|
||||
appTitle = (defaultText = "Firefly III"),
|
||||
appVersion = 17,
|
||||
appMarketingVersion = (defaultText = "4.7.7"),
|
||||
appVersion = 18,
|
||||
appMarketingVersion = (defaultText = "4.7.8"),
|
||||
|
||||
actions = [
|
||||
# Define your "new document" handlers here.
|
||||
|
11
Dockerfile
11
Dockerfile
@ -8,9 +8,9 @@ ENV CORES ${CORES:-1}
|
||||
ENV FIREFLY_PATH /var/www/firefly-iii/
|
||||
ENV CURL_VERSION 7.60.0
|
||||
ENV OPENSSL_VERSION 1.1.1-pre6
|
||||
ENV COMPOSER_ALLOW_SUPERUSER 1
|
||||
|
||||
LABEL version="1.0" maintainer="thegrumpydictator@gmail.com"
|
||||
|
||||
LABEL version="1.1" maintainer="thegrumpydictator@gmail.com"
|
||||
|
||||
# install packages
|
||||
RUN apt-get update -y && \
|
||||
@ -20,6 +20,7 @@ RUN apt-get update -y && \
|
||||
wget \
|
||||
libpng-dev \
|
||||
libicu-dev \
|
||||
libldap2-dev \
|
||||
libedit-dev \
|
||||
libtidy-dev \
|
||||
libxml2-dev \
|
||||
@ -35,6 +36,8 @@ RUN apt-get update -y && \
|
||||
locales && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
# LDAP install
|
||||
RUN docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ && docker-php-ext-install ldap
|
||||
|
||||
# Install latest curl
|
||||
RUN cd /tmp && \
|
||||
@ -66,6 +69,9 @@ COPY ./.deploy/docker/firefly-iii.conf /etc/supervisor/conf.d/firefly-iii.conf
|
||||
# copy cron job supervisor conf file.
|
||||
COPY ./.deploy/docker/cronjob.conf /etc/supervisor/conf.d/cronjob.conf
|
||||
|
||||
# copy ca certs to correct location
|
||||
COPY ./.deploy/docker/cacert.pem /usr/local/ssl/cert.pem
|
||||
|
||||
# test crons added via crontab
|
||||
RUN echo "0 3 * * * /usr/local/bin/php /var/www/firefly-iii/artisan firefly:cron" | crontab -
|
||||
#RUN (crontab -l ; echo "*/1 * * * * free >> /var/www/firefly-iii/public/cron.html") 2>&1 | crontab -
|
||||
@ -104,7 +110,6 @@ ADD . $FIREFLY_PATH
|
||||
RUN rm -rf /usr/local/lib/libcurl.so.4 && ln -s /usr/lib/x86_64-linux-gnu/libcurl.so.4.4.0 /usr/local/lib/libcurl.so.4
|
||||
|
||||
# Run composer
|
||||
ENV COMPOSER_ALLOW_SUPERUSER 1
|
||||
RUN composer install --prefer-dist --no-dev --no-scripts --no-suggest
|
||||
|
||||
# Expose port 80
|
||||
|
3
app.json
3
app.json
@ -51,6 +51,9 @@
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "heroku/php"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/heroku/heroku-buildpack-locale"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
|
@ -290,6 +290,7 @@ class TransactionController extends Controller
|
||||
'withdrawal' => [TransactionType::WITHDRAWAL,],
|
||||
'withdrawals' => [TransactionType::WITHDRAWAL,],
|
||||
'expense' => [TransactionType::WITHDRAWAL,],
|
||||
'expenses' => [TransactionType::WITHDRAWAL,],
|
||||
'income' => [TransactionType::DEPOSIT,],
|
||||
'deposit' => [TransactionType::DEPOSIT,],
|
||||
'deposits' => [TransactionType::DEPOSIT,],
|
||||
|
@ -58,8 +58,8 @@ class PiggyBankRequest extends Request
|
||||
'account_id' => $this->integer('account_id'),
|
||||
'targetamount' => $this->string('target_amount'),
|
||||
'current_amount' => $current,
|
||||
'start_date' => $this->date('start_date'),
|
||||
'target_date' => $this->date('target_date'),
|
||||
'startdate' => $this->date('start_date'),
|
||||
'targetdate' => $this->date('target_date'),
|
||||
'notes' => $this->string('notes'),
|
||||
];
|
||||
}
|
||||
|
@ -75,8 +75,10 @@ class RuleRequest extends Request
|
||||
$validTriggers = array_keys(config('firefly.rule-triggers'));
|
||||
$validActions = array_keys(config('firefly.rule-actions'));
|
||||
|
||||
// some actions require text:
|
||||
$contextActions = implode(',', config('firefly.rule-actions-text'));
|
||||
// some triggers and actions require text:
|
||||
$contextTriggers = implode(',', config('firefly.context-rule-triggers'));
|
||||
$contextActions = implode(',', config('firefly.context-rule-actions'));
|
||||
|
||||
|
||||
$rules = [
|
||||
'title' => 'required|between:1,100|uniqueObjectForUser:rules,title',
|
||||
@ -86,7 +88,7 @@ class RuleRequest extends Request
|
||||
'trigger' => 'required|in:store-journal,update-journal',
|
||||
'rule_triggers.*.name' => 'required|in:' . implode(',', $validTriggers),
|
||||
'rule_triggers.*.stop_processing' => 'boolean',
|
||||
'rule_triggers.*.value' => 'required|min:1|ruleTriggerValue',
|
||||
'rule_triggers.*.value' => 'required_if:rule_actions.*.type,' . $contextTriggers . '|min:1|ruleTriggerValue',
|
||||
'rule_actions.*.name' => 'required|in:' . implode(',', $validActions),
|
||||
'rule_actions.*.value' => 'required_if:rule_actions.*.type,' . $contextActions . '|ruleActionValue',
|
||||
'rule_actions.*.stop_processing' => 'boolean',
|
||||
|
392
app/Console/Commands/ApplyRules.php
Normal file
392
app/Console/Commands/ApplyRules.php
Normal file
@ -0,0 +1,392 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
|
||||
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
|
||||
use FireflyIII\TransactionRules\Processor;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class ApplyRules
|
||||
*/
|
||||
class ApplyRules extends Command
|
||||
{
|
||||
use VerifiesAccessToken;
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This command will apply your rules and rule groups on a selection of your transactions.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature
|
||||
= 'firefly:apply-rules
|
||||
{--user=1 : The user ID that the import should import for.}
|
||||
{--token= : The user\'s access token.}
|
||||
{--accounts= : A comma-separated list of asset accounts or liabilities to apply your rules to.}
|
||||
{--rule_groups= : A comma-separated list of rule groups to apply. Take the ID\'s of these rule groups from the Firefly III interface.}
|
||||
{--rules= : A comma-separated list of rules to apply. Take the ID\'s of these rules from the Firefly III interface. Using this option overrules the option that selects rule groups.}
|
||||
{--all_rules : If set, will overrule both settings and simply apply ALL of your rules.}
|
||||
{--start_date= : The date of the earliest transaction to be included (inclusive). If omitted, will be your very first transaction ever. Format: YYYY-MM-DD}
|
||||
{--end_date= : The date of the latest transaction to be included (inclusive). If omitted, will be your latest transaction ever. Format: YYYY-MM-DD}';
|
||||
/** @var Collection */
|
||||
private $accounts;
|
||||
/** @var Carbon */
|
||||
private $endDate;
|
||||
/** @var Collection */
|
||||
private $results;
|
||||
/** @var Collection */
|
||||
private $ruleGroups;
|
||||
/** @var Collection */
|
||||
private $rules;
|
||||
/** @var Carbon */
|
||||
private $startDate;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->accounts = new Collection;
|
||||
$this->rules = new Collection;
|
||||
$this->ruleGroups = new Collection;
|
||||
$this->results = new Collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
if (!$this->verifyAccessToken()) {
|
||||
$this->error('Invalid access token.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$result = $this->verifyInput();
|
||||
if (false === $result) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// get transactions from asset accounts.
|
||||
/** @var TransactionCollectorInterface $collector */
|
||||
$collector = app(TransactionCollectorInterface::class);
|
||||
$collector->setUser($this->getUser());
|
||||
$collector->setAccounts($this->accounts);
|
||||
$collector->setRange($this->startDate, $this->endDate);
|
||||
$transactions = $collector->getTransactions();
|
||||
$count = $transactions->count();
|
||||
|
||||
// first run all rule groups:
|
||||
/** @var RuleGroupRepositoryInterface $ruleGroupRepos */
|
||||
$ruleGroupRepos = app(RuleGroupRepositoryInterface::class);
|
||||
$ruleGroupRepos->setUser($this->getUser());
|
||||
|
||||
/** @var RuleGroup $ruleGroup */
|
||||
foreach ($this->ruleGroups as $ruleGroup) {
|
||||
$this->line(sprintf('Going to apply rule group "%s" to %d transaction(s).', $ruleGroup->title, $count));
|
||||
$rules = $ruleGroupRepos->getActiveStoreRules($ruleGroup);
|
||||
$this->applyRuleSelection($rules, $transactions, true);
|
||||
}
|
||||
|
||||
// then run all rules (rule groups should be empty).
|
||||
if ($this->rules->count() > 0) {
|
||||
|
||||
$this->line(sprintf('Will apply %d rule(s) to %d transaction(s)', $this->rules->count(), $transactions->count()));
|
||||
$this->applyRuleSelection($this->rules, $transactions, false);
|
||||
}
|
||||
|
||||
// filter results:
|
||||
$this->results = $this->results->unique(
|
||||
function (Transaction $transaction) {
|
||||
return (int)$transaction->journal_id;
|
||||
}
|
||||
);
|
||||
|
||||
$this->line('');
|
||||
if (0 === $this->results->count()) {
|
||||
$this->line('The rules were fired but did not influence any transactions.');
|
||||
}
|
||||
if ($this->results->count() > 0) {
|
||||
$this->line(sprintf('The rule(s) was/were fired, and influenced %d transaction(s).', $this->results->count()));
|
||||
foreach ($this->results as $result) {
|
||||
$this->line(
|
||||
vsprintf(
|
||||
'Transaction #%d: "%s" (%s %s)',
|
||||
[
|
||||
$result->journal_id,
|
||||
$result->description,
|
||||
$result->transaction_currency_code,
|
||||
round($result->transaction_amount, $result->transaction_currency_dp),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $rules
|
||||
* @param Collection $transactions
|
||||
* @param bool $breakProcessing
|
||||
*
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
private function applyRuleSelection(Collection $rules, Collection $transactions, bool $breakProcessing): void
|
||||
{
|
||||
$bar = $this->output->createProgressBar($rules->count() * $transactions->count());
|
||||
foreach ($rules as $rule) {
|
||||
/** @var Processor $processor */
|
||||
$processor = app(Processor::class);
|
||||
$processor->make($rule, true);
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
/** @var Rule $rule */
|
||||
$bar->advance();
|
||||
$result = $processor->handleTransaction($transaction);
|
||||
if (true === $result) {
|
||||
$this->results->push($transaction);
|
||||
}
|
||||
}
|
||||
if (true === $rule->stop_processing && true === $breakProcessing) {
|
||||
$this->line('');
|
||||
$this->line(sprintf('Rule #%d ("%s") says to stop processing.', $rule->id, $rule->title));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->line('');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function grabAllRules(): void
|
||||
{
|
||||
if (true === $this->option('all_rules')) {
|
||||
/** @var RuleRepositoryInterface $ruleRepos */
|
||||
$ruleRepos = app(RuleRepositoryInterface::class);
|
||||
$ruleRepos->setUser($this->getUser());
|
||||
$this->rules = $ruleRepos->getAll();
|
||||
|
||||
// reset rule groups.
|
||||
$this->ruleGroups = new Collection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function parseDates(): void
|
||||
{
|
||||
// parse start date.
|
||||
$startDate = Carbon::create()->startOfMonth();
|
||||
$startString = $this->option('start_date');
|
||||
if (null === $startString) {
|
||||
/** @var JournalRepositoryInterface $repository */
|
||||
$repository = app(JournalRepositoryInterface::class);
|
||||
$repository->setUser($this->getUser());
|
||||
$first = $repository->firstNull();
|
||||
if (null !== $first) {
|
||||
$startDate = $first->date;
|
||||
}
|
||||
}
|
||||
if (null !== $startString && '' !== $startString) {
|
||||
$startDate = Carbon::createFromFormat('Y-m-d', $startString);
|
||||
}
|
||||
|
||||
// parse end date
|
||||
$endDate = Carbon::now();
|
||||
$endString = $this->option('end_date');
|
||||
if (null !== $endString && '' !== $endString) {
|
||||
$endDate = Carbon::createFromFormat('Y-m-d', $endString);
|
||||
}
|
||||
|
||||
if ($startDate > $endDate) {
|
||||
[$endDate, $startDate] = [$startDate, $endDate];
|
||||
}
|
||||
|
||||
$this->startDate = $startDate;
|
||||
$this->endDate = $endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
private function verifyInput(): bool
|
||||
{
|
||||
// verify account.
|
||||
$result = $this->verifyInputAccounts();
|
||||
if (false === $result) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// verify rule groups.
|
||||
$result = $this->verifyRuleGroups();
|
||||
if (false === $result) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// verify rules.
|
||||
$result = $this->verifyRules();
|
||||
if (false === $result) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$this->grabAllRules();
|
||||
$this->parseDates();
|
||||
|
||||
//$this->line('Number of rules found: ' . $this->rules->count());
|
||||
$this->line('Start date is ' . $this->startDate->format('Y-m-d'));
|
||||
$this->line('End date is ' . $this->endDate->format('Y-m-d'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
private function verifyInputAccounts(): bool
|
||||
{
|
||||
$accountString = $this->option('accounts');
|
||||
if (null === $accountString || '' === $accountString) {
|
||||
$this->error('Please use the --accounts to indicate the accounts to apply rules to.');
|
||||
|
||||
return false;
|
||||
}
|
||||
$finalList = new Collection;
|
||||
$accountList = explode(',', $accountString);
|
||||
|
||||
if (0 === \count($accountList)) {
|
||||
$this->error('Please use the --accounts to indicate the accounts to apply rules to.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$accountRepository->setUser($this->getUser());
|
||||
|
||||
foreach ($accountList as $accountId) {
|
||||
$accountId = (int)$accountId;
|
||||
$account = $accountRepository->findNull($accountId);
|
||||
if (null !== $account
|
||||
&& \in_array(
|
||||
$account->accountType->type, [AccountType::DEFAULT, AccountType::DEBT, AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE], true
|
||||
)) {
|
||||
$finalList->push($account);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === $finalList->count()) {
|
||||
$this->error('Please make sure all accounts in --accounts are asset accounts or liabilities.');
|
||||
|
||||
return false;
|
||||
}
|
||||
$this->accounts = $finalList;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
private function verifyRuleGroups(): bool
|
||||
{
|
||||
$ruleGroupString = $this->option('rule_groups');
|
||||
if (null === $ruleGroupString || '' === $ruleGroupString) {
|
||||
// can be empty.
|
||||
return true;
|
||||
}
|
||||
$ruleGroupList = explode(',', $ruleGroupString);
|
||||
|
||||
if (0 === \count($ruleGroupList)) {
|
||||
// can be empty.
|
||||
|
||||
return true;
|
||||
}
|
||||
/** @var RuleGroupRepositoryInterface $ruleGroupRepos */
|
||||
$ruleGroupRepos = app(RuleGroupRepositoryInterface::class);
|
||||
$ruleGroupRepos->setUser($this->getUser());
|
||||
|
||||
foreach ($ruleGroupList as $ruleGroupId) {
|
||||
$ruleGroupId = (int)$ruleGroupId;
|
||||
$ruleGroup = $ruleGroupRepos->find($ruleGroupId);
|
||||
$this->ruleGroups->push($ruleGroup);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
private function verifyRules(): bool
|
||||
{
|
||||
$ruleString = $this->option('rules');
|
||||
if (null === $ruleString || '' === $ruleString) {
|
||||
// can be empty.
|
||||
return true;
|
||||
}
|
||||
$finalList = new Collection;
|
||||
$ruleList = explode(',', $ruleString);
|
||||
|
||||
if (0 === \count($ruleList)) {
|
||||
// can be empty.
|
||||
|
||||
return true;
|
||||
}
|
||||
/** @var RuleRepositoryInterface $ruleRepos */
|
||||
$ruleRepos = app(RuleRepositoryInterface::class);
|
||||
$ruleRepos->setUser($this->getUser());
|
||||
|
||||
foreach ($ruleList as $ruleId) {
|
||||
$ruleId = (int)$ruleId;
|
||||
$rule = $ruleRepos->find($ruleId);
|
||||
if (null !== $rule) {
|
||||
$finalList->push($rule);
|
||||
}
|
||||
}
|
||||
if ($finalList->count() > 0) {
|
||||
// reset rule groups.
|
||||
$this->ruleGroups = new Collection;
|
||||
$this->rules = $finalList;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -33,7 +33,7 @@ use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Storage;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Class CreateExport.
|
||||
@ -136,10 +136,14 @@ class CreateExport extends Command
|
||||
$processor->createZipFile();
|
||||
$disk = Storage::disk('export');
|
||||
$fileName = sprintf('export-%s.zip', date('Y-m-d_H-i-s'));
|
||||
$disk->move($job->key . '.zip', $fileName);
|
||||
$localPath = storage_path('export') . '/' . $job->key . '.zip';
|
||||
|
||||
$this->line('The export has finished! You can find the ZIP file in this location:');
|
||||
$this->line(storage_path(sprintf('export/%s', $fileName)));
|
||||
// "move" from local to export disk
|
||||
$disk->put($fileName, file_get_contents($localPath));
|
||||
unlink($localPath);
|
||||
|
||||
$this->line('The export has finished! You can find the ZIP file in export disk with file name:');
|
||||
$this->line($fileName);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class EncryptFile extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Encrypts a file and places it in the storage/upload directory.';
|
||||
protected $description = 'Encrypts a file and places it in the upload disk.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
|
@ -79,7 +79,7 @@ class UseEncryption extends Command
|
||||
$fqn = sprintf('FireflyIII\Models\%s', $class);
|
||||
$encrypt = true === config('firefly.encryption') ? 0 : 1;
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
$set = $fqn::where($indicator, $encrypt)->get();
|
||||
$set = $fqn::where($indicator, $encrypt)->withTrashed()->get();
|
||||
|
||||
foreach ($set as $entry) {
|
||||
$newName = $entry->$field;
|
||||
|
@ -23,17 +23,37 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Trait VerifiesAccessToken.
|
||||
*
|
||||
* Verifies user access token for sensitive commands.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
trait VerifiesAccessToken
|
||||
{
|
||||
/**
|
||||
* @return User
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getUser(): User
|
||||
{
|
||||
$userId = (int)$this->option('user');
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
$user = $repository->findNull($userId);
|
||||
if (null === $user) {
|
||||
throw new FireflyException('User is unexpectedly NULL');
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method to make sure trait knows about method "option".
|
||||
*
|
||||
|
@ -42,7 +42,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Storage;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
@ -184,7 +184,7 @@ class ExpandedProcessor implements ProcessorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ZIP file.
|
||||
* Create a ZIP file locally (!) in storage_path('export').
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
@ -195,9 +195,9 @@ class ExpandedProcessor implements ProcessorInterface
|
||||
{
|
||||
$zip = new ZipArchive;
|
||||
$file = $this->job->key . '.zip';
|
||||
$fullPath = storage_path('export') . '/' . $file;
|
||||
$localPath = storage_path('export') . '/' . $file;
|
||||
|
||||
if (true !== $zip->open($fullPath, ZipArchive::CREATE)) {
|
||||
if (true !== $zip->open($localPath, ZipArchive::CREATE)) {
|
||||
throw new FireflyException('Cannot store zip file.');
|
||||
}
|
||||
// for each file in the collection, add it to the zip file.
|
||||
|
@ -26,7 +26,7 @@ namespace FireflyIII\Export\Exporter;
|
||||
|
||||
use FireflyIII\Export\Entry\Entry;
|
||||
use League\Csv\Writer;
|
||||
use Storage;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Class CsvExporter.
|
||||
@ -57,15 +57,11 @@ class CsvExporter extends BasicExporter implements ExporterInterface
|
||||
*/
|
||||
public function run(): bool
|
||||
{
|
||||
// create temporary file:
|
||||
$this->tempFile();
|
||||
|
||||
// necessary for CSV writer:
|
||||
$fullPath = storage_path('export') . DIRECTORY_SEPARATOR . $this->fileName;
|
||||
|
||||
// choose file name:
|
||||
$this->fileName = $this->job->key . '-records.csv';
|
||||
|
||||
//we create the CSV into memory
|
||||
$writer = Writer::createFromPath($fullPath);
|
||||
$writer = Writer::createFromString('');
|
||||
$rows = [];
|
||||
|
||||
// get field names for header row:
|
||||
@ -86,18 +82,9 @@ class CsvExporter extends BasicExporter implements ExporterInterface
|
||||
$rows[] = $line;
|
||||
}
|
||||
$writer->insertAll($rows);
|
||||
$disk = Storage::disk('export');
|
||||
$disk->put($this->fileName, $writer->getContent());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a temp file.
|
||||
*/
|
||||
private function tempFile()
|
||||
{
|
||||
$this->fileName = $this->job->key . '-records.csv';
|
||||
// touch file in export directory:
|
||||
$disk = Storage::disk('export');
|
||||
$disk->put($this->fileName, '');
|
||||
}
|
||||
}
|
||||
|
@ -120,8 +120,8 @@ class TransactionFactory
|
||||
Log::debug(sprintf('Expect source destination to be of type "%s"', $destinationType));
|
||||
|
||||
// find source and destination account:
|
||||
$sourceAccount = $this->findAccount($sourceType, $data['source_id'], $data['source_name']);
|
||||
$destinationAccount = $this->findAccount($destinationType, $data['destination_id'], $data['destination_name']);
|
||||
$sourceAccount = $this->findAccount($sourceType, (int)$data['source_id'], $data['source_name']);
|
||||
$destinationAccount = $this->findAccount($destinationType, (int)$data['destination_id'], $data['destination_name']);
|
||||
|
||||
if (null === $sourceAccount || null === $destinationAccount) {
|
||||
$debugData = $data;
|
||||
|
@ -29,6 +29,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
||||
use FireflyIII\Generator\Report\Support;
|
||||
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
|
||||
use FireflyIII\Helpers\Filter\DoubleTransactionFilter;
|
||||
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
|
||||
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
|
||||
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
|
||||
@ -216,9 +217,9 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
||||
->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||
->setTags($this->tags)->withOpposingAccount();
|
||||
$collector->removeFilter(TransferFilter::class);
|
||||
|
||||
$collector->addFilter(OpposingAccountFilter::class);
|
||||
$collector->addFilter(PositiveAmountFilter::class);
|
||||
$collector->addFilter(DoubleTransactionFilter::class);
|
||||
|
||||
$transactions = $collector->getTransactions();
|
||||
$this->expenses = $transactions;
|
||||
|
@ -45,6 +45,12 @@ class AutomationHandler
|
||||
*/
|
||||
public function reportJournals(RequestedReportOnJournals $event): bool
|
||||
{
|
||||
$sendReport = envNonEmpty('SEND_REPORT_JOURNALS', true);
|
||||
|
||||
if (false === $sendReport) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Log::debug('In reportJournals.');
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
|
@ -30,7 +30,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\MessageBag;
|
||||
use Log;
|
||||
use Storage;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
/**
|
||||
@ -94,7 +94,7 @@ class AttachmentHelper implements AttachmentHelperInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file location for an attachment,
|
||||
* Returns the file path relative to upload disk for an attachment,
|
||||
*
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
@ -102,8 +102,7 @@ class AttachmentHelper implements AttachmentHelperInterface
|
||||
*/
|
||||
public function getAttachmentLocation(Attachment $attachment): string
|
||||
{
|
||||
$path = sprintf('%s%sat-%d.data', storage_path('upload'), DIRECTORY_SEPARATOR, (int)$attachment->id);
|
||||
|
||||
$path = sprintf('%sat-%d.data', DIRECTORY_SEPARATOR, (int)$attachment->id);
|
||||
return $path;
|
||||
}
|
||||
|
||||
@ -192,7 +191,7 @@ class AttachmentHelper implements AttachmentHelperInterface
|
||||
public function saveAttachmentsForModel(object $model, ?array $files): bool
|
||||
{
|
||||
if(!($model instanceof Model)) {
|
||||
return false;
|
||||
return false; // @codeCoverageIgnore
|
||||
}
|
||||
Log::debug(sprintf('Now in saveAttachmentsForModel for model %s', \get_class($model)));
|
||||
if (\is_array($files)) {
|
||||
@ -270,7 +269,7 @@ class AttachmentHelper implements AttachmentHelperInterface
|
||||
$fileObject->rewind();
|
||||
|
||||
if(0 === $file->getSize()) {
|
||||
throw new FireflyException('Cannot upload empty or non-existent file.');
|
||||
throw new FireflyException('Cannot upload empty or non-existent file.'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$content = $fileObject->fread($file->getSize());
|
||||
|
@ -28,6 +28,7 @@ use Carbon\Carbon;
|
||||
use DB;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Filter\CountAttachmentsFilter;
|
||||
use FireflyIII\Helpers\Filter\DoubleTransactionFilter;
|
||||
use FireflyIII\Helpers\Filter\FilterInterface;
|
||||
use FireflyIII\Helpers\Filter\InternalTransferFilter;
|
||||
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
|
||||
@ -53,26 +54,14 @@ use Log;
|
||||
/**
|
||||
* Class TransactionCollector
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class TransactionCollector implements TransactionCollectorInterface
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if ('testing' === env('APP_ENV')) {
|
||||
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @var array */
|
||||
private $accountIds = [];
|
||||
/** @var int */
|
||||
private $count = 0;
|
||||
|
||||
/** @var array */
|
||||
private $fields
|
||||
= [
|
||||
@ -139,6 +128,16 @@ class TransactionCollector implements TransactionCollectorInterface
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if ('testing' === env('APP_ENV')) {
|
||||
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filter
|
||||
*
|
||||
@ -253,6 +252,30 @@ class TransactionCollector implements TransactionCollectorInterface
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LengthAwarePaginator
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getPaginatedTransactions(): LengthAwarePaginator
|
||||
{
|
||||
if (true === $this->run) {
|
||||
throw new FireflyException('Cannot getPaginatedTransactions after run in TransactionCollector.');
|
||||
}
|
||||
$this->count();
|
||||
$set = $this->getTransactions();
|
||||
$journals = new LengthAwarePaginator($set, $this->count, $this->limit, $this->page);
|
||||
|
||||
return $journals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EloquentBuilder
|
||||
*/
|
||||
public function getQuery(): EloquentBuilder
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
@ -309,30 +332,6 @@ class TransactionCollector implements TransactionCollectorInterface
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LengthAwarePaginator
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getPaginatedTransactions(): LengthAwarePaginator
|
||||
{
|
||||
if (true === $this->run) {
|
||||
throw new FireflyException('Cannot getPaginatedTransactions after run in TransactionCollector.');
|
||||
}
|
||||
$this->count();
|
||||
$set = $this->getTransactions();
|
||||
$journals = new LengthAwarePaginator($set, $this->count, $this->limit, $this->page);
|
||||
|
||||
return $journals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EloquentBuilder
|
||||
*/
|
||||
public function getQuery(): EloquentBuilder
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TransactionCollectorInterface
|
||||
*/
|
||||
@ -784,14 +783,15 @@ class TransactionCollector implements TransactionCollectorInterface
|
||||
{
|
||||
// create all possible filters:
|
||||
$filters = [
|
||||
InternalTransferFilter::class => new InternalTransferFilter($this->accountIds),
|
||||
OpposingAccountFilter::class => new OpposingAccountFilter($this->accountIds),
|
||||
TransferFilter::class => new TransferFilter,
|
||||
PositiveAmountFilter::class => new PositiveAmountFilter,
|
||||
NegativeAmountFilter::class => new NegativeAmountFilter,
|
||||
SplitIndicatorFilter::class => new SplitIndicatorFilter,
|
||||
CountAttachmentsFilter::class => new CountAttachmentsFilter,
|
||||
TransactionViewFilter::class => new TransactionViewFilter,
|
||||
InternalTransferFilter::class => new InternalTransferFilter($this->accountIds),
|
||||
OpposingAccountFilter::class => new OpposingAccountFilter($this->accountIds),
|
||||
TransferFilter::class => new TransferFilter,
|
||||
PositiveAmountFilter::class => new PositiveAmountFilter,
|
||||
NegativeAmountFilter::class => new NegativeAmountFilter,
|
||||
SplitIndicatorFilter::class => new SplitIndicatorFilter,
|
||||
CountAttachmentsFilter::class => new CountAttachmentsFilter,
|
||||
TransactionViewFilter::class => new TransactionViewFilter,
|
||||
DoubleTransactionFilter::class => new DoubleTransactionFilter,
|
||||
];
|
||||
Log::debug(sprintf('Will run %d filters on the set.', \count($this->filters)));
|
||||
foreach ($this->filters as $enabled) {
|
||||
|
60
app/Helpers/Filter/DoubleTransactionFilter.php
Normal file
60
app/Helpers/Filter/DoubleTransactionFilter.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* DoubleTransactionFilter.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\Helpers\Filter;
|
||||
|
||||
|
||||
use FireflyIII\Models\Transaction;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
*
|
||||
* Used when the final collection contains double transactions, which can happen when viewing the tag report.
|
||||
* Class DoubleTransactionFilter
|
||||
*/
|
||||
class DoubleTransactionFilter implements FilterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Apply the filter.
|
||||
*
|
||||
* @param Collection $set
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function filter(Collection $set): Collection
|
||||
{
|
||||
$count = [];
|
||||
$result = new Collection;
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($set as $transaction) {
|
||||
$id = (int)$transaction->id;
|
||||
$count[$id] = isset($count[$id]) ? $count[$id] + 1 : 1;
|
||||
if (1 === $count[$id]) {
|
||||
$result->push($transaction);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -63,10 +63,14 @@ class TransferFilter implements FilterInterface
|
||||
$key = $journalId . '-' . implode(',', $transactionIds) . '-' . implode(',', $accountIds) . '-' . $amount;
|
||||
Log::debug(sprintf('Current transaction key is "%s"', $key));
|
||||
if (!isset($count[$key])) {
|
||||
Log::debug(sprintf('First instance of transaction #%d, add it.', $transaction->id));
|
||||
// not yet counted? add to new set and count it:
|
||||
$new->push($transaction);
|
||||
$count[$key] = 1;
|
||||
}
|
||||
if (isset($count[$key])) {
|
||||
Log::debug(sprintf('Second instance of transaction #%d, do NOT add it.', $transaction->id));
|
||||
}
|
||||
}
|
||||
|
||||
return $new;
|
||||
|
@ -123,7 +123,6 @@ class ReconcileController extends Controller
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function reconcile(Account $account, Carbon $start = null, Carbon $end = null)
|
||||
{
|
||||
|
@ -30,6 +30,7 @@ use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class ForgotPasswordController
|
||||
@ -58,6 +59,13 @@ class ForgotPasswordController extends Controller
|
||||
*/
|
||||
public function sendResetLinkEmail(Request $request, UserRepositoryInterface $repository)
|
||||
{
|
||||
$loginProvider = envNonEmpty('LOGIN_PROVIDER','eloquent');
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$message = sprintf('Cannot reset password when authenticating over "%s".', $loginProvider);
|
||||
Log::error($message);
|
||||
return view('error', compact('message'));
|
||||
}
|
||||
|
||||
$this->validateEmail($request);
|
||||
|
||||
// verify if the user is not a demo user. If so, we give him back an error.
|
||||
@ -90,6 +98,13 @@ class ForgotPasswordController extends Controller
|
||||
*/
|
||||
public function showLinkRequestForm()
|
||||
{
|
||||
$loginProvider = envNonEmpty('LOGIN_PROVIDER','eloquent');
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$message = sprintf('Cannot reset password when authenticating over "%s".', $loginProvider);
|
||||
|
||||
return view('error', compact('message'));
|
||||
}
|
||||
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
|
@ -129,8 +129,9 @@ class LoginController extends Controller
|
||||
*/
|
||||
public function showLoginForm(Request $request)
|
||||
{
|
||||
$count = DB::table('users')->count();
|
||||
if (0 === $count) {
|
||||
$count = DB::table('users')->count();
|
||||
$loginProvider = envNonEmpty('LOGIN_PROVIDER','eloquent');
|
||||
if (0 === $count && 'eloquent' === $loginProvider) {
|
||||
return redirect(route('register')); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
@ -141,13 +142,20 @@ class LoginController extends Controller
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
$allowRegistration = true;
|
||||
$allowReset = true;
|
||||
if (true === $singleUserMode && $userCount > 0) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
// single user mode is ignored when the user is not using eloquent:
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$allowRegistration = false;
|
||||
$allowReset = false;
|
||||
}
|
||||
|
||||
$email = $request->old('email');
|
||||
$remember = $request->old('remember');
|
||||
|
||||
return view('auth.login', compact('allowRegistration', 'email', 'remember'));
|
||||
return view('auth.login', compact('allowRegistration', 'email', 'remember','allowReset'));
|
||||
}
|
||||
}
|
||||
|
@ -71,9 +71,19 @@ class RegisterController extends Controller
|
||||
public function register(Request $request)
|
||||
{
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
if (true === $singleUserMode && $userCount > 0) {
|
||||
$allowRegistration = true;
|
||||
$loginProvider = envNonEmpty('LOGIN_PROVIDER','eloquent');
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
if (true === $singleUserMode && $userCount > 0 && 'eloquent' === $loginProvider) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
if (false === $allowRegistration) {
|
||||
$message = 'Registration is currently not available.';
|
||||
|
||||
return view('error', compact('message'));
|
||||
@ -102,13 +112,25 @@ class RegisterController extends Controller
|
||||
*/
|
||||
public function showRegistrationForm(Request $request)
|
||||
{
|
||||
// is demo site?
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
|
||||
$allowRegistration = true;
|
||||
$loginProvider = envNonEmpty('LOGIN_PROVIDER','eloquent');
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
if (true === $singleUserMode && $userCount > 0) {
|
||||
if (true === $isDemoSite) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
if (true === $singleUserMode && $userCount > 0 && 'eloquent' === $loginProvider) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
if (false === $allowRegistration) {
|
||||
$message = 'Registration is currently not available.';
|
||||
|
||||
return view('error', compact('message'));
|
||||
|
@ -28,6 +28,7 @@ use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
|
||||
/**
|
||||
* Class ResetPasswordController
|
||||
@ -70,7 +71,15 @@ class ResetPasswordController extends Controller
|
||||
*/
|
||||
public function showResetForm(Request $request, $token = null)
|
||||
{
|
||||
// is allowed to?
|
||||
$loginProvider = envNonEmpty('LOGIN_PROVIDER','eloquent');
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$message = sprintf('Cannot reset password when authenticating over "%s".', $loginProvider);
|
||||
|
||||
return view('error', compact('message'));
|
||||
}
|
||||
|
||||
|
||||
// is allowed to register?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
$allowRegistration = true;
|
||||
@ -83,4 +92,42 @@ class ResetPasswordController extends Controller
|
||||
['token' => $token, 'email' => $request->email, 'allowRegistration' => $allowRegistration]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the given user's password.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function reset(Request $request)
|
||||
{
|
||||
$loginProvider = envNonEmpty('LOGIN_PROVIDER','eloquent');
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$message = sprintf('Cannot reset password when authenticating over "%s".', $loginProvider);
|
||||
|
||||
return view('error', compact('message'));
|
||||
}
|
||||
|
||||
$this->validate($request, $this->rules(), $this->validationErrorMessages());
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$response = $this->broker()->reset(
|
||||
$this->credentials($request), function ($user, $password) {
|
||||
$this->resetPassword($user, $password);
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
return $response === Password::PASSWORD_RESET
|
||||
? $this->sendResetResponse($request, $response)
|
||||
: $this->sendResetFailedResponse($request, $response);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Controllers\DateCalculation;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
@ -63,7 +64,6 @@ class IndexController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show all budgets.
|
||||
*
|
||||
@ -134,5 +134,29 @@ class IndexController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
$budgetIds = $request->get('budgetIds');
|
||||
$page = (int)$request->get('page');
|
||||
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
|
||||
|
||||
$currentOrder = (($page - 1) * $pageSize) + 1;
|
||||
foreach ($budgetIds as $budgetId) {
|
||||
$budgetId = (int)$budgetId;
|
||||
$budget = $repository->findNull($budgetId);
|
||||
if (null !== $budget) {
|
||||
$repository->setBudgetOrder($budget, $currentOrder);
|
||||
}
|
||||
$currentOrder++;
|
||||
}
|
||||
|
||||
return response()->json(['OK']);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ class ShowController extends Controller
|
||||
'firefly.without_budget_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
$periods = $this->getBudgetPeriodOverview($end);
|
||||
$periods = $this->getNoBudgetPeriodOverview($end);
|
||||
$page = (int)$request->get('page');
|
||||
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
|
||||
|
||||
|
@ -375,7 +375,7 @@ class AccountController extends Controller
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.account.income-category');
|
||||
if ($cache->has()) {
|
||||
//return response()->json($cache->get()); // @codeCoverageIgnore
|
||||
return response()->json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// grab all journals:
|
||||
@ -531,7 +531,7 @@ class AccountController extends Controller
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.account.revenue-accounts');
|
||||
if ($cache->has()) {
|
||||
//return response()->json($cache->get()); // @codeCoverageIgnore
|
||||
return response()->json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
$start->subDay();
|
||||
|
||||
|
@ -68,7 +68,7 @@ class BillController extends Controller
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.bill.frontpage');
|
||||
if ($cache->has()) {
|
||||
//return response()->json($cache->get()); // @codeCoverageIgnore
|
||||
return response()->json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
/** @var CurrencyRepositoryInterface $currencyRepository */
|
||||
$currencyRepository = app(CurrencyRepositoryInterface::class);
|
||||
|
@ -32,6 +32,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Http\Controllers\DateCalculation;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
@ -40,6 +41,7 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
use DateCalculation;
|
||||
/** @var GeneratorInterface Chart generation methods. */
|
||||
protected $generator;
|
||||
|
||||
@ -97,17 +99,36 @@ class CategoryController extends Controller
|
||||
'entries' => [], 'type' => 'line', 'fill' => false,
|
||||
],
|
||||
];
|
||||
|
||||
while ($start <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($start, $range);
|
||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
|
||||
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
|
||||
$sum = bcadd($spent, $earned);
|
||||
$label = app('navigation')->periodShow($start, $range);
|
||||
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
|
||||
$chartData[1]['entries'][$label] = round($earned, 12);
|
||||
$chartData[2]['entries'][$label] = round($sum, 12);
|
||||
$start = app('navigation')->addPeriod($start, $range, 0);
|
||||
$step = $this->calculateStep($start, $end);
|
||||
$current = clone $start;
|
||||
switch ($step) {
|
||||
case '1D':
|
||||
while ($current <= $end) {
|
||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $current, $current);
|
||||
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $current, $current);
|
||||
$sum = bcadd($spent, $earned);
|
||||
$label = app('navigation')->periodShow($current, $step);
|
||||
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
|
||||
$chartData[1]['entries'][$label] = round($earned, 12);
|
||||
$chartData[2]['entries'][$label] = round($sum, 12);
|
||||
$current->addDay();
|
||||
}
|
||||
break;
|
||||
case '1W':
|
||||
case '1M':
|
||||
case '1Y':
|
||||
while ($current <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($current, $range);
|
||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $current, $currentEnd);
|
||||
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $current, $currentEnd);
|
||||
$sum = bcadd($spent, $earned);
|
||||
$label = app('navigation')->periodShow($current, $step);
|
||||
$chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
|
||||
$chartData[1]['entries'][$label] = round($earned, 12);
|
||||
$chartData[2]['entries'][$label] = round($sum, 12);
|
||||
$current= app('navigation')->addPeriod($current, $step, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$data = $this->generator->multiSet($chartData);
|
||||
@ -135,7 +156,7 @@ class CategoryController extends Controller
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.category.frontpage');
|
||||
if ($cache->has()) {
|
||||
//return response()->json($cache->get()); // @codeCoverageIgnore
|
||||
return response()->json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// currency repos:
|
||||
@ -168,7 +189,7 @@ class CategoryController extends Controller
|
||||
$noCategory = $repository->spentInPeriodPcWoCategory(new Collection, $start, $end);
|
||||
foreach ($noCategory as $currencyId => $spent) {
|
||||
$currencies[$currencyId] = $currencies[$currencyId] ?? $currencyRepository->findNull($currencyId);
|
||||
$tempData[] = [
|
||||
$tempData[] = [
|
||||
'name' => trans('firefly.no_category'),
|
||||
'spent' => bcmul($spent, '-1'),
|
||||
'spent_float' => (float)bcmul($spent, '-1'),
|
||||
|
@ -85,7 +85,7 @@ class JobStatusController extends Controller
|
||||
*/
|
||||
public function json(ImportJob $importJob): JsonResponse
|
||||
{
|
||||
$count = \count($importJob->transactions);
|
||||
$count = $this->repository->countTransactions($importJob);
|
||||
$json = [
|
||||
'status' => $importJob->status,
|
||||
'errors' => $importJob->errors,
|
||||
|
@ -48,7 +48,9 @@ class JavascriptController extends Controller
|
||||
*/
|
||||
public function accounts(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository): Response
|
||||
{
|
||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::DEBT,AccountType::LOAN,AccountType::MORTGAGE, AccountType::CREDITCARD]);
|
||||
$accounts = $repository->getAccountsByType(
|
||||
[AccountType::DEFAULT, AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]
|
||||
);
|
||||
$preference = app('preferences')->get('currencyPreference', config('firefly.default_currency', 'EUR'));
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$default = $currencyRepository->findByCodeNull($preference->data);
|
||||
@ -124,6 +126,7 @@ class JavascriptController extends Controller
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$lang = $pref->data;
|
||||
$dateRange = $this->getDateRangeConfig();
|
||||
$uid = substr(hash('sha256', auth()->user()->id . auth()->user()->email), 0, 12);
|
||||
|
||||
$data = [
|
||||
'currencyCode' => $currency->code,
|
||||
@ -133,6 +136,7 @@ class JavascriptController extends Controller
|
||||
'language' => $lang,
|
||||
'dateRangeTitle' => $dateRange['title'],
|
||||
'dateRangeConfig' => $dateRange['configuration'],
|
||||
'uid' => $uid,
|
||||
];
|
||||
$request->session()->keep(['two-factor-secret']);
|
||||
|
||||
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Json;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Account;
|
||||
@ -37,58 +38,16 @@ use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* TODO refactor so each auto-complete thing is a function call because lots of code duplication.
|
||||
* Class AutoCompleteController.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.TooManyPublicMethods)
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class AutoCompleteController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all accounts.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function allAccounts(Request $request, AccountRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
$search = (string)$request->get('search');
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('ac-all-accounts');
|
||||
// very unlikely a user will actually search for this string.
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
// find everything:
|
||||
$return = array_values(
|
||||
array_unique(
|
||||
$repository->getAccountsByType(
|
||||
[AccountType::REVENUE, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::DEFAULT, AccountType::ASSET]
|
||||
)->pluck('name')->toArray()
|
||||
)
|
||||
);
|
||||
if ('' !== $search) {
|
||||
$return = array_values(
|
||||
array_filter(
|
||||
$return, function (string $value) use ($search) {
|
||||
return !(false === stripos($value, $search));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
);
|
||||
}
|
||||
$cache->store($return);
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all journals.
|
||||
*
|
||||
@ -106,7 +65,7 @@ class AutoCompleteController extends Controller
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
return response()->json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
// find everything:
|
||||
$collector->setLimit(250)->setPage(1);
|
||||
@ -129,251 +88,79 @@ class AutoCompleteController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* List of revenue accounts.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param AccountRepositoryInterface $repository
|
||||
* @param Request $request
|
||||
* @param string $subject
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function assetAccounts(Request $request, AccountRepositoryInterface $repository): JsonResponse
|
||||
public function autoComplete(Request $request, string $subject): JsonResponse
|
||||
{
|
||||
$search = (string)$request->get('search');
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('ac-asset-accounts');
|
||||
// very unlikely a user will actually search for this string.
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
// find everything:
|
||||
$set = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$filtered = $set->filter(
|
||||
function (Account $account) {
|
||||
if (true === $account->active) {
|
||||
return $account;
|
||||
}
|
||||
$search = (string)$request->get('search');
|
||||
$unfiltered = null;
|
||||
$filtered = null;
|
||||
|
||||
return false; // @codeCoverageIgnore
|
||||
}
|
||||
);
|
||||
$return = array_values(array_unique($filtered->pluck('name')->toArray()));
|
||||
|
||||
if ('' !== $search) {
|
||||
$return = array_values(
|
||||
array_unique(
|
||||
array_filter(
|
||||
$return, function (string $value) use ($search) {
|
||||
return !(false === stripos($value, $search));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
)
|
||||
// search for all accounts.
|
||||
if ('all-accounts' === $subject) {
|
||||
$unfiltered = $this->getAccounts(
|
||||
[AccountType::REVENUE, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::DEFAULT, AccountType::ASSET, AccountType::LOAN,
|
||||
AccountType::DEBT, AccountType::MORTGAGE]
|
||||
);
|
||||
}
|
||||
$cache->store($return);
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all bills.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param BillRepositoryInterface $repository
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function bills(Request $request, BillRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
$search = (string)$request->get('search');
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('ac-bills');
|
||||
// very unlikely a user will actually search for this string.
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
// search for expense accounts.
|
||||
if ('expense-accounts' === $subject) {
|
||||
$unfiltered = $this->getAccounts([AccountType::EXPENSE, AccountType::BENEFICIARY]);
|
||||
}
|
||||
// find everything:
|
||||
$return = array_unique($repository->getActiveBills()->pluck('name')->toArray());
|
||||
|
||||
if ('' !== $search) {
|
||||
$return = array_values(
|
||||
array_unique(
|
||||
array_filter(
|
||||
$return, function (string $value) use ($search) {
|
||||
return !(false === stripos($value, $search));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
)
|
||||
);
|
||||
// search for revenue accounts.
|
||||
if ('revenue-accounts' === $subject) {
|
||||
$unfiltered = $this->getAccounts([AccountType::REVENUE]);
|
||||
}
|
||||
$cache->store($return);
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of budgets.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param BudgetRepositoryInterface $repository
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function budgets(Request $request, BudgetRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
$search = (string)$request->get('search');
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('ac-budgets');
|
||||
// very unlikely a user will actually search for this string.
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
// search for asset accounts.
|
||||
if ('asset-accounts' === $subject) {
|
||||
$unfiltered = $this->getAccounts([AccountType::ASSET, AccountType::DEFAULT]);
|
||||
}
|
||||
// find everything:
|
||||
$return = array_unique($repository->getBudgets()->pluck('name')->toArray());
|
||||
|
||||
if ('' !== $search) {
|
||||
$return = array_values(
|
||||
array_unique(
|
||||
array_filter(
|
||||
$return, function (string $value) use ($search) {
|
||||
return !(false === stripos($value, $search));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
)
|
||||
);
|
||||
// search for categories.
|
||||
if ('categories' === $subject) {
|
||||
$unfiltered = $this->getCategories();
|
||||
}
|
||||
$cache->store($return);
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of categories.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param CategoryRepositoryInterface $repository
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function categories(Request $request, CategoryRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
$search = (string)$request->get('search');
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('ac-categories');
|
||||
// very unlikely a user will actually search for this string.
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
// search for budgets.
|
||||
if ('budgets' === $subject) {
|
||||
$unfiltered = $this->getBudgets();
|
||||
}
|
||||
// find everything:
|
||||
$return = array_unique($repository->getCategories()->pluck('name')->toArray());
|
||||
if ('' !== $search) {
|
||||
$return = array_values(
|
||||
array_filter(
|
||||
$return, function (string $value) use ($search) {
|
||||
return !(false === stripos($value, $search));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
);
|
||||
|
||||
// search for tags
|
||||
if ('tags' === $subject) {
|
||||
$unfiltered = $this->getTags();
|
||||
}
|
||||
$cache->store($return);
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of currency names.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param CurrencyRepositoryInterface $repository
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function currencyNames(Request $request, CurrencyRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
$search = (string)$request->get('search');
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('ac-currency-names');
|
||||
// very unlikely a user will actually search for this string.
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
// search for bills
|
||||
if ('bills' === $subject) {
|
||||
$unfiltered = $this->getBills();
|
||||
}
|
||||
// find everything:
|
||||
$return = $repository->get()->pluck('name')->toArray();
|
||||
sort($return);
|
||||
|
||||
if ('' !== $search) {
|
||||
$return = array_values(
|
||||
array_unique(
|
||||
array_filter(
|
||||
$return, function (string $value) use ($search) {
|
||||
return !(false === stripos($value, $search));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
)
|
||||
);
|
||||
// search for currency names.
|
||||
if ('currency-names' === $subject) {
|
||||
$unfiltered = $this->getCurrencyNames();
|
||||
}
|
||||
$cache->store($return);
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all beneficiaries.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function expenseAccounts(Request $request, AccountRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
$search = (string)$request->get('search');
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('ac-expense-accounts');
|
||||
// very unlikely a user will actually search for this string.
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
if ('transaction_types' === $subject) {
|
||||
$unfiltered = $this->getTransactionTypes();
|
||||
}
|
||||
// find everything:
|
||||
$set = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
|
||||
$filtered = $set->filter(
|
||||
function (Account $account) {
|
||||
if (true === $account->active) {
|
||||
return $account;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
$return = array_unique($filtered->pluck('name')->toArray());
|
||||
|
||||
sort($return);
|
||||
|
||||
if ('' !== $search) {
|
||||
$return = array_values(
|
||||
array_unique(
|
||||
array_filter(
|
||||
$return, function (string $value) use ($search) {
|
||||
return !(false === stripos($value, $search));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
)
|
||||
);
|
||||
if ('transaction-types' === $subject) {
|
||||
$unfiltered = $this->getTransactionTypes();
|
||||
}
|
||||
$cache->store($return);
|
||||
|
||||
return response()->json($return);
|
||||
// filter results
|
||||
$filtered = $this->filterResult($unfiltered, $search);
|
||||
|
||||
if (null === $filtered) {
|
||||
throw new FireflyException(sprintf('Auto complete handler cannot handle "%s"', $subject)); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return response()->json($filtered);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -394,7 +181,7 @@ class AutoCompleteController extends Controller
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
return response()->json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
// find everything:
|
||||
$collector->setLimit(400)->setPage(1);
|
||||
@ -413,105 +200,14 @@ class AutoCompleteController extends Controller
|
||||
sort($return);
|
||||
|
||||
if ('' !== $search) {
|
||||
$return = array_values(
|
||||
array_unique(
|
||||
array_filter(
|
||||
$return, function (array $array) use ($search) {
|
||||
$value = $array['name'];
|
||||
|
||||
return !(false === stripos($value, $search));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$cache->store($return);
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of revenue accounts.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function revenueAccounts(Request $request, AccountRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
$search = (string)$request->get('search');
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('ac-revenue-accounts');
|
||||
// very unlikely a user will actually search for this string.
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
// find everything:
|
||||
$set = $repository->getAccountsByType([AccountType::REVENUE]);
|
||||
$filtered = $set->filter(
|
||||
function (Account $account) {
|
||||
if (true === $account->active) {
|
||||
return $account;
|
||||
}
|
||||
|
||||
return false;
|
||||
$return = array_filter(
|
||||
$return, function (array $array) use ($search) {
|
||||
$haystack = $array['name'];
|
||||
$result = stripos($haystack, $search);
|
||||
return !(false === $result);
|
||||
}
|
||||
);
|
||||
$return = array_unique($filtered->pluck('name')->toArray());
|
||||
sort($return);
|
||||
|
||||
if ('' !== $search) {
|
||||
$return = array_values(
|
||||
array_unique(
|
||||
array_filter(
|
||||
$return, function (string $value) use ($search) {
|
||||
return !(false === stripos($value, $search));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$cache->store($return);
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all beneficiaries.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param TagRepositoryInterface $tagRepository
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function tags(Request $request, TagRepositoryInterface $tagRepository): JsonResponse
|
||||
{
|
||||
$search = (string)$request->get('search');
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('ac-revenue-accounts');
|
||||
// very unlikely a user will actually search for this string.
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
// find everything:
|
||||
$return = array_unique($tagRepository->get()->pluck('tag')->toArray());
|
||||
sort($return);
|
||||
|
||||
if ('' !== $search) {
|
||||
$return = array_values(
|
||||
array_unique(
|
||||
array_filter(
|
||||
$return, function (string $value) use ($search) {
|
||||
return !(false === stripos($value, $search));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$cache->store($return);
|
||||
|
||||
@ -536,7 +232,7 @@ class AutoCompleteController extends Controller
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
return response()->json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
// find everything:
|
||||
$type = config('firefly.transactionTypesByWhat.' . $what);
|
||||
@ -563,43 +259,119 @@ class AutoCompleteController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* List if transaction types.
|
||||
* @param array $unfiltered
|
||||
* @param string $query
|
||||
*
|
||||
* @param Request $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @return array|null
|
||||
*/
|
||||
public function transactionTypes(Request $request, JournalRepositoryInterface $repository): JsonResponse
|
||||
private function filterResult(?array $unfiltered, string $query): ?array
|
||||
{
|
||||
$search = (string)$request->get('search');
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('ac-revenue-accounts');
|
||||
// very unlikely a user will actually search for this string.
|
||||
$key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
if (null === $unfiltered) {
|
||||
return null; // @codeCoverageIgnore
|
||||
}
|
||||
// find everything:
|
||||
$return = array_unique($repository->getTransactionTypes()->pluck('type')->toArray());
|
||||
sort($return);
|
||||
if ('' === $query) {
|
||||
sort($unfiltered);
|
||||
|
||||
if ('' !== $search) {
|
||||
return $unfiltered;
|
||||
}
|
||||
$return = [];
|
||||
if ('' !== $query) {
|
||||
$return = array_values(
|
||||
array_unique(
|
||||
array_filter(
|
||||
$return, function (string $value) use ($search) {
|
||||
return !(false === stripos($value, $search));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
array_filter(
|
||||
$unfiltered, function (string $value) use ($query) {
|
||||
return !(false === stripos($value, $query));
|
||||
}, ARRAY_FILTER_USE_BOTH
|
||||
)
|
||||
);
|
||||
}
|
||||
$cache->store($return);
|
||||
sort($return);
|
||||
|
||||
return response()->json($return);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
* @param array $types
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getAccounts(array $types): array
|
||||
{
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
// find everything:
|
||||
/** @var Collection $collection */
|
||||
$collection = $repository->getAccountsByType($types);
|
||||
$filtered = $collection->filter(
|
||||
function (Account $account) {
|
||||
return $account->active === true;
|
||||
}
|
||||
);
|
||||
$return = array_values(array_unique($filtered->pluck('name')->toArray()));
|
||||
|
||||
return $return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getBills(): array
|
||||
{
|
||||
$repository = app(BillRepositoryInterface::class);
|
||||
|
||||
return array_unique($repository->getActiveBills()->pluck('name')->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getBudgets(): array
|
||||
{
|
||||
$repository = app(BudgetRepositoryInterface::class);
|
||||
|
||||
return array_unique($repository->getBudgets()->pluck('name')->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getCategories(): array
|
||||
{
|
||||
$repository = app(CategoryRepositoryInterface::class);
|
||||
|
||||
return array_unique($repository->getCategories()->pluck('name')->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getCurrencyNames(): array
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
|
||||
return $repository->get()->pluck('name')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getTags(): array
|
||||
{
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
|
||||
return array_unique($repository->get()->pluck('tag')->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getTransactionTypes(): array
|
||||
{
|
||||
$repository = app(JournalRepositoryInterface::class);
|
||||
|
||||
return array_unique($repository->getTransactionTypes()->pluck('type')->toArray());
|
||||
}
|
||||
}
|
||||
|
@ -111,15 +111,49 @@ class ReconcileController extends Controller
|
||||
$cleared = $this->repository->getTransactionsById($clearedIds);
|
||||
$countCleared = 0;
|
||||
|
||||
Log::debug('Start transaction loop');
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$amount = bcadd($amount, $transaction->amount);
|
||||
}
|
||||
// find the account and opposing account for this transaction
|
||||
Log::debug(sprintf('Now at transaction #%d: %s', $transaction->journal_id, $transaction->description));
|
||||
$srcAccount = $this->accountRepos->findNull((int)$transaction->account_id);
|
||||
$dstAccount = $this->accountRepos->findNull((int)$transaction->opposing_account_id);
|
||||
$srcCurrency = (int)$this->accountRepos->getMetaValue($srcAccount, 'currency_id');
|
||||
$dstCurrency = (int)$this->accountRepos->getMetaValue($dstAccount, 'currency_id');
|
||||
|
||||
// is $account source or destination?
|
||||
if ($account->id === $srcAccount->id) {
|
||||
// source, and it matches the currency id or is 0
|
||||
if ($srcCurrency === $transaction->transaction_currency_id || 0 === $srcCurrency) {
|
||||
Log::debug(sprintf('Source matches currency: %s', $transaction->transaction_amount));
|
||||
$amount = bcadd($amount, $transaction->transaction_amount);
|
||||
}
|
||||
// destination, and it matches the foreign currency ID.
|
||||
if ($srcCurrency === $transaction->foreign_currency_id) {
|
||||
Log::debug(sprintf('Source matches foreign currency: %s', $transaction->transaction_foreign_amount));
|
||||
$amount = bcadd($amount, $transaction->transaction_foreign_amount);
|
||||
}
|
||||
}
|
||||
|
||||
if ($account->id === $dstAccount->id) {
|
||||
// destination, and it matches the currency id or is 0
|
||||
if ($dstCurrency === $transaction->transaction_currency_id || 0 === $dstCurrency) {
|
||||
Log::debug(sprintf('Destination matches currency: %s', app('steam')->negative($transaction->transaction_amount)));
|
||||
$amount = bcadd($amount, app('steam')->negative($transaction->transaction_amount));
|
||||
}
|
||||
// destination, and it matches the foreign currency ID.
|
||||
if ($dstCurrency === $transaction->foreign_currency_id) {
|
||||
Log::debug(sprintf('Destination matches foreign currency: %s', $transaction->transaction_foreign_amount));
|
||||
$amount = bcadd($amount, $transaction->transaction_foreign_amount);
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Amount is now %s', $amount));
|
||||
}
|
||||
Log::debug('End transaction loop');
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($cleared as $transaction) {
|
||||
if ($transaction->transactionJournal->date <= $end) {
|
||||
$clearedAmount = bcadd($clearedAmount, $transaction->amount); // @codeCoverageIgnore
|
||||
if ($transaction->date <= $end) {
|
||||
$clearedAmount = bcadd($clearedAmount, $transaction->transaction_amount); // @codeCoverageIgnore
|
||||
++$countCleared;
|
||||
}
|
||||
}
|
||||
@ -201,6 +235,7 @@ class ReconcileController extends Controller
|
||||
Log::debug(sprintf('Could not render: %s', $e->getMessage()));
|
||||
$html = 'Could not render accounts.reconcile.transactions';
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
return response()->json(['html' => $html, 'startBalance' => $startBalance, 'endBalance' => $endBalance]);
|
||||
|
@ -41,6 +41,7 @@ use FireflyIII\User;
|
||||
use Google2FA;
|
||||
use Hash;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Laravel\Passport\ClientRepository;
|
||||
use Log;
|
||||
@ -71,6 +72,7 @@ class ProfileController extends Controller
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
|
||||
$this->middleware(IsDemoUser::class)->except(['index']);
|
||||
$this->middleware(IsSandStormUser::class)->except('index');
|
||||
}
|
||||
@ -80,8 +82,15 @@ class ProfileController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function changeEmail()
|
||||
public function changeEmail(Request $request)
|
||||
{
|
||||
$loginProvider = config('firefly.login_provider');
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => $loginProvider]));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
$title = auth()->user()->email;
|
||||
$email = auth()->user()->email;
|
||||
$subTitle = (string)trans('firefly.change_your_email');
|
||||
@ -95,8 +104,15 @@ class ProfileController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function changePassword()
|
||||
public function changePassword(Request $request)
|
||||
{
|
||||
$loginProvider = config('firefly.login_provider');
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => $loginProvider]));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
$title = auth()->user()->email;
|
||||
$subTitle = (string)trans('firefly.change_your_password');
|
||||
$subTitleIcon = 'fa-key';
|
||||
@ -132,6 +148,10 @@ class ProfileController extends Controller
|
||||
*/
|
||||
public function confirmEmailChange(UserRepositoryInterface $repository, string $token)
|
||||
{
|
||||
$loginProvider = config('firefly.login_provider');
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
throw new FireflyException('Cannot confirm email change when authentication provider is not local.');
|
||||
}
|
||||
// find preference with this token value.
|
||||
/** @var Collection $set */
|
||||
$set = app('preferences')->findByName('email_change_confirm_token');
|
||||
@ -163,8 +183,12 @@ class ProfileController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function deleteAccount()
|
||||
public function deleteAccount(Request $request)
|
||||
{
|
||||
$loginProvider = config('firefly.login_provider');
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$request->session()->flash('warning', trans('firefly.delete_local_info_only', ['login_provider' => $loginProvider]));
|
||||
}
|
||||
$title = auth()->user()->email;
|
||||
$subTitle = (string)trans('firefly.delete_account');
|
||||
$subTitleIcon = 'fa-trash';
|
||||
@ -216,6 +240,7 @@ class ProfileController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$loginProvider = config('firefly.login_provider');
|
||||
// check if client token thing exists (default one)
|
||||
$count = DB::table('oauth_clients')
|
||||
->where('personal_access_client', 1)
|
||||
@ -241,7 +266,7 @@ class ProfileController extends Controller
|
||||
$accessToken = app('preferences')->set('access_token', $token);
|
||||
}
|
||||
|
||||
return view('profile.index', compact('subTitle', 'userId', 'accessToken', 'enabled2FA'));
|
||||
return view('profile.index', compact('subTitle', 'userId', 'accessToken', 'enabled2FA', 'loginProvider'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -254,6 +279,13 @@ class ProfileController extends Controller
|
||||
*/
|
||||
public function postChangeEmail(EmailFormRequest $request, UserRepositoryInterface $repository)
|
||||
{
|
||||
$loginProvider = config('firefly.login_provider');
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => $loginProvider]));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$newEmail = $request->string('email');
|
||||
@ -299,6 +331,13 @@ class ProfileController extends Controller
|
||||
*/
|
||||
public function postChangePassword(ProfileFormRequest $request, UserRepositoryInterface $repository)
|
||||
{
|
||||
$loginProvider = config('firefly.login_provider');
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
$request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => $loginProvider]));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
// the request has already validated both new passwords must be equal.
|
||||
$current = $request->get('current_password');
|
||||
$new = $request->get('new_password');
|
||||
@ -396,6 +435,11 @@ class ProfileController extends Controller
|
||||
*/
|
||||
public function undoEmailChange(UserRepositoryInterface $repository, string $token, string $hash)
|
||||
{
|
||||
$loginProvider = config('firefly.login_provider');
|
||||
if ('eloquent' !== $loginProvider) {
|
||||
throw new FireflyException('Cannot confirm email change when authentication provider is not local.');
|
||||
}
|
||||
|
||||
// find preference with this token value.
|
||||
$set = app('preferences')->findByName('email_change_undo_token');
|
||||
$user = null;
|
||||
|
@ -192,7 +192,7 @@ class TagController extends Controller
|
||||
'firefly.journals_in_period_for_tag', ['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat),]
|
||||
);
|
||||
$periods = $this->getTagPeriodOverview($tag);
|
||||
$periods = $this->getTagPeriodOverview($tag, $start);
|
||||
$path = route('tags.show', [$tag->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
|
||||
|
||||
/** @var TransactionCollectorInterface $collector */
|
||||
|
@ -37,7 +37,6 @@ class SecureHeaders
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param string|null $guard
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@ -51,6 +50,7 @@ class SecureHeaders
|
||||
}
|
||||
$csp = [
|
||||
"default-src 'none'",
|
||||
"object-src 'self'",
|
||||
sprintf("script-src 'self' 'unsafe-eval' 'unsafe-inline' %s", $google),
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"base-uri 'self'",
|
||||
|
@ -24,6 +24,7 @@ namespace FireflyIII\Http\Requests;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Rules\UniqueIban;
|
||||
use FireflyIII\Rules\ZeroOrMore;
|
||||
|
||||
/**
|
||||
* Class AccountFormRequest.
|
||||
@ -116,7 +117,7 @@ class AccountFormRequest extends Request
|
||||
];
|
||||
|
||||
if ('liabilities' === $this->get('what')) {
|
||||
$rules['openingBalance'] = 'numeric|required|more:0';
|
||||
$rules['openingBalance'] = ['numeric', 'required', new ZeroOrMore];
|
||||
$rules['openingBalanceDate'] = 'date|required';
|
||||
}
|
||||
|
||||
|
@ -75,32 +75,31 @@ class RuleFormRequest extends Request
|
||||
$validTriggers = array_keys(config('firefly.rule-triggers'));
|
||||
$validActions = array_keys(config('firefly.rule-actions'));
|
||||
|
||||
// some actions require text:
|
||||
$contextActions = implode(',', config('firefly.rule-actions-text'));
|
||||
// some actions require text (aka context):
|
||||
$contextActions = implode(',', config('firefly.context-rule-actions'));
|
||||
|
||||
$titleRule = 'required|between:1,100|uniqueObjectForUser:rules,title';
|
||||
/** @var Rule $rule */
|
||||
$rule = $this->route()->parameter('rule');
|
||||
// some triggers require text (aka context):
|
||||
$contextTriggers = implode(',', config('firefly.context-rule-triggers'));
|
||||
|
||||
if (null !== $rule) {
|
||||
$titleRule = 'required|between:1,100|uniqueObjectForUser:rules,title,' . $rule->id;
|
||||
}
|
||||
// initial set of rules:
|
||||
$rules = [
|
||||
'title' => $titleRule,
|
||||
'title' => 'required|between:1,100|uniqueObjectForUser:rules,title',
|
||||
'description' => 'between:1,5000|nullable',
|
||||
'stop_processing' => 'boolean',
|
||||
'rule_group_id' => 'required|belongsToUser:rule_groups',
|
||||
'trigger' => 'required|in:store-journal,update-journal',
|
||||
'rule_triggers.*.name' => 'required|in:' . implode(',', $validTriggers),
|
||||
'rule_triggers.*.value' => 'required|min:1|ruleTriggerValue',
|
||||
'rule_triggers.*.value' => sprintf('required_if:rule_triggers.*.name,%s|min:1|ruleTriggerValue', $contextTriggers),
|
||||
'rule-actions.*.name' => 'required|in:' . implode(',', $validActions),
|
||||
'rule_actions.*.value' => sprintf('required_if:rule_actions.*.name,%s|min:1|ruleActionValue', $contextActions),
|
||||
'strict' => 'in:0,1',
|
||||
];
|
||||
// since Laravel does not support this stuff yet, here's a trick.
|
||||
for ($i = 0; $i < 10; ++$i) {
|
||||
$key = sprintf('rule_actions.%d.value', $i);
|
||||
$rule = sprintf('required-if:rule_actions.%d.name,%s|ruleActionValue', $i, $contextActions);
|
||||
$rules[$key] = $rule;
|
||||
|
||||
/** @var Rule $rule */
|
||||
$rule = $this->route()->parameter('rule');
|
||||
|
||||
if (null !== $rule) {
|
||||
$rules['title'] = 'required|between:1,100|uniqueObjectForUser:rules,title,' . $rule->id;
|
||||
}
|
||||
|
||||
return $rules;
|
||||
|
@ -168,8 +168,10 @@ class SplitJournalFormRequest extends Request
|
||||
/** @var array $array */
|
||||
foreach ($transactions as $array) {
|
||||
if (null !== $array['destination_id'] && null !== $array['source_id'] && $array['destination_id'] === $array['source_id']) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$validator->errors()->add('journal_source_id', (string)trans('validation.source_equals_destination'));
|
||||
$validator->errors()->add('journal_destination_id', (string)trans('validation.source_equals_destination'));
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
|
35
app/Import/JobConfiguration/FinTSConfigurationSteps.php
Normal file
35
app/Import/JobConfiguration/FinTSConfigurationSteps.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* FinTSConfigurationSteps.php
|
||||
* Copyright (c) 2018 https://github.com/bnw
|
||||
*
|
||||
* 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\Import\JobConfiguration;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class FinTSConfigurationSteps
|
||||
*/
|
||||
abstract class FinTSConfigurationSteps
|
||||
{
|
||||
public const NEW = 'new';
|
||||
public const CHOOSE_ACCOUNT = 'choose_account';
|
||||
public const GO_FOR_IMPORT = 'go-for-import';
|
||||
}
|
134
app/Import/JobConfiguration/FinTSJobConfiguration.php
Normal file
134
app/Import/JobConfiguration/FinTSJobConfiguration.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* FinTSJobConfiguration.php
|
||||
* Copyright (c) 2018 https://github.com/bnw
|
||||
*
|
||||
* 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\Import\JobConfiguration;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Support\Import\JobConfiguration\FinTS\ChooseAccountHandler;
|
||||
use FireflyIII\Support\Import\JobConfiguration\FinTS\FinTSConfigurationInterface;
|
||||
use FireflyIII\Support\Import\JobConfiguration\FinTS\NewFinTSJobHandler;
|
||||
use Illuminate\Support\MessageBag;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class FinTSJobConfiguration
|
||||
*/
|
||||
class FinTSJobConfiguration implements JobConfigurationInterface
|
||||
{
|
||||
/** @var ImportJob */
|
||||
private $importJob;
|
||||
|
||||
/**
|
||||
* Returns true when the initial configuration for this job is complete.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function configurationComplete(): bool
|
||||
{
|
||||
return $this->importJob->stage === FinTSConfigurationSteps::GO_FOR_IMPORT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store any data from the $data array into the job. Anything in the message bag will be flashed
|
||||
* as an error to the user, regardless of its content.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return MessageBag
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function configureJob(array $data): MessageBag
|
||||
{
|
||||
return $this->getConfigurationObject()->configureJob($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data required for the next step in the job configuration.
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getNextData(): array
|
||||
{
|
||||
return $this->getConfigurationObject()->getNextData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the view of the next step in the job configuration.
|
||||
*
|
||||
* @return string
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getNextView(): string
|
||||
{
|
||||
switch ($this->importJob->stage) {
|
||||
case FinTSConfigurationSteps::NEW:
|
||||
case FinTSConfigurationSteps::CHOOSE_ACCOUNT:
|
||||
return 'import.fints.' . $this->importJob->stage;
|
||||
break;
|
||||
default:
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new FireflyException(
|
||||
sprintf('FinTSJobConfiguration::getNextView() cannot handle stage "%s"', $this->importJob->stage)
|
||||
);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $importJob
|
||||
*/
|
||||
public function setImportJob(ImportJob $importJob): void
|
||||
{
|
||||
$this->importJob = $importJob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration handler for this specific stage.
|
||||
*
|
||||
* @return FinTSConfigurationInterface
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getConfigurationObject(): FinTSConfigurationInterface
|
||||
{
|
||||
$class = 'DoNotExist';
|
||||
switch ($this->importJob->stage) {
|
||||
case FinTSConfigurationSteps::NEW:
|
||||
$class = NewFinTSJobHandler::class;
|
||||
break;
|
||||
case FinTSConfigurationSteps::CHOOSE_ACCOUNT:
|
||||
$class = ChooseAccountHandler::class;
|
||||
break;
|
||||
}
|
||||
if (!class_exists($class)) {
|
||||
throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class)); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$configurator = app($class);
|
||||
$configurator->setImportJob($this->importJob);
|
||||
|
||||
return $configurator;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -78,13 +78,13 @@ class BunqRoutine implements RoutineInterface
|
||||
$handler->run();
|
||||
$transactions = $handler->getTransactions();
|
||||
// could be that more transactions will arrive in a second run.
|
||||
if (true === $handler->stillRunning) {
|
||||
if (true === $handler->isStillRunning()) {
|
||||
Log::debug('Handler indicates that it is still working.');
|
||||
$this->repository->setStatus($this->importJob, 'ready_to_run');
|
||||
$this->repository->setStage($this->importJob, 'go-for-import');
|
||||
}
|
||||
$this->repository->appendTransactions($this->importJob, $transactions);
|
||||
if (false === $handler->stillRunning) {
|
||||
if (false === $handler->isStillRunning()) {
|
||||
Log::info('Handler indicates that its done!');
|
||||
$this->repository->setStatus($this->importJob, 'provider_finished');
|
||||
$this->repository->setStage($this->importJob, 'final');
|
||||
@ -98,6 +98,8 @@ class BunqRoutine implements RoutineInterface
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Set the import job.
|
||||
*
|
||||
|
88
app/Import/Routine/FinTSRoutine.php
Normal file
88
app/Import/Routine/FinTSRoutine.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* FinTSRoutine.php
|
||||
* Copyright (c) 2017 https://github.com/bnw
|
||||
*
|
||||
* 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\Import\Routine;
|
||||
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Import\JobConfiguration\FinTSConfigurationSteps;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\Import\Routine\FinTS\StageImportDataHandler;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class FinTSRoutine
|
||||
*/
|
||||
class FinTSRoutine implements RoutineInterface
|
||||
{
|
||||
/** @var ImportJob */
|
||||
private $importJob;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* At the end of each run(), the import routine must set the job to the expected status.
|
||||
*
|
||||
* The final status of the routine must be "provider_finished".
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
Log::debug(sprintf('Now in FinTSRoutine::run() with status "%s" and stage "%s".', $this->importJob->status, $this->importJob->stage));
|
||||
$valid = ['ready_to_run']; // should be only ready_to_run
|
||||
if (\in_array($this->importJob->status, $valid, true)) {
|
||||
switch ($this->importJob->stage) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('FinTSRoutine cannot handle stage "%s".', $this->importJob->stage)); // @codeCoverageIgnore
|
||||
case FinTSConfigurationSteps::GO_FOR_IMPORT:
|
||||
$this->repository->setStatus($this->importJob, 'running');
|
||||
/** @var StageImportDataHandler $handler */
|
||||
$handler = app(StageImportDataHandler::class);
|
||||
$handler->setImportJob($this->importJob);
|
||||
$handler->run();
|
||||
$transactions = $handler->getTransactions();
|
||||
|
||||
$this->repository->setTransactions($this->importJob, $transactions);
|
||||
$this->repository->setStatus($this->importJob, 'provider_finished');
|
||||
$this->repository->setStage($this->importJob, 'final');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $importJob
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setImportJob(ImportJob $importJob): void
|
||||
{
|
||||
$this->importJob = $importJob;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($importJob->user);
|
||||
}
|
||||
|
||||
}
|
@ -46,7 +46,7 @@ use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Creates new transactions based upon arrays. Will first check the array for duplicates.
|
||||
* Creates new transactions based on arrays.
|
||||
*
|
||||
* Class ImportArrayStorage
|
||||
*
|
||||
@ -58,11 +58,11 @@ class ImportArrayStorage
|
||||
private $checkForTransfers = false;
|
||||
/** @var ImportJob The import job */
|
||||
private $importJob;
|
||||
/** @var JournalRepositoryInterface */
|
||||
/** @var JournalRepositoryInterface Journal repository for storage. */
|
||||
private $journalRepos;
|
||||
/** @var ImportJobRepositoryInterface Import job repository */
|
||||
private $repository;
|
||||
/** @var Collection The transfers. */
|
||||
/** @var Collection The transfers the user already has. */
|
||||
private $transfers;
|
||||
|
||||
/**
|
||||
@ -72,12 +72,12 @@ class ImportArrayStorage
|
||||
*/
|
||||
public function setImportJob(ImportJob $importJob): void
|
||||
{
|
||||
$this->importJob = $importJob;
|
||||
$this->countTransfers();
|
||||
|
||||
$this->importJob = $importJob;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($importJob->user);
|
||||
|
||||
$this->countTransfers();
|
||||
|
||||
$this->journalRepos = app(JournalRepositoryInterface::class);
|
||||
$this->journalRepos->setUser($importJob->user);
|
||||
|
||||
@ -116,7 +116,7 @@ class ImportArrayStorage
|
||||
app('preferences')->mark();
|
||||
|
||||
// email about this:
|
||||
event(new RequestedReportOnJournals($this->importJob->user_id, $collection));
|
||||
event(new RequestedReportOnJournals((int)$this->importJob->user_id, $collection));
|
||||
|
||||
return $collection;
|
||||
}
|
||||
@ -152,14 +152,14 @@ class ImportArrayStorage
|
||||
|
||||
/**
|
||||
* Count the number of transfers in the array. If this is zero, don't bother checking for double transfers.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*/
|
||||
private function countTransfers(): void
|
||||
{
|
||||
Log::debug('Now in count transfers.');
|
||||
Log::debug('Now in countTransfers()');
|
||||
/** @var array $array */
|
||||
$array = $this->importJob->transactions;
|
||||
$array = $this->repository->getTransactions($this->importJob);
|
||||
|
||||
|
||||
$count = 0;
|
||||
foreach ($array as $index => $transaction) {
|
||||
if (strtolower(TransactionType::TRANSFER) === strtolower($transaction['type'])) {
|
||||
@ -167,17 +167,44 @@ class ImportArrayStorage
|
||||
Log::debug(sprintf('Row #%d is a transfer, increase count to %d', $index + 1, $count));
|
||||
}
|
||||
}
|
||||
if (0 === $count) {
|
||||
Log::debug('Count is zero, will not check for duplicate transfers.');
|
||||
}
|
||||
Log::debug(sprintf('Count of transfers in import array is %d.', $count));
|
||||
if ($count > 0) {
|
||||
Log::debug(sprintf('Count is %d, will check for duplicate transfers.', $count));
|
||||
$this->checkForTransfers = true;
|
||||
|
||||
Log::debug('Will check for duplicate transfers.');
|
||||
// get users transfers. Needed for comparison.
|
||||
$this->getTransfers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index
|
||||
* @param array $transaction
|
||||
*
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function duplicateDetected(int $index, array $transaction): bool
|
||||
{
|
||||
$hash = $this->getHash($transaction);
|
||||
$existingId = $this->hashExists($hash);
|
||||
if (null !== $existingId) {
|
||||
$message = sprintf('Row #%d ("%s") could not be imported. It already exists.', $index, $transaction['description']);
|
||||
$this->logDuplicateObject($transaction, $existingId);
|
||||
$this->repository->addErrorMessage($this->importJob, $message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// do transfer detection:
|
||||
if ($this->checkForTransfers && $this->transferExists($transaction)) {
|
||||
$message = sprintf('Row #%d ("%s") could not be imported. Such a transfer already exists.', $index, $transaction['description']);
|
||||
$this->logDuplicateTransfer($transaction);
|
||||
$this->repository->addErrorMessage($this->importJob, $message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -193,9 +220,11 @@ class ImportArrayStorage
|
||||
unset($transaction['importHashV2'], $transaction['original-source']);
|
||||
$json = json_encode($transaction);
|
||||
if (false === $json) {
|
||||
// @codeCoverageIgnoreStart
|
||||
/** @noinspection ForgottenDebugOutputInspection */
|
||||
Log::error('Could not encode import array.', print_r($transaction, true));
|
||||
throw new FireflyException('Could not encode import array. Please see the logs.'); // @codeCoverageIgnore
|
||||
throw new FireflyException('Could not encode import array. Please see the logs.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
$hash = hash('sha256', $json, false);
|
||||
Log::debug(sprintf('The hash is: %s', $hash));
|
||||
@ -391,38 +420,21 @@ class ImportArrayStorage
|
||||
private function storeArray(): Collection
|
||||
{
|
||||
/** @var array $array */
|
||||
$array = $this->importJob->transactions;
|
||||
$array = $this->repository->getTransactions($this->importJob);
|
||||
$count = \count($array);
|
||||
$toStore = [];
|
||||
|
||||
Log::debug(sprintf('Now in store(). Count of items is %d', $count));
|
||||
Log::debug(sprintf('Now in store(). Count of items is %d.', $count));
|
||||
|
||||
/*
|
||||
* Detect duplicates in initial array:
|
||||
*/
|
||||
foreach ($array as $index => $transaction) {
|
||||
Log::debug(sprintf('Now at item %d out of %d', $index + 1, $count));
|
||||
$hash = $this->getHash($transaction);
|
||||
$existingId = $this->hashExists($hash);
|
||||
if (null !== $existingId) {
|
||||
$this->logDuplicateObject($transaction, $existingId);
|
||||
$this->repository->addErrorMessage(
|
||||
$this->importJob, sprintf(
|
||||
'Row #%d ("%s") could not be imported. It already exists.',
|
||||
$index, $transaction['description']
|
||||
)
|
||||
);
|
||||
if ($this->duplicateDetected($index, $transaction)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->checkForTransfers && $this->transferExists($transaction)) {
|
||||
$this->logDuplicateTransfer($transaction);
|
||||
$this->repository->addErrorMessage(
|
||||
$this->importJob, sprintf(
|
||||
'Row #%d ("%s") could not be imported. Such a transfer already exists.',
|
||||
$index,
|
||||
$transaction['description']
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$transaction['importHashV2'] = $hash;
|
||||
$transaction['importHashV2'] = $this->getHash($transaction);
|
||||
$toStore[] = $transaction;
|
||||
}
|
||||
$count = \count($toStore);
|
||||
@ -436,31 +448,8 @@ class ImportArrayStorage
|
||||
// now actually store them:
|
||||
$collection = new Collection;
|
||||
foreach ($toStore as $index => $store) {
|
||||
|
||||
// do duplicate detection again!
|
||||
$hash = $this->getHash($store);
|
||||
$existingId = $this->hashExists($hash);
|
||||
if (null !== $existingId) {
|
||||
$this->logDuplicateObject($store, $existingId);
|
||||
$this->repository->addErrorMessage(
|
||||
$this->importJob, sprintf(
|
||||
'Row #%d ("%s") could not be imported. It already exists.',
|
||||
$index, $store['description']
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// do transfer detection again!
|
||||
if ($this->checkForTransfers && $this->transferExists($store)) {
|
||||
$this->logDuplicateTransfer($store);
|
||||
$this->repository->addErrorMessage(
|
||||
$this->importJob, sprintf(
|
||||
'Row #%d ("%s") could not be imported. Such a transfer already exists.',
|
||||
$index,
|
||||
$store['description']
|
||||
)
|
||||
);
|
||||
if ($this->duplicateDetected($index, $store)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -554,8 +543,9 @@ class ImportArrayStorage
|
||||
Log::debug(sprintf('Comparison is a hit! (%s)', $hits));
|
||||
|
||||
// compare description:
|
||||
Log::debug(sprintf('Comparing "%s" to "%s"', $description, $transfer->description));
|
||||
if ($description !== $transfer->description) {
|
||||
$comparison = '(empty description)' === $transfer->description ? '' : $transfer->description;
|
||||
Log::debug(sprintf('Comparing "%s" to "%s" (original: "%s")', $description, $transfer->description, $comparison));
|
||||
if ($description !== $comparison) {
|
||||
continue; // @codeCoverageIgnore
|
||||
}
|
||||
++$hits;
|
||||
|
@ -154,15 +154,15 @@ class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue
|
||||
$processors = $this->collectProcessors();
|
||||
|
||||
// Execute the rules for each transaction
|
||||
foreach ($transactions as $transaction) {
|
||||
/** @var Processor $processor */
|
||||
foreach ($processors as $processor) {
|
||||
foreach ($processors as $processor) {
|
||||
foreach ($transactions as $transaction) {
|
||||
/** @var Processor $processor */
|
||||
$processor->handleTransaction($transaction);
|
||||
|
||||
// Stop processing this group if the rule specifies 'stop_processing'
|
||||
if ($processor->getRule()->stop_processing) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Stop processing this group if the rule specifies 'stop_processing'
|
||||
if ($processor->getRule()->stop_processing) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -203,6 +203,7 @@ class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue
|
||||
/** @var Processor $processor */
|
||||
$processor = app(Processor::class);
|
||||
$processor->make($rule);
|
||||
|
||||
return $processor;
|
||||
},
|
||||
$rules->all()
|
||||
|
@ -179,10 +179,6 @@ class ExecuteRuleOnExistingTransactions extends Job implements ShouldQueue
|
||||
++$misses;
|
||||
}
|
||||
Log::info(sprintf('Current progress: %d Transactions. Hits: %d, misses: %d', $total, $hits, $misses));
|
||||
// Stop processing this group if the rule specifies 'stop_processing'
|
||||
if ($processor->getRule()->stop_processing) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log::info(sprintf('Total transactions: %d. Hits: %d, misses: %d', $total, $hits, $misses));
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @property-read string $email
|
||||
* @property bool encrypted
|
||||
* @property Collection budgetlimits
|
||||
* @property int $order
|
||||
*/
|
||||
class Budget extends Model
|
||||
{
|
||||
@ -61,7 +62,7 @@ class Budget extends Model
|
||||
'encrypted' => 'boolean',
|
||||
];
|
||||
/** @var array Fields that can be filled */
|
||||
protected $fillable = ['user_id', 'name', 'active'];
|
||||
protected $fillable = ['user_id', 'name', 'active','order'];
|
||||
/** @var array Hidden from view */
|
||||
protected $hidden = ['encrypted'];
|
||||
|
||||
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@ -45,6 +46,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @property array $errors
|
||||
* @property array extended_status
|
||||
* @property int id
|
||||
* @property Carbon $created_at
|
||||
*/
|
||||
class ImportJob extends Model
|
||||
{
|
||||
@ -56,6 +58,7 @@ class ImportJob extends Model
|
||||
*/
|
||||
protected $casts
|
||||
= [
|
||||
'user_id' => 'int',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'configuration' => 'array',
|
||||
|
@ -71,6 +71,7 @@ use FireflyIII\Validation\FireflyValidator;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Twig;
|
||||
use Twig_Extension_Debug;
|
||||
use TwigBridge\Extension\Loader\Functions;
|
||||
use Validator;
|
||||
|
||||
@ -105,6 +106,7 @@ class FireflyServiceProvider extends ServiceProvider
|
||||
Twig::addExtension(new Transaction);
|
||||
Twig::addExtension(new Rule);
|
||||
Twig::addExtension(new AmountFormat);
|
||||
Twig::addExtension(new Twig_Extension_Debug);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,7 +34,7 @@ use FireflyIII\User;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Storage;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* Class AttachmentRepository.
|
||||
@ -66,9 +66,9 @@ class AttachmentRepository implements AttachmentRepositoryInterface
|
||||
/** @var AttachmentHelperInterface $helper */
|
||||
$helper = app(AttachmentHelperInterface::class);
|
||||
|
||||
$file = $helper->getAttachmentLocation($attachment);
|
||||
$path = $helper->getAttachmentLocation($attachment);
|
||||
try {
|
||||
unlink($file);
|
||||
Storage::disk('upload')->delete($path);
|
||||
} catch (Exception $e) {
|
||||
Log::error(sprintf('Could not delete file for attachment %d: %s', $attachment->id, $e->getMessage()));
|
||||
}
|
||||
|
@ -381,7 +381,9 @@ class BillRepository implements BillRepositoryInterface
|
||||
*/
|
||||
public function getPaidDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
$dates = $bill->transactionJournals()->before($end)->after($start)->get(['transaction_journals.date'])->pluck('date');
|
||||
$dates = $bill->transactionJournals()->before($end)->after($start)->get([
|
||||
'transaction_journals.id','transaction_journals.date'
|
||||
])->pluck('date', 'id');
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
} catch (Exception $e) {
|
||||
Log::debug(sprintf('Could not delete budget limit: %s', $e->getMessage()));
|
||||
}
|
||||
Budget::where('order',0)->update(['order' => 100]);
|
||||
|
||||
// do the clean up by hand because Sqlite can be tricky with this.
|
||||
$budgetLimits = BudgetLimit::orderBy('created_at', 'DESC')->get(['id', 'budget_id', 'start_date', 'end_date']);
|
||||
@ -289,11 +290,14 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
public function getActiveBudgets(): Collection
|
||||
{
|
||||
/** @var Collection $set */
|
||||
$set = $this->user->budgets()->where('active', 1)->get();
|
||||
$set = $this->user->budgets()->where('active', 1)
|
||||
->get();
|
||||
|
||||
$set = $set->sortBy(
|
||||
function (Budget $budget) {
|
||||
return strtolower($budget->name);
|
||||
$str = str_pad((string)$budget->order, 4, '0', STR_PAD_LEFT) . strtolower($budget->name);
|
||||
|
||||
return $str;
|
||||
}
|
||||
);
|
||||
|
||||
@ -554,7 +558,9 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
|
||||
$set = $set->sortBy(
|
||||
function (Budget $budget) {
|
||||
return strtolower($budget->name);
|
||||
$str = str_pad((string)$budget->order, 4, '0', STR_PAD_LEFT) . strtolower($budget->name);
|
||||
|
||||
return $str;
|
||||
}
|
||||
);
|
||||
|
||||
@ -583,7 +589,9 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
|
||||
$set = $set->sortBy(
|
||||
function (Budget $budget) {
|
||||
return strtolower($budget->name);
|
||||
$str = str_pad((string)$budget->order, 4, '0', STR_PAD_LEFT) . strtolower($budget->name);
|
||||
|
||||
return $str;
|
||||
}
|
||||
);
|
||||
|
||||
@ -652,6 +660,18 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
return $availableBudget;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* @param int $order
|
||||
*/
|
||||
public function setBudgetOrder(Budget $budget, int $order): void
|
||||
{
|
||||
$budget->order = $order;
|
||||
$budget->save();
|
||||
}
|
||||
|
||||
/** @noinspection MoreThanThreeArgumentsInspection */
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
@ -660,7 +680,6 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/** @noinspection MoreThanThreeArgumentsInspection */
|
||||
/**
|
||||
* @param Collection $budgets
|
||||
* @param Collection $accounts
|
||||
@ -825,6 +844,8 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
|
||||
}
|
||||
|
||||
/** @noinspection MoreThanThreeArgumentsInspection */
|
||||
|
||||
/**
|
||||
* @param BudgetLimit $budgetLimit
|
||||
* @param array $data
|
||||
@ -848,7 +869,6 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
return $budgetLimit;
|
||||
}
|
||||
|
||||
/** @noinspection MoreThanThreeArgumentsInspection */
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* @param Carbon $start
|
||||
|
@ -156,7 +156,6 @@ interface BudgetRepositoryInterface
|
||||
*/
|
||||
public function getBudgetLimits(Budget $budget, Carbon $start = null, Carbon $end = null): Collection;
|
||||
|
||||
/** @noinspection MoreThanThreeArgumentsInspection */
|
||||
/**
|
||||
* @param Collection $budgets
|
||||
* @param Collection $accounts
|
||||
@ -167,6 +166,8 @@ interface BudgetRepositoryInterface
|
||||
*/
|
||||
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array;
|
||||
|
||||
/** @noinspection MoreThanThreeArgumentsInspection */
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
@ -195,7 +196,6 @@ interface BudgetRepositoryInterface
|
||||
*/
|
||||
public function getNoBudgetPeriodReport(Collection $accounts, Carbon $start, Carbon $end): array;
|
||||
|
||||
/** @noinspection MoreThanThreeArgumentsInspection */
|
||||
/**
|
||||
* @param TransactionCurrency $currency
|
||||
* @param Carbon $start
|
||||
@ -206,6 +206,14 @@ interface BudgetRepositoryInterface
|
||||
*/
|
||||
public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): AvailableBudget;
|
||||
|
||||
/** @noinspection MoreThanThreeArgumentsInspection */
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* @param int $order
|
||||
*/
|
||||
public function setBudgetOrder(Budget $budget, int $order): void;
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
|
@ -28,8 +28,8 @@ use FireflyIII\Models\ExportJob;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Log;
|
||||
use Storage;
|
||||
|
||||
/**
|
||||
* Class ExportJobRepository.
|
||||
@ -74,15 +74,17 @@ class ExportJobRepository implements ExportJobRepositoryInterface
|
||||
->whereIn('status', ['never_started', 'export_status_finished', 'export_downloaded'])
|
||||
->get();
|
||||
|
||||
$disk = Storage::disk('export');
|
||||
$files = $disk->files();
|
||||
|
||||
// loop set:
|
||||
/** @var ExportJob $entry */
|
||||
foreach ($set as $entry) {
|
||||
$key = $entry->key;
|
||||
$files = scandir(storage_path('export'), SCANDIR_SORT_NONE);
|
||||
/** @var string $file */
|
||||
/** @var array $file */
|
||||
foreach ($files as $file) {
|
||||
if (0 === strpos($file, $key)) {
|
||||
unlink(storage_path('export') . DIRECTORY_SEPARATOR . $file);
|
||||
if (0 === strpos($file['basename'], $key)) {
|
||||
$disk->delete($file['path']);
|
||||
}
|
||||
}
|
||||
try {
|
||||
|
@ -84,26 +84,36 @@ class ImportJobRepository implements ImportJobRepositoryInterface
|
||||
* @param array $transactions
|
||||
*
|
||||
* @return ImportJob
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
*/
|
||||
public function appendTransactions(ImportJob $job, array $transactions): ImportJob
|
||||
{
|
||||
Log::debug(sprintf('Now in appendTransactions(%s)', $job->key));
|
||||
$existingTransactions = $job->transactions;
|
||||
if (!\is_array($existingTransactions)) {
|
||||
$existingTransactions = [];
|
||||
}
|
||||
$new = array_merge($existingTransactions, $transactions);
|
||||
$existingTransactions = $this->getTransactions($job);
|
||||
$new = array_merge($existingTransactions, $transactions);
|
||||
Log::debug(sprintf('Old transaction count: %d', \count($existingTransactions)));
|
||||
Log::debug(sprintf('To be added transaction count: %d', \count($transactions)));
|
||||
Log::debug(sprintf('New count: %d', \count($new)));
|
||||
$job->transactions = $new;
|
||||
|
||||
|
||||
$job->save();
|
||||
$this->setTransactions($job, $new);
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countTransactions(ImportJob $job): int
|
||||
{
|
||||
$info = $job->transactions ?? [];
|
||||
if (isset($info['count'])) {
|
||||
return (int)$info['count'];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $importProvider
|
||||
*
|
||||
@ -201,6 +211,31 @@ class ImportJobRepository implements ImportJobRepositoryInterface
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return transactions from attachment.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return array
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
*/
|
||||
public function getTransactions(ImportJob $job): array
|
||||
{
|
||||
// this will overwrite all transactions currently in the job.
|
||||
$disk = Storage::disk('upload');
|
||||
$filename = sprintf('%s-%s.crypt.json', $job->created_at->format('Ymd'), $job->key);
|
||||
$array = [];
|
||||
if ($disk->exists($filename)) {
|
||||
$json = Crypt::decrypt($disk->get($filename));
|
||||
$array = json_decode($json, true);
|
||||
}
|
||||
if (false === $array) {
|
||||
$array = [];
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
* @param array $configuration
|
||||
@ -272,8 +307,17 @@ class ImportJobRepository implements ImportJobRepositoryInterface
|
||||
*/
|
||||
public function setTransactions(ImportJob $job, array $transactions): ImportJob
|
||||
{
|
||||
$job->transactions = $transactions;
|
||||
// this will overwrite all transactions currently in the job.
|
||||
$disk = Storage::disk('upload');
|
||||
$filename = sprintf('%s-%s.crypt.json', $job->created_at->format('Ymd'), $job->key);
|
||||
$json = Crypt::encrypt(json_encode($transactions));
|
||||
|
||||
// set count for easy access
|
||||
$array = ['count' => \count($transactions)];
|
||||
$job->transactions = $array;
|
||||
$job->save();
|
||||
// store file.
|
||||
$disk->put($filename, $json);
|
||||
|
||||
return $job;
|
||||
}
|
||||
@ -377,7 +421,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface
|
||||
$fileObject->rewind();
|
||||
|
||||
|
||||
if(0 === $file->getSize()) {
|
||||
if (0 === $file->getSize()) {
|
||||
throw new FireflyException('Cannot upload empty or non-existent file.');
|
||||
}
|
||||
|
||||
@ -390,7 +434,6 @@ class ImportJobRepository implements ImportJobRepositoryInterface
|
||||
return new MessageBag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
|
@ -35,6 +35,7 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
*/
|
||||
interface ImportJobRepositoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Add message to job.
|
||||
*
|
||||
@ -55,6 +56,13 @@ interface ImportJobRepositoryInterface
|
||||
*/
|
||||
public function appendTransactions(ImportJob $job, array $transactions): ImportJob;
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countTransactions(ImportJob $job): int;
|
||||
|
||||
/**
|
||||
* @param string $importProvider
|
||||
*
|
||||
@ -96,6 +104,15 @@ interface ImportJobRepositoryInterface
|
||||
*/
|
||||
public function getExtendedStatus(ImportJob $job): array;
|
||||
|
||||
/**
|
||||
* Return transactions from attachment.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTransactions(ImportJob $job): array;
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
* @param array $configuration
|
||||
|
@ -27,6 +27,9 @@ use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Factory\TransactionJournalFactory;
|
||||
use FireflyIII\Factory\TransactionJournalMetaFactory;
|
||||
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
|
||||
use FireflyIII\Helpers\Filter\InternalTransferFilter;
|
||||
use FireflyIII\Helpers\Filter\TransferFilter;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
@ -581,14 +584,23 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
*/
|
||||
public function getTransactionsById(array $transactionIds): Collection
|
||||
{
|
||||
$set = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->whereIn('transactions.id', $transactionIds)
|
||||
->where('transaction_journals.user_id', $this->user->id)
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereNull('transactions.deleted_at')
|
||||
->get(['transactions.*']);
|
||||
$journalIds = Transaction::whereIn('id', $transactionIds)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
|
||||
$journals = new Collection;
|
||||
foreach($journalIds as $journalId) {
|
||||
$result = $this->findNull((int)$journalId);
|
||||
if(null !== $result) {
|
||||
$journals->push($result);
|
||||
}
|
||||
}
|
||||
/** @var TransactionCollectorInterface $collector */
|
||||
$collector = app(TransactionCollectorInterface::class);
|
||||
$collector->setUser($this->user);
|
||||
$collector->setAllAssetAccounts();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
//$collector->addFilter(TransferFilter::class);
|
||||
|
||||
return $set;
|
||||
$collector->setJournals($journals)->withOpposingAccount();
|
||||
return $collector->getTransactions();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,6 +92,23 @@ class TagRepository implements TagRepositoryInterface
|
||||
return (string)$set->sum('transaction_amount');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function expenseInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
/** @var TransactionCollectorInterface $collector */
|
||||
$collector = app(TransactionCollectorInterface::class);
|
||||
$collector->setUser($this->user);
|
||||
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAllAssetAccounts()->setTag($tag);
|
||||
|
||||
return $collector->getTransactions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tag
|
||||
*
|
||||
@ -151,6 +168,23 @@ class TagRepository implements TagRepositoryInterface
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function incomeInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
/** @var TransactionCollectorInterface $collector */
|
||||
$collector = app(TransactionCollectorInterface::class);
|
||||
$collector->setUser($this->user);
|
||||
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAllAssetAccounts()->setTag($tag);
|
||||
|
||||
return $collector->getTransactions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
*
|
||||
@ -315,6 +349,23 @@ class TagRepository implements TagRepositoryInterface
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function transferredInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
/** @var TransactionCollectorInterface $collector */
|
||||
$collector = app(TransactionCollectorInterface::class);
|
||||
$collector->setUser($this->user);
|
||||
$collector->setRange($start, $end)->setTypes([TransactionType::TRANSFER])->setAllAssetAccounts()->setTag($tag);
|
||||
|
||||
return $collector->getTransactions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param array $data
|
||||
|
@ -55,6 +55,15 @@ interface TagRepositoryInterface
|
||||
*/
|
||||
public function earnedInPeriod(Tag $tag, Carbon $start, Carbon $end): string;
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function expenseInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection;
|
||||
|
||||
/**
|
||||
* @param string $tag
|
||||
*
|
||||
@ -83,6 +92,15 @@ interface TagRepositoryInterface
|
||||
*/
|
||||
public function get(): Collection;
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function incomeInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection;
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
*
|
||||
@ -147,6 +165,15 @@ interface TagRepositoryInterface
|
||||
*/
|
||||
public function tagCloud(?int $year): array;
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function transferredInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection;
|
||||
|
||||
/**
|
||||
* Update a tag.
|
||||
*
|
||||
|
45
app/Rules/ZeroOrMore.php
Normal file
45
app/Rules/ZeroOrMore.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace FireflyIII\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class ZeroOrMore
|
||||
*/
|
||||
class ZeroOrMore implements Rule
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the validation error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function message(): string
|
||||
{
|
||||
return trans('validation.zero_or_more');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the validation rule passes.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value): bool
|
||||
{
|
||||
$value = (string)$value;
|
||||
if ('' === $value) {
|
||||
return true;
|
||||
}
|
||||
$res = bccomp('0', $value);
|
||||
if ($res > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ namespace FireflyIII\Services\Internal\Destroy;
|
||||
use DB;
|
||||
use Exception;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\RecurrenceTransaction;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
@ -97,6 +98,9 @@ class AccountDestroyService
|
||||
}
|
||||
}
|
||||
|
||||
// delete piggy banks:
|
||||
PiggyBank::where('account_id', $account->id)->delete();
|
||||
|
||||
try {
|
||||
$account->delete();
|
||||
} catch (Exception $e) { // @codeCoverageIgnore
|
||||
|
@ -26,6 +26,7 @@ namespace FireflyIII\Services\Internal\File;
|
||||
use Crypt;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use Illuminate\Contracts\Encryption\EncryptException;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
@ -63,9 +64,8 @@ class EncryptService
|
||||
throw new FireflyException($message);
|
||||
}
|
||||
$newName = sprintf('%s.upload', $key);
|
||||
$path = storage_path('upload') . '/' . $newName;
|
||||
|
||||
file_put_contents($path, $content);
|
||||
$disk = Storage::disk('upload');
|
||||
$disk->put($newName, $content);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -217,13 +217,13 @@ trait AccountServiceTrait
|
||||
*/
|
||||
public function storeOpposingAccount(User $user, string $name): Account
|
||||
{
|
||||
$name .= ' initial balance';
|
||||
$opposingAccountName = (string)trans('firefly.initial_balance_account', ['name' => $name]);
|
||||
Log::debug('Going to create an opening balance opposing account.');
|
||||
/** @var AccountFactory $factory */
|
||||
$factory = app(AccountFactory::class);
|
||||
$factory->setUser($user);
|
||||
|
||||
return $factory->findOrCreate($name, AccountType::INITIAL_BALANCE);
|
||||
|
||||
return $factory->findOrCreate($opposingAccountName, AccountType::INITIAL_BALANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,7 +51,7 @@ class CLIToken implements BinderInterface
|
||||
|
||||
foreach ($users as $user) {
|
||||
$accessToken = app('preferences')->getForUser($user, 'access_token', null);
|
||||
if ($accessToken->data === $value) {
|
||||
if(null !== $accessToken && $accessToken->data === $value) {
|
||||
Log::info(sprintf('Recognized user #%d (%s) from his acccess token.', $user->id, $user->email));
|
||||
|
||||
return $value;
|
||||
|
121
app/Support/FinTS/FinTS.php
Normal file
121
app/Support/FinTS/FinTS.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* FinTS.php
|
||||
* Copyright (c) 2018 https://github.com/bnw
|
||||
*
|
||||
* 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\FinTS;
|
||||
|
||||
use Fhp\Model\SEPAAccount;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class FinTS
|
||||
*/
|
||||
class FinTS
|
||||
{
|
||||
/** @var \Fhp\FinTs */
|
||||
private $finTS;
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
if (!isset($config['fints_url'], $config['fints_port'], $config['fints_bank_code'], $config['fints_username'], $config['fints_password'])) {
|
||||
throw new FireflyException('Constructed FinTS with incomplete config.');
|
||||
}
|
||||
$this->finTS = new \Fhp\FinTs(
|
||||
$config['fints_url'],
|
||||
$config['fints_port'],
|
||||
$config['fints_bank_code'],
|
||||
$config['fints_username'],
|
||||
Crypt::decrypt($config['fints_password'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|string
|
||||
*/
|
||||
public function checkConnection()
|
||||
{
|
||||
try {
|
||||
$this->finTS->getSEPAAccounts();
|
||||
|
||||
return true;
|
||||
} catch (\Exception $exception) {
|
||||
return $exception->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accountNumber
|
||||
*
|
||||
* @return SEPAAccount
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getAccount(string $accountNumber)
|
||||
{
|
||||
$accounts = $this->getAccounts();
|
||||
$filteredAccounts = array_filter(
|
||||
$accounts, function (SEPAAccount $account) use ($accountNumber) {
|
||||
return $account->getAccountNumber() === $accountNumber;
|
||||
}
|
||||
);
|
||||
if (count($filteredAccounts) != 1) {
|
||||
throw new FireflyException("Cannot find account with number " . $accountNumber);
|
||||
}
|
||||
|
||||
return reset($filteredAccounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SEPAAccount[]
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getAccounts()
|
||||
{
|
||||
try {
|
||||
return $this->finTS->getSEPAAccounts();
|
||||
} catch (\Exception $exception) {
|
||||
throw new FireflyException($exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SEPAAccount $account
|
||||
* @param \DateTime $from
|
||||
* @param \DateTIme $to
|
||||
*
|
||||
* @return \Fhp\Model\StatementOfAccount\StatementOfAccount|null
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTIme $to)
|
||||
{
|
||||
try {
|
||||
return $this->finTS->getStatementOfAccount($account, $from, $to);
|
||||
} catch (\Exception $exception) {
|
||||
throw new FireflyException($exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
49
app/Support/FinTS/MetadataParser.php
Normal file
49
app/Support/FinTS/MetadataParser.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* FinTS.php
|
||||
* Copyright (c) 2018 https://github.com/bnw
|
||||
*
|
||||
* 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\FinTS;
|
||||
|
||||
use Fhp\Model\StatementOfAccount\Transaction as FinTSTransaction;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class MetadataParser
|
||||
*/
|
||||
class MetadataParser
|
||||
{
|
||||
/**
|
||||
* @param FinTSTransaction $transaction
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(FinTSTransaction $transaction): string
|
||||
{
|
||||
//Given a description like 'EREF+AbcCRED+DE123SVWZ+DefABWA+Ghi' or 'EREF+AbcCRED+DE123SVWZ+Def' return 'Def'
|
||||
$finTSDescription = $transaction->getDescription1();
|
||||
$matches = [];
|
||||
if (1 === preg_match('/SVWZ\+([^\+]*)([A-Z]{4}\+|$)/', $finTSDescription, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return $finTSDescription;
|
||||
}
|
||||
}
|
@ -60,12 +60,14 @@ trait PeriodOverview
|
||||
* and for each period, the amount of money spent and earned. This is a complex operation which is cached for
|
||||
* performance reasons.
|
||||
*
|
||||
* @param Account $account the account involved
|
||||
* @param Carbon $date
|
||||
* The method has been refactored recently for better performance.
|
||||
*
|
||||
* @param Account $account The account involved
|
||||
* @param Carbon $date The start date.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
protected function getAccountPeriodOverview(Account $account, Carbon $date): Collection // period overview
|
||||
protected function getAccountPeriodOverview(Account $account, Carbon $date): Collection
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
@ -95,15 +97,15 @@ trait PeriodOverview
|
||||
$collector = app(TransactionCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::DEPOSIT])
|
||||
->withOpposingAccount();
|
||||
$set = $collector->getTransactions();
|
||||
$earned = $this->groupByCurrency($set);
|
||||
$earnedSet = $collector->getTransactions();
|
||||
$earned = $this->groupByCurrency($earnedSet);
|
||||
|
||||
/** @var TransactionCollectorInterface $collector */
|
||||
$collector = app(TransactionCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::WITHDRAWAL])
|
||||
->withOpposingAccount();
|
||||
$set = $collector->getTransactions();
|
||||
$spent = $this->groupByCurrency($set);
|
||||
$spentSet = $collector->getTransactions();
|
||||
$spent = $this->groupByCurrency($spentSet);
|
||||
|
||||
$title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
@ -115,7 +117,6 @@ trait PeriodOverview
|
||||
'earned' => $earned,
|
||||
'transferred' => '0',
|
||||
'route' => route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -126,11 +127,84 @@ trait PeriodOverview
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets period overview used for budgets.
|
||||
* Overview for single category. Has been refactored recently.
|
||||
*
|
||||
* @param Category $category
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
protected function getBudgetPeriodOverview(Carbon $date): Collection
|
||||
protected function getCategoryPeriodOverview(Category $category, Carbon $date): Collection
|
||||
{
|
||||
/** @var JournalRepositoryInterface $journalRepository */
|
||||
$journalRepository = app(JournalRepositoryInterface::class);
|
||||
$range = app('preferences')->get('viewRange', '1M')->data;
|
||||
$first = $journalRepository->firstNull();
|
||||
$end = null === $first ? new Carbon : $first->date;
|
||||
$start = clone $date;
|
||||
|
||||
if ($end < $start) {
|
||||
[$start, $end] = [$end, $start]; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// properties for entries with their amounts.
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($range);
|
||||
$cache->addProperty('category-show-period-entries');
|
||||
$cache->addProperty($category->id);
|
||||
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$entries = new Collection;
|
||||
/** @var CategoryRepositoryInterface $categoryRepository */
|
||||
$categoryRepository = app(CategoryRepositoryInterface::class);
|
||||
|
||||
foreach ($dates as $currentDate) {
|
||||
$spent = $categoryRepository->spentInPeriodCollection(new Collection([$category]), new Collection, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $categoryRepository->earnedInPeriodCollection(new Collection([$category]), new Collection, $currentDate['start'], $currentDate['end']);
|
||||
$spent = $this->groupByCurrency($spent);
|
||||
$earned = $this->groupByCurrency($earned);
|
||||
|
||||
// amount transferred
|
||||
/** @var TransactionCollectorInterface $collector */
|
||||
$collector = app(TransactionCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->setCategory($category)
|
||||
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$transferred = $this->groupByCurrency($collector->getTransactions());
|
||||
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries->push(
|
||||
[
|
||||
'transactions' => 0,
|
||||
'title' => $title,
|
||||
'spent' => $spent,
|
||||
'earned' => $earned,
|
||||
'transferred' => $transferred,
|
||||
'route' => route('categories.show', [$category->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
]
|
||||
);
|
||||
}
|
||||
$cache->store($entries);
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as above, but for lists that involve transactions without a budget.
|
||||
*
|
||||
* This method has been refactored recently.
|
||||
*
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
protected function getNoBudgetPeriodOverview(Carbon $date): Collection
|
||||
{
|
||||
/** @var JournalRepositoryInterface $repository */
|
||||
$repository = app(JournalRepositoryInterface::class);
|
||||
@ -138,8 +212,12 @@ trait PeriodOverview
|
||||
$end = null === $first ? new Carbon : $first->date;
|
||||
$start = clone $date;
|
||||
$range = app('preferences')->get('viewRange', '1M')->data;
|
||||
$entries = new Collection;
|
||||
$cache = new CacheProperties;
|
||||
|
||||
if ($end < $start) {
|
||||
[$start, $end] = [$end, $start]; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('no-budget-period-entries');
|
||||
@ -149,7 +227,8 @@ trait PeriodOverview
|
||||
}
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$entries = new Collection;
|
||||
foreach ($dates as $currentDate) {
|
||||
/** @var TransactionCollectorInterface $collector */
|
||||
$collector = app(TransactionCollectorInterface::class);
|
||||
@ -162,12 +241,12 @@ trait PeriodOverview
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries->push(
|
||||
[
|
||||
'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
'transactions' => $count,
|
||||
'title' => $title,
|
||||
'spent' => $spent,
|
||||
'earned' => '0',
|
||||
'transferred' => '0',
|
||||
'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -177,133 +256,70 @@ trait PeriodOverview
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a period overview for category.
|
||||
*
|
||||
* TODO refactor me.
|
||||
*
|
||||
* @param Category $category
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
protected function getCategoryPeriodOverview(Category $category, Carbon $date): Collection // periodOverview method
|
||||
{
|
||||
/** @var JournalRepositoryInterface $journalRepository */
|
||||
$journalRepository = app(JournalRepositoryInterface::class);
|
||||
/** @var CategoryRepositoryInterface $categoryRepository */
|
||||
$categoryRepository = app(CategoryRepositoryInterface::class);
|
||||
|
||||
|
||||
$range = app('preferences')->get('viewRange', '1M')->data;
|
||||
$first = $journalRepository->firstNull();
|
||||
$end = null === $first ? new Carbon : $first->date;
|
||||
$start = clone $date;
|
||||
|
||||
// properties for entries with their amounts.
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($range);
|
||||
$cache->addProperty('categories.entries');
|
||||
$cache->addProperty($category->id);
|
||||
|
||||
if ($cache->has()) {
|
||||
//return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$entries = new Collection;
|
||||
|
||||
foreach ($dates as $currentDate) {
|
||||
$spent = $categoryRepository->spentInPeriodCollection(new Collection([$category]), new Collection, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $categoryRepository->earnedInPeriodCollection(new Collection([$category]), new Collection, $currentDate['start'], $currentDate['end']);
|
||||
$spent = $this->groupByCurrency($spent);
|
||||
$earned = $this->groupByCurrency($earned);
|
||||
// amount transferred
|
||||
/** @var TransactionCollectorInterface $collector */
|
||||
$collector = app(TransactionCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->setCategory($category)
|
||||
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$transferred = $this->groupByCurrency($collector->getTransactions());
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries->push(
|
||||
[
|
||||
'route' => route('categories.show', [$category->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
'title' => $title,
|
||||
'spent' => $spent,
|
||||
'earned' => $earned,
|
||||
'transferred' => $transferred,
|
||||
]
|
||||
);
|
||||
}
|
||||
$cache->store($entries);
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get overview of periods for tag.
|
||||
*
|
||||
* TODO refactor this.
|
||||
* This shows a period overview for a tag. It goes back in time and lists all relevant transactions and sums.
|
||||
*
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
protected function getTagPeriodOverview(Tag $tag): Collection // period overview for tags.
|
||||
protected function getTagPeriodOverview(Tag $tag, Carbon $date): Collection // period overview for tags.
|
||||
{
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
// get first and last tag date from tag:
|
||||
$range = app('preferences')->get('viewRange', '1M')->data;
|
||||
$range = app('preferences')->get('viewRange', '1M')->data;
|
||||
/** @var Carbon $end */
|
||||
$end = app('navigation')->endOfX($repository->lastUseDate($tag) ?? new Carbon, $range, null);
|
||||
$start = $repository->firstUseDate($tag) ?? new Carbon;
|
||||
$start = clone $date;
|
||||
$end = $repository->firstUseDate($tag) ?? new Carbon;
|
||||
|
||||
|
||||
if ($end < $start) {
|
||||
[$start, $end] = [$end, $start]; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// properties for entries with their amounts.
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('tag.entries');
|
||||
$cache->addProperty('tag-period-entries');
|
||||
$cache->addProperty($tag->id);
|
||||
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$collection = new Collection;
|
||||
$currentEnd = clone $end;
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$entries = new Collection;
|
||||
// while end larger or equal to start
|
||||
while ($currentEnd >= $start) {
|
||||
$currentStart = app('navigation')->startOfPeriod($currentEnd, $range);
|
||||
foreach ($dates as $currentDate) {
|
||||
|
||||
// get expenses and what-not in this period and this tag.
|
||||
$arr = [
|
||||
'string' => $end->format('Y-m-d'),
|
||||
'name' => app('navigation')->periodShow($currentEnd, $range),
|
||||
'start' => clone $currentStart,
|
||||
'end' => clone $currentEnd,
|
||||
'date' => clone $end,
|
||||
'spent' => $repository->spentInPeriod($tag, $currentStart, $currentEnd),
|
||||
'earned' => $repository->earnedInPeriod($tag, $currentStart, $currentEnd),
|
||||
];
|
||||
$collection->push($arr);
|
||||
$spentSet = $repository->expenseInPeriod($tag, $currentDate['start'], $currentDate['end']);
|
||||
$spent = $this->groupByCurrency($spentSet);
|
||||
$earnedSet = $repository->incomeInPeriod($tag, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $this->groupByCurrency($earnedSet);
|
||||
$transferredSet = $repository->transferredInPeriod($tag, $currentDate['start'], $currentDate['end']);
|
||||
$transferred = $this->groupByCurrency($transferredSet);
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
|
||||
$entries->push(
|
||||
[
|
||||
'transactions' => $spentSet->count() + $earnedSet->count() + $transferredSet->count(),
|
||||
'title' => $title,
|
||||
'spent' => $spent,
|
||||
'earned' => $earned,
|
||||
'transferred' => $transferred,
|
||||
'route' => route('tags.show', [$tag->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
]
|
||||
);
|
||||
|
||||
/** @var Carbon $currentEnd */
|
||||
$currentEnd = clone $currentStart;
|
||||
$currentEnd->subDay();
|
||||
}
|
||||
$cache->store($collection);
|
||||
$cache->store($entries);
|
||||
|
||||
return $collection;
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get period overview for index.
|
||||
*
|
||||
* TODO refactor me.
|
||||
* This list shows the overview of a type of transaction, for the period blocks on the list of transactions.
|
||||
*
|
||||
* @param string $what
|
||||
* @param Carbon $date
|
||||
@ -315,42 +331,60 @@ trait PeriodOverview
|
||||
/** @var JournalRepositoryInterface $repository */
|
||||
$repository = app(JournalRepositoryInterface::class);
|
||||
$range = app('preferences')->get('viewRange', '1M')->data;
|
||||
$first = $repository->firstNull();
|
||||
$start = Carbon::now()->subYear();
|
||||
$endJournal = $repository->firstNull();
|
||||
$end = null === $endJournal ? new Carbon : $endJournal->date;
|
||||
$start = clone $date;
|
||||
$types = config('firefly.transactionTypesByWhat.' . $what);
|
||||
$entries = new Collection;
|
||||
if (null !== $first) {
|
||||
$start = $first->date;
|
||||
}
|
||||
if ($date < $start) {
|
||||
[$start, $date] = [$date, $start]; // @codeCoverageIgnore
|
||||
|
||||
|
||||
if ($end < $start) {
|
||||
[$start, $end] = [$end, $start]; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// properties for entries with their amounts.
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('transactions-period-entries');
|
||||
$cache->addProperty($what);
|
||||
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $date, $range);
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$entries = new Collection;
|
||||
|
||||
foreach ($dates as $currentDate) {
|
||||
|
||||
// get all expenses, income or transfers:
|
||||
/** @var TransactionCollectorInterface $collector */
|
||||
$collector = app(TransactionCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->withOpposingAccount()->setTypes($types);
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$transactions = $collector->getTransactions();
|
||||
|
||||
if ($transactions->count() > 0) {
|
||||
$sums = $this->sumPerCurrency($transactions);
|
||||
$dateName = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
|
||||
$sum = $transactions->sum('transaction_amount');
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
$entries->push(
|
||||
[
|
||||
'name' => $dateName,
|
||||
'sums' => $sums,
|
||||
'sum' => $sum,
|
||||
'start' => $currentDate['start']->format('Y-m-d'),
|
||||
'end' => $currentDate['end']->format('Y-m-d'),
|
||||
]
|
||||
);
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$grouped = $this->groupByCurrency($transactions);
|
||||
$spent = [];
|
||||
$earned = [];
|
||||
$transferred = [];
|
||||
if ('expenses' === $what || 'withdrawal' === $what) {
|
||||
$spent = $grouped;
|
||||
}
|
||||
if ('revenue' === $what || 'deposit' === $what) {
|
||||
$earned = $grouped;
|
||||
}
|
||||
if ('transfer' === $what || 'transfers' === $what) {
|
||||
$transferred = $grouped;
|
||||
}
|
||||
$entries->push(
|
||||
[
|
||||
'transactions' => $transactions->count(),
|
||||
'title' => $title,
|
||||
'spent' => $spent,
|
||||
'earned' => $earned,
|
||||
'transferred' => $transferred,
|
||||
'route' => route('transactions.index', [$what, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $entries;
|
||||
|
@ -28,6 +28,7 @@ use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\ViewErrorBag;
|
||||
use Log;
|
||||
use URL;
|
||||
|
||||
@ -141,6 +142,10 @@ trait UserNavigation
|
||||
*/
|
||||
protected function rememberPreviousUri(string $identifier): void
|
||||
{
|
||||
session()->put($identifier, URL::previous());
|
||||
/** @var ViewErrorBag $errors */
|
||||
$errors = session()->get('errors');
|
||||
if(null === $errors || (null !== $errors && 0=== $errors->count())) {
|
||||
session()->put($identifier, URL::previous());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* ChooseAccountHandler.php
|
||||
* Copyright (c) 2018 https://github.com/bnw
|
||||
*
|
||||
* 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\JobConfiguration\FinTS;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Import\JobConfiguration\FinTSConfigurationSteps;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\FinTS\FinTS;
|
||||
use Illuminate\Support\MessageBag;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class ChooseAccountHandler
|
||||
*/
|
||||
class ChooseAccountHandler implements FinTSConfigurationInterface
|
||||
{
|
||||
/** @var AccountRepositoryInterface */
|
||||
private $accountRepository;
|
||||
/** @var ImportJob */
|
||||
private $importJob;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* Store data associated with current stage.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return MessageBag
|
||||
*/
|
||||
public function configureJob(array $data): MessageBag
|
||||
{
|
||||
$config = $this->repository->getConfiguration($this->importJob);
|
||||
$config['fints_account'] = (string)($data['fints_account'] ?? '');
|
||||
$config['local_account'] = (string)($data['local_account'] ?? '');
|
||||
$config['from_date'] = (string)($data['from_date'] ?? '');
|
||||
$config['to_date'] = (string)($data['to_date'] ?? '');
|
||||
$this->repository->setConfiguration($this->importJob, $config);
|
||||
|
||||
try {
|
||||
$finTS = app(FinTS::class, ['config' => $config]);
|
||||
$finTS->getAccount($config['fints_account']);
|
||||
} catch (FireflyException $e) {
|
||||
return new MessageBag([$e->getMessage()]);
|
||||
}
|
||||
|
||||
$this->repository->setStage($this->importJob, FinTSConfigurationSteps::GO_FOR_IMPORT);
|
||||
|
||||
return new MessageBag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data necessary to show the configuration screen.
|
||||
*
|
||||
* @return array
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
public function getNextData(): array
|
||||
{
|
||||
$finTS = app(FinTS::class, ['config' => $this->importJob->configuration]);
|
||||
$finTSAccounts = $finTS->getAccounts();
|
||||
$finTSAccountsData = [];
|
||||
foreach ($finTSAccounts as $account) {
|
||||
$finTSAccountsData[$account->getAccountNumber()] = $account->getIban();
|
||||
}
|
||||
|
||||
$localAccounts = [];
|
||||
foreach ($this->accountRepository->getAccountsByType([AccountType::ASSET]) as $localAccount) {
|
||||
$display_name = $localAccount->name;
|
||||
if ($localAccount->iban) {
|
||||
$display_name .= sprintf(' - %s', $localAccount->iban);
|
||||
}
|
||||
$localAccounts[$localAccount->id] = $display_name;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'fints_accounts' => $finTSAccountsData,
|
||||
'fints_account' => $this->importJob->configuration['fints_account'] ?? null,
|
||||
'local_accounts' => $localAccounts,
|
||||
'local_account' => $this->importJob->configuration['local_account'] ?? null,
|
||||
'from_date' => $this->importJob->configuration['from_date'] ?? (new Carbon('now - 1 month'))->format('Y-m-d'),
|
||||
'to_date' => $this->importJob->configuration['to_date'] ?? (new Carbon('now'))->format('Y-m-d'),
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $importJob
|
||||
*/
|
||||
public function setImportJob(ImportJob $importJob): void
|
||||
{
|
||||
$this->importJob = $importJob;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$this->repository->setUser($importJob->user);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* FinTSConfigurationInterface.php
|
||||
* Copyright (c) 2018 https://github.com/bnw
|
||||
*
|
||||
* 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\JobConfiguration\FinTS;
|
||||
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use Illuminate\Support\MessageBag;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
interface FinTSConfigurationInterface
|
||||
{
|
||||
/**
|
||||
* Store data associated with current stage.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return MessageBag
|
||||
*/
|
||||
public function configureJob(array $data): MessageBag;
|
||||
|
||||
/**
|
||||
* Get the data necessary to show the configuration screen.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getNextData(): array;
|
||||
|
||||
/**
|
||||
* @param ImportJob $importJob
|
||||
*/
|
||||
public function setImportJob(ImportJob $importJob): void;
|
||||
}
|
110
app/Support/Import/JobConfiguration/FinTS/NewFinTSJobHandler.php
Normal file
110
app/Support/Import/JobConfiguration/FinTS/NewFinTSJobHandler.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* NewFinTSJobHandler.php
|
||||
* Copyright (c) 2018 https://github.com/bnw
|
||||
*
|
||||
* 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\JobConfiguration\FinTS;
|
||||
|
||||
|
||||
use FireflyIII\Import\JobConfiguration\FinTSConfigurationSteps;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\FinTS\FinTS;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\MessageBag;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class NewFinTSJobHandler
|
||||
*/
|
||||
class NewFinTSJobHandler implements FinTSConfigurationInterface
|
||||
{
|
||||
/** @var ImportJob */
|
||||
private $importJob;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* Store data associated with current stage.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return MessageBag
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
public function configureJob(array $data): MessageBag
|
||||
{
|
||||
$config = [];
|
||||
|
||||
$config['fints_url'] = trim($data['fints_url'] ?? '');
|
||||
$config['fints_port'] = (int)($data['fints_port'] ?? '');
|
||||
$config['fints_bank_code'] = (string)($data['fints_bank_code'] ?? '');
|
||||
$config['fints_username'] = (string)($data['fints_username'] ?? '');
|
||||
$config['fints_password'] = (string)(Crypt::encrypt($data['fints_password']) ?? '');
|
||||
|
||||
$this->repository->setConfiguration($this->importJob, $config);
|
||||
|
||||
$incomplete = false;
|
||||
foreach ($config as $value) {
|
||||
$incomplete = '' === $value or $incomplete;
|
||||
}
|
||||
if ($incomplete) {
|
||||
return new MessageBag([trans('import.incomplete_fints_form')]);
|
||||
}
|
||||
|
||||
$finTS = app(FinTS::class, ['config' => $this->importJob->configuration]);
|
||||
if (true !== ($checkConnection = $finTS->checkConnection())) {
|
||||
return new MessageBag([trans('import.fints_connection_failed', ['originalError' => $checkConnection])]);
|
||||
}
|
||||
|
||||
$this->repository->setStage($this->importJob, FinTSConfigurationSteps::CHOOSE_ACCOUNT);
|
||||
|
||||
return new MessageBag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data necessary to show the configuration screen.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getNextData(): array
|
||||
{
|
||||
$config = $this->importJob->configuration;
|
||||
|
||||
return [
|
||||
'fints_url' => $config['fints_url'] ?? '',
|
||||
'fints_port' => $config['fints_port'] ?? '443',
|
||||
'fints_bank_code' => $config['fints_bank_code'] ?? '',
|
||||
'fints_username' => $config['fints_username'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $importJob
|
||||
*/
|
||||
public function setImportJob(ImportJob $importJob): void
|
||||
{
|
||||
$this->importJob = $importJob;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($importJob->user);
|
||||
}
|
||||
|
||||
}
|
@ -80,6 +80,14 @@ class StageImportDataHandler
|
||||
return $this->transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isStillRunning(): bool
|
||||
{
|
||||
return $this->stillRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws FireflyException
|
||||
|
189
app/Support/Import/Routine/FinTS/StageImportDataHandler.php
Normal file
189
app/Support/Import/Routine/FinTS/StageImportDataHandler.php
Normal file
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/**
|
||||
* StageImportDataHandler.php
|
||||
* Copyright (c) 2018 https://github.com/bnw
|
||||
*
|
||||
* 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\Routine\FinTS;
|
||||
|
||||
|
||||
use Fhp\Model\StatementOfAccount\Transaction;
|
||||
use Fhp\Model\StatementOfAccount\Transaction as FinTSTransaction;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account as LocalAccount;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\FinTS\FinTS;
|
||||
use FireflyIII\Support\FinTS\MetadataParser;
|
||||
use FireflyIII\Support\Import\Routine\File\OpposingAccountMapper;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class StageImportDataHandler
|
||||
*/
|
||||
class StageImportDataHandler
|
||||
{
|
||||
/** @var AccountRepositoryInterface */
|
||||
private $accountRepository;
|
||||
/** @var ImportJob */
|
||||
private $importJob;
|
||||
/** @var OpposingAccountMapper */
|
||||
private $mapper;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
/** @var array */
|
||||
private $transactions;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getTransactions(): array
|
||||
{
|
||||
return $this->transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
Log::debug('Now in StageImportDataHandler::run()');
|
||||
|
||||
$localAccount = $this->accountRepository->findNull((int)$this->importJob->configuration['local_account']);
|
||||
if (null === $localAccount) {
|
||||
throw new FireflyException(sprintf('Cannot find Firefly account with id #%d ' , $this->importJob->configuration['local_account']));
|
||||
}
|
||||
$finTS = app(FinTS::class, ['config' => $this->importJob->configuration]);
|
||||
$fintTSAccount = $finTS->getAccount($this->importJob->configuration['fints_account']);
|
||||
$statementOfAccount = $finTS->getStatementOfAccount(
|
||||
$fintTSAccount, new \DateTime($this->importJob->configuration['from_date']), new \DateTime($this->importJob->configuration['to_date'])
|
||||
);
|
||||
$collection = [];
|
||||
foreach ($statementOfAccount->getStatements() as $statement) {
|
||||
foreach ($statement->getTransactions() as $transaction) {
|
||||
$collection[] = $this->convertTransaction($transaction, $localAccount);
|
||||
}
|
||||
}
|
||||
|
||||
$this->transactions = $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $importJob
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setImportJob(ImportJob $importJob): void
|
||||
{
|
||||
$this->transactions = [];
|
||||
$this->importJob = $importJob;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$this->mapper = app(OpposingAccountMapper::class);
|
||||
$this->mapper->setUser($importJob->user);
|
||||
$this->repository->setUser($importJob->user);
|
||||
$this->accountRepository->setUser($importJob->user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FinTSTransaction $transaction
|
||||
* @param LocalAccount $source
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function convertTransaction(FinTSTransaction $transaction, LocalAccount $source): array
|
||||
{
|
||||
Log::debug(sprintf('Start converting transaction %s', $transaction->getDescription1()));
|
||||
|
||||
$amount = (string)$transaction->getAmount();
|
||||
$debitOrCredit = $transaction->getCreditDebit();
|
||||
// assume deposit.
|
||||
$type = TransactionType::DEPOSIT;
|
||||
Log::debug(sprintf('Amount is %s', $amount));
|
||||
|
||||
// inverse if not.
|
||||
if ($debitOrCredit !== Transaction::CD_CREDIT) {
|
||||
$type = TransactionType::WITHDRAWAL;
|
||||
$amount = bcmul($amount, '-1');
|
||||
}
|
||||
|
||||
$destination = $this->mapper->map(
|
||||
null,
|
||||
$amount,
|
||||
['iban' => $transaction->getAccountNumber(), 'name' => $transaction->getName()]
|
||||
);
|
||||
if ($debitOrCredit === Transaction::CD_CREDIT) {
|
||||
[$source, $destination] = [$destination, $source];
|
||||
}
|
||||
|
||||
if ($source->accountType->type === AccountType::ASSET && $destination->accountType->type === AccountType::ASSET) {
|
||||
$type = TransactionType::TRANSFER;
|
||||
Log::debug('Both are assets, will make transfer.');
|
||||
}
|
||||
|
||||
$metadataParser = new MetadataParser();
|
||||
$description = $metadataParser->getDescription($transaction);
|
||||
|
||||
$storeData = [
|
||||
'user' => $this->importJob->user_id,
|
||||
'type' => $type,
|
||||
'date' => $transaction->getValutaDate()->format('Y-m-d'),
|
||||
'description' => $description,
|
||||
'piggy_bank_id' => null,
|
||||
'piggy_bank_name' => null,
|
||||
'bill_id' => null,
|
||||
'bill_name' => null,
|
||||
'tags' => [],
|
||||
'internal_reference' => null,
|
||||
'external_id' => null,
|
||||
'notes' => null,
|
||||
'bunq_payment_id' => null,
|
||||
'original-source' => sprintf('fints-v%s', config('firefly.version')),
|
||||
'transactions' => [
|
||||
// single transaction:
|
||||
[
|
||||
'description' => null,
|
||||
'amount' => $amount,
|
||||
'currency_id' => null,
|
||||
'currency_code' => 'EUR',
|
||||
'foreign_amount' => null,
|
||||
'foreign_currency_id' => null,
|
||||
'foreign_currency_code' => null,
|
||||
'budget_id' => null,
|
||||
'budget_name' => null,
|
||||
'category_id' => null,
|
||||
'category_name' => null,
|
||||
'source_id' => $source->id,
|
||||
'source_name' => null,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_name' => null,
|
||||
'reconciled' => false,
|
||||
'identifier' => 0,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $storeData;
|
||||
}
|
||||
}
|
@ -225,6 +225,7 @@ class Transaction extends Twig_Extension
|
||||
}
|
||||
|
||||
$name = app('steam')->tryDecrypt($transaction->account_name);
|
||||
$iban = $transaction->account_iban;
|
||||
$transactionId = (int)$transaction->account_id;
|
||||
$type = $transaction->account_type;
|
||||
|
||||
@ -233,6 +234,7 @@ class Transaction extends Twig_Extension
|
||||
$name = $transaction->opposing_account_name;
|
||||
$transactionId = (int)$transaction->opposing_account_id;
|
||||
$type = $transaction->opposing_account_type;
|
||||
$iban = $transaction->opposing_account_iban;
|
||||
}
|
||||
|
||||
// Find the opposing account and use that one:
|
||||
@ -264,7 +266,7 @@ class Transaction extends Twig_Extension
|
||||
return $txt;
|
||||
}
|
||||
|
||||
$txt = sprintf('<a title="%1$s" href="%2$s">%1$s</a>', e($name), route('accounts.show', [$transactionId]));
|
||||
$txt = sprintf('<a title="%3$s" href="%2$s">%1$s</a>', e($name), route('accounts.show', [$transactionId]), $iban);
|
||||
|
||||
return $txt;
|
||||
}
|
||||
@ -385,12 +387,14 @@ class Transaction extends Twig_Extension
|
||||
$name = app('steam')->tryDecrypt($transaction->account_name);
|
||||
$transactionId = (int)$transaction->account_id;
|
||||
$type = $transaction->account_type;
|
||||
$iban = $transaction->account_iban;
|
||||
|
||||
// name is present in object, use that one:
|
||||
if (null !== $transaction->opposing_account_id && 1 === bccomp($transaction->transaction_amount, '0')) {
|
||||
$name = $transaction->opposing_account_name;
|
||||
$transactionId = (int)$transaction->opposing_account_id;
|
||||
$type = $transaction->opposing_account_type;
|
||||
$iban = $transaction->opposing_account_iban;
|
||||
}
|
||||
// Find the opposing account and use that one:
|
||||
if (null === $transaction->opposing_account_id && 1 === bccomp($transaction->transaction_amount, '0')) {
|
||||
@ -415,7 +419,7 @@ class Transaction extends Twig_Extension
|
||||
return $txt;
|
||||
}
|
||||
|
||||
$txt = sprintf('<a title="%1$s" href="%2$s">%1$s</a>', e($name), route('accounts.show', [$transactionId]));
|
||||
$txt = sprintf('<a title="%3$s" href="%2$s">%1$s</a>', e($name), route('accounts.show', [$transactionId]), $iban);
|
||||
|
||||
return $txt;
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class AppendDescription.
|
||||
* Class PrependDescription.
|
||||
*/
|
||||
class PrependDescription implements ActionInterface
|
||||
{
|
||||
|
@ -90,7 +90,7 @@ final class ToAccountIs extends AbstractTrigger implements TriggerInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
Log::debug(sprintf('RuleTrigger ToAccountIs for journal #%d: "%s" is NOT "%s", return true.', $journal->id, $toAccountName, $search));
|
||||
Log::debug(sprintf('RuleTrigger ToAccountIs for journal #%d: "%s" is NOT "%s", return false.', $journal->id, $toAccountName, $search));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -313,10 +313,8 @@ class FireflyValidator extends Validator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateRuleTriggerValue(string $attribute, string $value): bool
|
||||
public function validateRuleTriggerValue(string $attribute, string $value = null): bool
|
||||
{
|
||||
//
|
||||
|
||||
// first, get the index from this string:
|
||||
$parts = explode('.', $attribute);
|
||||
$index = (int)($parts[1] ?? '0');
|
||||
|
@ -46,7 +46,7 @@ if (!function_exists('envNonEmpty')) {
|
||||
function envNonEmpty(string $key, $default = null)
|
||||
{
|
||||
$result = env($key, $default);
|
||||
if (is_string($result) && $result === '') {
|
||||
if (is_string($result) && '' === $result) {
|
||||
$result = $default;
|
||||
}
|
||||
|
||||
|
40
changelog.md
40
changelog.md
@ -2,6 +2,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [4.7.8] - 2018-10-28
|
||||
### Added
|
||||
- [Issue 1005](https://github.com/firefly-iii/firefly-iii/issues/1005) You can now configure Firefly III to use LDAP.
|
||||
- [Issue 1071](https://github.com/firefly-iii/firefly-iii/issues/1071) You can execute transaction rules using the command line (so you can cronjob it)
|
||||
- [Issue 1108](https://github.com/firefly-iii/firefly-iii/issues/1108) You can now reorder budgets.
|
||||
- [Issue 1159](https://github.com/firefly-iii/firefly-iii/issues/1159) The ability to import transactions from FinTS-enabled banks.
|
||||
- [Issue 1727](https://github.com/firefly-iii/firefly-iii/issues/1727) You can now use SFTP as storage for uploads and exports.
|
||||
- [Issue 1733](https://github.com/firefly-iii/firefly-iii/issues/1733) You can configure Firefly III not to send emails with transaction information in them.
|
||||
|
||||
|
||||
|
||||
### Changed
|
||||
- [Issue 1040](https://github.com/firefly-iii/firefly-iii/issues/1040) Fixed various things that would not scale properly in the past.
|
||||
- [Issue 1771](https://github.com/firefly-iii/firefly-iii/issues/1771) A link to the transaction that fits the bill.
|
||||
- [Issue 1800](https://github.com/firefly-iii/firefly-iii/issues/1800) Icon updated to match others.
|
||||
- MySQL database connection now forces the InnoDB to be used.
|
||||
|
||||
### Fixed
|
||||
- [Issue 1583](https://github.com/firefly-iii/firefly-iii/issues/1583) Some times recurring transactions would not fire.
|
||||
- [Issue 1607](https://github.com/firefly-iii/firefly-iii/issues/1607) Problems with the bunq API, finally solved?! (I feel like a clickbait YouTube video now)
|
||||
- [Issue 1698](https://github.com/firefly-iii/firefly-iii/issues/1698) Certificate problems in the Docker container
|
||||
- [Issue 1751](https://github.com/firefly-iii/firefly-iii/issues/1751) Bug in autocomplete
|
||||
- [Issue 1760](https://github.com/firefly-iii/firefly-iii/issues/1760) Tag report bad math
|
||||
- [Issue 1765](https://github.com/firefly-iii/firefly-iii/issues/1765) API inconsistencies for piggy banks.
|
||||
- [Issue 1774](https://github.com/firefly-iii/firefly-iii/issues/1774) Integer exception in SQLite databases
|
||||
- [Issue 1775](https://github.com/firefly-iii/firefly-iii/issues/1775) Heroku now supports all locales
|
||||
- [Issue 1778](https://github.com/firefly-iii/firefly-iii/issues/1778) More autocomplete problems fixed
|
||||
- [Issue 1747](https://github.com/firefly-iii/firefly-iii/issues/1747) Rules now stop at the right moment.
|
||||
- [Issue 1781](https://github.com/firefly-iii/firefly-iii/issues/1781) Problems when creating new rules.
|
||||
- [Issue 1784](https://github.com/firefly-iii/firefly-iii/issues/1784) Can now create a liability with an empty balance.
|
||||
- [Issue 1785](https://github.com/firefly-iii/firefly-iii/issues/1785) Redirect error
|
||||
- [Issue 1790](https://github.com/firefly-iii/firefly-iii/issues/1790) Show attachments for bills.
|
||||
- [Issue 1792](https://github.com/firefly-iii/firefly-iii/issues/1792) Mention excluded accounts.
|
||||
- [Issue 1798](https://github.com/firefly-iii/firefly-iii/issues/1798) Could not recreate deleted piggy banks
|
||||
- [Issue 1805](https://github.com/firefly-iii/firefly-iii/issues/1805) Fixes when handling foreign currencies
|
||||
- [Issue 1807](https://github.com/firefly-iii/firefly-iii/issues/1807) Also decrypt deleted records.
|
||||
- [Issue 1812](https://github.com/firefly-iii/firefly-iii/issues/1812) Fix in transactions API
|
||||
- [Issue 1815](https://github.com/firefly-iii/firefly-iii/issues/1815) Opening balance account name can now be translated.
|
||||
- [Issue 1830](https://github.com/firefly-iii/firefly-iii/issues/1830) Multi-user in a single browser could leak autocomplete data.
|
||||
|
||||
## [4.7.7] - 2018-10-01
|
||||
|
||||
This version of Firefly III requires PHP 7.2. I've already started using several features from 7.2. Please make sure you upgrade.
|
||||
|
@ -53,8 +53,12 @@
|
||||
"ext-intl": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-json": "*",
|
||||
"ext-ldap": "*",
|
||||
"adldap2/adldap2-laravel": "^4.0",
|
||||
"bacon/bacon-qr-code": "1.*",
|
||||
"bunq/sdk_php": "dev-master#8c1faefc111d9b970168a1837ca165d854954941",
|
||||
"danhunsaker/laravel-flysystem-others": "^1.3",
|
||||
"davejamesmiller/laravel-breadcrumbs": "5.*",
|
||||
"doctrine/dbal": "2.*",
|
||||
"fideloper/proxy": "4.*",
|
||||
@ -63,7 +67,11 @@
|
||||
"laravelcollective/html": "5.7.*",
|
||||
"league/commonmark": "0.*",
|
||||
"league/csv": "9.*",
|
||||
"league/flysystem-replicate-adapter": "^1.0",
|
||||
"league/flysystem-sftp": "^1.0",
|
||||
"league/fractal": "^0.17.0",
|
||||
"litipk/flysystem-fallback-adapter": "0.1.2",
|
||||
"mschindler83/fints-hbci-php": "^1.0",
|
||||
"pragmarx/google2fa": "3.*",
|
||||
"pragmarx/google2fa-laravel": "0.*",
|
||||
"rcrowe/twigbridge": "0.9.*",
|
||||
|
787
composer.lock
generated
787
composer.lock
generated
File diff suppressed because it is too large
Load Diff
259
config/adldap.php
Normal file
259
config/adldap.php
Normal file
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
use Adldap\Schemas\ActiveDirectory;
|
||||
use Adldap\Schemas\FreeIPA;
|
||||
use Adldap\Schemas\OpenLDAP;
|
||||
|
||||
/*
|
||||
* Get schema from .env file.
|
||||
*/
|
||||
$schema = OpenLDAP::class;
|
||||
|
||||
if ('FreeIPA' === envNonEmpty('ADLDAP_CONNECTION_SCHEME', 'OpenLDAP')) {
|
||||
$schema = FreeIPA::class;
|
||||
}
|
||||
if ('ActiveDirectory' === envNonEmpty('ADLDAP_CONNECTION_SCHEME', 'OpenLDAP')) {
|
||||
$schema = ActiveDirectory::class;
|
||||
}
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array stores the connections that are added to Adldap. You can add
|
||||
| as many connections as you like.
|
||||
|
|
||||
| The key is the name of the connection you wish to use and the value is
|
||||
| an array of configuration settings.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'default' => [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Auto Connect
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If auto connect is true, Adldap will try to automatically connect to
|
||||
| your LDAP server in your configuration. This allows you to assume
|
||||
| connectivity rather than having to connect manually
|
||||
| in your application.
|
||||
|
|
||||
| If this is set to false, you **must** connect manually before running
|
||||
| LDAP operations.
|
||||
|
|
||||
*/
|
||||
|
||||
'auto_connect' => env('ADLDAP_AUTO_CONNECT', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The connection class to use to run raw LDAP operations on.
|
||||
|
|
||||
| Custom connection classes must implement:
|
||||
|
|
||||
| Adldap\Connections\ConnectionInterface
|
||||
|
|
||||
*/
|
||||
|
||||
'connection' => Adldap\Connections\Ldap::class,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Schema
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The schema class to use for retrieving attributes and generating models.
|
||||
|
|
||||
| You can also set this option to `null` to use the default schema class.
|
||||
|
|
||||
| For OpenLDAP, you must use the schema:
|
||||
|
|
||||
| Adldap\Schemas\OpenLDAP::class
|
||||
|
|
||||
| For FreeIPA, you must use the schema:
|
||||
|
|
||||
| Adldap\Schemas\FreeIPA::class
|
||||
|
|
||||
| Custom schema classes must implement Adldap\Schemas\SchemaInterface
|
||||
|
|
||||
*/
|
||||
|
||||
'schema' => $schema,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Connection Settings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This connection settings array is directly passed into the Adldap constructor.
|
||||
|
|
||||
| Feel free to add or remove settings you don't need.
|
||||
|
|
||||
*/
|
||||
|
||||
'connection_settings' => [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Account Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The account prefix option is the prefix of your user accounts in LDAP directory.
|
||||
|
|
||||
| This string is prepended to authenticating users usernames.
|
||||
|
|
||||
*/
|
||||
|
||||
'account_prefix' => env('ADLDAP_ACCOUNT_PREFIX', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Account Suffix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The account suffix option is the suffix of your user accounts in your LDAP directory.
|
||||
|
|
||||
| This string is appended to authenticating users usernames.
|
||||
|
|
||||
*/
|
||||
|
||||
'account_suffix' => env('ADLDAP_ACCOUNT_SUFFIX', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Domain Controllers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The domain controllers option is an array of servers located on your
|
||||
| network that serve Active Directory. You can insert as many servers or
|
||||
| as little as you'd like depending on your forest (with the
|
||||
| minimum of one of course).
|
||||
|
|
||||
| These can be IP addresses of your server(s), or the host name.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain_controllers' => explode(' ', env('ADLDAP_CONTROLLERS', '127.0.0.1')),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Port
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The port option is used for authenticating and binding to your LDAP server.
|
||||
|
|
||||
*/
|
||||
|
||||
'port' => env('ADLDAP_PORT', 389),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Timeout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The timeout option allows you to configure the amount of time in
|
||||
| seconds that your application waits until a response
|
||||
| is received from your LDAP server.
|
||||
|
|
||||
*/
|
||||
|
||||
'timeout' => env('ADLDAP_TIMEOUT', 5),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Base Distinguished Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The base distinguished name is the base distinguished name you'd
|
||||
| like to perform query operations on. An example base DN would be:
|
||||
|
|
||||
| dc=corp,dc=acme,dc=org
|
||||
|
|
||||
| A correct base DN is required for any query results to be returned.
|
||||
|
|
||||
*/
|
||||
|
||||
'base_dn' => env('ADLDAP_BASEDN', 'dc=temp'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Administrator Account Suffix / Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to set a different account prefix and suffix
|
||||
| for your configured administrator account upon binding.
|
||||
|
|
||||
| If left empty or set to `null`, your `account_prefix` and
|
||||
| `account_suffix` options above will be used.
|
||||
|
|
||||
*/
|
||||
|
||||
'admin_account_prefix' => env('ADLDAP_ADMIN_ACCOUNT_PREFIX', ''),
|
||||
'admin_account_suffix' => env('ADLDAP_ADMIN_ACCOUNT_SUFFIX', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Administrator Username & Password
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When connecting to your LDAP server, a username and password is required
|
||||
| to be able to query and run operations on your server(s). You can
|
||||
| use any user account that has these permissions. This account
|
||||
| does not need to be a domain administrator unless you
|
||||
| require changing and resetting user passwords.
|
||||
|
|
||||
*/
|
||||
|
||||
'admin_username' => env('ADLDAP_ADMIN_USERNAME', ''),
|
||||
'admin_password' => env('ADLDAP_ADMIN_PASSWORD', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Follow Referrals
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The follow referrals option is a boolean to tell active directory
|
||||
| to follow a referral to another server on your network if the
|
||||
| server queried knows the information your asking for exists,
|
||||
| but does not yet contain a copy of it locally.
|
||||
|
|
||||
| This option is defaulted to false.
|
||||
|
|
||||
*/
|
||||
|
||||
'follow_referrals' => env('ADLDAP_FOLLOW_REFFERALS', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| SSL & TLS
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If you need to be able to change user passwords on your server, then an
|
||||
| SSL or TLS connection is required. All other operations are allowed
|
||||
| on unsecured protocols.
|
||||
|
|
||||
| One of these options are definitely recommended if you
|
||||
| have the ability to connect to your server securely.
|
||||
|
|
||||
*/
|
||||
|
||||
'use_ssl' => env('ADLDAP_USE_SSL', false),
|
||||
'use_tls' => env('ADLDAP_USE_TLS', false),
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
317
config/adldap_auth.php
Normal file
317
config/adldap_auth.php
Normal file
@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
use Adldap\Laravel\Scopes\UidScope;
|
||||
use Adldap\Laravel\Scopes\UpnScope;
|
||||
|
||||
|
||||
// default OpenLDAP scopes.
|
||||
$scopes = [
|
||||
UidScope::class,
|
||||
];
|
||||
if ('FreeIPA' === env('ADLDAP_CONNECTION_SCHEME')) {
|
||||
$scopes = [
|
||||
UpnScope::class,
|
||||
];
|
||||
}
|
||||
if ('ActiveDirectory' === env('ADLDAP_CONNECTION_SCHEME')) {
|
||||
$scopes = [
|
||||
UpnScope::class,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The LDAP connection to use for laravel authentication.
|
||||
|
|
||||
| You must specify connections in your `config/adldap.php` configuration file.
|
||||
|
|
||||
| This must be a string.
|
||||
|
|
||||
*/
|
||||
|
||||
'connection' => envNonEmpty('ADLDAP_CONNECTION', 'default'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Provider
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The LDAP authentication provider to use depending
|
||||
| if you require database synchronization.
|
||||
|
|
||||
| For synchronizing LDAP users to your local applications database, use the provider:
|
||||
|
|
||||
| Adldap\Laravel\Auth\DatabaseUserProvider::class
|
||||
|
|
||||
| Otherwise, if you just require LDAP authentication, use the provider:
|
||||
|
|
||||
| Adldap\Laravel\Auth\NoDatabaseUserProvider::class
|
||||
|
|
||||
*/
|
||||
|
||||
'provider' => Adldap\Laravel\Auth\DatabaseUserProvider::class,
|
||||
//'provider' => Adldap\Laravel\Auth\NoDatabaseUserProvider::class,
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Rules
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Rules allow you to control user authentication requests depending on scenarios.
|
||||
|
|
||||
| You can create your own rules and insert them here.
|
||||
|
|
||||
| All rules must extend from the following class:
|
||||
|
|
||||
| Adldap\Laravel\Validation\Rules\Rule
|
||||
|
|
||||
*/
|
||||
|
||||
'rules' => [
|
||||
|
||||
// Denys deleted users from authenticating.
|
||||
Adldap\Laravel\Validation\Rules\DenyTrashed::class,
|
||||
|
||||
// Allows only manually imported users to authenticate.
|
||||
// Adldap\Laravel\Validation\Rules\OnlyImported::class,
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Scopes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Scopes allow you to restrict the LDAP query that locates
|
||||
| users upon import and authentication.
|
||||
|
|
||||
| All scopes must implement the following interface:
|
||||
|
|
||||
| Adldap\Laravel\Scopes\ScopeInterface
|
||||
|[
|
||||
|
||||
// Only allows users with a user principal name to authenticate.
|
||||
// Remove this if you're using OpenLDAP.
|
||||
//Adldap\Laravel\Scopes\UpnScope::class,
|
||||
|
||||
// Only allows users with a uid to authenticate.
|
||||
// Uncomment if you're using OpenLDAP.
|
||||
Adldap\Laravel\Scopes\UidScope::class,
|
||||
|
||||
],
|
||||
*/
|
||||
|
||||
'scopes' => $scopes,
|
||||
|
||||
'usernames' => [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| LDAP
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Discover:
|
||||
|
|
||||
| The discover value is the users attribute you would
|
||||
| like to locate LDAP users by in your directory.
|
||||
|
|
||||
| For example, using the default configuration below, if you're
|
||||
| authenticating users with an email address, your LDAP server
|
||||
| will be queried for a user with the a `userprincipalname`
|
||||
| equal to the entered email address.
|
||||
|
|
||||
| Authenticate:
|
||||
|
|
||||
| The authenticate value is the users attribute you would
|
||||
| like to use to bind to your LDAP server.
|
||||
|
|
||||
| For example, when a user is located by the above 'discover'
|
||||
| attribute, the users attribute you specify below will
|
||||
| be used as the username to bind to your LDAP server.
|
||||
|
|
||||
*/
|
||||
|
||||
'ldap' => [
|
||||
|
||||
'discover' => envNonEmpty('ADLDAP_DISCOVER_FIELD', 'userprincipalname'),
|
||||
'authenticate' => envNonEmpty('ADLDAP_AUTH_FIELD', 'distinguishedname'),
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Eloquent
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The value you enter is the database column name used for locating
|
||||
| the local database record of the authenticating user.
|
||||
|
|
||||
| If you're using a `username` column instead, change this to `username`.
|
||||
|
|
||||
| This option is only applicable to the DatabaseUserProvider.
|
||||
|
|
||||
*/
|
||||
|
||||
'eloquent' => 'email',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Windows Authentication Middleware (SSO)
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Discover:
|
||||
|
|
||||
| The 'discover' value is the users attribute you would
|
||||
| like to locate LDAP users by in your directory.
|
||||
|
|
||||
| For example, if 'samaccountname' is the value, then your LDAP server is
|
||||
| queried for a user with the 'samaccountname' equal to the value of
|
||||
| $_SERVER['AUTH_USER'].
|
||||
|
|
||||
| If a user is found, they are imported (if using the DatabaseUserProvider)
|
||||
| into your local database, then logged in.
|
||||
|
|
||||
| Key:
|
||||
|
|
||||
| The 'key' value represents the 'key' of the $_SERVER
|
||||
| array to pull the users account name from.
|
||||
|
|
||||
| For example, $_SERVER['AUTH_USER'].
|
||||
|
|
||||
*/
|
||||
|
||||
'windows' => [
|
||||
'discover' => envNonEmpty('WINDOWS_SSO_DISCOVER', 'samaccountname'),
|
||||
'key' => envNonEmpty('WINDOWS_SSO_KEY', 'AUTH_USER'),
|
||||
],
|
||||
],
|
||||
|
||||
'passwords' => [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Sync
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The password sync option allows you to automatically synchronize users
|
||||
| LDAP passwords to your local database. These passwords are hashed
|
||||
| natively by Laravel using the bcrypt() method.
|
||||
|
|
||||
| Enabling this option would also allow users to login to their accounts
|
||||
| using the password last used when an LDAP connection was present.
|
||||
|
|
||||
| If this option is disabled, the local database account is applied a
|
||||
| random 16 character hashed password upon every login, and will
|
||||
| lose access to this account upon loss of LDAP connectivity.
|
||||
|
|
||||
| This option must be true or false and is only applicable
|
||||
| to the DatabaseUserProvider.
|
||||
|
|
||||
*/
|
||||
|
||||
'sync' => env('ADLDAP_PASSWORD_SYNC', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Column
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the column of your users database table
|
||||
| that is used to store passwords.
|
||||
|
|
||||
| Set this to `null` if you do not have a password column.
|
||||
|
|
||||
| This option is only applicable to the DatabaseUserProvider.
|
||||
|
|
||||
*/
|
||||
|
||||
'column' => 'password',
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Login Fallback
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The login fallback option allows you to login as a user located on the
|
||||
| local database if active directory authentication fails.
|
||||
|
|
||||
| Set this to true if you would like to enable it.
|
||||
|
|
||||
| This option must be true or false and is only
|
||||
| applicable to the DatabaseUserProvider.
|
||||
|
|
||||
*/
|
||||
|
||||
'login_fallback' => env('ADLDAP_LOGIN_FALLBACK', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sync Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Attributes specified here will be added / replaced on the user model
|
||||
| upon login, automatically synchronizing and keeping the attributes
|
||||
| up to date.
|
||||
|
|
||||
| The array key represents the users Laravel model key, and
|
||||
| the value represents the users LDAP attribute.
|
||||
|
|
||||
| This option must be an array and is only applicable
|
||||
| to the DatabaseUserProvider.
|
||||
|
|
||||
*/
|
||||
|
||||
'sync_attributes' => [
|
||||
|
||||
'email' => envNonEmpty('ADLDAP_SYNC_FIELD', 'userprincipalname'),
|
||||
//'name' => 'cn',
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logging
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| User authentication attempts will be logged using Laravel's
|
||||
| default logger if this setting is enabled.
|
||||
|
|
||||
| No credentials are logged, only usernames.
|
||||
|
|
||||
| This is usually stored in the '/storage/logs' directory
|
||||
| in the root of your application.
|
||||
|
|
||||
| This option is useful for debugging as well as auditing.
|
||||
|
|
||||
| You can freely remove any events you would not like to log below,
|
||||
| as well as use your own listeners if you would prefer.
|
||||
|
|
||||
*/
|
||||
|
||||
'logging' => [
|
||||
'enabled' => true,
|
||||
'events' => [
|
||||
|
||||
\Adldap\Laravel\Events\Importing::class => \Adldap\Laravel\Listeners\LogImport::class,
|
||||
\Adldap\Laravel\Events\Synchronized::class => \Adldap\Laravel\Listeners\LogSynchronized::class,
|
||||
\Adldap\Laravel\Events\Synchronizing::class => \Adldap\Laravel\Listeners\LogSynchronizing::class,
|
||||
\Adldap\Laravel\Events\Authenticated::class => \Adldap\Laravel\Listeners\LogAuthenticated::class,
|
||||
\Adldap\Laravel\Events\Authenticating::class => \Adldap\Laravel\Listeners\LogAuthentication::class,
|
||||
\Adldap\Laravel\Events\AuthenticationFailed::class => \Adldap\Laravel\Listeners\LogAuthenticationFailure::class,
|
||||
\Adldap\Laravel\Events\AuthenticationRejected::class => \Adldap\Laravel\Listeners\LogAuthenticationRejection::class,
|
||||
\Adldap\Laravel\Events\AuthenticationSuccessful::class => \Adldap\Laravel\Listeners\LogAuthenticationSuccess::class,
|
||||
\Adldap\Laravel\Events\DiscoveredWithCredentials::class => \Adldap\Laravel\Listeners\LogDiscovery::class,
|
||||
\Adldap\Laravel\Events\AuthenticatedWithWindows::class => \Adldap\Laravel\Listeners\LogWindowsAuth::class,
|
||||
\Adldap\Laravel\Events\AuthenticatedModelTrashed::class => \Adldap\Laravel\Listeners\LogTrashedModel::class,
|
||||
|
||||
],
|
||||
],
|
||||
|
||||
];
|
@ -62,7 +62,6 @@ return [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'driver' => 'passport',
|
||||
'provider' => 'users',
|
||||
@ -88,14 +87,9 @@ return [
|
||||
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'driver' => envNonEmpty('LOGIN_PROVIDER', 'eloquent'),//'adldap',
|
||||
'model' => FireflyIII\User::class,
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
],
|
||||
|
||||
/*
|
||||
|
@ -28,7 +28,7 @@ $username = '';
|
||||
$password = '';
|
||||
$database = '';
|
||||
|
||||
if (!($databaseUrl === false)) {
|
||||
if (!(false === $databaseUrl)) {
|
||||
$options = parse_url($databaseUrl);
|
||||
$host = $options['host'];
|
||||
$username = $options['user'];
|
||||
@ -57,7 +57,7 @@ return [
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'engine' => 'InnoDB',
|
||||
],
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
|
@ -21,6 +21,21 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$uploadDisk = [
|
||||
'driver' => 'mirror',
|
||||
'disks' => ['local-upload'],
|
||||
];
|
||||
|
||||
$exportDisk = [
|
||||
'driver' => 'mirror',
|
||||
'disks' => ['local-export'],
|
||||
];
|
||||
|
||||
// setting the SFTP host is enough to trigger the SFTP option.
|
||||
if ('' !== env('SFTP_HOST', '')) {
|
||||
array_push($uploadDisk['disks'], 'sftp-upload');
|
||||
array_push($exportDisk['disks'], 'sftp-export');
|
||||
}
|
||||
|
||||
return [
|
||||
|
||||
@ -59,25 +74,68 @@ return [
|
||||
| may even configure multiple disks of the same driver. Defaults have
|
||||
| been setup for each driver as an example of the required options.
|
||||
|
|
||||
| Supported Drivers: "local", "ftp", "s3", "rackspace"
|
||||
| Supported: "local", "ftp", "s3", "rackspace", "null", "azure", "copy",
|
||||
| "dropbox", "gridfs", "memory", "phpcr", "replicate", "sftp",
|
||||
| "vfs", "webdav", "zip", "bos", "cloudinary", "eloquent",
|
||||
| "fallback", "github", "gdrive", "google", "mirror", "onedrive",
|
||||
| "oss", "qiniu", "redis", "runabove", "sae", "smb", "temp"
|
||||
|
|
||||
*/
|
||||
|
||||
'disks' => [
|
||||
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app'),
|
||||
],
|
||||
|
||||
'upload' => [
|
||||
// local storage configuration for upload and export:
|
||||
'local-upload' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('upload'),
|
||||
],
|
||||
'export' => [
|
||||
'local-export' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('export'),
|
||||
],
|
||||
|
||||
// SFTP storage configuration for upload and export:
|
||||
'sftp-upload' => [
|
||||
'driver' => 'sftp',
|
||||
'host' => env('SFTP_HOST', '127.0.0.1'),
|
||||
'port' => env('SFTP_PORT', 22),
|
||||
'username' => env('SFTP_USERNAME', 'anonymous'),
|
||||
'password' => env('SFTP_PASSWORD', ''),
|
||||
'root' => env('SFTP_UPLOAD_PATH', ''),
|
||||
'privateKey' => env('SFTP_PRIV_KEY'),
|
||||
|
||||
// Optional SFTP Settings
|
||||
// 'timeout' => 30,
|
||||
// 'directoryPerm' => 0755,
|
||||
// 'permPublic' => 0644,
|
||||
// 'permPrivate' => 0600,
|
||||
],
|
||||
|
||||
'sftp-export' => [
|
||||
'driver' => 'sftp',
|
||||
'host' => env('SFTP_HOST', '127.0.0.1'),
|
||||
'port' => env('SFTP_PORT', 22),
|
||||
'username' => env('SFTP_USERNAME', 'anonymous'),
|
||||
'password' => env('SFTP_PASSWORD', ''),
|
||||
'root' => env('SFTP_EXPORT_PATH', ''),
|
||||
'privateKey' => env('SFTP_PRIV_KEY'),
|
||||
|
||||
// Optional SFTP Settings
|
||||
// 'timeout' => 30,
|
||||
// 'directoryPerm' => 0755,
|
||||
// 'permPublic' => 0644,
|
||||
// 'permPrivate' => 0600,
|
||||
],
|
||||
|
||||
// final configuration of upload disk and export disk.
|
||||
'upload' => $uploadDisk,
|
||||
'export' => $exportDisk,
|
||||
|
||||
// various other paths:
|
||||
'database' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('database'),
|
||||
@ -98,6 +156,9 @@ return [
|
||||
'visibility' => 'public',
|
||||
],
|
||||
|
||||
// unused storage backends.
|
||||
/*
|
||||
|
||||
's3' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('AWS_KEY'),
|
||||
@ -106,6 +167,340 @@ return [
|
||||
'bucket' => env('AWS_BUCKET'),
|
||||
],
|
||||
|
||||
'sftp' => [
|
||||
'driver' => 'sftp',
|
||||
'host' => 'sftp.example.com',
|
||||
'username' => 'username',
|
||||
'password' => 'password',
|
||||
// Optional SFTP Settings
|
||||
// 'privateKey' => 'path/to/or/contents/of/privatekey',
|
||||
// 'port' => 22,
|
||||
// 'root' => '/path/to/root',
|
||||
// 'timeout' => 30,
|
||||
// 'directoryPerm' => 0755,
|
||||
// 'permPublic' => 0644,
|
||||
// 'permPrivate' => 0600,
|
||||
],
|
||||
|
||||
'rackspace' => [
|
||||
'driver' => 'rackspace',
|
||||
'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/',
|
||||
'username' => 'your-username',
|
||||
'key' => 'your-key',
|
||||
'region' => 'IAD',
|
||||
'url_type' => 'publicURL',
|
||||
'container' => 'your-container',
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'null',
|
||||
],
|
||||
|
||||
'azure' => [
|
||||
'driver' => 'azure',
|
||||
'accountName' => 'your-account-name',
|
||||
'apiKey' => 'your-api-key',
|
||||
'container' => 'your-container',
|
||||
],
|
||||
|
||||
'gridfs' => [
|
||||
'driver' => 'gridfs',
|
||||
'server' => 'your-server',
|
||||
'context' => 'your-context',
|
||||
'dbName' => 'your-db-name',
|
||||
// You can also provide other MongoDB connection options here
|
||||
],
|
||||
|
||||
'memory' => [
|
||||
'driver' => 'memory',
|
||||
],
|
||||
|
||||
'phpcr-jackrabbit' => [
|
||||
'driver' => 'phpcr',
|
||||
'jackrabbit_url' => 'your-jackrabbit-url',
|
||||
'workspace' => 'your-workspace',
|
||||
'root' => 'your-root',
|
||||
// Optional PHPCR Settings
|
||||
// 'userId' => 'your-user-id',
|
||||
// 'password' => 'your-password',
|
||||
],
|
||||
|
||||
'phpcr-dbal' => [
|
||||
'driver' => 'phpcr',
|
||||
'database' => 'mysql',
|
||||
'workspace' => 'your-workspace',
|
||||
'root' => 'your-root',
|
||||
// Optional PHPCR Settings
|
||||
// 'userId' => 'your-user-id',
|
||||
// 'password' => 'your-password',
|
||||
],
|
||||
|
||||
'phpcr-prismic' => [
|
||||
'driver' => 'phpcr',
|
||||
'prismic_uri' => 'your-prismic-uri',
|
||||
'workspace' => 'your-workspace',
|
||||
'root' => 'your-root',
|
||||
// Optional PHPCR Settings
|
||||
// 'userId' => 'your-user-id',
|
||||
// 'password' => 'your-password',
|
||||
],
|
||||
|
||||
'replicate' => [
|
||||
'driver' => 'replicate',
|
||||
'master' => 'local',
|
||||
'replica' => 's3',
|
||||
],
|
||||
|
||||
'vfs' => [
|
||||
'driver' => 'vfs',
|
||||
],
|
||||
|
||||
'webdav' => [
|
||||
'driver' => 'webdav',
|
||||
'baseUri' => 'http://example.org/dav/',
|
||||
// Optional WebDAV Settings
|
||||
// 'userName' => 'user',
|
||||
// 'password' => 'password',
|
||||
// 'proxy' => 'locahost:8888',
|
||||
// 'authType' => 'digest', // alternately 'ntlm' or 'basic'
|
||||
// 'encoding' => 'all', // same as ['deflate', 'gzip', 'identity']
|
||||
],
|
||||
|
||||
'zip' => [
|
||||
'driver' => 'zip',
|
||||
'path' => 'path/to/file.zip',
|
||||
// Alternate value if twistor/flysystem-stream-wrapper is available
|
||||
// 'path' => 'local://path/to/file.zip',
|
||||
],
|
||||
|
||||
'backblaze' => [
|
||||
'driver' => 'backblaze',
|
||||
'account_id' => 'your-account-id',
|
||||
'application_key' => 'your-app-key',
|
||||
'bucket' => 'your-bucket',
|
||||
],
|
||||
|
||||
'bos' => [
|
||||
'driver' => 'bos',
|
||||
'credentials' => [
|
||||
'ak' => 'your-access-key-id',
|
||||
'sk' => 'your-secret-access-key',
|
||||
],
|
||||
'bucket' => 'your-bucket',
|
||||
// Optional BOS Settings
|
||||
// 'endpoint' => 'http://bj.bcebos.com',
|
||||
],
|
||||
|
||||
'clamav' => [
|
||||
'driver' => 'clamav',
|
||||
'server' => 'tcp://127.0.0.1:3310',
|
||||
'drive' => 'local',
|
||||
// Optional ClamAV Settings
|
||||
// 'copy_scan' => false,
|
||||
],
|
||||
|
||||
'cloudinary' => [
|
||||
'driver' => 'cloudinary',
|
||||
'api_key' => env('CLOUDINARY_API_KEY'),
|
||||
'api_secret' => env('CLOUDINARY_API_SECRET'),
|
||||
'cloud_name' => env('CLOUDINARY_CLOUD_NAME'),
|
||||
],
|
||||
|
||||
'dropbox' => [
|
||||
'driver' => 'dropbox',
|
||||
'authToken' => 'your-auth-token',
|
||||
],
|
||||
|
||||
'eloquent' => [
|
||||
'driver' => 'eloquent',
|
||||
// Optional Eloquent Settings
|
||||
// 'model' => '\Rokde\Flysystem\Adapter\Model\FileModel',
|
||||
],
|
||||
|
||||
'fallback' => [
|
||||
'driver' => 'fallback',
|
||||
'main' => 'local',
|
||||
'fallback' => 's3',
|
||||
],
|
||||
|
||||
'gdrive' => [
|
||||
'driver' => 'gdrive',
|
||||
'client_id' => 'your-client-id',
|
||||
'secret' => 'your-secret',
|
||||
'token' => 'your-token',
|
||||
// Optional GDrive Settings
|
||||
// 'root' => 'your-root-directory',
|
||||
// 'paths_sheet' => 'your-paths-sheet',
|
||||
// 'paths_cache_drive' => 'local',
|
||||
],
|
||||
|
||||
'github' => [
|
||||
'driver' => 'github',
|
||||
'project' => 'yourname/project',
|
||||
'token' => 'your-github-token',
|
||||
],
|
||||
|
||||
'google' => [
|
||||
'driver' => 'google',
|
||||
'project_id' => 'your-project-id',
|
||||
'bucket' => 'your-bucket',
|
||||
// Optional Google Cloud Storage Settings
|
||||
// 'prefix' => 'prefix/path/for/drive',
|
||||
// 'url' => 'http://your.custom.cname/',
|
||||
// 'key_file' => 'path/to/file.json',
|
||||
//
|
||||
// Alternate value if twistor/flysystem-stream-wrapper is available
|
||||
// 'key_file' => 'local://path/to/file.json',
|
||||
],
|
||||
|
||||
'http' => [
|
||||
'driver' => 'http',
|
||||
'root' => 'http://example.com',
|
||||
// Optional HTTP Settings
|
||||
// 'use_head' => true,
|
||||
// 'context' => [],
|
||||
],
|
||||
|
||||
'onedrive' => [
|
||||
'driver' => 'onedrive',
|
||||
'access_token' => 'your-access-token',
|
||||
// Options only needed for ignited/flysystem-onedrive
|
||||
// 'base_url' => 'https://api.onedrive.com/v1.0/',
|
||||
// 'use_logger' => false,
|
||||
// Option only used by nicolasbeauvais/flysystem-onedrive
|
||||
// 'root' => 'root',
|
||||
],
|
||||
|
||||
'openstack' => [
|
||||
'driver' => 'openstack',
|
||||
'auth_url' => 'your-auth-url',
|
||||
'region' => 'your-region',
|
||||
'user_id' => 'your-user-id',
|
||||
'password' => 'your-password',
|
||||
'project_id' => 'your-project-id',
|
||||
'container' => 'your-container',
|
||||
],
|
||||
|
||||
'oss' => [
|
||||
'driver' => 'oss',
|
||||
'access_id' => env('OSS_ACCESS_KEY_ID'),
|
||||
'access_key' => env('OSS_ACCESS_KEY_SECRET'),
|
||||
'endpoint' => env('OSS_ENDPOINT'),
|
||||
'bucket' => env('OSS_BUCKET'),
|
||||
// Optional OSS Settings
|
||||
// 'prefix' => '',
|
||||
// 'region' => '', // One of 'hangzhou', 'qingdao', 'beijing', 'hongkong',
|
||||
// // 'shenzhen', 'shanghai', 'west-1' and 'southeast-1'
|
||||
],
|
||||
|
||||
'pdo' => [
|
||||
'driver' => 'pdo',
|
||||
'database' => 'default',
|
||||
],
|
||||
|
||||
'qcloud' => [
|
||||
'driver' => 'qcloud',
|
||||
'app_id' => 'your-app-id',
|
||||
'secret_id' => 'your-secret-id',
|
||||
'secret_key' => 'your-secret-key',
|
||||
'bucket' => 'your-bucket-name',
|
||||
'protocol' => 'https',
|
||||
// Optional Tencent/Qcloud COS Settings
|
||||
// 'domain' => 'your-domain',
|
||||
// 'timeout' => 60,
|
||||
// 'region' => 'gz',
|
||||
],
|
||||
|
||||
'qiniu' => [
|
||||
'driver' => 'qiniu',
|
||||
'accessKey' => 'your-access-key',
|
||||
'secretKey' => 'your-secret-key',
|
||||
'bucket' => 'your-bucket',
|
||||
'domain' => 'xxxx.qiniudn.com',
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
],
|
||||
|
||||
'runabove' => [
|
||||
'driver' => 'runabove',
|
||||
'username' => 'your-username',
|
||||
'password' => 'your-password',
|
||||
'tenantId' => 'your-tenantId',
|
||||
// Optional Runabove Settings
|
||||
// 'container' => 'container',
|
||||
// 'region' => 'SBG1', // One of 'SBG1', 'BHS1', and 'GRA1'
|
||||
],
|
||||
|
||||
'selectel' => [
|
||||
'driver' => 'selectel',
|
||||
'username' => 'your-username',
|
||||
'password' => 'your-password',
|
||||
'container' => 'your-container',
|
||||
// Optional Selectel Settings
|
||||
// 'domain' => '',
|
||||
],
|
||||
|
||||
'sharefile' => [
|
||||
'driver' => 'sharefile',
|
||||
'hostname' => 'sharefile.example.com',
|
||||
'client_id' => 'your-client-id',
|
||||
'secret' => 'your-secret',
|
||||
'username' => 'your-username',
|
||||
'password' => 'your-password',
|
||||
],
|
||||
|
||||
'smb' => [
|
||||
'driver' => 'smb',
|
||||
'host' => 'smb.example.com',
|
||||
'username' => 'your-username',
|
||||
'password' => 'your-password',
|
||||
'path' => 'path/to/shared/directory/for/root',
|
||||
],
|
||||
|
||||
'temp' => [
|
||||
'driver' => 'temp',
|
||||
// Optional TempDir Settings
|
||||
// 'prefix' => '',
|
||||
// 'tempdir' => '/tmp',
|
||||
],
|
||||
|
||||
'upyun' => [
|
||||
'driver' => 'upyun',
|
||||
'bucket' => 'your-bucket',
|
||||
'operator' => 'operator-name',
|
||||
'password' => 'operator-password',
|
||||
'protocol' => 'https',
|
||||
'domain' => 'example.b0.upaiyun.com',
|
||||
],
|
||||
|
||||
'yandex' => [
|
||||
'driver' => 'yandex',
|
||||
'access_token' => 'your-access-token',
|
||||
// Optional Yandex Settings
|
||||
// 'prefix' => 'app:/',
|
||||
],
|
||||
*/
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Automatically Register Stream Wrappers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is a list of the filesystem "disks" to automatically register the
|
||||
| stream wrappers for on application start. Any "disk" you don't want to
|
||||
| register on every application load will have to be manually referenced
|
||||
| before attempting stream access, as the stream wrapper is otherwise only
|
||||
| registered when used.
|
||||
|
|
||||
*/
|
||||
/*
|
||||
// Disabled, pending "twistor/flysystem-stream-wrapper" dependency
|
||||
'autowrap' => [
|
||||
'local',
|
||||
],
|
||||
*/
|
||||
];
|
||||
|
@ -91,10 +91,11 @@ return [
|
||||
'is_demo_site' => false,
|
||||
],
|
||||
'encryption' => null === env('USE_ENCRYPTION') || env('USE_ENCRYPTION') === true,
|
||||
'version' => '4.7.7',
|
||||
'api_version' => '0.8',
|
||||
'db_version' => 5,
|
||||
'version' => '4.7.8',
|
||||
'api_version' => '0.81',
|
||||
'db_version' => 6,
|
||||
'maxUploadSize' => 15242880,
|
||||
'login_provider' => env('LOGIN_PROVIDER', 'eloquent'),
|
||||
'allowedMimes' => [
|
||||
/* plain files */
|
||||
'text/plain',
|
||||
@ -238,16 +239,25 @@ return [
|
||||
'languages' => [
|
||||
// completed languages
|
||||
'en_US' => ['name_locale' => 'English', 'name_english' => 'English'],
|
||||
'es_ES' => ['name_locale' => 'Español', 'name_english' => 'Spanish'],
|
||||
'de_DE' => ['name_locale' => 'Deutsch', 'name_english' => 'German'],
|
||||
'fr_FR' => ['name_locale' => 'Français', 'name_english' => 'French'],
|
||||
//'id_ID' => ['name_locale' => 'Bahasa Indonesia', 'name_english' => 'Indonesian'],
|
||||
'it_IT' => ['name_locale' => 'Italiano', 'name_english' => 'Italian'],
|
||||
'nl_NL' => ['name_locale' => 'Nederlands', 'name_english' => 'Dutch'],
|
||||
'pl_PL' => ['name_locale' => 'Polski', 'name_english' => 'Polish '],
|
||||
//'pt_BR' => ['name_locale' => 'Português do Brasil', 'name_english' => 'Portuguese (Brazil)'],
|
||||
'ru_RU' => ['name_locale' => 'Русский', 'name_english' => 'Russian'],
|
||||
//'tr_TR' => ['name_locale' => 'Türkçe', 'name_english' => 'Turkish'],
|
||||
'es_ES' => ['name_locale' => 'Español', 'name_english' => 'Spanish'], // 2018-10-26: 96%
|
||||
'de_DE' => ['name_locale' => 'Deutsch', 'name_english' => 'German'], // 2018-10-26: 100%
|
||||
'fr_FR' => ['name_locale' => 'Français', 'name_english' => 'French'], // 2018-10-26: 100%
|
||||
//'id_ID' => ['name_locale' => 'Bahasa Indonesia', 'name_english' => 'Indonesian'], // 2018-10-26: 61% :(
|
||||
'it_IT' => ['name_locale' => 'Italiano', 'name_english' => 'Italian'], // 2018-10-26: 100%
|
||||
'nl_NL' => ['name_locale' => 'Nederlands', 'name_english' => 'Dutch'], // 2018-10-26: 100%
|
||||
'pl_PL' => ['name_locale' => 'Polski', 'name_english' => 'Polish '], // 2018-10-26: 76%
|
||||
'pt_BR' => ['name_locale' => 'Português do Brasil', 'name_english' => 'Portuguese (Brazil)'], // 2018-10-26: 77%
|
||||
'ru_RU' => ['name_locale' => 'Русский', 'name_english' => 'Russian'], // 2018-10-26: 80%
|
||||
//'tr_TR' => ['name_locale' => 'Türkçe', 'name_english' => 'Turkish'], // 2018-10-26: 71%
|
||||
|
||||
// very far away:
|
||||
//'ca_ES' => ['name_locale' => 'Catalan', 'name_english' => 'Catalan'], // 2018-10-26: 0%
|
||||
//'cs_CZ' => ['name_locale' => 'Czech', 'name_english' => 'Czech'], // 2018-10-26: 8%
|
||||
//'he_IL' => ['name_locale' => 'Hebrew', 'name_english' => 'Hebrew'], // 2018-10-26: 3%
|
||||
//'hu_HU' => ['name_locale' => 'Hungarian', 'name_english' => 'Hungarian'], // 2018-10-26: 40%
|
||||
//'nb_NO' => ['name_locale' => 'Norwegian', 'name_english' => 'Norwegian'], // 2018-10-26: 54%
|
||||
//'sl_SI' => ['name_locale' => 'Slovenian', 'name_english' => 'Slovenian'], // 2018-10-26: 10%
|
||||
//'uk_UA' => ['name_locale' => 'Ukranian', 'name_english' => 'Ukranian'], // 2018-10-26: 3%
|
||||
],
|
||||
'transactionTypesByWhat' => [
|
||||
'expenses' => ['Withdrawal'],
|
||||
@ -374,23 +384,59 @@ return [
|
||||
'convert_deposit' => ConvertToDeposit::class,
|
||||
'convert_transfer' => ConvertToTransfer::class,
|
||||
],
|
||||
'rule-actions-text' => [
|
||||
'context-rule-actions' => [
|
||||
'set_category',
|
||||
'set_budget',
|
||||
'add_tag',
|
||||
'remove_tag',
|
||||
'link_to_bill',
|
||||
'set_description',
|
||||
'append_description',
|
||||
'prepend_description',
|
||||
'set_source_account',
|
||||
'set_destination_account',
|
||||
'set_notes',
|
||||
'append_notes',
|
||||
'prepend_notes',
|
||||
'link_to_bill',
|
||||
'convert_withdrawal',
|
||||
'convert_deposit',
|
||||
'convert_transfer',
|
||||
],
|
||||
'test-triggers' => [
|
||||
'context-rule-triggers' => [
|
||||
'from_account_starts',
|
||||
'from_account_ends',
|
||||
'from_account_is',
|
||||
'from_account_contains',
|
||||
'to_account_starts',
|
||||
'to_account_ends',
|
||||
'to_account_is',
|
||||
'to_account_contains',
|
||||
'amount_less',
|
||||
'amount_exactly',
|
||||
'amount_more',
|
||||
'description_starts',
|
||||
'description_ends',
|
||||
'description_contains',
|
||||
'description_is',
|
||||
'transaction_type',
|
||||
'category_is',
|
||||
'budget_is',
|
||||
'tag_is',
|
||||
'currency_is',
|
||||
'notes_contain',
|
||||
'notes_start',
|
||||
'notes_end',
|
||||
'notes_are',
|
||||
],
|
||||
|
||||
|
||||
'test-triggers' => [
|
||||
'limit' => 10,
|
||||
'range' => 200,
|
||||
],
|
||||
'default_currency' => 'EUR',
|
||||
'default_language' => 'en_US',
|
||||
'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category',
|
||||
'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after'],
|
||||
'default_currency' => 'EUR',
|
||||
'default_language' => 'en_US',
|
||||
'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category',
|
||||
'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after'],
|
||||
// tag notes has_attachments
|
||||
];
|
||||
|
@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
use FireflyIII\Import\JobConfiguration\BunqJobConfiguration;
|
||||
use FireflyIII\Import\JobConfiguration\FakeJobConfiguration;
|
||||
use FireflyIII\Import\JobConfiguration\FileJobConfiguration;
|
||||
use FireflyIII\Import\JobConfiguration\FinTSJobConfiguration;
|
||||
use FireflyIII\Import\JobConfiguration\SpectreJobConfiguration;
|
||||
use FireflyIII\Import\JobConfiguration\YnabJobConfiguration;
|
||||
use FireflyIII\Import\Prerequisites\BunqPrerequisites;
|
||||
@ -34,6 +35,7 @@ use FireflyIII\Import\Prerequisites\YnabPrerequisites;
|
||||
use FireflyIII\Import\Routine\BunqRoutine;
|
||||
use FireflyIII\Import\Routine\FakeRoutine;
|
||||
use FireflyIII\Import\Routine\FileRoutine;
|
||||
use FireflyIII\Import\Routine\FinTSRoutine;
|
||||
use FireflyIII\Import\Routine\SpectreRoutine;
|
||||
use FireflyIII\Import\Routine\YnabRoutine;
|
||||
use FireflyIII\Support\Import\Routine\File\CSVProcessor;
|
||||
@ -49,6 +51,7 @@ return [
|
||||
'plaid' => false,
|
||||
'quovo' => false,
|
||||
'yodlee' => false,
|
||||
'fints' => true,
|
||||
'bad' => false, // always disabled
|
||||
],
|
||||
// demo user can use these import providers (when enabled):
|
||||
@ -61,6 +64,7 @@ return [
|
||||
'plaid' => false,
|
||||
'quovo' => false,
|
||||
'yodlee' => false,
|
||||
'fints' => false,
|
||||
],
|
||||
// a normal user user can use these import providers (when enabled):
|
||||
'allowed_for_user' => [
|
||||
@ -72,6 +76,7 @@ return [
|
||||
'plaid' => true,
|
||||
'quovo' => true,
|
||||
'yodlee' => true,
|
||||
'fints' => true,
|
||||
],
|
||||
// some providers have pre-requisites.
|
||||
'has_prereq' => [
|
||||
@ -83,6 +88,7 @@ return [
|
||||
'plaid' => true,
|
||||
'quovo' => true,
|
||||
'yodlee' => true,
|
||||
'fints' => false,
|
||||
],
|
||||
// if so, there must be a class to handle them.
|
||||
'prerequisites' => [
|
||||
@ -94,6 +100,7 @@ return [
|
||||
'plaid' => false,
|
||||
'quovo' => false,
|
||||
'yodlee' => false,
|
||||
'fints' => false,
|
||||
],
|
||||
// some providers may need extra configuration per job
|
||||
'has_job_config' => [
|
||||
@ -105,6 +112,7 @@ return [
|
||||
'plaid' => false,
|
||||
'quovo' => false,
|
||||
'yodlee' => false,
|
||||
'fints' => true,
|
||||
],
|
||||
// if so, this is the class that handles it.
|
||||
'configuration' => [
|
||||
@ -116,6 +124,7 @@ return [
|
||||
'plaid' => false,
|
||||
'quovo' => false,
|
||||
'yodlee' => false,
|
||||
'fints' => FinTSJobConfiguration::class,
|
||||
],
|
||||
// this is the routine that runs the actual import.
|
||||
'routine' => [
|
||||
@ -127,6 +136,7 @@ return [
|
||||
'plaid' => false,
|
||||
'quovo' => false,
|
||||
'yodlee' => false,
|
||||
'fints' => FinTSRoutine::class,
|
||||
],
|
||||
|
||||
'options' => [
|
||||
|
@ -14,7 +14,7 @@ services:
|
||||
- FF_DB_CONNECTION=pgsql
|
||||
- TZ=Europe/Amsterdam
|
||||
- APP_LOG_LEVEL=debug
|
||||
image: jc5x/firefly-iii:develop
|
||||
image: jc5x/firefly-iii
|
||||
links:
|
||||
- firefly_iii_db
|
||||
networks:
|
||||
@ -34,7 +34,7 @@ services:
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=firefly
|
||||
- POSTGRES_USER=firefly
|
||||
image: "postgres:latest"
|
||||
image: "postgres:10"
|
||||
networks:
|
||||
- firefly_iii_net
|
||||
volumes:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user