Merge branch 'release/4.7.0'

This commit is contained in:
James Cole
2018-01-31 07:14:15 +01:00
392 changed files with 10159 additions and 3181 deletions

12
.codeclimate.yml Normal file
View File

@@ -0,0 +1,12 @@
---
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/

View File

@@ -1,12 +1,24 @@
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
# Never set it to "testing".
APP_ENV=${FF_APP_ENV}
APP_DEBUG=false
APP_NAME=FireflyIII
APP_KEY=${FF_APP_KEY}
APP_LOG=daily
APP_LOG_LEVEL=warning
APP_URL=http://localhost
TRUSTED_PROXIES=
# Set to true if you want to see debug information in error screens.
APP_DEBUG=false
# This should be your email address
SITE_OWNER=mail@example.com
# The encryption key for your database and sessions. Keep this very secure.
# If you generate a new one all existing data must be considered LOST.
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
APP_KEY=${FF_APP_KEY}
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
APP_URL=${APP_URL}
TRUSTED_PROXIES=${TRUSTED_PROXIES}
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
DB_CONNECTION=mysql
DB_HOST=${FF_DB_HOST}
DB_PORT=3306
@@ -14,19 +26,27 @@ DB_DATABASE=${FF_DB_NAME}
DB_USERNAME=${FF_DB_USER}
DB_PASSWORD=${FF_DB_PASSWORD}
BROADCAST_DRIVER=log
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
APP_LOG=daily
# Log level. You can set this from least severe to most severe:
# debug, info, notice, warning, error, critical, alert, emergency
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
# nothing will get logged, ever.
APP_LOG_LEVEL=warning
# If you're looking for performance improvements, you could install memcached.
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
# Cookie settings. Should not be necessary to change these.
COOKIE_PATH="/"
COOKIE_DOMAIN=
COOKIE_SECURE=false
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# If you want Firefly III to mail you, update these settings
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
# Firefly III can send you the following messages
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
MAPBOX_API_KEY=
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
ANALYTICS_ID=
SITE_OWNER=mail@example.com
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
USE_ENCRYPTION=true
# Leave the following configuration vars as is.
# Unless you like to tinker and know what you're doing.
APP_NAME=FireflyIII
BROADCAST_DRIVER=log
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=
IS_DOCKER=true
IS_SANDSTORM=false
IS_HEROKU=false

View File

@@ -1,12 +1,24 @@
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
# Never set it to "testing".
APP_ENV=local
# Set to true if you want to see debug information in error screens.
APP_DEBUG=false
APP_NAME=FireflyIII
# This should be your email address
SITE_OWNER=mail@example.com
# The encryption key for your database and sessions. Keep this very secure.
# If you generate a new one all existing data must be considered LOST.
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
APP_KEY=SomeRandomStringOf32CharsExactly
APP_LOG=daily
APP_LOG_LEVEL=notice
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
APP_URL=http://localhost
TRUSTED_PROXIES=
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
@@ -14,19 +26,27 @@ DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
BROADCAST_DRIVER=log
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
APP_LOG=daily
# Log level. You can set this from least severe to most severe:
# debug, info, notice, warning, error, critical, alert, emergency
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
# nothing will get logged, ever.
APP_LOG_LEVEL=notice
# If you're looking for performance improvements, you could install memcached.
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
# Cookie settings. Should not be necessary to change these.
COOKIE_PATH="/"
COOKIE_DOMAIN=
COOKIE_SECURE=false
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# If you want Firefly III to mail you, update these settings
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
# Firefly III can send you the following messages
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
MAPBOX_API_KEY=
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
ANALYTICS_ID=
SITE_OWNER=mail@example.com
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
USE_ENCRYPTION=true
# Leave the following configuration vars as is.
# Unless you like to tinker and know what you're doing.
APP_NAME=FireflyIII
BROADCAST_DRIVER=log
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=
IS_DOCKER=false
IS_SANDSTORM=false
IS_HEROKU=false

View File

@@ -1,12 +1,24 @@
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
# Never set it to "testing".
APP_ENV=heroku
APP_DEBUG=true
APP_NAME=FireflyIII
APP_KEY=7ahyYVPVsmxjdhsweWCauGeJfwc92NP2
APP_LOG=errorlog
APP_LOG_LEVEL=debug
APP_URL=http://localhost
TRUSTED_PROXIES=*
# Set to true if you want to see debug information in error screens.
APP_DEBUG=false
# This should be your email address
SITE_OWNER=heroku@example.com
# The encryption key for your database and sessions. Keep this very secure.
# If you generate a new one all existing data must be considered LOST.
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
APP_KEY=7ahyYVPVsmxjdhsweWCauGeJfwc92NP2
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
APP_URL=http://localhost
TRUSTED_PROXIES=
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
DB_CONNECTION=pgsql
@@ -14,19 +26,27 @@ DB_CONNECTION=pgsql
BROADCAST_DRIVER=log
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
APP_LOG=errorlog
# Log level. You can set this from least severe to most severe:
# debug, info, notice, warning, error, critical, alert, emergency
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
# nothing will get logged, ever.
APP_LOG_LEVEL=debug
# If you're looking for performance improvements, you could install memcached.
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
# Cookie settings. Should not be necessary to change these.
COOKIE_PATH="/"
COOKIE_DOMAIN=
COOKIE_SECURE=false
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# If you want Firefly III to mail you, update these settings
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
# Firefly III can send you the following messages
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
MAPBOX_API_KEY=
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
ANALYTICS_ID=
SITE_OWNER=heroku@example.com
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
USE_ENCRYPTION=true
# Leave the following configuration vars as is.
# Unless you like to tinker and know what you're doing.
APP_NAME=FireflyIII
BROADCAST_DRIVER=log
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=
IS_DOCKER=false
IS_SANDSTORM=false
IS_HEROKU=true

View File

@@ -1,12 +1,24 @@
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
# Never set it to "testing".
APP_ENV=local
# Set to true if you want to see debug information in error screens.
APP_DEBUG=false
APP_NAME=FireflyIII
# This should be your email address
SITE_OWNER=sandstorm@example.com
# The encryption key for your database and sessions. Keep this very secure.
# If you generate a new one all existing data must be considered LOST.
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
APP_KEY=SomeRandomStringOf32CharsExactly
APP_LOG=syslog
APP_LOG_LEVEL=info
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
APP_URL=http://localhost
TRUSTED_PROXIES=
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
@@ -14,19 +26,27 @@ DB_DATABASE=firefly
DB_USERNAME=firefly
DB_PASSWORD=firefly
BROADCAST_DRIVER=log
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
APP_LOG=syslog
# Log level. You can set this from least severe to most severe:
# debug, info, notice, warning, error, critical, alert, emergency
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
# nothing will get logged, ever.
APP_LOG_LEVEL=info
# If you're looking for performance improvements, you could install memcached.
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
# Cookie settings. Should not be necessary to change these.
COOKIE_PATH="/"
COOKIE_DOMAIN=
COOKIE_SECURE=false
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# If you want Firefly III to mail you, update these settings
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
# Firefly III can send you the following messages
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
MAPBOX_API_KEY=
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
ANALYTICS_ID=
SITE_OWNER=mail@example.com
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
USE_ENCRYPTION=true
# Leave the following configuration vars as is.
# Unless you like to tinker and know what you're doing.
APP_NAME=FireflyIII
BROADCAST_DRIVER=log
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=
IS_DOCKER=false
IS_SANDSTORM=true
IS_HEROKU=false

View File

@@ -4,7 +4,7 @@
## Feature requests
I am always interested in expanding Firefly III's many features. If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.org/requested-features/).
I am always interested in expanding Firefly III's many features. Just open a ticket or [drop me a line](mailto:thegrumpydictator@gmail.com).
## Pull requests

View File

@@ -3,10 +3,9 @@
# This script only runs once, when the app connects to sandstorm.
set -euo pipefail
echo "In build.sh"
cd /opt/app
cp .env.sandstorm .env
if [ -f /opt/app/composer.json ] ; then

View File

@@ -1,3 +1,32 @@
# 4.7.0
- Support for Russian and Portuguese (Brazil)
- Support for the Spectre API (Salt Edge)
- Many strings now translatable thanks to [Nik-vr](https://github.com/Nik-vr) ([issue 1118](https://github.com/firefly-iii/firefly-iii/issues/1118), [issue 1116](https://github.com/firefly-iii/firefly-iii/issues/1116), [issue 1109](https://github.com/firefly-iii/firefly-iii/issues/1109), )
- Many buttons to quickly create stuff
- Sum of tables in reports, requested by [MacPaille](https://github.com/MacPaille) ([issue 1106](https://github.com/firefly-iii/firefly-iii/issues/1106))
- Future versions of Firefly III will notify you there is a new version, as suggested by [8bitgentleman](https://github.com/8bitgentleman) in [issue 1050](https://github.com/firefly-iii/firefly-iii/issues/1050)
- Improved net worth box [issue 1101](https://github.com/firefly-iii/firefly-iii/issues/1101) ([Nik-vr](https://github.com/Nik-vr))
- Nice dropdown in transaction list [issue 1082](https://github.com/firefly-iii/firefly-iii/issues/1082)
- Better support for local fonts thanks to [devlearner](https://github.com/devlearner) ([issue 1145](https://github.com/firefly-iii/firefly-iii/issues/1145))
- Improve attachment support and view capabilities (suggested by [trinhit](https://github.com/trinhit) in [issue 1146](https://github.com/firefly-iii/firefly-iii/issues/1146))
- Whole new [read me file](https://github.com/firefly-iii/firefly-iii/blob/master/readme.md), [new end user documentation](https://firefly-iii.readthedocs.io/en/latest/) and an [updated website](https://www.firefly-iii.org/)!
- Many charts and info-blocks now scale property ([issue 989](https://github.com/firefly-iii/firefly-iii/issues/989) and [issue 1040](https://github.com/firefly-iii/firefly-iii/issues/1040))
- Charts work in IE thanks to [devlearner](https://github.com/devlearner) ([issue 1107](https://github.com/firefly-iii/firefly-iii/issues/1107))
- Various fixes in import routine
- Bug that left charts empty ([issue 1088](https://github.com/firefly-iii/firefly-iii/issues/1088)), reported by various users amongst which [jinformatique](https://github.com/jinformatique)
- [Issue 1124](https://github.com/firefly-iii/firefly-iii/issues/1124), as reported by [gavu](https://github.com/gavu)
- [Issue 1125](https://github.com/firefly-iii/firefly-iii/issues/1125), as reported by [gavu](https://github.com/gavu)
- [Issue 1126](https://github.com/firefly-iii/firefly-iii/issues/1126), as reported by [gavu](https://github.com/gavu)
- [Issue 1131](https://github.com/firefly-iii/firefly-iii/issues/1131), as reported by [dp87](https://github.com/dp87)
- [Issue 1129](https://github.com/firefly-iii/firefly-iii/issues/1129), as reported by [gavu](https://github.com/gavu)
- [Issue 1132](https://github.com/firefly-iii/firefly-iii/issues/1132), as reported by [gavu](https://github.com/gavu)
- Issue with cache in Sandstorm ([issue 1130](https://github.com/firefly-iii/firefly-iii/issues/1130))
- [Issue 1134](https://github.com/firefly-iii/firefly-iii/issues/1134)
- [Issue 1140](https://github.com/firefly-iii/firefly-iii/issues/1140)
- [Issue 1141](https://github.com/firefly-iii/firefly-iii/issues/1141), reported by [ErikFontanel](https://github.com/ErikFontanel)
- [Issue 1142](https://github.com/firefly-iii/firefly-iii/issues/1142)
- Removed many access rights from the demo user
# 4.6.13
- [Issue 1074](https://github.com/firefly-iii/firefly-iii/issues/1074), suggested by [MacPaille](https://github.com/MacPaille)
- [Issue 1077](https://github.com/firefly-iii/firefly-iii/issues/1077), suggested by [wtercato](https://github.com/wtercato)

View File

@@ -1,6 +1,7 @@
#!/bin/bash
# Runs every time we create a new grain!
echo "Now in launcher.sh"
# Create a bunch of folders under the clean /var that php, nginx, and mysql expect to exist
mkdir -p /var/lib/mysql
@@ -30,7 +31,6 @@ mkdir -p /var/storage/framework/views
mkdir -p /var/storage/logs
mkdir -p /var/storage/upload
# Ensure mysql tables created
HOME=/etc/mysql /usr/bin/mysql_install_db --force
@@ -58,5 +58,9 @@ echo "Migrating..."
php /opt/app/artisan migrate --seed --force
echo "Done!"
echo "Clear cache.."
php /opt/app/artisan cache:clear
echo "Done"
# Start nginx.
/usr/sbin/nginx -c /opt/app/.sandstorm/service-config/nginx.conf -g "daemon off;"

View File

@@ -200,6 +200,7 @@ lib/x86_64-linux-gnu/libwrap.so.0.7.6
lib/x86_64-linux-gnu/libz.so.1
lib/x86_64-linux-gnu/libz.so.1.2.8
lib64/ld-linux-x86-64.so.2
opt/app/.codeclimate.yml
opt/app/.env
opt/app/.env.docker
opt/app/.env.example
@@ -237,10 +238,7 @@ opt/app/.sandstorm/service-config/mime.types
opt/app/.sandstorm/service-config/nginx.conf
opt/app/.sandstorm/setup.sh
opt/app/.sandstorm/stack
opt/app/CHANGELOG.md
opt/app/CODE_OF_CONDUCT.md
opt/app/LICENSE
opt/app/README.md
opt/app/app.json
opt/app/app/Console/Commands/CreateExport.php
opt/app/app/Console/Commands/CreateImport.php
@@ -644,6 +642,7 @@ opt/app/app/Services/Spectre/Object/Login.php
opt/app/app/Services/Spectre/Object/SpectreObject.php
opt/app/app/Services/Spectre/Object/Token.php
opt/app/app/Services/Spectre/Object/Transaction.php
opt/app/app/Services/Spectre/Object/TransactionExtra.php
opt/app/app/Services/Spectre/Request/CreateTokenRequest.php
opt/app/app/Services/Spectre/Request/ListAccountsRequest.php
opt/app/app/Services/Spectre/Request/ListCustomersRequest.php
@@ -687,7 +686,6 @@ opt/app/app/Support/Preferences.php
opt/app/app/Support/Search/Modifier.php
opt/app/app/Support/Search/Search.php
opt/app/app/Support/Search/SearchInterface.php
opt/app/app/Support/SingleCacheProperties.php
opt/app/app/Support/Steam.php
opt/app/app/Support/Twig/AmountFormat.php
opt/app/app/Support/Twig/Extension/Transaction.php
@@ -762,6 +760,7 @@ opt/app/artisan
opt/app/bootstrap/app.php
opt/app/bootstrap/cache/packages.php
opt/app/bootstrap/cache/services.php
opt/app/changelog.md
opt/app/composer.json
opt/app/composer.lock
opt/app/composer.phar
@@ -829,56 +828,121 @@ opt/app/public/css/jquery-ui/jquery-ui.theme.min.css
opt/app/public/favicon-16x16.png
opt/app/public/favicon-32x32.png
opt/app/public/favicon.ico
opt/app/public/fonts/SourceSansPro-Bold-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-Bold-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-Bold-cyrillic.woff
opt/app/public/fonts/SourceSansPro-Bold-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-Bold-greek-ext.woff
opt/app/public/fonts/SourceSansPro-Bold-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-Bold-greek.woff
opt/app/public/fonts/SourceSansPro-Bold-greek.woff2
opt/app/public/fonts/SourceSansPro-Bold-latin-ext.woff
opt/app/public/fonts/SourceSansPro-Bold-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-Bold-latin.woff
opt/app/public/fonts/SourceSansPro-Bold-latin.woff2
opt/app/public/fonts/SourceSansPro-Bold-vietnamese.woff
opt/app/public/fonts/SourceSansPro-Bold-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-greek-ext.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-greek.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-greek.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-latin-ext.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-latin.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-latin.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-vietnamese.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-Italic-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-Italic-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-Italic-cyrillic.woff
opt/app/public/fonts/SourceSansPro-Italic-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-Italic-greek-ext.woff
opt/app/public/fonts/SourceSansPro-Italic-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-Italic-greek.woff
opt/app/public/fonts/SourceSansPro-Italic-greek.woff2
opt/app/public/fonts/SourceSansPro-Italic-latin-ext.woff
opt/app/public/fonts/SourceSansPro-Italic-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-Italic-latin.woff
opt/app/public/fonts/SourceSansPro-Italic-latin.woff2
opt/app/public/fonts/SourceSansPro-Italic-vietnamese.woff
opt/app/public/fonts/SourceSansPro-Italic-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-Light-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-Light-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-Light-cyrillic.woff
opt/app/public/fonts/SourceSansPro-Light-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-Light-greek-ext.woff
opt/app/public/fonts/SourceSansPro-Light-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-Light-greek.woff
opt/app/public/fonts/SourceSansPro-Light-greek.woff2
opt/app/public/fonts/SourceSansPro-Light-latin-ext.woff
opt/app/public/fonts/SourceSansPro-Light-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-Light-latin.woff
opt/app/public/fonts/SourceSansPro-Light-latin.woff2
opt/app/public/fonts/SourceSansPro-Light-vietnamese.woff
opt/app/public/fonts/SourceSansPro-Light-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic.woff
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-greek-ext.woff
opt/app/public/fonts/SourceSansPro-LightItalic-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-greek.woff
opt/app/public/fonts/SourceSansPro-LightItalic-greek.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-latin-ext.woff
opt/app/public/fonts/SourceSansPro-LightItalic-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-latin.woff
opt/app/public/fonts/SourceSansPro-LightItalic-latin.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-vietnamese.woff
opt/app/public/fonts/SourceSansPro-LightItalic-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-Regular-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-Regular-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-Regular-cyrillic.woff
opt/app/public/fonts/SourceSansPro-Regular-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-Regular-greek-ext.woff
opt/app/public/fonts/SourceSansPro-Regular-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-Regular-greek.woff
opt/app/public/fonts/SourceSansPro-Regular-greek.woff2
opt/app/public/fonts/SourceSansPro-Regular-latin-ext.woff
opt/app/public/fonts/SourceSansPro-Regular-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-Regular-latin.woff
opt/app/public/fonts/SourceSansPro-Regular-latin.woff2
opt/app/public/fonts/SourceSansPro-Regular-vietnamese.woff
opt/app/public/fonts/SourceSansPro-Regular-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic.woff
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-greek-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBold-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-greek.woff
opt/app/public/fonts/SourceSansPro-SemiBold-greek.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-latin-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBold-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-latin.woff
opt/app/public/fonts/SourceSansPro-SemiBold-latin.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-vietnamese.woff
opt/app/public/fonts/SourceSansPro-SemiBold-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-vietnamese.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-vietnamese.woff2
opt/app/public/fonts/lato-100.woff
opt/app/public/fonts/lato-100.woff2
opt/app/public/fonts/roboto-light-300.woff
opt/app/public/fonts/roboto-light-300.woff2
opt/app/public/images/error.png
opt/app/public/images/image.png
@@ -1004,6 +1068,7 @@ opt/app/public/report.html
opt/app/public/robots.txt
opt/app/public/safari-pinned-tab.svg
opt/app/public/web.config
opt/app/readme.md
opt/app/resources/lang/de_DE/auth.php
opt/app/resources/lang/de_DE/bank.php
opt/app/resources/lang/de_DE/breadcrumbs.php
@@ -1092,6 +1157,34 @@ opt/app/resources/lang/pl_PL/list.php
opt/app/resources/lang/pl_PL/pagination.php
opt/app/resources/lang/pl_PL/passwords.php
opt/app/resources/lang/pl_PL/validation.php
opt/app/resources/lang/pt_BR/auth.php
opt/app/resources/lang/pt_BR/bank.php
opt/app/resources/lang/pt_BR/breadcrumbs.php
opt/app/resources/lang/pt_BR/config.php
opt/app/resources/lang/pt_BR/csv.php
opt/app/resources/lang/pt_BR/demo.php
opt/app/resources/lang/pt_BR/firefly.php
opt/app/resources/lang/pt_BR/form.php
opt/app/resources/lang/pt_BR/import.php
opt/app/resources/lang/pt_BR/intro.php
opt/app/resources/lang/pt_BR/list.php
opt/app/resources/lang/pt_BR/pagination.php
opt/app/resources/lang/pt_BR/passwords.php
opt/app/resources/lang/pt_BR/validation.php
opt/app/resources/lang/ru_RU/auth.php
opt/app/resources/lang/ru_RU/bank.php
opt/app/resources/lang/ru_RU/breadcrumbs.php
opt/app/resources/lang/ru_RU/config.php
opt/app/resources/lang/ru_RU/csv.php
opt/app/resources/lang/ru_RU/demo.php
opt/app/resources/lang/ru_RU/firefly.php
opt/app/resources/lang/ru_RU/form.php
opt/app/resources/lang/ru_RU/import.php
opt/app/resources/lang/ru_RU/intro.php
opt/app/resources/lang/ru_RU/list.php
opt/app/resources/lang/ru_RU/pagination.php
opt/app/resources/lang/ru_RU/passwords.php
opt/app/resources/lang/ru_RU/validation.php
opt/app/resources/lang/tr_TR/auth.php
opt/app/resources/lang/tr_TR/bank.php
opt/app/resources/lang/tr_TR/breadcrumbs.php
@@ -1169,7 +1262,6 @@ opt/app/resources/views/demo/accounts/index.twig
opt/app/resources/views/demo/budgets/index.twig
opt/app/resources/views/demo/currencies/index.twig
opt/app/resources/views/demo/home.twig
opt/app/resources/views/demo/import/configure.twig
opt/app/resources/views/demo/import/index.twig
opt/app/resources/views/demo/index.twig
opt/app/resources/views/demo/no-demo-text.twig
@@ -2962,6 +3054,7 @@ opt/app/vendor/league/flysystem/docs/adapter/aws-s3-v2.md
opt/app/vendor/league/flysystem/docs/adapter/aws-s3-v3.md
opt/app/vendor/league/flysystem/docs/adapter/azure.md
opt/app/vendor/league/flysystem/docs/adapter/copy.md
opt/app/vendor/league/flysystem/docs/adapter/digitalocean-spaces.md
opt/app/vendor/league/flysystem/docs/adapter/dropbox.md
opt/app/vendor/league/flysystem/docs/adapter/ftp.md
opt/app/vendor/league/flysystem/docs/adapter/gridfs.md
@@ -2981,10 +3074,15 @@ opt/app/vendor/league/flysystem/docs/creating-an-adapter.md
opt/app/vendor/league/flysystem/docs/index.md
opt/app/vendor/league/flysystem/docs/installation.md
opt/app/vendor/league/flysystem/docs/integrations.md
opt/app/vendor/league/flysystem/docs/logo/become_a_patron_button.png
opt/app/vendor/league/flysystem/docs/logo/become_a_patron_button@2x.png
opt/app/vendor/league/flysystem/docs/logo/become_a_patron_button@3x.png
opt/app/vendor/league/flysystem/docs/logo/laravel.svg
opt/app/vendor/league/flysystem/docs/mount-manager.md
opt/app/vendor/league/flysystem/docs/performance.md
opt/app/vendor/league/flysystem/docs/plugins.md
opt/app/vendor/league/flysystem/docs/recipes.md
opt/app/vendor/league/flysystem/docs/sponsors.md
opt/app/vendor/league/flysystem/docs/upgrade-to-1.0.0.md
opt/app/vendor/league/flysystem/src/Adapter/AbstractAdapter.php
opt/app/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php
@@ -3032,6 +3130,7 @@ opt/app/vendor/league/flysystem/src/Util.php
opt/app/vendor/league/flysystem/src/Util/ContentListingFormatter.php
opt/app/vendor/league/flysystem/src/Util/MimeType.php
opt/app/vendor/league/flysystem/src/Util/StreamHasher.php
opt/app/vendor/league/flysystem/wait_for_ftp_service.php
opt/app/vendor/monolog/monolog/.php_cs
opt/app/vendor/monolog/monolog/CHANGELOG.md
opt/app/vendor/monolog/monolog/LICENSE
@@ -3413,6 +3512,9 @@ opt/app/vendor/ramsey/uuid/CONTRIBUTING.md
opt/app/vendor/ramsey/uuid/LICENSE
opt/app/vendor/ramsey/uuid/README.md
opt/app/vendor/ramsey/uuid/composer.json
opt/app/vendor/ramsey/uuid/docs/Makefile
opt/app/vendor/ramsey/uuid/docs/conf.py
opt/app/vendor/ramsey/uuid/docs/index.rst
opt/app/vendor/ramsey/uuid/src/BinaryUtils.php
opt/app/vendor/ramsey/uuid/src/Builder/DefaultUuidBuilder.php
opt/app/vendor/ramsey/uuid/src/Builder/DegradedUuidBuilder.php
@@ -4369,6 +4471,7 @@ opt/app/vendor/symfony/debug/Tests/Fixtures2/RequiredTwice.php
opt/app/vendor/symfony/debug/Tests/HeaderMock.php
opt/app/vendor/symfony/debug/Tests/MockExceptionHandler.php
opt/app/vendor/symfony/debug/Tests/phpt/debug_class_loader.phpt
opt/app/vendor/symfony/debug/Tests/phpt/decorate_exception_hander.phpt
opt/app/vendor/symfony/debug/Tests/phpt/exception_rethrown.phpt
opt/app/vendor/symfony/debug/Tests/phpt/fatal_with_nested_handlers.phpt
opt/app/vendor/symfony/debug/composer.json
@@ -4786,6 +4889,7 @@ opt/app/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/SaveSessionListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/SessionListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php
@@ -4997,6 +5101,7 @@ opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php
opt/app/vendor/symfony/routing/Tests/Fixtures/CustomCompiledRoute.php
opt/app/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php
opt/app/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php
opt/app/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php
opt/app/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php
opt/app/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php
opt/app/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php

View File

@@ -15,8 +15,8 @@ const pkgdef :Spk.PackageDefinition = (
manifest = (
appTitle = (defaultText = "Firefly III"),
appVersion = 7,
appMarketingVersion = (defaultText = "4.6.13"),
appVersion = 8,
appMarketingVersion = (defaultText = "4.7.0"),
actions = [
# Define your "new document" handlers here.

View File

@@ -57,6 +57,7 @@ http {
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_read_timeout 900;
fastcgi_param QUERY_STRING $query_string;

View File

@@ -2,6 +2,7 @@
# When you change this file, you must take manual action. Read this doc:
# - https://docs.sandstorm.io/en/latest/vagrant-spk/customizing/#setupsh
echo "Now in setup.sh"
set -euo pipefail
@@ -14,8 +15,11 @@ apt-get install -y python-software-properties software-properties-common
# install all languages
sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# id_ID.UTF-8 UTF-8/id_ID.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# nl_NL.UTF-8 UTF-8/nl_NL.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# pl_PL.UTF-8 UTF-8/pl_PL.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# ru_RU.UTF-8 UTF-8/ru_RU.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# tr_TR.UTF-8 UTF-8/tr_TR.UTF-8 UTF-8/g' /etc/locale.gen
dpkg-reconfigure --frontend=noninteractive locales

View File

@@ -1,51 +1,58 @@
# .scrutinizer.yml
tools:
external_code_coverage: false
filter:
paths:
---
build:
nodes:
analysis:
project_setup:
override: true
tests:
override:
- php-scrutinizer-run
checks:
javascript: true
php:
align_assignments: true
avoid_fixme_comments: true
avoid_multiple_statements_on_same_line: true
avoid_perl_style_comments: true
avoid_todo_comments: true
duplication: false
encourage_single_quotes: true
newline_at_end_of_file: true
no_goto: true
no_long_variable_names:
maximum: "20"
no_short_method_names:
minimum: "3"
no_short_variable_names:
minimum: "3"
optional_parameters_at_the_end: true
parameter_doc_comments: true
remove_extra_empty_lines: true
return_doc_comment_if_not_inferrable: true
return_doc_comments: true
uppercase_constants: true
use_self_instead_of_fqcn: true
coding_style:
php:
spaces:
around_operators:
concatenation: true
other:
after_type_cast: false
filter:
excluded_paths:
- database/migrations/*
- bootstrap/*
- config/*
- docker/*
- public/js/lib/*
- public/lib/adminlte/js/*
- public/lib/bootstrap/js/*
- resources/*
- routes/*
- storage/*
paths:
- app/*
- public/js/ff/*
excluded_paths:
- "database/migrations/*"
- "bootstrap/*"
- "config/*"
- "docker/*"
- "public/js/lib/*"
- "public/lib/adminlte/js/*"
- "public/lib/bootstrap/js/*"
- "resources/*"
- "routes/*"
- "storage/*"
checks:
php:
use_self_instead_of_fqcn: true
uppercase_constants: true
return_doc_comments: true
return_doc_comment_if_not_inferrable: true
remove_extra_empty_lines: true
parameter_doc_comments: true
optional_parameters_at_the_end: true
no_short_variable_names:
minimum: '3'
no_short_method_names:
minimum: '3'
no_long_variable_names:
maximum: '20'
no_goto: true
newline_at_end_of_file: true
encourage_single_quotes: true
avoid_todo_comments: true
avoid_perl_style_comments: true
avoid_fixme_comments: true
avoid_multiple_statements_on_same_line: true
align_assignments: true
duplication: false
javascript: true
coding_style:
php:
spaces:
around_operators:
concatenation: true
other:
after_type_cast: false
tools:
external_code_coverage: false

View File

@@ -24,6 +24,7 @@ script:
after_success:
- travis_retry php vendor/bin/php-coveralls -x storage/build/clover-all.xml
- bash <(curl -s https://codecov.io/bash) -f storage/build/clover-all.xml
# safelist
branches:

View File

@@ -1,89 +0,0 @@
# Firefly III: A personal finances manager
[![Requires PHP7.1](https://img.shields.io/badge/php-7.1-red.svg)](https://secure.php.net/downloads.php) [![Latest Stable Version](https://poser.pugx.org/grumpydictator/firefly-iii/v/stable)](https://packagist.org/packages/grumpydictator/firefly-iii) [![License](https://img.shields.io/badge/license-GPL-lightgrey.svg)](https://www.gnu.org/licenses/gpl.html) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
[![The index of Firefly III](https://firefly-iii.org/static/screenshots/4.6.12/tiny/index.png)](https://firefly-iii.org/static/screenshots/4.6.12/index.png) [![The account overview of Firefly III](https://firefly-iii.org/static/screenshots/4.6.12/tiny/account.png)](https://firefly-iii.org/static/screenshots/4.6.12/account.png)
[![Overview of all budgets](https://firefly-iii.org/static/screenshots/4.6.12/tiny/budget.png)](https://firefly-iii.org/static/screenshots/4.6.12/budget.png) [![Overview of a category](https://firefly-iii.org/static/screenshots/4.6.12/tiny/category.png)](https://firefly-iii.org/static/screenshots/4.6.12/category.png)
[![View of a report](https://firefly-iii.org/static/screenshots/4.6.12/tiny/report1.png)](https://firefly-iii.org/static/screenshots/4.6.12/report1.png) [![View of another report](https://firefly-iii.org/static/screenshots/4.6.12/tiny/report2.png)](https://firefly-iii.org/static/screenshots/4.6.12/report2.png)
"Firefly III" is a financial manager for your personal finances. It can help you keep track of your expenses and income.
Firefly III supports the use of budgets. You can categorize and tag your transactions.
It also supports credit cards, shared household accounts and savings accounts.
There are many financial reports available.
## Want to try Firefly III?
There is a **[demo site](https://demo.firefly-iii.org)** with an example financial administration already present. You can use Docker, Heroku or Sandstorm.io (see below) to quickly setup your own instance.
## Install Firefly III
### Using docker
You can use docker-compose to [set up your personal secure](https://firefly-iii.org/using-docker.html) Firefly III environment. Advanced users can use the Dockerfile directly.
### Using vagrant (or other VMs)
You can install Firefly III on any Linux or Windows machine. You'll need a web server (preferrably on Linux) and access to the command line. Please read the [installation guide](https://firefly-iii.org/using-installing.html).
### Using Heroku
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master)
Register for a free Heroku account and instantly run Firefly III on your very own cloud instance.
### Using Sandstorm.io
You can find Firefly III in [the Sandstorm.io marketplace](https://apps.sandstorm.io/app/uws252ya9mep4t77tevn85333xzsgrpgth8q4y1rhknn1hammw70). You can run it on your own installation or on Oasis.
### Other options
Firefly III is also available as a package on [https://softaculous.com/](Softaculous) and [AMPPS](https://www.ampps.com/).
## More about Firefly III
Personal financial management is pretty difficult, and everybody has their own approach to it.
Some people make budgets, other people limit their cashflow by throwing away their credit cards,
others try to increase their current cashflow. There are tons of ways to save and earn money.
Firefly works on the principle that if you know where you're money is going, you can stop it from going there.
### Some advantages of using Firefly
- Firefly can import any CSV file, so migrating from other systems is easy.
- Firefly runs on your own server, so you are fully in control of your data. Remember, there is no such thing as "the cloud", its just somebody elses computer!
- Firefly has lots of features without being fancy or bloated.
- If you feel you're missing something you can just ask me and I'll add it!
Firefly III has become pretty awesome over the years! (but please excuse me for bragging, it's just that I'm proud of it).
[You can read more about Firefly III, and its features, on the website](https://firefly-iii.org/).
### Contributing
Please read [CONTRIBUTING.md](https://github.com/firefly-iii/firefly-iii/blob/master/.github/CONTRIBUTING.md) for details on contributing, and the process for submitting pull requests. Please check out the [code of conduct](https://github.com/firefly-iii/firefly-iii/blob/master/CODE_OF_CONDUCT.md) as well.
### Versioning
We use [SemVer](http://semver.org/) for versioning. For the versions available, see [the tags](https://github.com/firefly-iii/firefly-iii/tags) on this repository.
### Authors
* James Cole
* Over time, [many people have contributed to Firefly III](https://github.com/firefly-iii/firefly-iii/graphs/contributors).
### License
This work [is licensed](https://github.com/firefly-iii/firefly-iii/blob/master/LICENSE) under the [GPL v3](https://www.gnu.org/licenses/gpl.html).
### Other stuff
If you like Firefly III and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!)
If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com).
### Alternatives
If you are looking for alternatives, check out [Kickball's Awesome-Selfhosted list](https://github.com/Kickball/awesome-selfhosted) which features not only Firefly III but also noteworthy alternatives such as [Silverstrike](https://github.com/agstrike/silverstrike).
[![Build Status](https://travis-ci.org/firefly-iii/firefly-iii.svg?branch=master)](https://travis-ci.org/firefly-iii/firefly-iii) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [![Coverage Status](https://coveralls.io/repos/github/firefly-iii/firefly-iii/badge.svg?branch=master)](https://coveralls.io/github/firefly-iii/firefly-iii?branch=master)

View File

@@ -178,7 +178,7 @@ class CreateImport extends Command
$cwd = getcwd();
$validTypes = config('import.options.file.import_formats');
$type = strtolower($this->option('type'));
if (null === $user->id) {
if (null === $user) {
$this->error(sprintf('There is no user with ID %d.', $this->option('user')));
return false;

View File

@@ -220,7 +220,7 @@ class UpgradeDatabase extends Command
// when mismatch in transaction:
if (!(intval($transaction->transaction_currency_id) === intval($currency->id))) {
$transaction->foreign_currency_id = $transaction->transaction_currency_id;
$transaction->foreign_currency_id = intval($transaction->transaction_currency_id);
$transaction->foreign_amount = $transaction->amount;
$transaction->transaction_currency_id = $currency->id;
$transaction->save();
@@ -412,12 +412,10 @@ class UpgradeDatabase extends Command
if (!(intval($transaction->transaction_currency_id) === intval($currency->id)) && null === $transaction->foreign_amount) {
Log::debug(
sprintf(
'Transaction #%d has a currency setting (#%d) (%s) that should be #%d (%s). Amount remains %s, currency is changed.',
'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.',
$transaction->id,
$transaction->transaction_currency_id,
$this->var_dump_ret(intval($transaction->transaction_currency_id)),
$currency->id,
$this->var_dump_ret(intval($currency->id)),
$transaction->amount
)
);
@@ -504,19 +502,4 @@ class UpgradeDatabase extends Command
return;
}
/**
* @param null $mixed
*
* @return string
*/
private function var_dump_ret($mixed = null): string
{
ob_start();
var_dump($mixed);
$content = ob_get_contents();
ob_end_clean();
return trim($content);
}
}

View File

@@ -36,7 +36,7 @@ trait VerifiesAccessToken
/**
* Abstract method to make sure trait knows about method "option".
*
* @param null $key
* @param string|null $key
*
* @return mixed
*/
@@ -55,7 +55,7 @@ trait VerifiesAccessToken
$repository = app(UserRepositoryInterface::class);
$user = $repository->find($userId);
if (null === $user->id) {
if (null === $user) {
Log::error(sprintf('verifyAccessToken(): no such user for input "%d"', $userId));
return false;

View File

@@ -52,7 +52,7 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
*/
public function __construct()
{
// @var AttachmentRepositoryInterface repository
/** @var AttachmentRepositoryInterface repository */
$this->repository = app(AttachmentRepositoryInterface::class);
// make storage:
$this->uploadDisk = Storage::disk('upload');

View File

@@ -26,6 +26,7 @@ use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Support\Collection;
use Steam;
@@ -173,7 +174,7 @@ class MonthReportGenerator implements ReportGeneratorInterface
$startBalance = $dayBeforeBalance;
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
// @var Transaction $journal
/** @var Transaction $transaction */
foreach ($journals as $transaction) {
$transaction->before = $startBalance;
$transactionAmount = $transaction->transaction_amount;

View File

@@ -194,7 +194,9 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
*/
private function summarizeByBudget(Collection $collection): array
{
$result = [];
$result = [
'sum' => '0',
];
/** @var Transaction $transaction */
foreach ($collection as $transaction) {
$jrnlBudId = intval($transaction->transaction_journal_budget_id);
@@ -202,6 +204,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
$budgetId = max($jrnlBudId, $transBudId);
$result[$budgetId] = $result[$budgetId] ?? '0';
$result[$budgetId] = bcadd($transaction->transaction_amount, $result[$budgetId]);
$result['sum'] = bcadd($result['sum'], $transaction->transaction_amount);
}
return $result;

View File

@@ -102,7 +102,12 @@ class Support
*/
protected function getObjectSummary(array $spent, array $earned): array
{
$return = [];
$return = [
'sum' => [
'spent' => '0',
'earned' => '0',
],
];
/**
* @var int
@@ -110,10 +115,11 @@ class Support
*/
foreach ($spent as $objectId => $entry) {
if (!isset($return[$objectId])) {
$return[$objectId] = ['spent' => 0, 'earned' => 0];
$return[$objectId] = ['spent' => '0', 'earned' => '0'];
}
$return[$objectId]['spent'] = $entry;
$return['sum']['spent'] = bcadd($return['sum']['spent'], $entry);
}
unset($entry);
@@ -123,10 +129,11 @@ class Support
*/
foreach ($earned as $objectId => $entry) {
if (!isset($return[$objectId])) {
$return[$objectId] = ['spent' => 0, 'earned' => 0];
$return[$objectId] = ['spent' => '0', 'earned' => '0'];
}
$return[$objectId]['earned'] = $entry;
$return['sum']['earned'] = bcadd($return['sum']['earned'], $entry);
}
return $return;
@@ -139,12 +146,15 @@ class Support
*/
protected function summarizeByAccount(Collection $collection): array
{
$result = [];
$result = [
'sum' => '0',
];
/** @var Transaction $transaction */
foreach ($collection as $transaction) {
$accountId = $transaction->account_id;
$result[$accountId] = $result[$accountId] ?? '0';
$result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]);
$result['sum'] = bcadd($result['sum'], $transaction->transaction_amount);
}
return $result;

View File

@@ -25,11 +25,11 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Factories\RoleFactory;
use FireflyIII\Mail\ConfirmEmailChangeMail;
use FireflyIII\Mail\RegisteredUser as RegisteredUserMail;
use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail;
use FireflyIII\Mail\UndoEmailChangeMail;
use FireflyIII\Models\Role;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Auth\Events\Login;
@@ -74,11 +74,12 @@ class UserEventHandler
*/
public function checkSingleUserIsAdmin(Login $event): bool
{
Log::debug('In checkSingleUserIsAdmin');
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
/** @var User $user */
$user = $event->user;
$count = User::count();
$count = $repository->count();
if ($count > 1) {
// if more than one user, do nothing.
@@ -93,17 +94,16 @@ class UserEventHandler
return true;
}
// user is the only user but does not have role "owner".
$role = Role::where('name', 'owner')->first();
$role = $repository->getRole('owner');
if (is_null($role)) {
// create role, does not exist. Very strange situation so let's raise a big fuss about it.
$role = Role::create(['name' => 'owner', 'display_name' => 'Site Owner', 'description' => 'User runs this instance of FF3']);
$role = $repository->createRole('owner', 'Site Owner', 'User runs this instance of FF3');
Log::error('Could not find role "owner". This is weird.');
}
Log::info(sprintf('Gave user #%d role #%d ("%s")', $user->id, $role->id, $role->name));
// give user the role
$user->attachRole($role);
$user->save();
$repository->attachRole($user, 'owner');
return true;
}

View File

@@ -25,6 +25,9 @@ namespace FireflyIII\Handlers\Events;
use FireflyConfig;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Services\Github\Object\Release;
use FireflyIII\Services\Github\Request\UpdateRequest;
use FireflyIII\User;
use Log;
@@ -45,6 +48,7 @@ class VersionCheckEventHandler
return;
}
/** @var User $user */
$user = $event->user;
if (!$user->hasRole('owner')) {
@@ -55,7 +59,7 @@ class VersionCheckEventHandler
$lastCheckTime = FireflyConfig::get('last_update_check', time());
$now = time();
if ($now - $lastCheckTime->data < 604800) {
Log::debug('Checked for updates less than a week ago.');
Log::debug(sprintf('Checked for updates less than a week ago (on %s).', date('Y-m-d H:i:s', $lastCheckTime->data)));
return;
@@ -71,8 +75,42 @@ class VersionCheckEventHandler
return;
}
// actually check for update and inform the user.
$current = config('firefly.version');
/** @var UpdateRequest $request */
$request = app(UpdateRequest::class);
$check = -2;
$first = new Release(['id' => '0', 'title' => '0', 'updated' => '2017-01-01', 'content' => '']);
try {
$request->call();
$releases = $request->getReleases();
// first entry should be the latest entry:
/** @var Release $first */
$first = reset($releases);
$check = version_compare($current, $first->getTitle());
FireflyConfig::set('last_update_check', time());
} catch (FireflyException $e) {
Log::error(sprintf('Could not check for updates: %s', $e->getMessage()));
}
$string = 'no result: ' . $check;
if ($check === -2) {
$string = strval(trans('firefly.update_check_error'));
}
if ($check === -1) {
// there is a new FF version!
$monthAndDayFormat = (string)trans('config.month_and_day');
$string = strval(
trans(
'firefly.update_new_version_alert',
['your_version' => $current, 'new_version' => $first->getTitle(), 'date' => $first->getUpdated()->formatLocalized($monthAndDayFormat)]
)
);
}
if ($check !== 0) {
// flash info
session()->flash('info', $string);
}
return;
}
}

View File

@@ -37,6 +37,7 @@ use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\JoinClause;
@@ -232,7 +233,7 @@ class JournalCollector implements JournalCollectorInterface
$countQuery->getQuery()->groups = null;
$countQuery->getQuery()->orders = null;
$countQuery->groupBy('accounts.user_id');
$this->count = $countQuery->count();
$this->count = intval($countQuery->count());
return $this->count;
}
@@ -243,6 +244,17 @@ class JournalCollector implements JournalCollectorInterface
public function getJournals(): Collection
{
$this->run = true;
// find query set in cache.
$hash = hash('sha256', $this->query->toSql() . serialize($this->query->getBindings()));
$key = 'query-' . substr($hash, -8);
$cache = new CacheProperties;
$cache->addProperty($key);
if ($cache->has()) {
Log::debug(sprintf('Return cache of query with ID "%s".', $key));
return $cache->get(); // @codeCoverageIgnore
}
/** @var Collection $set */
$set = $this->query->get(array_values($this->fields));
@@ -263,6 +275,8 @@ class JournalCollector implements JournalCollectorInterface
$transaction->opposing_account_iban = app('steam')->tryDecrypt($transaction->opposing_account_iban);
}
);
Log::debug(sprintf('Cached query with ID "%s".', $key));
$cache->store($set);
return $set;
}

View File

@@ -27,6 +27,7 @@ use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;

View File

@@ -136,7 +136,7 @@ class PopupReport implements PopupReportInterface
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($attributes['accounts'])->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->setRange($attributes['startDate'], $attributes['endDate'])
->setRange($attributes['startDate'], $attributes['endDate'])->withOpposingAccount()
->setCategory($category);
$journals = $collector->getJournals();

View File

@@ -39,7 +39,6 @@ use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
use Preferences;
use Steam;
use View;
@@ -51,6 +50,13 @@ use View;
*/
class AccountController extends Controller
{
/** @var CurrencyRepositoryInterface */
private $currencyRepos;
/** @var JournalRepositoryInterface */
private $journalRepos;
/** @var AccountRepositoryInterface */
private $repository;
/**
*
*/
@@ -64,6 +70,10 @@ class AccountController extends Controller
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', trans('firefly.accounts'));
$this->repository = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->journalRepos = app(JournalRepositoryInterface::class);
return $next($request);
}
);
@@ -77,9 +87,7 @@ class AccountController extends Controller
*/
public function create(Request $request, string $what = 'asset')
{
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$allCurrencies = $repository->get();
$allCurrencies = $this->currencyRepos->get();
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
$defaultCurrency = app('amount')->getDefaultCurrency();
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
@@ -102,16 +110,15 @@ class AccountController extends Controller
}
/**
* @param AccountRepositoryInterface $repository
* @param Account $account
* @param Account $account
*
* @return View
*/
public function delete(AccountRepositoryInterface $repository, Account $account)
public function delete(Account $account)
{
$typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$subTitle = trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]);
$accountList = ExpandedForm::makeSelectListWithEmpty($repository->getAccountsByType([$account->accountType->type]));
$accountList = ExpandedForm::makeSelectListWithEmpty($this->repository->getAccountsByType([$account->accountType->type]));
unset($accountList[$account->id]);
// put previous url in session
@@ -127,14 +134,14 @@ class AccountController extends Controller
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(Request $request, AccountRepositoryInterface $repository, Account $account)
public function destroy(Request $request, Account $account)
{
$type = $account->accountType->type;
$typeName = config('firefly.shortNamesByFullName.' . $type);
$name = $account->name;
$moveTo = $repository->find(intval($request->get('move_account_before_delete')));
$moveTo = $this->repository->find(intval($request->get('move_account_before_delete')));
$repository->destroy($account, $moveTo);
$this->repository->destroy($account, $moveTo);
$request->session()->flash('success', strval(trans('firefly.' . $typeName . '_deleted', ['name' => $name])));
Preferences::mark();
@@ -154,17 +161,13 @@ class AccountController extends Controller
* @return View
*
* @throws FireflyException
* @throws FireflyException
* @throws FireflyException
*/
public function edit(Request $request, Account $account, AccountRepositoryInterface $repository)
public function edit(Request $request, Account $account)
{
/** @var CurrencyRepositoryInterface $currencyRepos */
$currencyRepos = app(CurrencyRepositoryInterface::class);
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$allCurrencies = $currencyRepos->get();
$allCurrencies = $this->currencyRepos->get();
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
$roles = [];
foreach (config('firefly.accountRoles') as $role) {
@@ -184,7 +187,7 @@ class AccountController extends Controller
$openingBalanceAmount = '0' === $account->getOpeningBalanceAmount() ? '' : $openingBalanceAmount;
$openingBalanceDate = $account->getOpeningBalanceDate();
$openingBalanceDate = 1900 === $openingBalanceDate->year ? null : $openingBalanceDate->format('Y-m-d');
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
$currency = $this->currencyRepos->find(intval($account->getMeta('currency_id')));
$preFilled = [
'accountNumber' => $account->getMeta('accountNumber'),
@@ -199,7 +202,7 @@ class AccountController extends Controller
'notes' => '',
];
/** @var Note $note */
$note = $repository->getNote($account);
$note = $this->repository->getNote($account);
if (null !== $note) {
$preFilled['notes'] = $note->text;
}
@@ -224,19 +227,18 @@ class AccountController extends Controller
}
/**
* @param Request $request
* @param AccountRepositoryInterface $repository
* @param string $what
* @param Request $request
* @param string $what
*
* @return View
*/
public function index(Request $request, AccountRepositoryInterface $repository, string $what)
public function index(Request $request, string $what)
{
$what = $what ?? 'asset';
$subTitle = trans('firefly.' . $what . '_accounts');
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$types = config('firefly.accountTypesByIdentifier.' . $what);
$collection = $repository->getAccountsByType($types);
$collection = $this->repository->getAccountsByType($types);
$total = $collection->count();
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
@@ -272,10 +274,9 @@ class AccountController extends Controller
/**
* Show an account.
*
* @param Request $request
* @param JournalRepositoryInterface $repository
* @param Account $account
* @param string $moment
* @param Request $request
* @param Account $account
* @param string $moment
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*
@@ -284,23 +285,21 @@ class AccountController extends Controller
*
* @throws FireflyException
*/
public function show(Request $request, JournalRepositoryInterface $repository, Account $account, string $moment = '')
public function show(Request $request, Account $account, string $moment = '')
{
if (AccountType::INITIAL_BALANCE === $account->accountType->type) {
return $this->redirectToOriginalAccount($account);
}
/** @var CurrencyRepositoryInterface $currencyRepos */
$currencyRepos = app(CurrencyRepositoryInterface::class);
$range = Preferences::get('viewRange', '1M')->data;
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$chartUri = route('chart.account.single', [$account->id]);
$start = null;
$end = null;
$periods = new Collection;
$currencyId = intval($account->getMeta('currency_id'));
$currency = $currencyRepos->find($currencyId);
$range = Preferences::get('viewRange', '1M')->data;
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$chartUri = route('chart.account.single', [$account->id]);
$start = null;
$end = null;
$periods = new Collection;
$currencyId = intval($account->getMeta('currency_id'));
$currency = $this->currencyRepos->find($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
}
@@ -309,7 +308,7 @@ class AccountController extends Controller
if ('all' === $moment) {
$subTitle = trans('firefly.all_journals_for_account', ['name' => $account->name]);
$chartUri = route('chart.account.all', [$account->id]);
$first = $repository->first();
$first = $this->journalRepos->first();
$start = $first->date ?? new Carbon;
$end = new Carbon;
}
@@ -317,12 +316,13 @@ class AccountController extends Controller
// prep for "specific date" view.
if (strlen($moment) > 0 && 'all' !== $moment) {
$start = new Carbon($moment);
$start = app('navigation')->startOfPeriod($start, $range);
$end = app('navigation')->endOfPeriod($start, $range);
$fStart = $start->formatLocalized($this->monthAndDayFormat);
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
$subTitle = trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d')]);
$periods = $this->getPeriodOverview($account);
$periods = $this->getPeriodOverview($account, $start);
}
// prep for current period view
@@ -332,7 +332,7 @@ class AccountController extends Controller
$fStart = $start->formatLocalized($this->monthAndDayFormat);
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
$subTitle = trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
$periods = $this->getPeriodOverview($account);
$periods = $this->getPeriodOverview($account, null);
}
// grab journals:
@@ -351,15 +351,14 @@ class AccountController extends Controller
}
/**
* @param AccountFormRequest $request
* @param AccountRepositoryInterface $repository
* @param AccountFormRequest $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function store(AccountFormRequest $request, AccountRepositoryInterface $repository)
public function store(AccountFormRequest $request)
{
$data = $request->getAccountData();
$account = $repository->store($data);
$account = $this->repository->store($data);
$request->session()->flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
Preferences::mark();
@@ -390,10 +389,10 @@ class AccountController extends Controller
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function update(AccountFormRequest $request, AccountRepositoryInterface $repository, Account $account)
public function update(AccountFormRequest $request, Account $account)
{
$data = $request->getAccountData();
$repository->update($account, $data);
$this->repository->update($account, $data);
$request->session()->flash('success', strval(trans('firefly.updated_account', ['name' => $account->name])));
Preferences::mark();
@@ -435,56 +434,55 @@ class AccountController extends Controller
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function getPeriodOverview(Account $account): Collection
private function getPeriodOverview(Account $account, ?Carbon $date): Collection
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$start = $repository->oldestJournalDate($account);
$range = Preferences::get('viewRange', '1M')->data;
$start = app('navigation')->startOfPeriod($start, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
$count = 0;
$range = Preferences::get('viewRange', '1M')->data;
$start = $this->repository->oldestJournalDate($account);
$end = $date ?? new Carbon;
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('account-show-period-entries');
$cache->addProperty($account->id);
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
Log::debug('Going to get period expenses and incomes.');
while ($end >= $start && $count < 90) {
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
// loop dates
foreach ($dates as $date) {
// try a collector for income:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)->setTypes([TransactionType::DEPOSIT])->withOpposingAccount();
$collector->setAccounts(new Collection([$account]))->setRange($date['start'], $date['end'])->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$earned = strval($collector->getJournals()->sum('transaction_amount'));
// try a collector for expenses:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)->setTypes([TransactionType::WITHDRAWAL])->withOpposingAccount();
$spent = strval($collector->getJournals()->sum('transaction_amount'));
$dateStr = $end->format('Y-m-d');
$dateName = app('navigation')->periodShow($end, $range);
$collector->setAccounts(new Collection([$account]))->setRange($date['start'], $date['end'])->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$spent = strval($collector->getJournals()->sum('transaction_amount'));
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['start'], $date['period']);
$entries->push(
[
'string' => $dateStr,
'name' => $dateName,
'spent' => $spent,
'earned' => $earned,
'date' => clone $end,]
'date' => clone $date['end'],]
);
$end = app('navigation')->subtractPeriod($end, $range, 1);
++$count;
}
$cache->store($entries);
return $entries;

View File

@@ -125,7 +125,7 @@ class UserController extends Controller
$list = ['twoFactorAuthEnabled', 'twoFactorAuthSecret'];
$preferences = Preferences::getArrayForUser($user, $list);
$user->isAdmin = $user->hasRole('owner');
$is2faEnabled = true === $preferences['twoFactorAuthEnabled'];
$is2faEnabled = 1 === $preferences['twoFactorAuthEnabled'];
$has2faSecret = null !== $preferences['twoFactorAuthSecret'];
$user->has2FA = ($is2faEnabled && $has2faSecret) ? true : false;
$user->prefs = $preferences;

View File

@@ -22,7 +22,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use File;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Requests\AttachmentFormRequest;
use FireflyIII\Models\Attachment;
@@ -40,6 +39,9 @@ use View;
*/
class AttachmentController extends Controller
{
/** @var AttachmentRepositoryInterface */
private $repository;
/**
*
*/
@@ -52,6 +54,7 @@ class AttachmentController extends Controller
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-paperclip');
app('view')->share('title', trans('firefly.attachments'));
$this->repository = app(AttachmentRepositoryInterface::class);
return $next($request);
}
@@ -74,17 +77,16 @@ class AttachmentController extends Controller
}
/**
* @param Request $request
* @param AttachmentRepositoryInterface $repository
* @param Attachment $attachment
* @param Request $request
* @param Attachment $attachment
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(Request $request, AttachmentRepositoryInterface $repository, Attachment $attachment)
public function destroy(Request $request, Attachment $attachment)
{
$name = $attachment->filename;
$repository->destroy($attachment);
$this->repository->destroy($attachment);
$request->session()->flash('success', strval(trans('firefly.attachment_deleted', ['name' => $name])));
Preferences::mark();
@@ -93,17 +95,16 @@ class AttachmentController extends Controller
}
/**
* @param AttachmentRepositoryInterface $repository
* @param Attachment $attachment
* @param Attachment $attachment
*
* @return mixed
*
* @throws FireflyException
*/
public function download(AttachmentRepositoryInterface $repository, Attachment $attachment)
public function download(Attachment $attachment)
{
if ($repository->exists($attachment)) {
$content = $repository->getContent($attachment);
if ($this->repository->exists($attachment)) {
$content = $this->repository->getContent($attachment);
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
/** @var LaravelResponse $response */
@@ -145,37 +146,15 @@ class AttachmentController extends Controller
}
/**
* @param Attachment $attachment
*
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function preview(Attachment $attachment)
{
$image = 'images/page_green.png';
if ('application/pdf' === $attachment->mime) {
$image = 'images/page_white_acrobat.png';
}
$file = public_path($image);
$response = Response::make(File::get($file));
$response->header('Content-Type', 'image/png');
return $response;
}
/**
* @param AttachmentFormRequest $request
* @param AttachmentRepositoryInterface $repository
* @param Attachment $attachment
* @param AttachmentFormRequest $request
* @param Attachment $attachment
*
* @return \Illuminate\Http\RedirectResponse
*/
public function update(AttachmentFormRequest $request, AttachmentRepositoryInterface $repository, Attachment $attachment)
public function update(AttachmentFormRequest $request, Attachment $attachment)
{
$data = $request->getAttachmentData();
$repository->update($attachment, $data);
$this->repository->update($attachment, $data);
$request->session()->flash('success', strval(trans('firefly.attachment_updated', ['name' => $attachment->filename])));
Preferences::mark();
@@ -191,4 +170,25 @@ class AttachmentController extends Controller
// redirect to previous URL.
return redirect($this->getPreviousUri('attachments.edit.uri'));
}
/**
* @param Attachment $attachment
*
* @return \Illuminate\Http\Response
* @throws FireflyException
*/
public function view(Attachment $attachment)
{
if ($this->repository->exists($attachment)) {
$content = $this->repository->getContent($attachment);
return Response::make(
$content, 200, [
'Content-Type' => $attachment->mime,
'Content-Disposition' => 'inline; filename="' . $attachment->filename . '"',
]
);
}
throw new FireflyException('Could not find the indicated attachment. The file is no longer there.');
}
}

View File

@@ -262,7 +262,7 @@ class BillController extends Controller
}
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, $lastPaidDate);
$hideBill = true;
$subTitle = e($bill->name);
$subTitle = $bill->name;
return view('bills.show', compact('transactions', 'yearAverage', 'overallAverage', 'year', 'hideBill', 'bill', 'subTitle'));
}

View File

@@ -611,21 +611,19 @@ class BudgetController extends Controller
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
Log::debug('Going to get period expenses and incomes.');
while ($end >= $start) {
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
$dates = app('navigation')->blockPeriods($start, $end, $range);
foreach ($dates as $date) {
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutBudget()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutBudget()->withOpposingAccount()->setTypes(
[TransactionType::WITHDRAWAL]
);
$set = $collector->getJournals();
$sum = strval($set->sum('transaction_amount') ?? '0');
$journals = $set->count();
$dateStr = $end->format('Y-m-d');
$dateName = app('navigation')->periodShow($end, $range);
$entries->push(['string' => $dateStr, 'name' => $dateName, 'count' => $journals, 'sum' => $sum, 'date' => clone $end]);
$end = app('navigation')->subtractPeriod($end, $range, 1);
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
$entries->push(['string' => $dateStr, 'name' => $dateName, 'count' => $journals, 'sum' => $sum, 'date' => clone $date['end']]);
}
$cache->store($entries);

View File

@@ -46,6 +46,13 @@ use View;
*/
class CategoryController extends Controller
{
/** @var AccountRepositoryInterface */
private $accountRepos;
/** @var JournalRepositoryInterface */
private $journalRepos;
/** @var CategoryRepositoryInterface */
private $repository;
/**
*
*/
@@ -57,6 +64,9 @@ class CategoryController extends Controller
function ($request, $next) {
app('view')->share('title', trans('firefly.categories'));
app('view')->share('mainTitleIcon', 'fa-bar-chart');
$this->journalRepos = app(JournalRepositoryInterface::class);
$this->repository = app(CategoryRepositoryInterface::class);
$this->accountRepos = app(AccountRepositoryInterface::class);
return $next($request);
}
@@ -95,16 +105,15 @@ class CategoryController extends Controller
}
/**
* @param Request $request
* @param CategoryRepositoryInterface $repository
* @param Category $category
* @param Request $request
* @param Category $category
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(Request $request, CategoryRepositoryInterface $repository, Category $category)
public function destroy(Request $request, Category $category)
{
$name = $category->name;
$repository->destroy($category);
$this->repository->destroy($category);
$request->session()->flash('success', strval(trans('firefly.deleted_category', ['name' => $name])));
Preferences::mark();
@@ -132,21 +141,21 @@ class CategoryController extends Controller
}
/**
* @param CategoryRepositoryInterface $repository
* @param Request $request
*
* @return View
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index(Request $request, CategoryRepositoryInterface $repository)
public function index(Request $request)
{
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$collection = $repository->getCategories();
$collection = $this->repository->getCategories();
$total = $collection->count();
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
$collection->each(
function (Category $category) use ($repository) {
$category->lastActivity = $repository->lastUseDate($category, new Collection);
function (Category $category) {
$category->lastActivity = $this->repository->lastUseDate($category, new Collection);
}
);
@@ -158,13 +167,12 @@ class CategoryController extends Controller
}
/**
* @param Request $request
* @param JournalRepositoryInterface $repository
* @param string $moment
* @param Request $request
* @param string $moment
*
* @return View
*/
public function noCategory(Request $request, JournalRepositoryInterface $repository, string $moment = '')
public function noCategory(Request $request, string $moment = '')
{
// default values:
$range = Preferences::get('viewRange', '1M')->data;
@@ -177,27 +185,27 @@ class CategoryController extends Controller
// prep for "all" view.
if ('all' === $moment) {
$subTitle = trans('firefly.all_journals_without_category');
$first = $repository->first();
$first = $this->journalRepos->first();
$start = $first->date ?? new Carbon;
$end = new Carbon;
}
// prep for "specific date" view.
if (strlen($moment) > 0 && 'all' !== $moment) {
$start = new Carbon($moment);
$start = app('navigation')->startOfPeriod(new Carbon($moment), $range);
$end = app('navigation')->endOfPeriod($start, $range);
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getNoCategoryPeriodOverview();
$periods = $this->getNoCategoryPeriodOverview($start);
}
// prep for current period
if (0 === strlen($moment)) {
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$periods = $this->getNoCategoryPeriodOverview();
$periods = $this->getNoCategoryPeriodOverview($start);
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@@ -248,14 +256,14 @@ class CategoryController extends Controller
// prep for "specific date" view.
if (strlen($moment) > 0 && 'all' !== $moment) {
$start = new Carbon($moment);
$start = app('navigation')->startOfPeriod(new Carbon($moment), $range);
$end = app('navigation')->endOfPeriod($start, $range);
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name,
'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat),]
);
$periods = $this->getPeriodOverview($category);
$periods = $this->getPeriodOverview($category, $start);
$path = route('categories.show', [$category->id, $moment]);
}
@@ -265,7 +273,7 @@ class CategoryController extends Controller
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
/** @var Carbon $end */
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview($category);
$periods = $this->getPeriodOverview($category, $start);
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
@@ -336,38 +344,36 @@ class CategoryController extends Controller
}
/**
* @param Carbon $theDate
*
* @return Collection
*/
private function getNoCategoryPeriodOverview(): Collection
private function getNoCategoryPeriodOverview(Carbon $theDate): Collection
{
$repository = app(JournalRepositoryInterface::class);
$first = $repository->first();
$start = $first->date ?? new Carbon;
$range = Preferences::get('viewRange', '1M')->data;
$start = app('navigation')->startOfPeriod($start, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
$range = Preferences::get('viewRange', '1M')->data;
$first = $this->journalRepos->first();
$start = $first->date ?? new Carbon;
$end = $theDate ?? new Carbon;
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('no-budget-period-entries');
$cache->addProperty('no-category-period-entries');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
Log::debug(sprintf('Going to get period expenses and incomes between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d')));
while ($end >= $start) {
Log::debug('Loop!');
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
foreach ($dates as $date) {
// count journals without category in this period:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()
->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$count = $collector->getJournals()->count();
@@ -375,7 +381,7 @@ class CategoryController extends Controller
// amount transferred
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transferred = Steam::positive($collector->getJournals()->sum('transaction_amount'));
@@ -383,17 +389,20 @@ class CategoryController extends Controller
// amount spent
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes(
[TransactionType::WITHDRAWAL]
);
$spent = $collector->getJournals()->sum('transaction_amount');
// amount earned
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::DEPOSIT]);
$earned = $collector->getJournals()->sum('transaction_amount');
$dateStr = $end->format('Y-m-d');
$dateName = app('navigation')->periodShow($end, $range);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes(
[TransactionType::DEPOSIT]
);
$earned = $collector->getJournals()->sum('transaction_amount');
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
$entries->push(
[
'string' => $dateStr,
@@ -402,10 +411,9 @@ class CategoryController extends Controller
'spent' => $spent,
'earned' => $earned,
'transferred' => $transferred,
'date' => clone $end,
'date' => clone $date['end'],
]
);
$end = app('navigation')->subtractPeriod($end, $range, 1);
}
Log::debug('End of loops');
$cache->store($entries);
@@ -418,45 +426,39 @@ class CategoryController extends Controller
*
* @return Collection
*/
private function getPeriodOverview(Category $category): Collection
private function getPeriodOverview(Category $category, Carbon $date): Collection
{
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$first = $repository->firstUseDate($category);
if (null === $first) {
$first = new Carbon; // @codeCoverageIgnore
}
$range = Preferences::get('viewRange', '1M')->data;
$first = app('navigation')->startOfPeriod($first, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
$count = 0;
$range = Preferences::get('viewRange', '1M')->data;
$first = $this->journalRepos->first();
$start = $first->date ?? new Carbon;
$end = $date ?? new Carbon;
$accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
// properties for entries with their amounts.
$cache = new CacheProperties();
$cache->addProperty($first);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($range);
$cache->addProperty('categories.entries');
$cache->addProperty($category->id);
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
while ($end >= $first && $count < 90) {
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
$dateStr = $end->format('Y-m-d');
$dateName = app('navigation')->periodShow($end, $range);
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
foreach ($dates as $date) {
$spent = $this->repository->spentInPeriod(new Collection([$category]), $accounts, $date['start'], $date['end']);
$earned = $this->repository->earnedInPeriod(new Collection([$category]), $accounts, $date['start'], $date['end']);
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
// amount transferred
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->setCategory($category)
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->setCategory($category)
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transferred = Steam::positive($collector->getJournals()->sum('transaction_amount'));
@@ -469,11 +471,9 @@ class CategoryController extends Controller
'earned' => $earned,
'sum' => bcadd($earned, $spent),
'transferred' => $transferred,
'date' => clone $end,
'date' => clone $date['end'],
]
);
$end = app('navigation')->subtractPeriod($end, $range, 1);
++$count;
}
$cache->store($entries);

View File

@@ -76,21 +76,46 @@ class AccountController extends Controller
$repository = app(AccountRepositoryInterface::class);
$start = $repository->oldestJournalDate($account);
$end = new Carbon;
$format = (string)trans('config.month_and_day');
$range = Steam::balanceInRange($account, $start, $end);
$current = clone $start;
$previous = array_values($range)[0];
$chartData = [];
while ($end >= $current) {
$theDate = $current->format('Y-m-d');
$balance = $range[$theDate] ?? $previous;
$label = $current->formatLocalized($format);
$chartData[$label] = $balance;
$previous = $balance;
$current->addDay();
// depending on diff, do something with range of chart.
$step = '1D';
$months = $start->diffInMonths($end);
if ($months > 3) {
$step = '1W';
}
if ($months > 24) {
$step = '1M'; // @codeCoverageIgnore
}
if ($months > 100) {
$step = '1Y'; // @codeCoverageIgnore
}
$chartData = [];
$current = clone $start;
switch ($step) {
case '1D':
$format = (string)trans('config.month_and_day');
$range = Steam::balanceInRange($account, $start, $end);
$previous = array_values($range)[0];
while ($end >= $current) {
$theDate = $current->format('Y-m-d');
$balance = $range[$theDate] ?? $previous;
$label = $current->formatLocalized($format);
$chartData[$label] = floatval($balance);
$previous = $balance;
$current->addDay();
}
break;
case '1W':
case '1M': // @codeCoverageIgnore
case '1Y': // @codeCoverageIgnore
while ($end >= $current) {
$balance = floatval(Steam::balance($account, $current));
$label = app('navigation')->periodShow($current, $step);
$chartData[$label] = $balance;
$current = app('navigation')->addPeriod($current, $step, 1);
}
break;
}
$data = $this->generator->singleSet($account->name, $chartData);
$cache->store($data);

View File

@@ -37,7 +37,6 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Preferences;
use Response;
use Steam;
@@ -78,37 +77,47 @@ class BudgetController extends Controller
*/
public function budget(Budget $budget)
{
$first = $this->repository->firstUseDate($budget);
$range = Preferences::get('viewRange', '1M')->data;
$currentStart = app('navigation')->startOfPeriod($first, $range);
$last = session('end', new Carbon);
$cache = new CacheProperties();
$cache->addProperty($first);
$cache->addProperty($last);
$start = $this->repository->firstUseDate($budget);
$end = session('end', new Carbon);
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('chart.budget.budget');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$final = clone $last;
$final->addYears(2);
// depending on diff, do something with range of chart.
$step = '1D';
$months = $start->diffInMonths($end);
if ($months > 3) {
$step = '1W';
}
if ($months > 24) {
$step = '1M';
}
if ($months > 60) {
$step = '1Y'; // @codeCoverageIgnore
}
$budgetCollection = new Collection([$budget]);
$last = app('navigation')->endOfX($last, $range, $final); // not to overshoot.
$entries = [];
while ($currentStart < $last) {
// periodspecific dates:
$currentEnd = app('navigation')->endOfPeriod($currentStart, $range);
// sub another day because reasons.
$currentEnd->subDay();
$spent = $this->repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd);
$format = app('navigation')->periodShow($currentStart, $range);
$entries[$format] = bcmul($spent, '-1');
$currentStart = clone $currentEnd;
$currentStart->addDays(2);
$chartData = [];
$current = clone $start;
$current = app('navigation')->startOfPeriod($current, $step);
while ($end >= $current) {
$currentEnd = app('navigation')->endOfPeriod($current, $step);
if ($step === '1Y') {
$currentEnd->subDay(); // @codeCoverageIgnore
}
$spent = $this->repository->spentInPeriod($budgetCollection, new Collection, $current, $currentEnd);
$label = app('navigation')->periodShow($current, $step);
$chartData[$label] = floatval(bcmul($spent, '-1'));
$current = clone $currentEnd;
$current->addDay();
}
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $entries);
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
$cache->store($data);

View File

@@ -25,6 +25,8 @@ namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use DB;
use Exception;
use FireflyIII\Http\Middleware\IsDemoUser;
use Illuminate\Http\Request;
use Log;
use Monolog\Handler\RotatingFileHandler;
@@ -34,6 +36,15 @@ use Monolog\Handler\RotatingFileHandler;
*/
class DebugController extends Controller
{
/**
* HomeController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(IsDemoUser::class);
}
/**
* @param Request $request
@@ -74,7 +85,11 @@ class DebugController extends Controller
if ($handler instanceof RotatingFileHandler) {
$logFile = $handler->getUrl();
if (null !== $logFile) {
$logContent = file_get_contents($logFile);
try {
$logContent = file_get_contents($logFile);
} catch (Exception $e) {
// don't care
}
}
}
}

View File

@@ -200,20 +200,25 @@ class HomeController extends Controller
'login', 'logout', 'password.reset', 'profile.confirm-email-change', 'profile.undo-email-change',
'register', 'report.options', 'routes', 'rule-groups.down', 'rule-groups.up', 'rules.up', 'rules.down',
'rules.select', 'search.search', 'test-flash', 'transactions.link.delete', 'transactions.link.switch',
'two-factor.lost', 'report.options',
'two-factor.lost', 'reports.options', 'debug', 'import.create-job', 'import.download', 'import.start', 'import.status.json',
'preferences.delete-code', 'rules.test-triggers', 'piggy-banks.remove-money', 'piggy-banks.add-money',
'accounts.reconcile.transactions', 'accounts.reconcile.overview', 'export.download',
'transactions.clone', 'two-factor.index',
];
$return = '&nbsp;';
/** @var Route $route */
foreach ($set as $route) {
$name = $route->getName();
if (null !== $name && in_array('GET', $route->methods()) && strlen($name) > 0) {
$found = false;
foreach ($ignore as $string) {
if (false !== strpos($name, $string)) {
if (!(false === stripos($name, $string))) {
$found = true;
break;
}
}
if (!$found) {
if ($found === false) {
$return .= 'touch ' . $route->getName() . '.md;';
}
}

View File

@@ -55,7 +55,7 @@ class ConfigurationController extends Controller
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['index']);
$this->middleware(IsDemoUser::class);
}
/**

View File

@@ -33,6 +33,7 @@ use Log;
use Response;
use View;
/**
* Class FileController.
*/
@@ -57,8 +58,7 @@ class IndexController extends Controller
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['create', 'index']);
$this->middleware(IsDemoUser::class)->except(['index']);
}
/**

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Import;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Response;
@@ -47,6 +48,7 @@ class StatusController extends Controller
return $next($request);
}
);
$this->middleware(IsDemoUser::class);
}
/**
@@ -93,20 +95,29 @@ class StatusController extends Controller
$result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
$result['show_percentage'] = true;
}
if ('finished' === $job->status) {
$tagId = $job->extended_status['tag'];
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$tag = $repository->find($tagId);
$result['finished'] = true;
$result['finishedText'] = trans('import.status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
$result['finished'] = true;
$tagId = intval($job->extended_status['tag']);
if ($tagId !== 0) {
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$tag = $repository->find($tagId);
$count = $tag->transactionJournals()->count();
$result['finishedText'] = trans(
'import.status_finished_job', ['count' => $count, 'link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]
);
}
if ($tagId === 0) {
$result['finishedText'] = trans('import.status_finished_no_tag'); // @codeCoverageIgnore
}
}
if ('running' === $job->status) {
$result['started'] = true;
$result['running'] = true;
}
$result['percentage'] = $result['percentage'] > 100 ? 100 : $result['percentage'];
return Response::json($result);
}

View File

@@ -25,11 +25,13 @@ namespace FireflyIII\Http\Controllers\Json;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Response;
@@ -170,7 +172,7 @@ class BoxController extends Controller
*
* @return \Illuminate\Http\JsonResponse
*/
public function netWorth(AccountRepositoryInterface $repository)
public function netWorth(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepos)
{
$date = new Carbon(date('Y-m-d')); // needed so its per day.
/** @var Carbon $start */
@@ -193,16 +195,32 @@ class BoxController extends Controller
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$netWorth = [];
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$currency = app('amount')->getDefaultCurrency();
$balances = app('steam')->balancesByAccounts($accounts, $date);
$sum = '0';
foreach ($balances as $entry) {
$sum = bcadd($sum, $entry);
/** @var Account $account */
foreach ($accounts as $account) {
$accountCurrency = $currency;
$balance = $balances[$account->id] ?? '0';
$currencyId = intval($account->getMeta('currency_id'));
if ($currencyId !== 0) {
$accountCurrency = $currencyRepos->find($currencyId);
}
if (!isset($netWorth[$accountCurrency->id])) {
$netWorth[$accountCurrency->id]['currency'] = $accountCurrency;
$netWorth[$accountCurrency->id]['sum'] = '0';
}
$netWorth[$accountCurrency->id]['sum'] = bcadd($netWorth[$accountCurrency->id]['sum'], $balance);
}
$return = [];
foreach ($netWorth as $currencyId => $data) {
$return[$currencyId] = app('amount')->formatAnything($data['currency'], $data['sum'], false);
}
$return = [
'net_worth' => app('amount')->formatAnything($currency, $sum, false),
'net_worths' => array_values($return),
];
$cache->store($return);

View File

@@ -383,7 +383,7 @@ class PiggyBankController extends Controller
{
$note = $piggyBank->notes()->first();
$events = $repository->getEvents($piggyBank);
$subTitle = e($piggyBank->name);
$subTitle = $piggyBank->name;
return view('piggy-banks.show', compact('piggyBank', 'events', 'subTitle', 'note'));
}

View File

@@ -59,16 +59,16 @@ class ReportController extends Controller
parent::__construct();
$this->middleware(
function ($request, $next) {
// @var AccountRepositoryInterface $repository
/** @var AccountRepositoryInterface accountRepository */
$this->accountRepository = app(AccountRepositoryInterface::class);
// @var BudgetRepositoryInterface $repository
/** @var BudgetRepositoryInterface budgetRepository */
$this->budgetRepository = app(BudgetRepositoryInterface::class);
// @var CategoryRepositoryInterface categoryRepository
/** @var CategoryRepositoryInterface categoryRepository */
$this->categoryRepository = app(CategoryRepositoryInterface::class);
// @var PopupReportInterface popupHelper
/** @var PopupReportInterface popupHelper */
$this->popupHelper = app(PopupReportInterface::class);
return $next($request);

View File

@@ -128,7 +128,7 @@ class PreferencesController extends Controller
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation.
*/
public function postCode(TokenFormRequest $request)
public function postCode(/** @scrutinizer ignore-unused */ TokenFormRequest $request)
{
Preferences::set('twoFactorAuthEnabled', 1);
Preferences::set('twoFactorAuthSecret', Session::get('two-factor-secret'));

View File

@@ -129,12 +129,12 @@ class CategoryController extends Controller
$report = [];
/** @var Category $category */
foreach ($categories as $category) {
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
if (0 !== bccomp($spent, '0')) {
$report[$category->id] = ['name' => $category->name, 'spent' => $spent, 'id' => $category->id];
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $end);
if (0 !== bccomp($spent, '0') || 0 !== bccomp($earned, '0')) {
$report[$category->id] = ['name' => $category->name, 'spent' => $spent, 'earned' => $earned, 'id' => $category->id];
}
}
// sort the result
// Obtain a list of columns
$sum = [];

View File

@@ -568,7 +568,7 @@ class ExpenseController extends Controller
];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$currencyId = intval($transaction->transaction_currency_id);
// if not set, set to zero:
if (!isset($sum['per_currency'][$currencyId])) {

View File

@@ -48,6 +48,9 @@ class ReportController extends Controller
/** @var ReportHelperInterface */
protected $helper;
/** @var BudgetRepositoryInterface */
private $repository;
/**
*
*/
@@ -55,13 +58,13 @@ class ReportController extends Controller
{
parent::__construct();
$this->helper = app(ReportHelperInterface::class);
$this->middleware(
function ($request, $next) {
app('view')->share('title', trans('firefly.reports'));
app('view')->share('mainTitleIcon', 'fa-line-chart');
View::share('subTitleIcon', 'fa-calendar');
$this->helper = app(ReportHelperInterface::class);
$this->repository = app(BudgetRepositoryInterface::class);
return $next($request);
}
@@ -87,6 +90,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle', trans(
@@ -120,6 +124,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle',
@@ -157,6 +162,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle',
@@ -195,6 +201,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle',
@@ -233,6 +240,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle',
@@ -265,6 +273,7 @@ class ReportController extends Controller
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$accountList = join(',', $accounts->pluck('id')->toArray());
$this->repository->cleanupBudgets();
return view('reports.index', compact('months', 'accounts', 'start', 'accountList', 'customFiscalYear'));
}
@@ -391,6 +400,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle',

View File

@@ -38,6 +38,11 @@ use URL;
*/
class LinkController extends Controller
{
/** @var JournalRepositoryInterface */
private $journalRepository;
/** @var LinkTypeRepositoryInterface */
private $repository;
/**
*
*/
@@ -50,6 +55,9 @@ class LinkController extends Controller
app('view')->share('title', trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-repeat');
$this->journalRepository = app(JournalRepositoryInterface::class);
$this->repository = app(LinkTypeRepositoryInterface::class);
return $next($request);
}
);
@@ -70,14 +78,13 @@ class LinkController extends Controller
}
/**
* @param LinkTypeRepositoryInterface $repository
* @param TransactionJournalLink $link
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(LinkTypeRepositoryInterface $repository, TransactionJournalLink $link)
public function destroy(TransactionJournalLink $link)
{
$repository->destroyLink($link);
$this->repository->destroyLink($link);
Session::flash('success', strval(trans('firefly.deleted_link')));
Preferences::mark();
@@ -87,18 +94,13 @@ class LinkController extends Controller
/**
* @param JournalLinkRequest $request
* @param LinkTypeRepositoryInterface $repository
* @param JournalRepositoryInterface $journalRepository
* @param TransactionJournal $journal
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function store(
JournalLinkRequest $request,
LinkTypeRepositoryInterface $repository,
JournalRepositoryInterface $journalRepository,
TransactionJournal $journal
) {
public function store(JournalLinkRequest $request, TransactionJournal $journal)
{
Log::debug('We are here (store)');
$linkInfo = $request->getLinkInfo();
if (0 === $linkInfo['transaction_journal_id']) {
@@ -106,30 +108,28 @@ class LinkController extends Controller
return redirect(route('transactions.show', [$journal->id]));
}
$other = $journalRepository->find($linkInfo['transaction_journal_id']);
$alreadyLinked = $repository->findLink($journal, $other);
$other = $this->journalRepository->find($linkInfo['transaction_journal_id']);
$alreadyLinked = $this->repository->findLink($journal, $other);
if ($alreadyLinked) {
Session::flash('error', trans('firefly.journals_error_linked'));
return redirect(route('transactions.show', [$journal->id]));
}
Log::debug(sprintf('Journal is %d, opposing is %d', $journal->id, $other->id));
$repository->storeLink($linkInfo, $other, $journal);
$this->repository->storeLink($linkInfo, $other, $journal);
Session::flash('success', trans('firefly.journals_linked'));
return redirect(route('transactions.show', [$journal->id]));
}
/**
* @param LinkTypeRepositoryInterface $repository
* @param TransactionJournalLink $link
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function switchLink(LinkTypeRepositoryInterface $repository, TransactionJournalLink $link)
public function switchLink(TransactionJournalLink $link)
{
$repository->switchLink($link);
$this->repository->switchLink($link);
return redirect(URL::previous());
}

View File

@@ -272,7 +272,7 @@ class TransactionController extends Controller
$return = [];
/** @var Transaction $transaction */
foreach ($collection as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$currencyId = intval($transaction->transaction_currency_id);
// save currency information:
if (!isset($return[$currencyId])) {

View File

@@ -24,11 +24,10 @@ namespace FireflyIII\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Log;
use Preferences;
use Auth;
use Session;
/**
* Class AuthenticateTwoFactor.
*/
@@ -45,26 +44,14 @@ class AuthenticateTwoFactor
*/
public function handle(Request $request, Closure $next, $guard = null)
{
// do the usual auth, again:
if (Auth::guard($guard)->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
}
return redirect()->guest('login');
}
if (1 === intval(auth()->user()->blocked)) {
Auth::guard($guard)->logout();
Session::flash('logoutMessage', trans('firefly.block_account_logout'));
return redirect()->guest('login');
}
$is2faEnabled = Preferences::get('twoFactorAuthEnabled', false)->data;
$has2faSecret = null !== Preferences::get('twoFactorAuthSecret');
// grab 2auth information from session.
$is2faAuthed = 'true' === $request->cookie('twoFactorAuthenticated');
$is2faAuthed = 'true' === $request->cookie('twoFactorAuthenticated');
if ($is2faEnabled && $has2faSecret && !$is2faAuthed) {
Log::debug('Does not seem to be 2 factor authed, redirect.');

View File

@@ -51,9 +51,9 @@ class IsDemoUser
/** @var User $user */
$user = auth()->user();
if ($user->hasRole('demo')) {
Session::flash('warning', strval(trans('firefly.not_available_demo_user')));
Session::flash('info', strval(trans('firefly.not_available_demo_user')));
return redirect(route('index'));
return redirect($request->session()->previousUrl());
}
return $next($request);

View File

@@ -60,8 +60,8 @@ class TrustProxies extends Middleware
public function __construct(Repository $config)
{
$trustedProxies = env('TRUSTED_PROXIES', null);
if (null !== $trustedProxies && strlen($trustedProxies) > 0) {
$this->proxies = $trustedProxies;
if (false !== $trustedProxies && null !== $trustedProxies && strlen($trustedProxies) > 0) {
$this->proxies = strval($trustedProxies);
}
parent::__construct($config);

View File

@@ -48,7 +48,7 @@ class JournalLinkRequest extends Request
$parts = explode('_', $linkType);
$return['link_type_id'] = intval($parts[0]);
$return['transaction_journal_id'] = $this->integer('link_journal_id');
$return['comments'] = strlen($this->string('comments')) > 0 ? $this->string('comments') : null;
$return['notes'] = strlen($this->string('notes')) > 0 ? $this->string('notes') : '';
$return['direction'] = $parts[1];
if (0 === $return['transaction_journal_id'] && ctype_digit($this->string('link_other'))) {
$return['transaction_journal_id'] = $this->integer('link_other');

View File

@@ -88,11 +88,9 @@ class FileConfigurator implements ConfiguratorInterface
if (is_null($this->job)) {
throw new FireflyException('Cannot call configureJob() without a job.');
}
$class = $this->getConfigurationClass();
$job = $this->job;
/** @var ConfigurationInterface $object */
$object = app($class);
$object->setJob($job);
$object = app($this->getConfigurationClass());
$object->setJob($this->job);
$result = $object->storeConfiguration($data);
$this->warning = $object->getWarningMessage();
@@ -111,12 +109,9 @@ class FileConfigurator implements ConfiguratorInterface
if (is_null($this->job)) {
throw new FireflyException('Cannot call getNextData() without a job.');
}
$class = $this->getConfigurationClass();
$job = $this->job;
/** @var ConfigurationInterface $object */
$object = app($class);
$object->setJob($job);
$object = app($this->getConfigurationClass());
$object->setJob($this->job);
return $object->getData();
}
@@ -192,6 +187,12 @@ class FileConfigurator implements ConfiguratorInterface
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
// set number of steps to 100:
$extendedStatus = $this->getExtendedStatus();
$extendedStatus['steps'] = 6;
$extendedStatus['done'] = 0;
$this->setExtendedStatus($extendedStatus);
$config = $this->getConfig();
$newConfig = array_merge($this->defaultConfig, $config);
$this->repository->setConfiguration($job, $newConfig);
@@ -246,4 +247,28 @@ class FileConfigurator implements ConfiguratorInterface
return $class;
}
/**
* Shorthand method to return the extended status.
*
* @codeCoverageIgnore
* @return array
*/
private function getExtendedStatus(): array
{
return $this->repository->getExtendedStatus($this->job);
}
/**
* Shorthand method to set the extended status.
*
* @codeCoverageIgnore
* @param array $extended
*/
private function setExtendedStatus(array $extended): void
{
$this->repository->setExtendedStatus($this->job, $extended);
return;
}
}

View File

@@ -24,6 +24,7 @@ namespace FireflyIII\Import\Configuration;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Support\Import\Configuration\Spectre\HaveAccounts;
use Log;
@@ -35,6 +36,9 @@ class SpectreConfigurator implements ConfiguratorInterface
/** @var ImportJob */
private $job;
/** @var ImportJobRepositoryInterface */
private $repository;
/** @var string */
private $warning = '';
@@ -51,12 +55,14 @@ class SpectreConfigurator implements ConfiguratorInterface
* @param array $data
*
* @return bool
* @throws FireflyException
*/
public function configureJob(array $data): bool
{
$config = $this->job->configuration;
$stage = $config['stage'];
$status = $this->job->status;
if (is_null($this->job)) {
throw new FireflyException('Cannot call configureJob() without a job.');
}
$stage = $this->getConfig()['stage'] ?? 'initial';
Log::debug(sprintf('in getNextData(), for stage "%s".', $stage));
switch ($stage) {
case 'have-accounts':
@@ -66,13 +72,11 @@ class SpectreConfigurator implements ConfiguratorInterface
$class->storeConfiguration($data);
// update job for next step and set to "configured".
$config = $this->job->configuration;
$config['stage'] = 'have-account-mapping';
$this->job->configuration = $config;
$this->job->status = 'configured';
$this->job->save();
$config = $this->getConfig();
$config['stage'] = 'have-account-mapping';
$this->repository->setConfiguration($this->job, $config);
return true;
break;
default:
throw new FireflyException(sprintf('Cannot store configuration when job is in state "%s"', $stage));
break;
@@ -83,12 +87,16 @@ class SpectreConfigurator implements ConfiguratorInterface
* Return the data required for the next step in the job configuration.
*
* @return array
* @throws FireflyException
*/
public function getNextData(): array
{
$config = $this->job->configuration;
$stage = $config['stage'];
$status = $this->job->status;
if (is_null($this->job)) {
throw new FireflyException('Cannot call configureJob() without a job.');
}
$config = $this->getConfig();
$stage = $config['stage'] ?? 'initial';
Log::debug(sprintf('in getNextData(), for stage "%s".', $stage));
switch ($stage) {
case 'has-token':
@@ -96,9 +104,13 @@ class SpectreConfigurator implements ConfiguratorInterface
$config['is-redirected'] = true;
$config['stage'] = 'user-logged-in';
$status = 'configured';
break;
// update config and status:
$this->repository->setConfiguration($this->job, $config);
$this->repository->setStatus($this->job, $status);
return $this->repository->getConfiguration($this->job);
case 'have-accounts':
// use special class:
/** @var HaveAccounts $class */
$class = app(HaveAccounts::class);
$class->setJob($this->job);
@@ -107,37 +119,30 @@ class SpectreConfigurator implements ConfiguratorInterface
return $data;
default:
return [];
break;
}
// update config and status:
$this->job->configuration = $config;
$this->job->status = $status;
$this->job->save();
return $this->job->configuration;
}
/**
* @return string
* @throws FireflyException
*/
public function getNextView(): string
{
$config = $this->job->configuration;
$stage = $config['stage'];
if (is_null($this->job)) {
throw new FireflyException('Cannot call configureJob() without a job.');
}
$stage = $this->getConfig()['stage'] ?? 'initial';
Log::debug(sprintf('in getNextView(), for stage "%s".', $stage));
switch ($stage) {
case 'has-token':
// redirect to Spectre.
Log::info('User is being redirected to Spectre.');
return 'import.spectre.redirect';
break;
case 'have-accounts':
return 'import.spectre.accounts';
break;
default:
return '';
break;
}
}
@@ -154,11 +159,14 @@ class SpectreConfigurator implements ConfiguratorInterface
/**
* @return bool
* @throws FireflyException
*/
public function isJobConfigured(): bool
{
$config = $this->job->configuration;
$stage = $config['stage'];
if (is_null($this->job)) {
throw new FireflyException('Cannot call configureJob() without a job.');
}
$stage = $this->getConfig()['stage'] ?? 'initial';
Log::debug(sprintf('in isJobConfigured(), for stage "%s".', $stage));
switch ($stage) {
case 'has-token':
@@ -176,9 +184,14 @@ class SpectreConfigurator implements ConfiguratorInterface
/**
* @param ImportJob $job
*/
public function setJob(ImportJob $job)
public function setJob(ImportJob $job): void
{
$defaultConfig = [
// make repository
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
// set default config:
$defaultConfig = [
'has-token' => false,
'token' => '',
'token-expires' => 0,
@@ -190,16 +203,31 @@ class SpectreConfigurator implements ConfiguratorInterface
'accounts' => '',
'accounts-mapped' => '',
'auto-start' => true,
'apply-rules' => true,
'match-bills' => false,
];
$extendedStatus = $job->extended_status;
$extendedStatus['steps'] = 100;
$currentConfig = $this->repository->getConfiguration($job);
$finalConfig = array_merge($defaultConfig, $currentConfig);
// set default extended status:
$extendedStatus = $this->repository->getExtendedStatus($job);
$extendedStatus['steps'] = 6;
$config = $job->configuration;
$finalConfig = array_merge($defaultConfig, $config);
$job->configuration = $finalConfig;
$job->extended_status = $extendedStatus;
$job->save();
// save to job:
$job = $this->repository->setConfiguration($job, $finalConfig);
$job = $this->repository->setExtendedStatus($job, $extendedStatus);
$this->job = $job;
return;
}
/**
* Shorthand method.
*
* @return array
*/
private function getConfig(): array
{
return $this->repository->getConfiguration($this->job);
}
}

View File

@@ -77,25 +77,25 @@ class Amount implements ConverterInterface
Log::debug(sprintf('Searched from the left for "." in amount "%s", assume this is the decimal sign.', $value));
$decimal = '.';
}
unset($options, $res);
unset($res);
}
// if decimal is dot, replace all comma's and spaces with nothing. then parse as float (round to 4 pos)
if ('.' === $decimal) {
$search = [',', ' '];
$value = str_replace($search, '', $value);
$search = [',', ' '];
$value = str_replace($search, '', $value);
Log::debug(sprintf('Converted amount from "%s" to "%s".', $original, $value));
}
if (',' === $decimal) {
$search = ['.', ' '];
$value = str_replace($search, '', $value);
$value = str_replace(',', '.', $value);
$search = ['.', ' '];
$value = str_replace($search, '', $value);
$value = str_replace(',', '.', $value);
Log::debug(sprintf('Converted amount from "%s" to "%s".', $original, $value));
}
if (null === $decimal) {
// replace all:
$search = ['.', ' ', ','];
$value = str_replace($search, '', $value);
$search = ['.', ' ', ','];
$value = str_replace($search, '', $value);
Log::debug(sprintf('No decimal character found. Converted amount from "%s" to "%s".', $original, $value));
}

View File

@@ -90,6 +90,7 @@ class CsvProcessor implements FileProcessorInterface
Log::debug('Now in CsvProcessor run(). Job is now running...');
$entries = new Collection($this->getImportArray());
$this->addStep();
Log::notice('Building importable objects from CSV file.');
Log::debug(sprintf('Number of entries: %d', $entries->count()));
$notImported = $entries->filter(
@@ -97,8 +98,7 @@ class CsvProcessor implements FileProcessorInterface
$row = array_values($row);
if ($this->rowAlreadyImported($row)) {
$message = sprintf('Row #%d has already been imported.', $index);
$this->repository->addStepsDone($this->job, 5);
$this->addError($index, $message);
$this->repository->addError($this->job, $index, $message);
Log::info($message);
return null;
@@ -107,30 +107,24 @@ class CsvProcessor implements FileProcessorInterface
return $row;
}
);
$this->addStep();
Log::debug(sprintf('Number of entries left: %d', $notImported->count()));
// set (new) number of steps:
$extended = $this->getExtendedStatus();
$steps = $notImported->count() * 5;
$extended['steps'] = $steps;
$this->setExtendedStatus($extended);
Log::debug(sprintf('Number of steps: %d', $steps));
$notImported->each(
function (array $row, int $index) {
$journal = $this->importRow($index, $row);
$this->objects->push($journal);
$this->repository->addStepsDone($this->job, 1);
}
);
$this->addStep();
return true;
}
/**
* @codeCoverageIgnore
* Shorthand method
* Shorthand method to set the extended status.
*
* @codeCoverageIgnore
* @param array $array
*/
public function setExtendedStatus(array $array)
@@ -155,20 +149,13 @@ class CsvProcessor implements FileProcessorInterface
}
/**
* Shorthand method.
* Shorthand method to add a step.
*
* @codeCoverageIgnore
*
* @param int $index
* @param string $message
*/
private function addError(int $index, string $message): void
private function addStep()
{
$extended = $this->getExtendedStatus();
$extended['errors'][$index][] = $message;
$this->setExtendedStatus($extended);
return;
$this->repository->addStepsDone($this->job, 1);
}
/**
@@ -202,9 +189,9 @@ class CsvProcessor implements FileProcessorInterface
}
/**
* @codeCoverageIgnore
* Shorthand method.
* Shorthand method to return configuration.
*
* @codeCoverageIgnore
* @return array
*/
private function getConfig(): array
@@ -212,24 +199,11 @@ class CsvProcessor implements FileProcessorInterface
return $this->repository->getConfiguration($this->job);
}
/**
* @codeCoverageIgnore
* Shorthand method.
*
* @return array
*/
private function getExtendedStatus(): array
{
return $this->repository->getExtendedStatus($this->job);
}
/**
* @return Iterator
*
* @throws \League\Csv\Exception
* @throws \League\Csv\Exception
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
private function getImportArray(): Iterator
{
@@ -375,7 +349,7 @@ class CsvProcessor implements FileProcessorInterface
*/
private function specifics(array $row): array
{
$config = $this->job->configuration;
$config = $this->getConfig();
$names = array_keys($config['specifics'] ?? []);
foreach ($names as $name) {
if (!in_array($name, $this->validSpecifics)) {

View File

@@ -48,6 +48,8 @@ class ImportJournal
public $currency;
/** @var string */
public $description = '';
/** @var ImportCurrency */
public $foreignCurrency;
/** @var string */
public $hash;
/** @var array */
@@ -71,6 +73,8 @@ class ImportJournal
/** @var string */
private $externalId = '';
/** @var array */
private $foreignAmount;
/** @var array */
private $modifiers = [];
/** @var User */
private $user;
@@ -80,12 +84,13 @@ class ImportJournal
*/
public function __construct()
{
$this->asset = new ImportAccount;
$this->opposing = new ImportAccount;
$this->bill = new ImportBill;
$this->category = new ImportCategory;
$this->budget = new ImportBudget;
$this->currency = new ImportCurrency;
$this->asset = new ImportAccount;
$this->opposing = new ImportAccount;
$this->bill = new ImportBill;
$this->category = new ImportCategory;
$this->budget = new ImportBudget;
$this->currency = new ImportCurrency;
$this->foreignCurrency = new ImportCurrency;
}
/**
@@ -179,6 +184,8 @@ class ImportJournal
*/
public function setValue(array $array)
{
$array['mapped'] = $array['mapped'] ?? null;
$array['value'] = $array['value'] ?? null;
switch ($array['role']) {
default:
throw new FireflyException(sprintf('ImportJournal cannot handle "%s" with value "%s".', $array['role'], $array['value']));
@@ -188,6 +195,12 @@ class ImportJournal
case 'amount':
$this->amount = $array;
break;
case 'amount_foreign':
$this->foreignAmount = $array;
break;
case 'foreign-currency-code':
$this->foreignCurrency->setId($array);
break;
case 'amount_debit':
$this->amountDebit = $array;
break;

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Import\Prerequisites;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\User;
use Illuminate\Http\Request;
use Illuminate\Support\MessageBag;
@@ -62,9 +63,13 @@ class FilePrerequisites implements PrerequisitesInterface
* True if prerequisites. False if not.
*
* @return bool
* @throws FireflyException
*/
public function hasPrerequisites(): bool
{
if($this->user->hasRole('demo')) {
throw new FireflyException('Apologies, the demo user cannot import files.');
}
return false;
}

View File

@@ -28,6 +28,7 @@ use FireflyIII\Import\FileProcessor\FileProcessorInterface;
use FireflyIII\Import\Storage\ImportStorage;
use FireflyIII\Models\ImportJob;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Illuminate\Support\Collection;
use Log;
@@ -46,6 +47,9 @@ class FileRoutine implements RoutineInterface
/** @var ImportJob */
private $job;
/** @var ImportJobRepositoryInterface */
private $repository;
/**
* ImportRoutine constructor.
*/
@@ -84,26 +88,32 @@ class FileRoutine implements RoutineInterface
*/
public function run(): bool
{
if ('configured' !== $this->job->status) {
Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->job->status));
if ('configured' !== $this->getStatus()) {
Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->getStatus()));
return false;
}
set_time_limit(0);
Log::info(sprintf('Start with import job %s', $this->job->key));
// total steps: 6
$this->setTotalSteps(6);
$importObjects = $this->getImportObjects();
$this->lines = $importObjects->count();
$this->addStep();
// total steps can now be extended. File has been scanned. 7 steps per line:
$this->addTotalSteps(7 * $this->lines);
// once done, use storage thing to actually store them:
Log::info(sprintf('Returned %d valid objects from file processor', $this->lines));
$storage = $this->storeObjects($importObjects);
$this->addStep();
Log::debug('Back in run()');
// update job:
$this->job->status = 'finished';
$this->job->save();
Log::debug('Updated job...');
Log::debug(sprintf('%d journals in $storage->journals', $storage->journals->count()));
@@ -114,6 +124,10 @@ class FileRoutine implements RoutineInterface
// create tag, link tag to all journals:
$this->createImportTag();
$this->addStep();
// update job:
$this->setStatus('finished');
Log::info(sprintf('Done with import job %s', $this->job->key));
@@ -125,7 +139,9 @@ class FileRoutine implements RoutineInterface
*/
public function setJob(ImportJob $job)
{
$this->job = $job;
$this->job = $job;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
}
/**
@@ -134,18 +150,16 @@ class FileRoutine implements RoutineInterface
protected function getImportObjects(): Collection
{
$objects = new Collection;
$config = $this->job->configuration;
$fileType = $config['file-type'] ?? 'csv';
$fileType = $this->getConfig()['file-type'] ?? 'csv';
// will only respond to "file"
$class = config(sprintf('import.options.file.processors.%s', $fileType));
/** @var FileProcessorInterface $processor */
$processor = app($class);
$processor->setJob($this->job);
if ('configured' === $this->job->status) {
if ('configured' === $this->getStatus()) {
// set job as "running"...
$this->job->status = 'running';
$this->job->save();
$this->setStatus('running');
Log::debug('Job is configured, start with run()');
$processor->run();
@@ -155,6 +169,14 @@ class FileRoutine implements RoutineInterface
return $objects;
}
/**
* Shorthand method.
*/
private function addStep()
{
$this->repository->addStepsDone($this->job, 1);
}
/**
*
*/
@@ -167,11 +189,12 @@ class FileRoutine implements RoutineInterface
return new Tag;
}
$this->addTotalSteps($this->journals->count() + 2);
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$repository->setUser($this->job->user);
$data = [
$data = [
'tag' => trans('import.import_with_key', ['key' => $this->job->key]),
'date' => new Carbon,
'description' => null,
@@ -180,11 +203,11 @@ class FileRoutine implements RoutineInterface
'zoomLevel' => null,
'tagMode' => 'nothing',
];
$tag = $repository->store($data);
$extended = $this->job->extended_status;
$extended['tag'] = $tag->id;
$this->job->extended_status = $extended;
$this->job->save();
$tag = $repository->store($data);
$this->addStep();
$extended = $this->getExtendedStatus();
$extended['tag'] = $tag->id;
$this->setExtendedStatus($extended);
Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag));
Log::debug('Looping journals...');
@@ -193,12 +216,81 @@ class FileRoutine implements RoutineInterface
foreach ($journalIds as $journalId) {
Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId));
DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]);
$this->addStep();
}
Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $this->journals->count(), $tag->id, $tag->tag));
$this->addStep();
return $tag;
}
/**
* Shorthand method
*
* @return array
*/
private function getConfig(): array
{
return $this->repository->getConfiguration($this->job);
}
/**
* @return array
*/
private function getExtendedStatus(): array
{
return $this->repository->getExtendedStatus($this->job);
}
/**
* Shorthand method.
*
* @return string
*/
private function getStatus(): string
{
return $this->repository->getStatus($this->job);
}
/**
* @param array $extended
*/
private function setExtendedStatus(array $extended): void
{
$this->repository->setExtendedStatus($this->job, $extended);
return;
}
/**
* Shorthand
*
* @param string $status
*/
private function setStatus(string $status): void
{
$this->repository->setStatus($this->job, $status);
}
/**
* Shorthand
*
* @param int $steps
*/
private function setTotalSteps(int $steps)
{
$this->repository->setTotalSteps($this->job, $steps);
}
/**
* Shorthand
*
* @param int $steps
*/
private function addTotalSteps(int $steps)
{
$this->repository->addTotalSteps($this->job, $steps);
}
/**
* @param Collection $objects
*
@@ -206,9 +298,10 @@ class FileRoutine implements RoutineInterface
*/
private function storeObjects(Collection $objects): ImportStorage
{
$config = $this->getConfig();
$storage = new ImportStorage;
$storage->setJob($this->job);
$storage->setDateFormat($this->job->configuration['date-format']);
$storage->setDateFormat($config['date-format']);
$storage->setObjects($objects);
$storage->store();
Log::info('Back in storeObjects()');

View File

@@ -22,15 +22,21 @@ declare(strict_types=1);
namespace FireflyIII\Import\Routine;
use Carbon\Carbon;
use DB;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Import\Object\ImportJournal;
use FireflyIII\Import\Storage\ImportStorage;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\Services\Spectre\Exception\DuplicatedCustomerException;
use FireflyIII\Services\Spectre\Exception\SpectreException;
use FireflyIII\Services\Spectre\Object\Account;
use FireflyIII\Services\Spectre\Object\Customer;
use FireflyIII\Services\Spectre\Object\Login;
use FireflyIII\Services\Spectre\Object\Token;
use FireflyIII\Services\Spectre\Object\Transaction;
use FireflyIII\Services\Spectre\Request\CreateTokenRequest;
use FireflyIII\Services\Spectre\Request\ListAccountsRequest;
use FireflyIII\Services\Spectre\Request\ListCustomersRequest;
@@ -116,15 +122,14 @@ class SpectreRoutine implements RoutineInterface
*/
public function run(): bool
{
if ('configured' === $this->job->status) {
if ('configured' === $this->getStatus()) {
$this->repository->updateStatus($this->job, 'running');
}
Log::info(sprintf('Start with import job %s using Spectre.', $this->job->key));
set_time_limit(0);
// check if job has token first!
$config = $this->job->configuration;
$stage = $config['stage'];
$stage = $this->getConfig()['stage'] ?? 'unknown';
switch ($stage) {
case 'initial':
@@ -144,10 +149,7 @@ class SpectreRoutine implements RoutineInterface
throw new FireflyException(sprintf('Cannot handle stage %s', $stage));
}
var_dump($config);
exit;
throw new FireflyException('Application cannot handle this.');
return true;
}
/**
@@ -189,6 +191,7 @@ class SpectreRoutine implements RoutineInterface
}
}
Preferences::setForUser($this->job->user, 'spectre_customer', $customer->toArray());
return $customer;
@@ -202,21 +205,20 @@ class SpectreRoutine implements RoutineInterface
*/
protected function getCustomer(): Customer
{
$config = $this->job->configuration;
$config = $this->getConfig();
if (!is_null($config['customer'])) {
$customer = new Customer($config['customer']);
return $customer;
}
$customer = $this->createCustomer();
$config['customer'] = [
$customer = $this->createCustomer();
$config['customer'] = [
'id' => $customer->getId(),
'identifier' => $customer->getIdentifier(),
'secret' => $customer->getSecret(),
];
$this->job->configuration = $config;
$this->job->save();
$this->setConfig($config);
return $customer;
}
@@ -259,20 +261,20 @@ class SpectreRoutine implements RoutineInterface
Log::debug(sprintf('Token is %s', $token->getToken()));
// update job, give it the token:
$config = $this->job->configuration;
$config['has-token'] = true;
$config['token'] = $token->getToken();
$config['token-expires'] = $token->getExpiresAt()->format('U');
$config['token-url'] = $token->getConnectUrl();
$config['stage'] = 'has-token';
$this->job->configuration = $config;
$config = $this->getConfig();
$config['has-token'] = true;
$config['token'] = $token->getToken();
$config['token-expires'] = $token->getExpiresAt()->format('U');
$config['token-url'] = $token->getConnectUrl();
$config['stage'] = 'has-token';
$this->setConfig($config);
Log::debug('Job config is now', $config);
// update job, set status to "configuring".
$this->job->status = 'configuring';
$this->job->save();
$this->setStatus('configuring');
Log::debug(sprintf('Job status is now %s', $this->job->status));
$this->addStep();
}
/**
@@ -287,6 +289,7 @@ class SpectreRoutine implements RoutineInterface
$request = new ListLoginsRequest($this->job->user);
$request->setCustomer($customer);
$request->call();
$logins = $request->getLogins();
/** @var Login $final */
$final = null;
@@ -302,8 +305,13 @@ class SpectreRoutine implements RoutineInterface
}
}
if (is_null($final)) {
throw new FireflyException('No valid login attempt found.');
Log::error('Could not find a valid login for this user.');
$this->repository->addError($this->job, 0, 'Spectre connection failed. Did you use invalid credentials, press Cancel or failed the 2FA challenge?');
$this->repository->setStatus($this->job, 'error');
return;
}
$this->addStep();
// list the users accounts using this login.
$accountRequest = new ListAccountsRequest($this->job->user);
@@ -319,45 +327,266 @@ class SpectreRoutine implements RoutineInterface
}
// update job:
$config = $this->job->configuration;
$config['accounts'] = $all;
$config['login'] = $login->toArray();
$config['stage'] = 'have-accounts';
$this->job->configuration = $config;
$this->job->status = 'configuring';
$this->job->save();
$config = $this->getConfig();
$config['accounts'] = $all;
$config['login'] = $login->toArray();
$config['stage'] = 'have-accounts';
$this->setConfig($config);
$this->setStatus('configuring');
$this->addStep();
return;
}
/**
* Shorthand method.
*/
private function addStep()
{
$this->repository->addStepsDone($this->job, 1);
}
/**
* Shorthand
*
* @param int $steps
*/
private function addTotalSteps(int $steps)
{
$this->repository->addTotalSteps($this->job, $steps);
}
/**
* @return array
*/
private function getConfig(): array
{
return $this->repository->getConfiguration($this->job);
}
/**
* Shorthand method.
*
* @return array
*/
private function getExtendedStatus(): array
{
return $this->repository->getExtendedStatus($this->job);
}
/**
* Shorthand method.
*
* @return string
*/
private function getStatus(): string
{
return $this->repository->getStatus($this->job);
}
/**
* @param array $all
*
* @throws FireflyException
*/
private function importTransactions(array $all)
{
Log::debug('Going to import transactions');
$collection = new Collection;
// create import objects?
foreach ($all as $accountId => $data) {
Log::debug(sprintf('Now at account #%d', $accountId));
/** @var Transaction $transaction */
foreach ($data['transactions'] as $transaction) {
Log::debug(sprintf('Now at transaction #%d', $transaction->getId()));
/** @var Account $account */
$account = $data['account'];
$importJournal = new ImportJournal;
$importJournal->setUser($this->job->user);
$importJournal->asset->setDefaultAccountId($data['import_id']);
// call set value a bunch of times for various data entries:
$tags = [];
$tags[] = $transaction->getMode();
$tags[] = $transaction->getStatus();
if ($transaction->isDuplicated()) {
$tags[] = 'possibly-duplicated';
}
$extra = $transaction->getExtra()->toArray();
$notes = '';
$notes .= strval(trans('import.imported_from_account', ['account' => $account->getName()])) . ' '
. "\n"; // double space for newline in Markdown.
foreach ($extra as $key => $value) {
switch ($key) {
case 'account_number':
$importJournal->setValue(['role' => 'account-number', 'value' => $value]);
break;
case 'original_category':
case 'original_subcategory':
case 'customer_category_code':
case 'customer_category_name':
$tags[] = $value;
break;
case 'payee':
$importJournal->setValue(['role' => 'opposing-name', 'value' => $value]);
break;
case 'original_amount':
$importJournal->setValue(['role' => 'amount_foreign', 'value' => $value]);
break;
case 'original_currency_code':
$importJournal->setValue(['role' => 'foreign-currency-code', 'value' => $value]);
break;
default:
$notes .= $key . ': ' . $value . ' '; // for newline in Markdown.
}
}
// hash
$importJournal->setHash($transaction->getHash());
// account ID (Firefly III account):
$importJournal->setValue(['role' => 'account-id', 'value' => $data['import_id'], 'mapped' => $data['import_id']]);
// description:
$importJournal->setValue(['role' => 'description', 'value' => $transaction->getDescription()]);
// date:
$importJournal->setValue(['role' => 'date-transaction', 'value' => $transaction->getMadeOn()->toIso8601String()]);
// amount
$importJournal->setValue(['role' => 'amount', 'value' => $transaction->getAmount()]);
$importJournal->setValue(['role' => 'currency-code', 'value' => $transaction->getCurrencyCode()]);
// various meta fields:
$importJournal->setValue(['role' => 'category-name', 'value' => $transaction->getCategory()]);
$importJournal->setValue(['role' => 'note', 'value' => $notes]);
$importJournal->setValue(['role' => 'tags-comma', 'value' => join(',', $tags)]);
$collection->push($importJournal);
}
}
$this->addStep();
Log::debug(sprintf('Going to try and store all %d them.', $collection->count()));
$this->addTotalSteps(7 * $collection->count());
// try to store them (seven steps per transaction)
$storage = new ImportStorage;
$storage->setJob($this->job);
$storage->setDateFormat('Y-m-d\TH:i:sO');
$storage->setObjects($collection);
$storage->store();
Log::info('Back in importTransactions()');
// link to tag
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$repository->setUser($this->job->user);
$data = [
'tag' => trans('import.import_with_key', ['key' => $this->job->key]),
'date' => new Carbon,
'description' => null,
'latitude' => null,
'longitude' => null,
'zoomLevel' => null,
'tagMode' => 'nothing',
];
$tag = $repository->store($data);
$extended = $this->getExtendedStatus();
$extended['tag'] = $tag->id;
$this->setExtendedStatus($extended);
Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag));
Log::debug('Looping journals...');
$journalIds = $storage->journals->pluck('id')->toArray();
$tagId = $tag->id;
$this->addTotalSteps(count($journalIds));
foreach ($journalIds as $journalId) {
Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId));
DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]);
$this->addStep();
}
Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $storage->journals->count(), $tag->id, $tag->tag));
// set status to "finished"?
// update job:
$this->setStatus('finished');
$this->addStep();
return;
}
/**
* @throws FireflyException
* @throws SpectreException
*/
private function runStageHaveMapping()
{
// for each spectre account id in 'account-mappings'.
// find FF account
// get transactions.
// import?!
$config = $this->job->configuration;
$config = $this->getConfig();
$accounts = $config['accounts'] ?? [];
$all = [];
$count = 0;
/** @var array $accountArray */
foreach ($accounts as $accountArray) {
$account = new Account($accountArray);
$importId = intval($config['accounts-mapped'][$account->getid()] ?? 0);
$doImport = $importId !== 0 ? true : false;
if (!$doImport) {
Log::debug(sprintf('Will NOT import from Spectre account #%d ("%s")', $account->getId(), $account->getName()));
continue;
}
// import into account
// grab all transactions
$listTransactionsRequest = new ListTransactionsRequest($this->job->user);
$listTransactionsRequest->setAccount($account);
$listTransactionsRequest->call();
$transactions = $listTransactionsRequest->getTransactions();
var_dump($transactions);exit;
$transactions = $listTransactionsRequest->getTransactions();
$all[$account->getId()] = [
'account' => $account,
'import_id' => $importId,
'transactions' => $transactions,
];
$count += count($transactions);
}
var_dump($config);
exit;
Log::debug(sprintf('Total number of transactions: %d', $count));
$this->addStep();
$this->importTransactions($all);
}
/**
* Shorthand.
*
* @param array $config
*/
private function setConfig(array $config): void
{
$this->repository->setConfiguration($this->job, $config);
return;
}
/**
* Shorthand method.
*
* @param array $extended
*/
private function setExtendedStatus(array $extended): void
{
$this->repository->setExtendedStatus($this->job, $extended);
return;
}
/**
* Shorthand.
*
* @param string $status
*/
private function setStatus(string $status): void
{
$this->repository->setStatus($this->job, $status);
}
}

View File

@@ -30,11 +30,22 @@ use FireflyIII\Models\ImportJob;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use Illuminate\Support\Collection;
use Log;
use Preferences;
/**
* Is capable of storing individual ImportJournal objects.
* Adds 7 steps per object stored:
* 1. get all import data from import journal
* 2. is not a duplicate
* 3. create the journal
* 4. store journal
* 5. run rules
* 6. run bills
* 7. finished storing object
*
* Class ImportStorage.
*/
class ImportStorage
@@ -53,6 +64,8 @@ class ImportStorage
protected $defaultCurrencyId = 1;
/** @var ImportJob */
protected $job;
/** @var ImportJobRepositoryInterface */
protected $repository;
/** @var Collection */
protected $rules;
/** @var bool */
@@ -63,6 +76,8 @@ class ImportStorage
private $matchBills = false;
/** @var Collection */
private $objects;
/** @var int */
private $total = 0;
/** @var array */
private $transfers = [];
@@ -89,13 +104,17 @@ class ImportStorage
*/
public function setJob(ImportJob $job)
{
$this->job = $job;
$currency = app('amount')->getDefaultCurrencyByUser($this->job->user);
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
$config = $this->repository->getConfiguration($job);
$currency = app('amount')->getDefaultCurrencyByUser($job->user);
$this->defaultCurrencyId = $currency->id;
$this->job = $job;
$this->transfers = $this->getTransfers();
$config = $job->configuration;
$this->applyRules = $config['apply-rules'] ?? false;
$this->matchBills = $config['match-bills'] ?? false;
if (true === $this->applyRules) {
Log::debug('applyRules seems to be true, get the rules.');
$this->rules = $this->getRules();
@@ -108,6 +127,8 @@ class ImportStorage
}
Log::debug(sprintf('Value of apply rules is %s', var_export($this->applyRules, true)));
Log::debug(sprintf('Value of match bills is %s', var_export($this->matchBills, true)));
}
/**
@@ -116,6 +137,7 @@ class ImportStorage
public function setObjects(Collection $objects)
{
$this->objects = $objects;
$this->total = $objects->count();
}
/**
@@ -129,6 +151,7 @@ class ImportStorage
function (ImportJournal $importJournal, int $index) {
try {
$this->storeImportJournal($index, $importJournal);
$this->addStep();
} catch (FireflyException | ErrorException | Exception $e) {
$this->errors->push($e->getMessage());
Log::error(sprintf('Cannot import row #%d because: %s', $index, $e->getMessage()));
@@ -150,7 +173,7 @@ class ImportStorage
*/
protected function storeImportJournal(int $index, ImportJournal $importJournal): bool
{
Log::debug(sprintf('Going to store object #%d with description "%s"', $index, $importJournal->getDescription()));
Log::debug(sprintf('Going to store object #%d/%d with description "%s"', ($index + 1), $this->total, $importJournal->getDescription()));
$assetAccount = $importJournal->asset->getAccount();
$amount = $importJournal->getAmount();
$currencyId = $this->getCurrencyId($importJournal);
@@ -159,9 +182,7 @@ class ImportStorage
$opposingAccount = $this->getOpposingAccount($importJournal->opposing, $assetAccount->id, $amount);
$transactionType = $this->getTransactionType($amount, $opposingAccount);
$description = $importJournal->getDescription();
// First step done!
$this->job->addStepsDone(1);
$this->addStep();
/**
* Check for double transfer.
@@ -175,13 +196,17 @@ class ImportStorage
'opposing' => $opposingAccount->name,
];
if ($this->isDoubleTransfer($parameters) || $this->hashAlreadyImported($importJournal->hash)) {
$this->job->addStepsDone(3);
// throw error
$message = sprintf('Detected a possible duplicate, skip this one (hash: %s).', $importJournal->hash);
Log::error($message, $parameters);
// add five steps to keep the pace:
$this->addSteps(5);
throw new FireflyException($message);
}
unset($parameters);
$this->addStep();
// store journal and create transactions:
$parameters = [
@@ -197,9 +222,7 @@ class ImportStorage
];
$journal = $this->storeJournal($parameters);
unset($parameters);
// Another step done!
$this->job->addStepsDone(1);
$this->addStep();
// store meta object things:
$this->storeCategory($journal, $importJournal->category->getCategory());
@@ -221,31 +244,31 @@ class ImportStorage
// set journal completed:
$journal->completed = true;
$journal->save();
// Another step done!
$this->job->addStepsDone(1);
$this->addStep();
// run rules if config calls for it:
if (true === $this->applyRules) {
Log::info('Will apply rules to this journal.');
$this->applyRules($journal);
}
Preferences::setForUser($this->job->user, 'lastActivity', microtime());
if (!(true === $this->applyRules)) {
Log::info('Will NOT apply rules to this journal.');
}
$this->addStep();
// match bills if config calls for it.
if (true === $this->matchBills) {
Log::info('Cannot match bills (yet).');
Log::info('Will match bills.');
$this->matchBills($journal);
}
if (!(true === $this->matchBills)) {
Log::info('Cannot match bills (yet), but do not have to.');
}
$this->addStep();
// Another step done!
$this->job->addStepsDone(1);
$this->journals->push($journal);
Log::info(sprintf('Imported new journal #%d: "%s", amount %s %s.', $journal->id, $journal->description, $journal->transactionCurrency->code, $amount));
@@ -253,6 +276,24 @@ class ImportStorage
return true;
}
/**
* Shorthand method.
*/
private function addStep()
{
$this->repository->addStepsDone($this->job, 1);
}
/**
* Shorthand method
*
* @param int $steps
*/
private function addSteps(int $steps)
{
$this->repository->addStepsDone($this->job, $steps);
}
/**
* @param array $parameters
*
@@ -269,7 +310,6 @@ class ImportStorage
$amount = app('steam')->positive($parameters['amount']);
$names = [$parameters['asset'], $parameters['opposing']];
$transfer = [];
sort($names);

View File

@@ -121,11 +121,11 @@ trait ImportSupport
{
$transaction = new Transaction;
$transaction->account_id = $parameters['account'];
$transaction->transaction_journal_id = $parameters['id'];
$transaction->transaction_currency_id = $parameters['currency'];
$transaction->transaction_journal_id = intval($parameters['id']);
$transaction->transaction_currency_id = intval($parameters['currency']);
$transaction->amount = $parameters['amount'];
$transaction->foreign_currency_id = $parameters['foreign_currency'];
$transaction->foreign_amount = $parameters['foreign_amount'];
$transaction->foreign_currency_id = intval($parameters['foreign_currency']) === 0 ? null : intval($parameters['foreign_currency']);
$transaction->foreign_amount = null === $transaction->foreign_currency_id ? null : $parameters['foreign_amount'];
$transaction->save();
if (null === $transaction->id) {
$errorText = join(', ', $transaction->getErrors()->all());
@@ -155,6 +155,7 @@ trait ImportSupport
* @param ImportJournal $importJournal
*
* @return int
* @throws FireflyException
*/
private function getCurrencyId(ImportJournal $importJournal): int
{
@@ -192,7 +193,7 @@ trait ImportSupport
{
// use given currency by import journal.
$currency = $importJournal->currency->getTransactionCurrency();
if (null !== $currency->id && $currency->id !== $currencyId) {
if (null !== $currency->id && intval($currency->id) !== intval($currencyId)) {
return $currency->id;
}
@@ -216,6 +217,7 @@ trait ImportSupport
* @see ImportSupport::getTransactionType
*
* @return Account
* @throws FireflyException
*/
private function getOpposingAccount(ImportAccount $account, int $forbiddenAccount, string $amount): Account
{
@@ -435,8 +437,6 @@ trait ImportSupport
if (!$journal->save()) {
$errorText = join(', ', $journal->getErrors()->all());
// add three steps:
$this->job->addStepsDone(3);
// throw error
throw new FireflyException($errorText);
}

View File

@@ -212,7 +212,7 @@ class Account extends Model
*
* @return string
*/
public function getNameAttribute($value): string
public function getNameAttribute($value): ?string
{
if ($this->encrypted) {
return Crypt::decrypt($value);

View File

@@ -30,6 +30,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* Class ExportJob.
*
* @property User $user
* @property string $key
*/
class ExportJob extends Model
{

View File

@@ -82,32 +82,6 @@ class ImportJob extends Model
throw new NotFoundHttpException;
}
/**
* @param int $index
* @param string $message
*
* @return bool
*/
public function addError(int $index, string $message): bool
{
$extended = $this->extended_status;
$extended['errors'][$index][] = $message;
$this->extended_status = $extended;
return true;
}
/**
* @param int $count
*/
public function addStepsDone(int $count)
{
$status = $this->extended_status;
$status['done'] += $count;
$this->extended_status = $status;
$this->save();
}
/**
* @param int $count
*/
@@ -117,6 +91,7 @@ class ImportJob extends Model
$status['steps'] += $count;
$this->extended_status = $status;
$this->save();
Log::debug(sprintf('Add %d to total steps for job "%s" making total steps %d', $count, $this->key, $status['steps']));
}
/**

View File

@@ -158,7 +158,7 @@ class PiggyBank extends Model
public function leftOnAccount(Carbon $date): string
{
$balance = Steam::balanceIgnoreVirtual($this->account, $date);
// @var PiggyBank $p
/** @var PiggyBank $piggyBank */
foreach ($this->account->piggyBanks as $piggyBank) {
$currentAmount = $piggyBank->currentRelevantRep()->currentamount ?? '0';

View File

@@ -66,6 +66,7 @@ use Watson\Validating\ValidatingTrait;
* @property string $transaction_currency_symbol
* @property int $transaction_currency_dp
* @property string $transaction_currency_code
* @property string $description
*/
class Transaction extends Model
{

View File

@@ -28,6 +28,9 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class TransactionCurrency.
*
* @property string $code
*
*/
class TransactionCurrency extends Model
{

View File

@@ -92,8 +92,6 @@ class TransactionJournal extends Model
if (auth()->check()) {
$journalId = intval($value);
$journal = auth()->user()->transactionJournals()->where('transaction_journals.id', $journalId)
->with('transactionType')
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->first(['transaction_journals.*']);
if (!is_null($journal)) {
return $journal;

View File

@@ -95,6 +95,15 @@ class TransactionJournalLink extends Model
return $this->belongsTo(LinkType::class);
}
/**
* @codeCoverageIgnore
* Get all of the notes.
*/
public function notes()
{
return $this->morphMany(Note::class, 'noteable');
}
/**
* @codeCoverageIgnore
*

View File

@@ -699,6 +699,7 @@ class AccountRepository implements AccountRepositoryInterface
return null;
}
return $iban;
}
}

View File

@@ -67,8 +67,11 @@ class BudgetRepository implements BudgetRepositoryInterface
if ($entry->ct > 1) {
$newest = BudgetLimit::where('start_date', $entry->start_date)->where('end_date', $entry->end_date)
->where('budget_id', $entry->budget_id)->orderBy('updated_at', 'DESC')->first(['budget_limits.*']);
BudgetLimit::where('start_date', $entry->start_date)->where('end_date', $entry->end_date)
->where('budget_id', $entry->budget_id)->where('id', '!=', $newest->id)->delete();
if (!is_null($newest)) {
BudgetLimit::where('start_date', $entry->start_date)->where('end_date', $entry->end_date)
->where('budget_id', $entry->budget_id)
->where('id', '!=', $newest->id)->delete();
}
}
}

View File

@@ -42,6 +42,21 @@ class ImportJobRepository implements ImportJobRepositoryInterface
/** @var User */
private $user;
/**
* @param ImportJob $job
* @param int $index
* @param string $error
*
* @return ImportJob
*/
public function addError(ImportJob $job, int $index, string $error): ImportJob
{
$extended = $this->getExtendedStatus($job);
$extended['errors'][$index][] = $error;
return $this->setExtendedStatus($job, $extended);
}
/**
* @param ImportJob $job
* @param int $steps
@@ -50,9 +65,28 @@ class ImportJobRepository implements ImportJobRepositoryInterface
*/
public function addStepsDone(ImportJob $job, int $steps = 1): ImportJob
{
$job->addStepsDone($steps);
$status = $this->getExtendedStatus($job);
$status['done'] += $steps;
Log::debug(sprintf('Add %d to steps done for job "%s" making steps done %d', $steps, $job->key, $status['done']));
return $this->setExtendedStatus($job, $status);
}
/**
* @param ImportJob $job
* @param int $steps
*
* @return ImportJob
*/
public function addTotalSteps(ImportJob $job, int $steps = 1): ImportJob
{
$extended = $this->getExtendedStatus($job);
$total = $extended['steps'] ?? 0;
$total += $steps;
$extended['steps'] = $total;
return $this->setExtendedStatus($job, $extended);
return $job;
}
/**
@@ -161,6 +195,16 @@ class ImportJobRepository implements ImportJobRepositoryInterface
return [];
}
/**
* @param ImportJob $job
*
* @return string
*/
public function getStatus(ImportJob $job): string
{
return $job->status;
}
/**
* @param ImportJob $job
* @param UploadedFile $file
@@ -277,6 +321,50 @@ class ImportJobRepository implements ImportJobRepositoryInterface
return $job;
}
/**
* @param ImportJob $job
* @param string $status
*
* @return ImportJob
*/
public function setStatus(ImportJob $job, string $status): ImportJob
{
$job->status = $status;
$job->save();
return $job;
}
/**
* @param ImportJob $job
* @param int $steps
*
* @return ImportJob
*/
public function setStepsDone(ImportJob $job, int $steps): ImportJob
{
$status = $this->getExtendedStatus($job);
$status['done'] = $steps;
Log::debug(sprintf('Set steps done for job "%s" to %d', $job->key, $steps));
return $this->setExtendedStatus($job, $status);
}
/**
* @param ImportJob $job
* @param int $count
*
* @return ImportJob
*/
public function setTotalSteps(ImportJob $job, int $count): ImportJob
{
$status = $this->getExtendedStatus($job);
$status['steps'] = $count;
Log::debug(sprintf('Set total steps for job "%s" to %d', $job->key, $count));
return $this->setExtendedStatus($job, $status);
}
/**
* @param User $user
*/

View File

@@ -31,6 +31,16 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
*/
interface ImportJobRepositoryInterface
{
/**
* @param ImportJob $job
* @param int $index
* @param string $error
*
* @return ImportJob
*/
public function addError(ImportJob $job, int $index, string $error): ImportJob;
/**
* @param ImportJob $job
* @param int $steps
@@ -39,6 +49,14 @@ interface ImportJobRepositoryInterface
*/
public function addStepsDone(ImportJob $job, int $steps = 1): ImportJob;
/**
* @param ImportJob $job
* @param int $steps
*
* @return ImportJob
*/
public function addTotalSteps(ImportJob $job, int $steps = 1): ImportJob;
/**
* Return number of imported rows with this hash value.
*
@@ -80,6 +98,13 @@ interface ImportJobRepositoryInterface
*/
public function getExtendedStatus(ImportJob $job): array;
/**
* @param ImportJob $job
*
* @return string
*/
public function getStatus(ImportJob $job);
/**
* @param ImportJob $job
* @param UploadedFile $file
@@ -112,6 +137,30 @@ interface ImportJobRepositoryInterface
*/
public function setExtendedStatus(ImportJob $job, array $array): ImportJob;
/**
* @param ImportJob $job
* @param string $status
*
* @return ImportJob
*/
public function setStatus(ImportJob $job, string $status): ImportJob;
/**
* @param ImportJob $job
* @param int $count
*
* @return ImportJob
*/
public function setStepsDone(ImportJob $job, int $steps): ImportJob;
/**
* @param ImportJob $job
* @param int $count
*
* @return ImportJob
*/
public function setTotalSteps(ImportJob $job, int $count): ImportJob;
/**
* @param User $user
*/

View File

@@ -27,7 +27,6 @@ use FireflyIII\Models\AccountType;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\SingleCacheProperties;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
@@ -70,14 +69,6 @@ class JournalTasker implements JournalTaskerInterface
*/
public function getTransactionsOverview(TransactionJournal $journal): array
{
$cache = new SingleCacheProperties;
$cache->addProperty('transaction-overview');
$cache->addProperty($journal->id);
$cache->addProperty($journal->updated_at);
if ($cache->has()) {
return $cache->get();
}
// get all transaction data + the opposite site in one list.
$set = $journal
->transactions()// "source"
->leftJoin(
@@ -177,7 +168,6 @@ class JournalTasker implements JournalTaskerInterface
$transactions[] = $transaction;
}
$cache->store($transactions);
return $transactions;
}

View File

@@ -24,6 +24,7 @@ namespace FireflyIII\Repositories\LinkType;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\LinkType;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalLink;
use FireflyIII\User;
@@ -188,10 +189,19 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface
$link->source()->associate($right);
$link->destination()->associate($left);
}
$link->comment = $link['comments'] ?? null;
$link->save();
// make note in noteable:
if (strlen($information['notes']) > 0) {
$dbNote = $link->notes()->first();
if (null === $dbNote) {
$dbNote = new Note();
$dbNote->noteable()->associate($link);
}
$dbNote->text = trim($information['notes']);
$dbNote->save();
}
return $link;
}

View File

@@ -126,7 +126,7 @@ class TagRepository implements TagRepositoryInterface
public function findByTag(string $tag): Tag
{
$tags = $this->user->tags()->get();
// @var Tag $tag
/** @var Tag $databaseTag */
foreach ($tags as $databaseTag) {
if ($databaseTag->tag === $tag) {
return $databaseTag;

View File

@@ -128,6 +128,18 @@ class UserRepository implements UserRepositoryInterface
return $this->all()->count();
}
/**
* @param string $name
* @param string $displayName
* @param string $description
*
* @return Role
*/
public function createRole(string $name, string $displayName, string $description): Role
{
return Role::create(['name' => $name, 'display_name' => $displayName, 'description' => $description]);
}
/**
* @param User $user
*
@@ -178,6 +190,16 @@ class UserRepository implements UserRepositoryInterface
return User::first();
}
/**
* @param string $role
*
* @return Role|null
*/
public function getRole(string $role): ?Role
{
return Role::where('name', $role)->first();
}
/**
* Return basic user information.
*
@@ -215,8 +237,8 @@ class UserRepository implements UserRepositoryInterface
->where('budgets.user_id', $user->id)->get(['budget_limits.budget_id'])->count();
$return['export_jobs'] = $user->exportJobs()->count();
$return['export_jobs_success'] = $user->exportJobs()->where('status', 'export_downloaded')->count();
$return['import_jobs'] = $user->exportJobs()->count();
$return['import_jobs_success'] = $user->exportJobs()->where('status', 'import_complete')->count();
$return['import_jobs'] = $user->importJobs()->count();
$return['import_jobs_success'] = $user->importJobs()->where('status', 'finished')->count();
$return['rule_groups'] = $user->ruleGroups()->count();
$return['rules'] = $user->rules()->count();
$return['tags'] = $user->tags()->count();

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\User;
use FireflyIII\Models\Role;
use FireflyIII\User;
use Illuminate\Support\Collection;
@@ -30,6 +31,15 @@ use Illuminate\Support\Collection;
*/
interface UserRepositoryInterface
{
/**
* @param string $name
* @param string $displayName
* @param string $description
*
* @return Role
*/
public function createRole(string $name, string $displayName, string $description): Role;
/**
* Returns a collection of all users.
*
@@ -37,6 +47,13 @@ interface UserRepositoryInterface
*/
public function all(): Collection;
/**
* @param string $role
*
* @return Role|null
*/
public function getRole(string $role): ?Role;
/**
* Gives a user a role.
*

View File

@@ -78,6 +78,14 @@ class Account extends SpectreObject
return $this->id;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return array
*/

View File

@@ -23,19 +23,167 @@ declare(strict_types=1);
namespace FireflyIII\Services\Spectre\Object;
use Carbon\Carbon;
/**
* Class Transaction
*/
class Transaction extends SpectreObject
{
/** @var int */
private $accountId;
/** @var string */
private $amount;
/** @var string */
private $category;
/** @var Carbon */
private $createdAt;
/** @var string */
private $currencyCode;
/** @var string */
private $description;
/** @var bool */
private $duplicated;
/** @var TransactionExtra */
private $extra;
/** @var int */
private $id;
/** @var Carbon */
private $madeOn;
/** @var string */
private $mode;
/** @var string */
private $status;
/** @var Carbon */
private $updatedAt;
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* Transaction constructor.
*
* @param array $data
*/
public function __construct(array $data) {
var_dump($data);
exit;
public function __construct(array $data)
{
$this->id = $data['id'];
$this->mode = $data['mode'];
$this->status = $data['status'];
$this->madeOn = new Carbon($data['made_on']);
$this->amount = $data['amount'];
$this->currencyCode = $data['currency_code'];
$this->description = $data['description'];
$this->category = $data['category'];
$this->duplicated = $data['duplicated'];
$this->extra = new TransactionExtra($data['extra'] ?? []);
$this->accountId = $data['account_id'];
$this->createdAt = new Carbon($data['created_at']);
$this->updatedAt = new Carbon($data['updated_at']);
}
/**
* @return string
*/
public function getMode(): string
{
return $this->mode;
}
/**
* @return string
*/
public function getStatus(): string
{
return $this->status;
}
/**
* @return bool
*/
public function isDuplicated(): bool
{
return $this->duplicated;
}
/**
* @return TransactionExtra
*/
public function getExtra(): TransactionExtra
{
return $this->extra;
}
/**
* @return string
*/
public function getAmount(): string
{
return strval($this->amount);
}
/**
* @return string
*/
public function getCategory(): string
{
return $this->category;
}
/**
* @return string
*/
public function getCurrencyCode(): string
{
return $this->currencyCode;
}
/**
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
/**
* @return string
*/
public function getHash(): string
{
$array = [
'id' => $this->id,
'mode' => $this->mode,
'status' => $this->status,
'made_on' => $this->madeOn->toIso8601String(),
'amount' => $this->amount,
'currency_code' => $this->currencyCode,
'description' => $this->description,
'category' => $this->category,
'duplicated' => $this->duplicated,
'extra' => $this->extra->toArray(),
'account_id' => $this->accountId,
'created_at' => $this->createdAt->toIso8601String(),
'updated_at' => $this->updatedAt->toIso8601String(),
];
$hashed = hash('sha256', json_encode($array));
return $hashed;
}
/**
* @return Carbon
*/
public function getMadeOn(): Carbon
{
return $this->madeOn;
}
}

View File

@@ -0,0 +1,169 @@
<?php
/**
* TransactionExtra.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Services\Spectre\Object;
use Carbon\Carbon;
/**
* Class TransactionExtra
*/
class TransactionExtra extends SpectreObject
{
/** @var string */
private $accountBalanceSnapshot;
/** @var string */
private $accountNumber;
/** @var string */
private $additional;
/** @var string */
private $assetAmount;
/** @var string */
private $assetCode;
/** @var string */
private $categorizationConfidence;
/** @var string */
private $checkNumber;
/** @var string */
private $customerCategoryCode;
/** @var string */
private $customerCategoryName;
/** @var string */
private $id;
/** @var string */
private $information;
/** @var string */
private $mcc;
/** @var string */
private $originalAmount;
/** @var string */
private $originalCategory;
/** @var string */
private $originalCurrencyCode;
/** @var string */
private $originalSubCategory;
/** @var string */
private $payee;
/** @var bool */
private $possibleDuplicate;
/** @var Carbon */
private $postingDate;
/** @var Carbon */
private $postingTime;
/** @var string */
private $recordNumber;
/** @var array */
private $tags;
/** @var Carbon */
private $time;
/** @var string */
private $type;
/** @var string */
private $unitPrice;
/** @var string */
private $units;
/**
* TransactionExtra constructor.
*
* @param array $data
*/
public function __construct(array $data)
{
$this->id = $data['id'] ?? null;
$this->recordNumber = $data['record_number'] ?? null;
$this->information = $data['information'] ?? null;
$this->time = isset($data['time']) ? new Carbon($data['time']) : null;
$this->postingDate = isset($data['posting_date']) ? new Carbon($data['posting_date']) : null;
$this->postingTime = isset($data['posting_time']) ? new Carbon($data['posting_time']) : null;
$this->accountNumber = $data['account_number'] ?? null;
$this->originalAmount = $data['original_amount'] ?? null;
$this->originalCurrencyCode = $data['original_currency_code'] ?? null;
$this->assetCode = $data['asset_code'] ?? null;
$this->assetAmount = $data['asset_amount'] ?? null;
$this->originalCategory = $data['original_category'] ?? null;
$this->originalSubCategory = $data['original_subcategory'] ?? null;
$this->customerCategoryCode = $data['customer_category_code'] ?? null;
$this->customerCategoryName = $data['customer_category_name'] ?? null;
$this->possibleDuplicate = $data['possible_duplicate'] ?? null;
$this->tags = $data['tags'] ?? null;
$this->mcc = $data['mcc'] ?? null;
$this->payee = $data['payee'] ?? null;
$this->type = $data['type'] ?? null;
$this->checkNumber = $data['check_number'] ?? null;
$this->units = $data['units'] ?? null;
$this->additional = $data['additional'] ?? null;
$this->unitPrice = $data['unit_price'] ?? null;
$this->accountBalanceSnapshot = $data['account_balance_snapshot'] ?? null;
$this->categorizationConfidence = $data['categorization_confidence'] ?? null;
}
/**
* @return string|null
*/
public function getId(): ?string
{
return $this->id;
}
/**
* @return array
*/
public function toArray(): array
{
$array = [
'id' => $this->id,
'record_number' => $this->recordNumber,
'information' => $this->information,
'time' => is_null($this->time) ? null : $this->time->toIso8601String(),
'posting_date' => is_null($this->postingDate) ? null : $this->postingDate->toIso8601String(),
'posting_time' => is_null($this->postingTime) ? null : $this->postingTime->toIso8601String(),
'account_number' => $this->accountNumber,
'original_amount' => $this->originalAmount,
'original_currency_code' => $this->originalCurrencyCode,
'asset_code' => $this->assetCode,
'asset_amount' => $this->assetAmount,
'original_category' => $this->originalCategory,
'original_subcategory' => $this->originalSubCategory,
'customer_category_code' => $this->customerCategoryCode,
'customer_category_name' => $this->customerCategoryName,
'possible_duplicate' => $this->possibleDuplicate,
'tags' => $this->tags,
'mcc' => $this->mcc,
'payee' => $this->payee,
'type' => $this->type,
'check_number' => $this->checkNumber,
'units' => $this->units,
'additional' => $this->additional,
'unit_price' => $this->unitPrice,
'account_balance_snapshot' => $this->accountBalanceSnapshot,
'categorization_confidence' => $this->categorizationConfidence,
];
return $array;
}
}

View File

@@ -23,9 +23,12 @@ declare(strict_types=1);
namespace FireflyIII\Services\Spectre\Request;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Services\Spectre\Exception\SpectreException;
use FireflyIII\Services\Spectre\Object\Account;
use FireflyIII\Services\Spectre\Object\Transaction;
use Log;
/**
* Class ListTransactionsRequest
*/
@@ -37,7 +40,8 @@ class ListTransactionsRequest extends SpectreRequest
private $transactions = [];
/**
*
* @throws FireflyException
* @throws SpectreException
*/
public function call(): void
{
@@ -45,7 +49,7 @@ class ListTransactionsRequest extends SpectreRequest
$nextId = 0;
while ($hasNextPage) {
Log::debug(sprintf('Now calling ListTransactionsRequest for next_id %d', $nextId));
$parameters = ['from_id' => $nextId,'account_id' => $this->account->getId()];
$parameters = ['from_id' => $nextId, 'account_id' => $this->account->getId()];
$uri = '/api/v3/transactions?' . http_build_query($parameters);
$response = $this->sendSignedSpectreGet($uri, []);
@@ -59,7 +63,7 @@ class ListTransactionsRequest extends SpectreRequest
$nextId = $response['meta']['next_id'];
Log::debug(sprintf('Next ID is now %d.', $nextId));
} else {
Log::debug('No next page.');
Log::debug('No next page, done with ListTransactionsRequest.');
}
// store customers:

View File

@@ -45,7 +45,7 @@ class TagList implements BinderInterface
$list = [];
$incoming = explode(',', $value);
foreach ($incoming as $entry) {
$list[] = trim($entry);
$list[] = strtolower(trim($entry));
}
$list = array_unique($list);
if (count($list) === 0) {
@@ -57,7 +57,7 @@ class TagList implements BinderInterface
$collection = $allTags->filter(
function (Tag $tag) use ($list) {
return in_array($tag->tag, $list);
return in_array(strtolower($tag->tag), $list);
}
);

View File

@@ -143,14 +143,13 @@ class Roles implements ConfigurationInterface
$this->saveConfig($config);
$this->ignoreUnmappableColumns();
$this->setRolesComplete();
$config = $this->getConfig();
$config['stage'] = 'map';
$this->saveConfig($config);
$this->isMappingNecessary();
$res = $this->isRolesComplete();
if ($res === true) {
$config = $this->getConfig();
$config['stage'] = 'map';
$this->saveConfig($config);
$this->isMappingNecessary();
}
return true;
}
@@ -225,6 +224,56 @@ class Roles implements ConfigurationInterface
return true;
}
/**
* @return bool
*/
private function isRolesComplete(): bool
{
$config = $this->getConfig();
$count = $config['column-count'];
$assigned = 0;
// check if data actually contains amount column (foreign amount does not count)
$hasAmount = false;
$hasForeignAmount = false;
$hasForeignCode = false;
for ($i = 0; $i < $count; ++$i) {
$role = $config['column-roles'][$i] ?? '_ignore';
if ('_ignore' !== $role) {
++$assigned;
}
if (in_array($role, ['amount', 'amount_credit', 'amount_debit'])) {
$hasAmount = true;
}
if ($role === 'foreign-currency-code') {
$hasForeignCode = true;
}
if ($role === 'amount_foreign') {
$hasForeignAmount = true;
}
}
if ($assigned > 0 && $hasAmount && ($hasForeignCode === false && $hasForeignAmount === false)) {
$this->warning = '';
$this->saveConfig($config);
return true;
}
// warn if has foreign amount but no currency code:
if ($hasForeignAmount && !$hasForeignCode) {
$this->warning = strval(trans('import.foreign_amount_warning'));
return false;
}
if (0 === $assigned || !$hasAmount) {
$this->warning = strval(trans('import.roles_warning'));
return false;
}
return false;
}
/**
* make unique example data.
*/
@@ -284,36 +333,6 @@ class Roles implements ConfigurationInterface
$this->repository->setConfiguration($this->job, $array);
}
/**
* @return bool
*/
private function setRolesComplete(): bool
{
$config = $this->getConfig();
$count = $config['column-count'];
$assigned = 0;
$hasAmount = false;
for ($i = 0; $i < $count; ++$i) {
$role = $config['column-roles'][$i] ?? '_ignore';
if ('_ignore' !== $role) {
++$assigned;
}
if (in_array($role, ['amount', 'amount_credit', 'amount_debit'])) {
$hasAmount = true;
}
}
if ($assigned > 0 && $hasAmount) {
$config['column-roles-complete'] = true;
$this->warning = '';
}
if (0 === $assigned || !$hasAmount) {
$this->warning = strval(trans('import.roles_warning'));
}
$this->saveConfig($config);
return true;
}
/**
* @return bool
*/

View File

@@ -51,7 +51,6 @@ class HaveAccounts implements ConfigurationInterface
$accountRepository = app(AccountRepositoryInterface::class);
/** @var CurrencyRepositoryInterface $currencyRepository */
$currencyRepository = app(CurrencyRepositoryInterface::class);
$data = [];
$config = $this->job->configuration;
$collection = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$defaultCurrency = app('amount')->getDefaultCurrency();

View File

@@ -42,6 +42,27 @@ use Illuminate\Support\Collection;
*/
trait TransactionJournalTrait
{
/**
* @param Builder $query
* @param string $table
*
* @return bool
*/
public static function isJoined(Builder $query, string $table): bool
{
$joins = $query->getQuery()->joins;
if (null === $joins) {
return false;
}
foreach ($joins as $join) {
if ($join->table === $table) {
return true;
}
}
return false;
}
/**
* @return string
*/
@@ -211,27 +232,6 @@ trait TransactionJournalTrait
*/
abstract public function isDeposit(): bool;
/**
* @param Builder $query
* @param string $table
*
* @return bool
*/
public function isJoined(Builder $query, string $table): bool
{
$joins = $query->getQuery()->joins;
if (null === $joins) {
return false;
}
foreach ($joins as $join) {
if ($join->table === $table) {
return true;
}
}
return false;
}
/**
* @return bool
*/

View File

@@ -80,6 +80,68 @@ class Navigation
return $date;
}
/**
* @param \Carbon\Carbon $start
* @param \Carbon\Carbon $end
* @param string $range
*
* @return array
* @throws FireflyException
*/
public function blockPeriods(\Carbon\Carbon $start, \Carbon\Carbon $end, string $range): array
{
$periods = [];
// Start by looping per period:
$perMonthEnd = clone $end;
$perMonthStart = clone $end;
$perMonthStart->startOfyear()->subYear();
$perMonthStart = $start->lt($perMonthStart) ? $perMonthStart : $start;
// loop first set:
while ($perMonthEnd >= $perMonthStart) {
$perMonthEnd = $this->startOfPeriod($perMonthEnd, $range);
$currentEnd = $this->endOfPeriod($perMonthEnd, $range);
if ($currentEnd->gt($start)) {
$periods[] = [
'start' => $perMonthEnd,
'end' => $currentEnd,
'period' => $range,
];
}
$perMonthEnd = $this->subtractPeriod($perMonthEnd, $range, 1);
}
// do not continue if date is already less
if ($perMonthEnd->lt($start)) {
return $periods;
}
// per year variables:
$perYearEnd = clone $perMonthStart;
$perYearStart = clone $perMonthStart;
unset($perMonthEnd, $currentEnd, $perMonthStart);
$perYearEnd->subYear();
$perYearStart->subYears(50);
$perYearStart = $start->lt($perYearStart) ? $perYearStart : $start;
$perYearStart->startOfYear();
// per year
while ($perYearEnd >= $perYearStart) {
$perYearEnd = $this->startOfPeriod($perYearEnd, '1Y');
$currentEnd = $this->endOfPeriod($perYearEnd, '1Y')->subDay()->endOfDay();
if ($currentEnd->gt($start)) {
$periods[] = [
'start' => $perYearEnd,
'end' => $currentEnd,
'period' => '1Y',
];
}
$perYearEnd = $this->subtractPeriod($perYearEnd, '1Y', 1);
}
return $periods;
}
/**
* @param \Carbon\Carbon $end
* @param $repeatFreq
@@ -194,7 +256,7 @@ class Navigation
{
// define period to increment
$increment = 'addDay';
$format = self::preferredCarbonFormat($start, $end);
$format = $this->preferredCarbonFormat($start, $end);
$displayFormat = strval(trans('config.month_and_day'));
// increment by month (for year)
if ($start->diffInMonths($end) > 1) {
@@ -419,6 +481,7 @@ class Navigation
return $date;
}
if ('custom' === $repeatFreq) {
return $date; // the date is already at the start.
}

View File

@@ -184,11 +184,11 @@ class Steam
$modified = null === $entry->modified ? '0' : strval($entry->modified);
$foreignModified = null === $entry->modified_foreign ? '0' : strval($entry->modified_foreign);
$amount = '0';
if ($currencyId === $entry->transaction_currency_id || 0 === $currencyId) {
if ($currencyId === intval($entry->transaction_currency_id) || 0 === $currencyId) {
// use normal amount:
$amount = $modified;
}
if ($currencyId === $entry->foreign_currency_id) {
if ($currencyId === intval($entry->foreign_currency_id)) {
// use foreign amount:
$amount = $foreignModified;
}

View File

@@ -27,7 +27,6 @@ use FireflyIII\Models\Attachment;
use FireflyIII\Models\Transaction as TransactionModel;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\SingleCacheProperties;
use Lang;
use Twig_Extension;
@@ -45,14 +44,6 @@ class Transaction extends Twig_Extension
*/
public function amount(TransactionModel $transaction): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('transaction-amount');
$cache->addProperty($transaction->id);
$cache->addProperty($transaction->updated_at);
if ($cache->has()) {
return $cache->get();
}
$amount = bcmul(app('steam')->positive(strval($transaction->transaction_amount)), '-1');
$format = '%s';
$coloured = true;
@@ -97,8 +88,6 @@ class Transaction extends Twig_Extension
$currency->decimal_places = $transaction->foreign_currency_dp;
$str .= ' (' . sprintf($format, app('amount')->formatAnything($currency, $amount, $coloured)) . ')';
}
$cache->store($str);
return $str;
}
@@ -109,15 +98,6 @@ class Transaction extends Twig_Extension
*/
public function amountArray(array $transaction): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('transaction-array-amount');
$cache->addProperty($transaction['source_id']);
$cache->addProperty($transaction['destination_id']);
$cache->addProperty($transaction['updated_at']);
if ($cache->has()) {
return $cache->get();
}
// first display amount:
$amount = TransactionType::WITHDRAWAL === $transaction['journal_type'] ? $transaction['source_amount']
: $transaction['destination_amount'];
@@ -135,8 +115,6 @@ class Transaction extends Twig_Extension
$fakeCurrency->symbol = $transaction['foreign_currency_symbol'];
$string .= ' (' . app('amount')->formatAnything($fakeCurrency, $amount, true) . ')';
}
$cache->store($string);
return $string;
}
@@ -147,19 +125,10 @@ class Transaction extends Twig_Extension
*/
public function budgets(TransactionModel $transaction): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('transaction-budgets');
$cache->addProperty($transaction->id);
$cache->addProperty($transaction->updated_at);
if ($cache->has()) {
return $cache->get();
}
// journal has a budget:
if (isset($transaction->transaction_journal_budget_id)) {
$name = app('steam')->tryDecrypt($transaction->transaction_journal_budget_name);
$txt = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$transaction->transaction_journal_budget_id]), $name, $name);
$cache->store($txt);
return $txt;
}
@@ -168,7 +137,6 @@ class Transaction extends Twig_Extension
if (isset($transaction->transaction_budget_id)) {
$name = app('steam')->tryDecrypt($transaction->transaction_budget_name);
$txt = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$transaction->transaction_budget_id]), $name, $name);
$cache->store($txt);
return $txt;
}
@@ -185,12 +153,10 @@ class Transaction extends Twig_Extension
}
$txt = join(', ', $str);
$cache->store($txt);
return $txt;
}
$txt = '';
$cache->store($txt);
return $txt;
}
@@ -202,19 +168,10 @@ class Transaction extends Twig_Extension
*/
public function categories(TransactionModel $transaction): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('transaction-categories');
$cache->addProperty($transaction->id);
$cache->addProperty($transaction->updated_at);
if ($cache->has()) {
return $cache->get();
}
// journal has a category:
if (isset($transaction->transaction_journal_category_id)) {
$name = app('steam')->tryDecrypt($transaction->transaction_journal_category_name);
$txt = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$transaction->transaction_journal_category_id]), $name, $name);
$cache->store($txt);
return $txt;
}
@@ -223,7 +180,6 @@ class Transaction extends Twig_Extension
if (isset($transaction->transaction_category_id)) {
$name = app('steam')->tryDecrypt($transaction->transaction_category_name);
$txt = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$transaction->transaction_category_id]), $name, $name);
$cache->store($txt);
return $txt;
}
@@ -240,14 +196,11 @@ class Transaction extends Twig_Extension
}
$txt = join(', ', $str);
$cache->store($txt);
return $txt;
}
$txt = '';
$cache->store($txt);
return $txt;
}
@@ -258,20 +211,11 @@ class Transaction extends Twig_Extension
*/
public function description(TransactionModel $transaction): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('description');
$cache->addProperty($transaction->id);
$cache->addProperty($transaction->updated_at);
if ($cache->has()) {
return $cache->get();
}
$description = $transaction->description;
if (strlen(strval($transaction->transaction_description)) > 0) {
$description = $transaction->transaction_description . '(' . $transaction->description . ')';
$description = $transaction->transaction_description . ' (' . $transaction->description . ')';
}
$cache->store($description);
return $description;
}
@@ -282,14 +226,6 @@ class Transaction extends Twig_Extension
*/
public function destinationAccount(TransactionModel $transaction): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('transaction-destination');
$cache->addProperty($transaction->id);
$cache->addProperty($transaction->updated_at);
if ($cache->has()) {
return $cache->get();
}
if (TransactionType::RECONCILIATION === $transaction->transaction_type_type) {
return '&mdash;';
}
@@ -325,13 +261,11 @@ class Transaction extends Twig_Extension
if (AccountType::CASH === $type) {
$txt = '<span class="text-success">(' . trans('firefly.cash') . ')</span>';
$cache->store($txt);
return $txt;
}
$txt = sprintf('<a title="%1$s" href="%2$s">%1$s</a>', e($name), route('accounts.show', [$transactionId]));
$cache->store($txt);
return $txt;
}
@@ -343,13 +277,6 @@ class Transaction extends Twig_Extension
*/
public function hasAttachments(TransactionModel $transaction): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('attachments');
$cache->addProperty($transaction->id);
$cache->addProperty($transaction->updated_at);
if ($cache->has()) {
return $cache->get();
}
$journalId = intval($transaction->journal_id);
$count = Attachment::whereNull('deleted_at')
->where('attachable_type', 'FireflyIII\Models\TransactionJournal')
@@ -357,13 +284,11 @@ class Transaction extends Twig_Extension
->count();
if ($count > 0) {
$res = sprintf('<i class="fa fa-paperclip" title="%s"></i>', Lang::choice('firefly.nr_of_attachments', $count, ['count' => $count]));
$cache->store($res);
return $res;
}
$res = '';
$cache->store($res);
return $res;
}
@@ -375,14 +300,6 @@ class Transaction extends Twig_Extension
*/
public function icon(TransactionModel $transaction): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('icon');
$cache->addProperty($transaction->id);
$cache->addProperty($transaction->updated_at);
if ($cache->has()) {
return $cache->get();
}
switch ($transaction->transaction_type_type) {
case TransactionType::WITHDRAWAL:
$txt = sprintf('<i class="fa fa-long-arrow-left fa-fw" title="%s"></i>', trans('firefly.withdrawal'));
@@ -403,7 +320,6 @@ class Transaction extends Twig_Extension
$txt = '';
break;
}
$cache->store($txt);
return $txt;
}
@@ -415,21 +331,11 @@ class Transaction extends Twig_Extension
*/
public function isReconciled(TransactionModel $transaction): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('transaction-reconciled');
$cache->addProperty($transaction->id);
$cache->addProperty($transaction->updated_at);
$cache->addProperty($transaction->reconciled);
if ($cache->has()) {
return $cache->get();
}
$icon = '';
if (1 === intval($transaction->reconciled)) {
$icon = '<i class="fa fa-check"></i>';
}
$cache->store($icon);
return $icon;
}
@@ -440,25 +346,14 @@ class Transaction extends Twig_Extension
*/
public function isSplit(TransactionModel $transaction): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('split');
$cache->addProperty($transaction->id);
$cache->addProperty($transaction->updated_at);
if ($cache->has()) {
return $cache->get();
}
$journalId = intval($transaction->journal_id);
$count = TransactionModel::where('transaction_journal_id', $journalId)->whereNull('deleted_at')->count();
if ($count > 2) {
$res = '<i class="fa fa-fw fa-share-alt" aria-hidden="true"></i>';
$cache->store($res);
return $res;
}
$res = '';
$cache->store($res);
return $res;
}
@@ -469,13 +364,6 @@ class Transaction extends Twig_Extension
*/
public function sourceAccount(TransactionModel $transaction): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('transaction-source');
$cache->addProperty($transaction->id);
$cache->addProperty($transaction->updated_at);
if ($cache->has()) {
return $cache->get();
}
if (TransactionType::RECONCILIATION === $transaction->transaction_type_type) {
return '&mdash;';
}
@@ -510,14 +398,11 @@ class Transaction extends Twig_Extension
if (AccountType::CASH === $type) {
$txt = '<span class="text-success">(' . trans('firefly.cash') . ')</span>';
$cache->store($txt);
return $txt;
}
$txt = sprintf('<a title="%1$s" href="%2$s">%1$s</a>', e($name), route('accounts.show', [$transactionId]));
$cache->store($txt);
return $txt;
}
}

View File

@@ -25,7 +25,6 @@ namespace FireflyIII\Support\Twig\Extension;
use FireflyIII\Models\Transaction as TransactionModel;
use FireflyIII\Models\TransactionJournal as JournalModel;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\SingleCacheProperties;
use Twig_Extension;
/**
@@ -40,14 +39,6 @@ class TransactionJournal extends Twig_Extension
*/
public function totalAmount(JournalModel $journal): string
{
$cache = new SingleCacheProperties;
$cache->addProperty('total-amount');
$cache->addProperty($journal->id);
$cache->addProperty($journal->updated_at);
if ($cache->has()) {
return $cache->get();
}
$transactions = $journal->transactions()->where('amount', '>', 0)->get();
$totals = [];
$type = $journal->transactionType->type;
@@ -84,7 +75,6 @@ class TransactionJournal extends Twig_Extension
$array[] = app('amount')->formatAnything($total['currency'], $total['amount']);
}
$txt = join(' / ', $array);
$cache->store($txt);
return $txt;
}

View File

@@ -256,9 +256,64 @@ class General extends Twig_Extension
return 'fa-file-o';
case 'application/pdf':
return 'fa-file-pdf-o';
/* image */
case 'image/png':
case 'image/jpeg':
case 'image/svg+xml':
case 'image/heic':
case 'image/heic-sequence':
case 'application/vnd.oasis.opendocument.image':
return 'fa-file-image-o';
/* MS word */
case 'application/msword':
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.template':
case 'application/x-iwork-pages-sffpages':
case 'application/vnd.sun.xml.writer':
case 'application/vnd.sun.xml.writer.template':
case 'application/vnd.sun.xml.writer.global':
case 'application/vnd.stardivision.writer':
case 'application/vnd.stardivision.writer-global':
case 'application/vnd.oasis.opendocument.text':
case 'application/vnd.oasis.opendocument.text-template':
case 'application/vnd.oasis.opendocument.text-web':
case 'application/vnd.oasis.opendocument.text-master':
return 'fa-file-word-o';
/* MS excel */
case 'application/vnd.ms-excel':
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.template':
case 'application/vnd.sun.xml.calc':
case 'application/vnd.sun.xml.calc.template':
case 'application/vnd.stardivision.calc':
case 'application/vnd.oasis.opendocument.spreadsheet':
case 'application/vnd.oasis.opendocument.spreadsheet-template':
return 'fa-file-excel-o';
/* MS powerpoint */
case 'application/vnd.ms-powerpoint':
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
case 'application/vnd.openxmlformats-officedocument.presentationml.template':
case 'application/vnd.openxmlformats-officedocument.presentationml.slideshow':
case 'application/vnd.sun.xml.impress':
case 'application/vnd.sun.xml.impress.template':
case 'application/vnd.stardivision.impress':
case 'application/vnd.oasis.opendocument.presentation':
case 'application/vnd.oasis.opendocument.presentation-template':
return 'fa-file-powerpoint-o';
/* calc */
case 'application/vnd.sun.xml.draw':
case 'application/vnd.sun.xml.draw.template':
case 'application/vnd.stardivision.draw':
case 'application/vnd.oasis.opendocument.chart':
return 'fa-paint-brush';
case 'application/vnd.oasis.opendocument.graphics':
case 'application/vnd.oasis.opendocument.graphics-template':
case 'application/vnd.sun.xml.math':
case 'application/vnd.stardivision.math':
case 'application/vnd.oasis.opendocument.formula':
case 'application/vnd.oasis.opendocument.database':
return 'fa-calculator';
}
},
['is_safe' => ['html']]

View File

@@ -32,6 +32,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Log;
use Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class User.
@@ -60,6 +61,23 @@ class User extends Authenticatable
*/
protected $table = 'users';
/**
* @param string $value
*
* @return User
*/
public static function routeBinder(string $value): User
{
if (auth()->check()) {
$userId = intval($value);
$user = self::find($userId);
if (!is_null($user)) {
return $user;
}
}
throw new NotFoundHttpException;
}
/**
* @codeCoverageIgnore
* Link to accounts.

View File

@@ -65,7 +65,7 @@ class FireflyValidator extends Validator
*
* @return bool
*/
public function validate2faCode($attribute, $value): bool
public function validate2faCode(/** @scrutinizer ignore-unused */ $attribute, $value): bool
{
if (!is_string($value) || null === $value || 6 != strlen($value)) {
return false;
@@ -85,7 +85,7 @@ class FireflyValidator extends Validator
*
* @return bool
*/
public function validateBelongsToUser($attribute, $value, $parameters): bool
public function validateBelongsToUser(/** @scrutinizer ignore-unused */ $attribute, $value, $parameters): bool
{
$field = $parameters[1] ?? 'id';
@@ -108,7 +108,7 @@ class FireflyValidator extends Validator
*
* @return bool
*/
public function validateBic($attribute, $value): bool
public function validateBic(/** @scrutinizer ignore-unused */ $attribute, $value): bool
{
$regex = '/^[a-z]{6}[0-9a-z]{2}([0-9a-z]{3})?\z/i';
$result = preg_match($regex, $value);
@@ -130,13 +130,63 @@ class FireflyValidator extends Validator
*
* @return bool
*/
public function validateIban($attribute, $value): bool
public function validateIban(/** @scrutinizer ignore-unused */ $attribute, $value): bool
{
if (!is_string($value) || null === $value || strlen($value) < 6) {
return false;
}
$value = strtoupper($value);
// strip spaces
$search = [
"\x20", // normal space
"\u{0001}", // start of heading
"\u{0002}", // start of text
"\u{0003}", // end of text
"\u{0004}", // end of transmission
"\u{0005}", // enquiry
"\u{0006}", // ACK
"\u{0007}", // BEL
"\u{0008}", // backspace
"\u{000E}", // shift out
"\u{000F}", // shift in
"\u{0010}", // data link escape
"\u{0011}", // DC1
"\u{0012}", // DC2
"\u{0013}", // DC3
"\u{0014}", // DC4
"\u{0015}", // NAK
"\u{0016}", // SYN
"\u{0017}", // ETB
"\u{0018}", // CAN
"\u{0019}", // EM
"\u{001A}", // SUB
"\u{001B}", // escape
"\u{001C}", // file separator
"\u{001D}", // group separator
"\u{001E}", // record separator
"\u{001F}", // unit separator
"\u{007F}", // DEL
"\u{00A0}", // non-breaking space
"\u{1680}", // ogham space mark
"\u{180E}", // mongolian vowel separator
"\u{2000}", // en quad
"\u{2001}", // em quad
"\u{2002}", // en space
"\u{2003}", // em space
"\u{2004}", // three-per-em space
"\u{2005}", // four-per-em space
"\u{2006}", // six-per-em space
"\u{2007}", // figure space
"\u{2008}", // punctuation space
"\u{2009}", // thin space
"\u{200A}", // hair space
"\u{200B}", // zero width space
"\u{202F}", // narrow no-break space
"\u{3000}", // ideographic space
"\u{FEFF}", // zero width no -break space
];
$replace = '';
$value = str_replace($search, $replace, $value);
$value = strtoupper($value);
$search = [' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
$replace = ['', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31',
@@ -160,7 +210,7 @@ class FireflyValidator extends Validator
*
* @return bool
*/
public function validateMore($attribute, $value, $parameters): bool
public function validateMore(/** @scrutinizer ignore-unused */ $attribute, $value, $parameters): bool
{
$compare = $parameters[0] ?? '0';
@@ -176,7 +226,7 @@ class FireflyValidator extends Validator
*
* @return bool
*/
public function validateMustExist($attribute, $value, $parameters): bool
public function validateMustExist(/** @scrutinizer ignore-unused */ $attribute, $value, $parameters): bool
{
$field = $parameters[1] ?? 'id';
@@ -285,7 +335,7 @@ class FireflyValidator extends Validator
*
* @return bool
*/
public function validateSecurePassword($attribute, $value): bool
public function validateSecurePassword(/** @scrutinizer ignore-unused */ $attribute, $value): bool
{
$verify = false;
if (isset($this->data['verify_password'])) {
@@ -310,7 +360,7 @@ class FireflyValidator extends Validator
*
* @return bool
*/
public function validateUniqueAccountForUser($attribute, $value, $parameters): bool
public function validateUniqueAccountForUser(/** @scrutinizer ignore-unused */ $attribute, $value, $parameters): bool
{
// because a user does not have to be logged in (tests and what-not).
if (!auth()->check()) {
@@ -340,7 +390,7 @@ class FireflyValidator extends Validator
*
* @return bool
*/
public function validateUniqueAccountNumberForUser($attribute, $value): bool
public function validateUniqueAccountNumberForUser(/** @scrutinizer ignore-unused */ $attribute, $value): bool
{
$accountId = $this->data['id'] ?? 0;
@@ -378,7 +428,7 @@ class FireflyValidator extends Validator
*
* @return bool
*/
public function validateUniqueObjectForUser($attribute, $value, $parameters): bool
public function validateUniqueObjectForUser(/** @scrutinizer ignore-unused */ $attribute, $value, $parameters): bool
{
$value = $this->tryDecrypt($value);
// exclude?
@@ -410,7 +460,7 @@ class FireflyValidator extends Validator
*
* @return bool
*/
public function validateUniquePiggyBankForUser($attribute, $value, $parameters): bool
public function validateUniquePiggyBankForUser(/** @scrutinizer ignore-unused */ $attribute, $value, $parameters): bool
{
$exclude = $parameters[0] ?? null;
$query = DB::table('piggy_banks')->whereNull('piggy_banks.deleted_at')

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