diff --git a/.ci/.env.ci b/.ci/.env.ci new file mode 100644 index 0000000000..94995a20c3 --- /dev/null +++ b/.ci/.env.ci @@ -0,0 +1,326 @@ +# 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 + +# This should be your email address. +# If you use Docker or similar, you can set this variable from a file by using SITE_OWNER_FILE +SITE_OWNER=mail@example.com + +# The encryption key for your sessions. Keep this very secure. +# If you generate a new one all existing attachments must be considered LOST. +# Change it to a string of exactly 32 chars or use something like `php artisan key:generate` to generate it. +# If you use Docker or similar, you can set this variable from a file by using APP_KEY_FILE +APP_KEY=SomeRandomStringOf32CharsExactly + +# +# Firefly III will launch using this language (for new users and unauthenticated visitors) +# For a list of available languages: https://github.com/firefly-iii/firefly-iii/tree/main/resources/lang +# +# If text is still in English, remember that not everything may have been translated. +DEFAULT_LANGUAGE=en_US + +# The locale defines how numbers are formatted. +# by default this value is the same as whatever the language is. +DEFAULT_LOCALE=equal + +# Change this value to your preferred time zone. +# Example: Europe/Amsterdam +# For a list of supported time zones, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +TZ=Europe/Amsterdam + +# TRUSTED_PROXIES is a useful variable when using Docker and/or a reverse proxy. +# Set it to ** and reverse proxies work just fine. +TRUSTED_PROXIES= + +# The log channel defines where your log entries go to. +# Several other options exist. You can use 'single' for one big fat error log (not recommended). +# Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself. +# A rotating log option is 'daily', creates 5 files that (surprise) rotate. +# Default setting 'stack' will log to 'daily' and to 'stdout' at the same time. + +# - Docker + versions <= 4.8.1.8 and before: use "stdout" +# - Docker + versions > 4.8.1.8 : use "docker_out" +# - Docker + versions >= 5.1.1 : use "stack" +# - For everything else (als not Docker) : use 'stack' + +LOG_CHANNEL=single + +# 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 + +# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III +# For other database types, please see the FAQ: https://docs.firefly-iii.org/support/faq +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE +# Use "pgsql" for PostgreSQL +# Use "mysql" for MySQL and MariaDB. +# Use "sqlite" for SQLite. +DB_CONNECTION=sqlite_test + +# MySQL supports SSL. You can configure it here. +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE +MYSQL_USE_SSL=false +MYSQL_SSL_VERIFY_SERVER_CERT=true +# You need to set at least of these options +MYSQL_SSL_CAPATH=/etc/ssl/certs/ +MYSQL_SSL_CA= +MYSQL_SSL_CERT= +MYSQL_SSL_KEY= +MYSQL_SSL_CIPHER= + +# PostgreSQL supports SSL. You can configure it here. +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE +PGSQL_SSL_MODE=prefer +PGSQL_SSL_ROOT_CERT=null +PGSQL_SSL_CERT=null +PGSQL_SSL_KEY=null +PGSQL_SSL_CRL_FILE=null + +# If you're looking for performance improvements, you could install memcached. +CACHE_DRIVER=array +SESSION_DRIVER=array + +# If you set either of these to 'redis', you might want to update these settings too +# If you use Docker or similar, you can set REDIS_HOST_FILE, REDIS_PASSWORD_FILE or +# REDIS_PORT_FILE to set the value from a file instead of from an environment variable + +# can be tcp, unix or http +REDIS_SCHEME=tcp + +# use only when using 'unix' for REDIS_SCHEME. Leave empty otherwise. +REDIS_PATH= + +# use only when using 'tcp' or 'http' for REDIS_SCHEME. Leave empty otherwise. +REDIS_HOST=127.0.0.1 +REDIS_PORT=6379 + +REDIS_PASSWORD=null +# always use quotes and make sure redis db "0" and "1" exists. Otherwise change accordingly. +REDIS_DB="0" +REDIS_CACHE_DB="1" + +# Cookie settings. Should not be necessary to change these. +# If you use Docker or similar, you can set COOKIE_DOMAIN_FILE to set +# the value from a file instead of from an environment variable +COOKIE_PATH="/" +COOKIE_DOMAIN= +COOKIE_SECURE=false + +# If you want Firefly III to mail you, update these settings +# For instructions, see: https://docs.firefly-iii.org/advanced-installation/email +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE +MAIL_MAILER=log +MAIL_HOST=null +MAIL_PORT=2525 +MAIL_FROM=changeme@example.com +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null + +# Other mail drivers: +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE +MAILGUN_DOMAIN= +MAILGUN_SECRET= + + +# If you are on EU region in mailgun, use api.eu.mailgun.net, otherwise use api.mailgun.net +# If you use Docker or similar, you can set this variable from a file by appending it with _FILE +MAILGUN_ENDPOINT=api.mailgun.net + +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE +MANDRILL_SECRET= +SPARKPOST_SECRET= + + +# Firefly III can send you the following messages +SEND_REGISTRATION_MAIL=true +SEND_ERROR_MESSAGE=true + +# These messages contain (sensitive) transaction information: +SEND_REPORT_JOURNALS=true + +# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places. +# If you use Docker or similar, you can set this variable from a file by appending it with _FILE +MAPBOX_API_KEY= + +# The map will default to this location: +MAP_DEFAULT_LAT=51.983333 +MAP_DEFAULT_LONG=5.916667 +MAP_DEFAULT_ZOOM=6 + +# Firefly III currently supports two provider for live Currency Exchange Rates: +# "fixer", and "ratesapi". +# RatesApi.IO (see https://ratesapi.io) is a FREE and OPEN SOURCE live currency exchange rates, +# built compatible with Fixer.IO, based on data published by European Central Bank, and doesn't require API key. +CER_PROVIDER=ratesapi + +# If you have select "fixer" as default currency exchange rates, +# set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. +# Please note that this WILL ONLY WORK FOR PAID fixer.io accounts because they severely limited +# the free API up to the point where you might as well offer nothing. +# If you use Docker or similar, you can set this variable from a file by appending it with _FILE +FIXER_API_KEY= + +# Firefly III has two options for user authentication. "eloquent" is the default, +# and "ldap" for LDAP servers. +# For full instructions on these settings please visit: +# https://docs.firefly-iii.org/advanced-installation/authentication +# If you use Docker or similar, you can set this variable from a file by appending it with _FILE +LOGIN_PROVIDER=eloquent + +# +# It's also possible to change the way users are authenticated. You could use Authelia for example. +# Authentication via the REMOTE_USER header is supported. Change the value below to "remote_user_guard". +# +# If you do this please read the documentation for instructions and warnings: +# https://docs.firefly-iii.org/advanced-installation/authentication +# +# This function is available in Firefly III v5.3.0 and higher. +AUTHENTICATION_GUARD=web + +# +# Likewise, it's impossible to log out users who's authentication is handled by an external system. +# Enter a custom URL here that will force a logout (your authentication provider can tell you). +# Setting this variable only works when AUTHENTICATION_GUARD != web +# +CUSTOM_LOGOUT_URI= + +# LDAP connection configuration +# OpenLDAP, FreeIPA or ActiveDirectory +# # If you use Docker or similar, you can set this variable from a file by appending it with _FILE +ADLDAP_CONNECTION_SCHEME=OpenLDAP +ADLDAP_AUTO_CONNECT=true + +# LDAP connection settings +# You can set the following variables from a file by appending them with _FILE: +# ADLDAP_CONTROLLERS, ADLDAP_PORT, ADLDAP_BASEDN +ADLDAP_CONTROLLERS= +ADLDAP_PORT=389 +ADLDAP_TIMEOUT=5 +ADLDAP_BASEDN="" +ADLDAP_FOLLOW_REFFERALS=false + +# SSL/TLS settings +ADLDAP_USE_SSL=false +ADLDAP_USE_TLS=false +ADLDAP_SSL_CACERTDIR= +ADLDAP_SSL_CACERTFILE= +ADLDAP_SSL_CERTFILE= +ADLDAP_SSL_KEYFILE= +ADLDAP_SSL_CIPHER_SUITE= +ADLDAP_SSL_REQUIRE_CERT= + +# You can set the following variables from a file by appending them with _FILE: +ADLDAP_ADMIN_USERNAME= +ADLDAP_ADMIN_PASSWORD= + +# You can set the following variables from a file by appending them with _FILE: +ADLDAP_ACCOUNT_PREFIX= +ADLDAP_ACCOUNT_SUFFIX= + + +# LDAP authentication settings. +ADLDAP_PASSWORD_SYNC=false +ADLDAP_LOGIN_FALLBACK=false + +ADLDAP_DISCOVER_FIELD=distinguishedname +ADLDAP_AUTH_FIELD=distinguishedname + +# Will allow SSO if your server provides an AUTH_USER field. +# You can set the following variables from a file by appending them with _FILE: +WINDOWS_SSO_ENABLED=false +WINDOWS_SSO_DISCOVER=samaccountname +WINDOWS_SSO_KEY=AUTH_USER + +# field to sync as local username. +# You can set the following variable from a file by appending it with _FILE: +ADLDAP_SYNC_FIELD=userprincipalname + +# You can disable the X-Frame-Options header if it interferes with tools like +# Organizr. This is at your own risk. Applications running in frames run the risk +# of leaking information to their parent frame. +DISABLE_FRAME_HEADER=false + +# You can disable the Content Security Policy header when you're using an ancient browser +# or any version of Microsoft Edge / Internet Explorer (which amounts to the same thing really) +# This leaves you with the risk of not being able to stop XSS bugs should they ever surface. +# This is at your own risk. +DISABLE_CSP_HEADER=false + +# If you wish to track your own behavior over Firefly III, set valid analytics tracker information here. +# Nobody uses this except for me on the demo site. But hey, feel free to use this if you want to. +# Do not prepend the TRACKER_URL with http:// or https:// +# The only tracker supported is Matomo. +# You can set the following variables from a file by appending them with _FILE: +TRACKER_SITE_ID= +TRACKER_URL= + +# +# Firefly III can collect telemetry on how you use Firefly III. This is opt-in. +# In order to allow this, change the following variable to true. +# To read more about this feature, go to this page: https://docs.firefly-iii.org/support/telemetry +SEND_TELEMETRY=false + +# You can fine tune the start-up of a Docker container by editing these environment variables. +# Use this at your own risk. Disabling certain checks and features may result in lost of inconsistent data. +# However if you know what you're doing you can significantly speed up container start times. +# Set each value to true to enable, or false to disable. + +# Check if the SQLite database exists. Can be skipped if you're not using SQLite. +# Won't significantly speed up things. +DKR_CHECK_SQLITE=true + +# Run database creation and migration commands. Disable this only if you're 100% sure the DB exists +# and is up to date. +DKR_RUN_MIGRATION=true + +# Run database upgrade commands. Disable this only when you're 100% sure your DB is up-to-date +# with the latest fixes (outside of migrations!) +DKR_RUN_UPGRADE=true + +# Verify database integrity. Includes all data checks and verifications. +# Disabling this makes Firefly III assume your DB is intact. +DKR_RUN_VERIFY=true + +# Run database reporting commands. When disabled, Firefly III won't go over your data to report current state. +# Disabling this should have no impact on data integrity or safety but it won't warn you of possible issues. +DKR_RUN_REPORT=true + +# Generate OAuth2 keys. +# When disabled, Firefly III won't attempt to generate OAuth2 Passport keys. This won't be an issue, IFF (if and only if) +# you had previously generated keys already and they're stored in your database for restoration. +DKR_RUN_PASSPORT_INSTALL=true + +# Leave the following configuration vars as is. +# Unless you like to tinker and know what you're doing. +APP_NAME=FireflyIII +ADLDAP_CONNECTION=default +BROADCAST_DRIVER=log +QUEUE_DRIVER=sync +CACHE_PREFIX=firefly +SEARCH_RESULT_LIMIT=50 +PUSHER_KEY= +PUSHER_SECRET= +PUSHER_ID= +DEMO_USERNAME= +DEMO_PASSWORD= +USE_ENCRYPTION=false +IS_HEROKU=false +FIREFLY_III_LAYOUT=v1 + +# +# If you have trouble configuring your Firefly III installation, DON'T BOTHER setting this variable. +# It won't work. It doesn't do ANYTHING. Don't believe the lies you read online. I'm not joking. +# This configuration value WILL NOT HELP. +# +# This variable is ONLY used in some of the emails Firefly III sends around. Nowhere else. +# So when configuring anything WEB related this variable doesn't do anything. Nothing +# +# If you're stuck I understand you get desperate but look SOMEWHERE ELSE. +# +APP_URL=http://localhost diff --git a/.ci/firefly-iii-standard.yml b/.ci/firefly-iii-standard.yml new file mode 100644 index 0000000000..c863d43be6 --- /dev/null +++ b/.ci/firefly-iii-standard.yml @@ -0,0 +1,250 @@ +parameters: + indentation: spaces + + file_extensions: + - php + + exclude_files: + - fixtures/* + - fixtures*/* + - temp/* + - tmp/* + +services: + # Checkers bellow aim on 1:1 copy of https://nette.org/en/coding-standard + + # General rules - https://nette.org/en/coding-standard#toc-general-rules + + # use tabs over spaces + # PHP_CodeSniffer\Standards\Generic\Sniffs\WhiteSpace\DisallowSpaceIndentSniff: ~ + # PHP code must use only UTF-8 without BOM + PhpCsFixer\Fixer\Basic\EncodingFixer: ~ + # tag must be omitted from files containing only PHP. + PhpCsFixer\Fixer\PhpTag\NoClosingTagFixer: ~ + # There must not be trailing whitespace at the end of lines. + PhpCsFixer\Fixer\Whitespace\NoTrailingWhitespaceFixer: ~ + # ...and at the end of blank lines. + PhpCsFixer\Fixer\Whitespace\NoWhitespaceInBlankLineFixer: ~ + # All files must end with a single blank line. + PhpCsFixer\Fixer\Whitespace\SingleBlankLineAtEofFixer: ~ + # File name should match class name if possible. + PhpCsFixer\Fixer\Basic\Psr4Fixer: ~ + # Enforces using shorthand scalar typehint variants in phpDocs: `int` instead of `integer` and `bool` instead of `boolean` + SlevomatCodingStandard\Sniffs\TypeHints\LongTypeHintsSniff: ~ + + # File Header - https://nette.org/en/coding-standard#toc-file-header + + # empty line before namespace + PhpCsFixer\Fixer\NamespaceNotation\SingleBlankLineBeforeNamespaceFixer: ~ + # 1 Use statement per line + PhpCsFixer\Fixer\Import\SingleImportPerStatementFixer: ~ + # Use statements are alphabetically ordered + PhpCsFixer\Fixer\Import\OrderedImportsFixer: ~ + # disallow group use declarations use FooLibrary\Bar\Baz\{ ClassA, ClassB, ClassC, ClassD as Fizbo } + SlevomatCodingStandard\Sniffs\Namespaces\DisallowGroupUseSniff: ~ + # Disallows leading backslash in use statement: use \Foo\Bar; + SlevomatCodingStandard\Sniffs\Namespaces\UseDoesNotStartWithBackslashSniff: ~ + # Looks for unused imports from other namespaces. + Nette\CodingStandard\Sniffs\Namespaces\UnusedUsesSniff: + searchAnnotations: yes + ignoredAnnotationNames: ['@testCase'] + ignoredAnnotations: ['@internal'] + + # Language Construct (should be placed before some other fixers) + + # Functions should be used with `$strict` param set to `true` + PhpCsFixer\Fixer\Strict\StrictParamFixer: ~ + # replaces is_null(parameter) expression with `null === parameter`. + PhpCsFixer\Fixer\LanguageConstruct\IsNullFixer: + use_yoda_style: true + # Calling `unset` on multiple items should be done in one call. + PhpCsFixer\Fixer\LanguageConstruct\CombineConsecutiveUnsetsFixer: ~ + # Replace all `<>` with `!=`. + PhpCsFixer\Fixer\Operator\StandardizeNotEqualsFixer: ~ + # Include/Require and file path should be divided with a single space. File path should not be placed under brackets. + PhpCsFixer\Fixer\ControlStructure\IncludeFixer: ~ + # Requires short ternary operator ?: when possible + SlevomatCodingStandard\Sniffs\ControlStructures\RequireShortTernaryOperatorSniff: ~ + + # Arrays - https://nette.org/en/coding-standard#toc-arrays + + # use short array fixes + PhpCsFixer\Fixer\ArrayNotation\ArraySyntaxFixer: + syntax: short + # use trailing command in last array element + PhpCsFixer\Fixer\ArrayNotation\TrailingCommaInMultilineArrayFixer: ~ + # PHP single-line arrays should not have trailing comma. + # PhpCsFixer\Fixer\ArrayNotation\NoTrailingCommaInSinglelineArrayFixer: ~ + # In array declaration, there MUST NOT be a whitespace before each comma. + PhpCsFixer\Fixer\ArrayNotation\NoWhitespaceBeforeCommaInArrayFixer: ~ + # Arrays should be formatted like function/method arguments, without leading or trailing single line space. + PhpCsFixer\Fixer\ArrayNotation\TrimArraySpacesFixer: ~ + # In array declaration, there MUST be a whitespace after each comma. + PhpCsFixer\Fixer\ArrayNotation\WhitespaceAfterCommaInArrayFixer: ~ + + # Strings + + # Convert `heredoc` to `nowdoc` where possible. + PhpCsFixer\Fixer\StringNotation\HeredocToNowdocFixer: ~ + # Convert double quotes to single quotes for simple strings. + PhpCsFixer\Fixer\StringNotation\SingleQuoteFixer: ~ + + # Keywords and True/False/Null - https://nette.org/en/coding-standard#toc-keywords-and-true-false-null + + # PHP keywords must be in lower case + PhpCsFixer\Fixer\Casing\LowercaseKeywordsFixer: ~ + # The PHP constants `true`, `false`, and `null` MUST be in lower case + PhpCsFixer\Fixer\Casing\LowercaseConstantsFixer: ~ + + # Method and Functions Calls - https://nette.org/en/coding-standard#toc-method-and-function-calls + + # Function defined by PHP should be called using the correct casing + PhpCsFixer\Fixer\Casing\NativeFunctionCasingFixer: ~ + # In the argument list, there must be one space after each comma, and there must no be a space before each comma + PhpCsFixer\Fixer\FunctionNotation\MethodArgumentSpaceFixer: ~ + # This sniff checks that there are two blank lines between functions declarations and single between signatures. + #Nette\CodingStandard\Sniffs\WhiteSpace\FunctionSpacingSniff: ~ + + # Classes - https://nette.org/en/coding-standard#toc-classes + + # Inside a classy element "self" should be preferred to the class name itself. + PhpCsFixer\Fixer\ClassNotation\SelfAccessorFixer: ~ + # class element order: constants, properties, from public to private + PhpCsFixer\Fixer\ClassNotation\OrderedClassElementsFixer: + order: + - use_trait + - constant + - constant_public + - constant_protected + - constant_private + - property_public + - property_protected + - property_private + + # Constants - https://nette.org/en/coding-standard#toc-constants + + # constant names are CAPITALIZED (manuall fixing only :() + PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\UpperCaseConstantNameSniff: ~ + + # Class Properties - https://nette.org/en/coding-standard#toc-class-properties + + # There MUST NOT be more than one property declared per statement. + PhpCsFixer\Fixer\ClassNotation\SingleClassElementPerStatementFixer: + elements: ['property'] + + # Methods - https://nette.org/en/coding-standard#toc-methods + + # They must be declared in camelCase. + PHP_CodeSniffer\Standards\PSR1\Sniffs\Methods\CamelCapsMethodNameSniff: ~ + # Checks that there's a single space between a typehint and a parameter name and no whitespace between a nullability symbol and a typehint + SlevomatCodingStandard\Sniffs\TypeHints\ParameterTypeHintSpacingSniff: ~ + # Spaces should be properly placed in a function declaration. + PhpCsFixer\Fixer\FunctionNotation\FunctionDeclarationFixer: ~ + # In function arguments there must not be arguments with default values before non-default ones. + PhpCsFixer\Fixer\FunctionNotation\NoUnreachableDefaultArgumentValueFixer: ~ + + # Constans, Class Properties, Methods + + # Constants and Properties should be separated by 1 blank line + #PhpCsFixer\Fixer\ClassNotation\ClassAttributesSeparationFixer: + # elements: [const, property] + + # Last property and 1st method should be separated by 2 spaces + Nette\CodingStandard\Fixer\ClassNotation\LastPropertyAndFirstMethodSeparationFixer: + space_count: 2 + + # Control Statements - https://nette.org/en/coding-standard#toc-control-statements + + # The keyword `elseif` should be used instead of `else if` so that all control keywords look like single words. + PhpCsFixer\Fixer\ControlStructure\ElseifFixer: ~ + # Remove useless semicolon statements. + PhpCsFixer\Fixer\Semicolon\NoEmptyStatementFixer: ~ + # Remove trailing commas in list() calls. + PhpCsFixer\Fixer\ControlStructure\NoTrailingCommaInListCallFixer: ~ + # Removes unneeded parentheses around control statements. + PhpCsFixer\Fixer\ControlStructure\NoUnneededControlParenthesesFixer: ~ + # A case should be followed by a colon and not a semicolon. + PhpCsFixer\Fixer\ControlStructure\SwitchCaseSemicolonToColonFixer: ~ + # The structure body must be indented once. + # The closing brace must be on the next line after the body. + # There should not be more than one statement per line. + #Nette\CodingStandard\Fixer\Basic\BracesFixer: + # allow_single_line_closure: true + # changes if (1 === $cond) to if ($cond === 1) + #SlevomatCodingStandard\Sniffs\ControlStructures\DisallowYodaComparisonSniff: ~ + # finds unreachable catch blocks: + SlevomatCodingStandard\Sniffs\Exceptions\DeadCatchSniff: ~ + + # Casting + + # A single space or none should be between cast and variable (int) $val + PhpCsFixer\Fixer\CastNotation\CastSpacesFixer: ~ + # Cast should be written in lower case. + PhpCsFixer\Fixer\CastNotation\LowercaseCastFixer: ~ + # Replaces `intval`, `floatval`, `doubleval`, `strval` and `boolval` function calls with according type casting operator + PhpCsFixer\Fixer\CastNotation\ModernizeTypesCastingFixer: ~ + # Short cast `bool` using double exclamation mark should not be used + PhpCsFixer\Fixer\CastNotation\NoShortBoolCastFixer: ~ + # Cast `(boolean)` and `(integer)` should be written as `(bool)` and `(int)`, `(double)` and `(real)` as `(float)` + PhpCsFixer\Fixer\CastNotation\ShortScalarCastFixer: ~ + + # Language Whitespace + + # Binary operators should be surrounded by at least one space. DO NOT USE + #PhpCsFixer\Fixer\Operator\BinaryOperatorSpacesFixer: ~ + # Unary operators should be placed adjacent to their operands. + PhpCsFixer\Fixer\Operator\UnaryOperatorSpacesFixer: ~ + # No space after the opening parenthesis and before the closing parenthesis + PhpCsFixer\Fixer\Whitespace\NoSpacesInsideParenthesisFixer: ~ + # There MUST NOT be spaces around offset braces $a[0] + PhpCsFixer\Fixer\Whitespace\NoSpacesAroundOffsetFixer: ~ + # There should not be space before or after object `T_OBJECT_OPERATOR` `->`. + PhpCsFixer\Fixer\Operator\ObjectOperatorWithoutWhitespaceFixer: ~ + # Standardize spaces around ternary operator. + PhpCsFixer\Fixer\Operator\TernaryOperatorSpacesFixer: ~ + # Concatenation $a . $b should be spaced according configuration + PhpCsFixer\Fixer\Operator\ConcatSpaceFixer: + spacing: one + # Removes extra spaces between colon and case value. + PhpCsFixer\Fixer\ControlStructure\SwitchCaseSpaceFixer: ~ + + # Comments + + # Docblocks should have the same indentation as the documented subject. + PhpCsFixer\Fixer\Phpdoc\PhpdocIndentFixer: ~ + # There should not be any empty comments. + PhpCsFixer\Fixer\Comment\NoEmptyCommentFixer: ~ + # There should not be empty PHPDoc blocks. + #PhpCsFixer\Fixer\Phpdoc\NoEmptyPhpdocFixer: ~ + # Phpdocs should start and end with content, excluding the very first and last line of the docblocks. + PhpCsFixer\Fixer\Phpdoc\PhpdocTrimFixer: ~ + # Single-line comments comments with only one line of actual content should use the `//` syntax. + PhpCsFixer\Fixer\Comment\SingleLineCommentStyleFixer: + comment_types: ['hash'] + # Require comments with single-line content to be written as one-liners + SlevomatCodingStandard\Sniffs\Commenting\RequireOneLinePropertyDocCommentSniff: ~ + + + # Properties MUST not be explicitly initialized with `null`. + #PhpCsFixer\Fixer\ClassNotation\NoNullPropertyInitializationFixer: ~ + + PhpCsFixer\Fixer\ControlStructure\NoBreakCommentFixer: + comment_text: 'break omitted' + + # declare(strict_types=1); + PhpCsFixer\Fixer\Strict\DeclareStrictTypesFixer: ~ + # Enforces consistent formatting of return typehints: function foo(): ?int + SlevomatCodingStandard\Sniffs\TypeHints\ReturnTypeHintSpacingSniff: ~ + # Use `null` coalescing operator `??` where possible. + PhpCsFixer\Fixer\Operator\TernaryToNullCoalescingFixer: ~ + + Nette\CodingStandard\Fixer\ClassNotation\ClassAndTraitVisibilityRequiredFixer: + elements: ['const', 'property', 'method'] + + # short list() syntax [] + PhpCsFixer\Fixer\ListNotation\ListSyntaxFixer: + syntax: short diff --git a/.ci/phpstan.neon b/.ci/phpstan.neon new file mode 100644 index 0000000000..f32333ea46 --- /dev/null +++ b/.ci/phpstan.neon @@ -0,0 +1,19 @@ +includes: + - ../vendor/nunomaduro/larastan/extension.neon + - ../vendor/ergebnis/phpstan-rules/rules.neon + - ../vendor/phpstan/phpstan-deprecation-rules/rules.neon + - ../vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon + +parameters: + ignoreErrors: + - '#is not allowed to extend#' + - '#is neither abstract nor final#' + - '#Control structures using switch should not be used\.#' + paths: + - ../app + - ../database + - ../routes + - ../bootstrap/app.php + + # The level 8 is the highest level. original was 5 + level: 0 diff --git a/.ci/phpstan.sh b/.ci/phpstan.sh new file mode 100755 index 0000000000..d215fb6e27 --- /dev/null +++ b/.ci/phpstan.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# Install composer packages +composer install --no-suggest --no-scripts --no-ansi + +# enable test .env file. +cp .ci/.env.ci .env + +# Do static code analysis. +./vendor/bin/phpstan analyse -c .ci/phpstan.neon --no-progress + +exit 0 \ No newline at end of file diff --git a/.ci/phpunit.sh b/.ci/phpunit.sh new file mode 100755 index 0000000000..6a2ff64bc9 --- /dev/null +++ b/.ci/phpunit.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# enable test .env file. +cp .ci/.env.ci ../.env + +# download test database +wget --quiet https://raw.githubusercontent.com/firefly-iii/test-data/main/test_db.sqlite -o storage/database/test_db.sqlite + +# run phpunit +./vendor/bin/phpunit --configuration phpunit.coverage.xml + +exit 0 diff --git a/.deploy/heroku/.env.heroku b/.deploy/heroku/.env.heroku index 78d8067adb..b649b5a1ae 100644 --- a/.deploy/heroku/.env.heroku +++ b/.deploy/heroku/.env.heroku @@ -5,77 +5,118 @@ APP_ENV=heroku # Set to true if you want to see debug information in error screens. APP_DEBUG=false -# This should be your email address +# This should be your email address. +# If you use Docker or similar, you can set this variable from a file by using SITE_OWNER_FILE 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 +# The encryption key for your sessions. Keep this very secure. +# If you generate a new one all existing attachments must be considered LOST. +# Change it to a string of exactly 32 chars or use something like `php artisan key:generate` to generate it. +# If you use Docker or similar, you can set this variable from a file by using APP_KEY_FILE APP_KEY=7ahyYVPVsmxjdhsweWCauGeJfwc92NP2 +# +# Firefly III will launch using this language (for new users and unauthenticated visitors) +# For a list of available languages: https://github.com/firefly-iii/firefly-iii/tree/main/resources/lang +# +# If text is still in English, remember that not everything may have been translated. +DEFAULT_LANGUAGE=en_US + +# The locale defines how numbers are formatted. +# by default this value is the same as whatever the language is. +DEFAULT_LOCALE=equal + # Change this value to your preferred time zone. # Example: Europe/Amsterdam +# For a list of supported time zones, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones TZ=UTC -# This variable must match your installation's external address but keep in mind that -# it's only used on the command line as a fallback value. -APP_URL=http://localhost - # TRUSTED_PROXIES is a useful variable when using Docker and/or a reverse proxy. +# Set it to ** and reverse proxies work just fine. TRUSTED_PROXIES=** # The log channel defines where your log entries go to. -# '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', 'errorlog' and 'stdout' which will log to the system itself. +# A rotating log option is 'daily', creates 5 files that (surprise) rotate. +# Default setting 'stack' will log to 'daily' and to 'stdout' at the same time. + +# - Docker + versions <= 4.8.1.8 and before: use "stdout" +# - Docker + versions > 4.8.1.8 : use "docker_out" +# - Docker + versions >= 5.1.1 : use "stack" +# - For everything else (als not Docker) : use 'stack' + LOG_CHANNEL=stdout # 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 +APP_LOG_LEVEL=notice # 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. +# For other database types, please see the FAQ: https://docs.firefly-iii.org/support/faq +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE +# Use "pgsql" for PostgreSQL +# Use "mysql" for MySQL and MariaDB. +# Use "sqlite" for SQLite. DB_CONNECTION=pgsql +# MySQL supports SSL. You can configure it here. +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE +MYSQL_USE_SSL=false +MYSQL_SSL_VERIFY_SERVER_CERT=true +# You need to set at least of these options +MYSQL_SSL_CAPATH=/etc/ssl/certs/ +MYSQL_SSL_CA= +MYSQL_SSL_CERT= +MYSQL_SSL_KEY= +MYSQL_SSL_CIPHER= # PostgreSQL supports SSL. You can configure it here. +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE PGSQL_SSL_MODE=prefer PGSQL_SSL_ROOT_CERT=null PGSQL_SSL_CERT=null PGSQL_SSL_KEY=null PGSQL_SSL_CRL_FILE=null - # If you're looking for performance improvements, you could install memcached. CACHE_DRIVER=file SESSION_DRIVER=file -# You can configure another file storage backend if you cannot use the local storage option. -# To set this up, fill in the following variables. The upload path is used to store uploaded -# files and the export path is to store exported data (before download). -SFTP_HOST= -SFTP_PORT= -SFTP_UPLOAD_PATH= -SFTP_EXPORT_PATH= +# If you set either of these to 'redis', you might want to update these settings too +# If you use Docker or similar, you can set REDIS_HOST_FILE, REDIS_PASSWORD_FILE or +# REDIS_PORT_FILE to set the value from a file instead of from an environment variable -# SFTP uses either the username/password combination or the private key to authenticate. -SFTP_USERNAME= -SFTP_PASSWORD= -SFTP_PRIV_KEY= +# can be tcp, unix or http +REDIS_SCHEME=tcp + +# use only when using 'unix' for REDIS_SCHEME. Leave empty otherwise. +REDIS_PATH= + +# use only when using 'tcp' or 'http' for REDIS_SCHEME. Leave empty otherwise. +REDIS_HOST=127.0.0.1 +REDIS_PORT=6379 + +REDIS_PASSWORD=null +# always use quotes and make sure redis db "0" and "1" exists. Otherwise change accordingly. +REDIS_DB="0" +REDIS_CACHE_DB="1" # Cookie settings. Should not be necessary to change these. +# If you use Docker or similar, you can set COOKIE_DOMAIN_FILE to set +# the value from a file instead of from an environment variable COOKIE_PATH="/" COOKIE_DOMAIN= COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings -# For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html -MAIL_DRIVER=log -MAIL_HOST=smtp.mailtrap.io +# For instructions, see: https://docs.firefly-iii.org/advanced-installation/email +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE +MAIL_MAILER=log +MAIL_HOST=null MAIL_PORT=2525 MAIL_FROM=changeme@example.com MAIL_USERNAME=null @@ -83,11 +124,20 @@ MAIL_PASSWORD=null MAIL_ENCRYPTION=null # Other mail drivers: +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE MAILGUN_DOMAIN= MAILGUN_SECRET= + + +# If you are on EU region in mailgun, use api.eu.mailgun.net, otherwise use api.mailgun.net +# If you use Docker or similar, you can set this variable from a file by appending it with _FILE +MAILGUN_ENDPOINT=api.mailgun.net + +# If you use Docker or similar, you can set these variables from a file by appending them with _FILE MANDRILL_SECRET= SPARKPOST_SECRET= + # Firefly III can send you the following messages SEND_REGISTRATION_MAIL=true SEND_ERROR_MESSAGE=true @@ -96,53 +146,85 @@ SEND_ERROR_MESSAGE=true SEND_REPORT_JOURNALS=true # Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places. +# If you use Docker or similar, you can set this variable from a file by appending it with _FILE MAPBOX_API_KEY= +# The map will default to this location: +MAP_DEFAULT_LAT=51.983333 +MAP_DEFAULT_LONG=5.916667 +MAP_DEFAULT_ZOOM=6 + # Firefly III currently supports two provider for live Currency Exchange Rates: -# "fixer" is the default (for backward compatibility), and "ratesapi" is the new one. +# "fixer", and "ratesapi". # RatesApi.IO (see https://ratesapi.io) is a FREE and OPEN SOURCE live currency exchange rates, -# built compatible with Fixer.IO, based on data published by European Central Bank, and don't require API key. -CER_PROVIDER=fixer +# built compatible with Fixer.IO, based on data published by European Central Bank, and doesn't require API key. +CER_PROVIDER=ratesapi + # If you have select "fixer" as default currency exchange rates, # set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. # Please note that this WILL ONLY WORK FOR PAID fixer.io accounts because they severely limited # the free API up to the point where you might as well offer nothing. +# If you use Docker or similar, you can set this variable from a file by appending it with _FILE FIXER_API_KEY= -# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here. -TRACKER_SITE_ID= -TRACKER_URL= - -# 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 - # Firefly III has two options for user authentication. "eloquent" is the default, # and "ldap" for LDAP servers. # For full instructions on these settings please visit: -# https://firefly-iii.readthedocs.io/en/latest/installation/authentication.html +# https://docs.firefly-iii.org/advanced-installation/authentication +# If you use Docker or similar, you can set this variable from a file by appending it with _FILE LOGIN_PROVIDER=eloquent +# +# It's also possible to change the way users are authenticated. You could use Authelia for example. +# Authentication via the REMOTE_USER header is supported. Change the value below to "remote_user_guard". +# +# If you do this please read the documentation for instructions and warnings: +# https://docs.firefly-iii.org/advanced-installation/authentication +# +# This function is available in Firefly III v5.3.0 and higher. +AUTHENTICATION_GUARD=web + +# +# Likewise, it's impossible to log out users who's authentication is handled by an external system. +# Enter a custom URL here that will force a logout (your authentication provider can tell you). +# Setting this variable only works when AUTHENTICATION_GUARD != web +# +CUSTOM_LOGOUT_URI= + # LDAP connection configuration # OpenLDAP, FreeIPA or ActiveDirectory +# # If you use Docker or similar, you can set this variable from a file by appending it with _FILE ADLDAP_CONNECTION_SCHEME=OpenLDAP ADLDAP_AUTO_CONNECT=true # LDAP connection settings +# You can set the following variables from a file by appending them with _FILE: +# ADLDAP_CONTROLLERS, ADLDAP_PORT, ADLDAP_BASEDN ADLDAP_CONTROLLERS= ADLDAP_PORT=389 ADLDAP_TIMEOUT=5 ADLDAP_BASEDN="" ADLDAP_FOLLOW_REFFERALS=false + +# SSL/TLS settings ADLDAP_USE_SSL=false ADLDAP_USE_TLS=false +ADLDAP_SSL_CACERTDIR= +ADLDAP_SSL_CACERTFILE= +ADLDAP_SSL_CERTFILE= +ADLDAP_SSL_KEYFILE= +ADLDAP_SSL_CIPHER_SUITE= +ADLDAP_SSL_REQUIRE_CERT= +# You can set the following variables from a file by appending them with _FILE: ADLDAP_ADMIN_USERNAME= ADLDAP_ADMIN_PASSWORD= +# You can set the following variables from a file by appending them with _FILE: ADLDAP_ACCOUNT_PREFIX= ADLDAP_ACCOUNT_SUFFIX= + # LDAP authentication settings. ADLDAP_PASSWORD_SYNC=false ADLDAP_LOGIN_FALLBACK=false @@ -151,25 +233,76 @@ ADLDAP_DISCOVER_FIELD=distinguishedname ADLDAP_AUTH_FIELD=distinguishedname # Will allow SSO if your server provides an AUTH_USER field. +# You can set the following variables from a file by appending them with _FILE: +WINDOWS_SSO_ENABLED=false WINDOWS_SSO_DISCOVER=samaccountname WINDOWS_SSO_KEY=AUTH_USER # field to sync as local username. +# You can set the following variable from a file by appending it with _FILE: ADLDAP_SYNC_FIELD=userprincipalname -# You can disable the X-Frame-Options header if it interfears with tools like -# Organizr. This is at your own risk. +# You can disable the X-Frame-Options header if it interferes with tools like +# Organizr. This is at your own risk. Applications running in frames run the risk +# of leaking information to their parent frame. DISABLE_FRAME_HEADER=false +# You can disable the Content Security Policy header when you're using an ancient browser +# or any version of Microsoft Edge / Internet Explorer (which amounts to the same thing really) +# This leaves you with the risk of not being able to stop XSS bugs should they ever surface. +# This is at your own risk. +DISABLE_CSP_HEADER=false + +# If you wish to track your own behavior over Firefly III, set valid analytics tracker information here. +# Nobody uses this except for me on the demo site. But hey, feel free to use this if you want to. +# Do not prepend the TRACKER_URL with http:// or https:// +# The only tracker supported is Matomo. +# You can set the following variables from a file by appending them with _FILE: +TRACKER_SITE_ID= +TRACKER_URL= + +# +# Firefly III can collect telemetry on how you use Firefly III. This is opt-in. +# In order to allow this, change the following variable to true. +# To read more about this feature, go to this page: https://docs.firefly-iii.org/support/telemetry +SEND_TELEMETRY=false + +# You can fine tune the start-up of a Docker container by editing these environment variables. +# Use this at your own risk. Disabling certain checks and features may result in lost of inconsistent data. +# However if you know what you're doing you can significantly speed up container start times. +# Set each value to true to enable, or false to disable. + +# Check if the SQLite database exists. Can be skipped if you're not using SQLite. +# Won't significantly speed up things. +DKR_CHECK_SQLITE=true + +# Run database creation and migration commands. Disable this only if you're 100% sure the DB exists +# and is up to date. +DKR_RUN_MIGRATION=true + +# Run database upgrade commands. Disable this only when you're 100% sure your DB is up-to-date +# with the latest fixes (outside of migrations!) +DKR_RUN_UPGRADE=true + +# Verify database integrity. Includes all data checks and verifications. +# Disabling this makes Firefly III assume your DB is intact. +DKR_RUN_VERIFY=true + +# Run database reporting commands. When disabled, Firefly III won't go over your data to report current state. +# Disabling this should have no impact on data integrity or safety but it won't warn you of possible issues. +DKR_RUN_REPORT=true + +# Generate OAuth2 keys. +# When disabled, Firefly III won't attempt to generate OAuth2 Passport keys. This won't be an issue, IFF (if and only if) +# you had previously generated keys already and they're stored in your database for restoration. +DKR_RUN_PASSPORT_INSTALL=true + # Leave the following configuration vars as is. # Unless you like to tinker and know what you're doing. APP_NAME=FireflyIII ADLDAP_CONNECTION=default BROADCAST_DRIVER=log QUEUE_DRIVER=sync -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null -REDIS_PORT=6379 CACHE_PREFIX=firefly SEARCH_RESULT_LIMIT=50 PUSHER_KEY= @@ -177,7 +310,18 @@ PUSHER_SECRET= PUSHER_ID= DEMO_USERNAME= DEMO_PASSWORD= -IS_SANDSTORM=false -IS_HEROKU=true -BUNQ_USE_SANDBOX=false -FFIII_LAYOUT=v1 +USE_ENCRYPTION=false +IS_HEROKU=false +FIREFLY_III_LAYOUT=v1 + +# +# If you have trouble configuring your Firefly III installation, DON'T BOTHER setting this variable. +# It won't work. It doesn't do ANYTHING. Don't believe the lies you read online. I'm not joking. +# This configuration value WILL NOT HELP. +# +# This variable is ONLY used in some of the emails Firefly III sends around. Nowhere else. +# So when configuring anything WEB related this variable doesn't do anything. Nothing +# +# If you're stuck I understand you get desperate but look SOMEWHERE ELSE. +# +APP_URL=http://localhost diff --git a/.env.example b/.env.example index 67e573fc3d..2fc8fb386f 100644 --- a/.env.example +++ b/.env.example @@ -15,7 +15,6 @@ SITE_OWNER=mail@example.com # If you use Docker or similar, you can set this variable from a file by using APP_KEY_FILE APP_KEY=SomeRandomStringOf32CharsExactly -# # Firefly III will launch using this language (for new users and unauthenticated visitors) # For a list of available languages: https://github.com/firefly-iii/firefly-iii/tree/main/resources/lang # @@ -93,9 +92,18 @@ SESSION_DRIVER=file # If you set either of these to 'redis', you might want to update these settings too # If you use Docker or similar, you can set REDIS_HOST_FILE, REDIS_PASSWORD_FILE or # REDIS_PORT_FILE to set the value from a file instead of from an environment variable + +# can be tcp, unix or http +REDIS_SCHEME=tcp + +# use only when using 'unix' for REDIS_SCHEME. Leave empty otherwise. +REDIS_PATH= + +# use only when using 'tcp' or 'http' for REDIS_SCHEME. Leave empty otherwise. REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null REDIS_PORT=6379 + +REDIS_PASSWORD=null # always use quotes and make sure redis db "0" and "1" exists. Otherwise change accordingly. REDIS_DB="0" REDIS_CACHE_DB="1" @@ -132,7 +140,6 @@ MAILGUN_ENDPOINT=api.mailgun.net MANDRILL_SECRET= SPARKPOST_SECRET= - # Firefly III can send you the following messages SEND_REGISTRATION_MAIL=true SEND_ERROR_MESSAGE=true @@ -149,19 +156,6 @@ MAP_DEFAULT_LAT=51.983333 MAP_DEFAULT_LONG=5.916667 MAP_DEFAULT_ZOOM=6 -# Firefly III currently supports two provider for live Currency Exchange Rates: -# "fixer", and "ratesapi". -# RatesApi.IO (see https://ratesapi.io) is a FREE and OPEN SOURCE live currency exchange rates, -# built compatible with Fixer.IO, based on data published by European Central Bank, and doesn't require API key. -CER_PROVIDER=ratesapi - -# If you have select "fixer" as default currency exchange rates, -# set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. -# Please note that this WILL ONLY WORK FOR PAID fixer.io accounts because they severely limited -# the free API up to the point where you might as well offer nothing. -# If you use Docker or similar, you can set this variable from a file by appending it with _FILE -FIXER_API_KEY= - # Firefly III has two options for user authentication. "eloquent" is the default, # and "ldap" for LDAP servers. # For full instructions on these settings please visit: @@ -169,17 +163,26 @@ FIXER_API_KEY= # If you use Docker or similar, you can set this variable from a file by appending it with _FILE LOGIN_PROVIDER=eloquent -# # It's also possible to change the way users are authenticated. You could use Authelia for example. # Authentication via the REMOTE_USER header is supported. Change the value below to "remote_user_guard". # +# This will also allow Windows SSO. +# # If you do this please read the documentation for instructions and warnings: # https://docs.firefly-iii.org/advanced-installation/authentication # # This function is available in Firefly III v5.3.0 and higher. AUTHENTICATION_GUARD=web +# If the guard is changed, Firefly III uses the 'REMOTE_USER' header as per RFC 3875. +# You can also use another header, like AUTH_USER when using Windows SSO. +# Some systems use X-Auth headers. In that case, use HTTP_X_AUTH_USERNAME or HTTP_X_AUTH_EMAIL +# Depending on your system, REMOTE_USER may need to be changed to HTTP_REMOTE_USER # +# Firefly III won't be able to send emails when the header you use isn't an email address. +# +AUTHENTICATION_GUARD_HEADER=REMOTE_USER + # Likewise, it's impossible to log out users who's authentication is handled by an external system. # Enter a custom URL here that will force a logout (your authentication provider can tell you). # Setting this variable only works when AUTHENTICATION_GUARD != web @@ -219,7 +222,6 @@ ADLDAP_ADMIN_PASSWORD= ADLDAP_ACCOUNT_PREFIX= ADLDAP_ACCOUNT_SUFFIX= - # LDAP authentication settings. ADLDAP_PASSWORD_SYNC=false ADLDAP_LOGIN_FALLBACK=false @@ -227,12 +229,6 @@ ADLDAP_LOGIN_FALLBACK=false ADLDAP_DISCOVER_FIELD=distinguishedname ADLDAP_AUTH_FIELD=distinguishedname -# Will allow SSO if your server provides an AUTH_USER field. -# You can set the following variables from a file by appending them with _FILE: -WINDOWS_SSO_ENABLED=false -WINDOWS_SSO_DISCOVER=samaccountname -WINDOWS_SSO_KEY=AUTH_USER - # field to sync as local username. # You can set the following variable from a file by appending it with _FILE: ADLDAP_SYNC_FIELD=userprincipalname @@ -256,7 +252,6 @@ DISABLE_CSP_HEADER=false TRACKER_SITE_ID= TRACKER_URL= -# # Firefly III can collect telemetry on how you use Firefly III. This is opt-in. # In order to allow this, change the following variable to true. # To read more about this feature, go to this page: https://docs.firefly-iii.org/support/telemetry @@ -299,13 +294,11 @@ ADLDAP_CONNECTION=default BROADCAST_DRIVER=log QUEUE_DRIVER=sync CACHE_PREFIX=firefly -SEARCH_RESULT_LIMIT=50 PUSHER_KEY= PUSHER_SECRET= PUSHER_ID= DEMO_USERNAME= DEMO_PASSWORD= -USE_ENCRYPTION=false IS_HEROKU=false FIREFLY_III_LAYOUT=v1 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3f4c6c849f..9bd485155b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,6 @@ version: 2 updates: - + # composer updates - package-ecosystem: "composer" directory: "/" # Location of package manifests diff --git a/.github/funding.yml b/.github/funding.yml index 2569052f0e..555d0eb65d 100644 --- a/.github/funding.yml +++ b/.github/funding.yml @@ -2,4 +2,4 @@ github: jc5 patreon: JC5 -custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA +custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=L62W7DVD5ETPC&source=url diff --git a/.github/workflows/laravel.yml b/.github/workflows/laravel.yml new file mode 100644 index 0000000000..4d07ea000f --- /dev/null +++ b/.github/workflows/laravel.yml @@ -0,0 +1,146 @@ +name: Firefly III + +on: + push: + branches-ignore: + - '**' + +jobs: + prepare: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Copy .env + run: test -f .env || cp .ci/.env.ci .env + - name: Prepare dependencies + run: | + set -euxo pipefail + export PATH=$PATH:$HOME/.composer/vendor/bin/ + composer global require hirak/prestissimo --no-plugins --no-scripts + composer install --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --no-suggest + + touch ./storage/database/database.sqlite + - name: Prepare Firefly III + run: | + chmod -R 777 storage bootstrap/cache + php artisan migrate --seed + php artisan firefly-iii:upgrade-database + - name: Upload database + uses: actions/upload-artifact@v2 + with: + name: database + path: storage/database/database.sqlite + - name: Upload cache + uses: actions/upload-artifact@v2 + with: + name: cache + path: bootstrap/cache/ + - name: Upload composer cache + uses: actions/upload-artifact@v2 + with: + name: composer + path: ~/.composer + + laravel-tests: + + runs-on: ubuntu-latest + + needs: + - prepare + + steps: + - uses: actions/checkout@v2 + - name: Copy .env + run: test -f .env || cp .ci/.env.ci .env + - name: Download database + uses: actions/download-artifact@v2 + with: + name: database + path: storage/database/database.sqlite + - name: Download cache + uses: actions/download-artifact@v2 + with: + name: cache + path: bootstrap/cache/ + - name: Download vendor + uses: actions/download-artifact@v2 + with: + name: composer + path: ~/.composer + - name: Install composer + run: composer install --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --no-suggest + + - name: PHPUnit tests + uses: php-actions/phpunit@v1 + with: + config: phpunit.xml + memory: 512M + + coding-standards: + + runs-on: ubuntu-latest + + needs: + - prepare + + steps: + - uses: actions/checkout@v2 + - name: Copy .env + run: test -f .env || cp .ci/.env.ci .env + - name: Download database + uses: actions/download-artifact@v2 + with: + name: database + path: storage/database/database.sqlite + - name: Download cache + uses: actions/download-artifact@v2 + with: + name: cache + path: bootstrap/cache/ + - name: Download vendor + uses: actions/download-artifact@v2 + with: + name: composer + path: ~/.composer + - name: install depenencies + run: | + composer global require nette/coding-standard + composer install --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --no-suggest + + - name: Execute code standard + run: /home/runner/.composer/vendor/bin/ecs check app tests --config ./.ci/firefly-iii-standard.yml + + phpstan: + + runs-on: ubuntu-latest + + needs: + - prepare + + steps: + - uses: actions/checkout@v2 + - name: Copy .env + run: test -f .env || cp .ci/.env.ci .env + - name: Download database + uses: actions/download-artifact@v2 + with: + name: database + path: storage/database/database.sqlite + - name: Download cache + uses: actions/download-artifact@v2 + with: + name: cache + path: bootstrap/cache/ + - name: Download vendor + uses: actions/download-artifact@v2 + with: + name: composer + path: ~/.composer + - name: Install depenencies + run: | + composer install --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --no-suggest + + - name: Execute PHPStan + run: vendor/bin/phpstan analyse -c .ci/phpstan.neon diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..43ce11270c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: php +php: +- '7.4' +dist: xenial +os: linux +cache: + directories: + - "/home/travis/.config" + - "/home/travis/build/firefly-iii/firefly-iii/vendor" + +branches: + only: + - develop + +before_script: +- phpenv config-rm xdebug.ini || return 0 +- composer global require hirak/prestissimo --no-plugins --no-scripts + +script: +- "./.ci/phpstan.sh" +- "./.ci/phpunit.sh" \ No newline at end of file diff --git a/app/Api/V1/Controllers/AccountController.php b/app/Api/V1/Controllers/AccountController.php index 08a4bf1fa3..572a8e1c02 100644 --- a/app/Api/V1/Controllers/AccountController.php +++ b/app/Api/V1/Controllers/AccountController.php @@ -45,15 +45,16 @@ use League\Fractal\Resource\Item; /** * Class AccountController. - * */ class AccountController extends Controller { use AccountFilter, TransactionFilter; - private AccountRepositoryInterface $repository; public const RESOURCE_KEY = 'accounts'; + private AccountRepositoryInterface $repository; + + /** * AccountController constructor. * @@ -165,7 +166,6 @@ class AccountController extends Controller * * @return JsonResponse * @codeCoverageIgnore - * */ public function piggyBanks(Account $account): JsonResponse { @@ -245,7 +245,6 @@ class AccountController extends Controller * @param Account $account * * @return JsonResponse - * */ public function transactions(Request $request, Account $account): JsonResponse { diff --git a/app/Api/V1/Controllers/AttachmentController.php b/app/Api/V1/Controllers/AttachmentController.php index 562f1f81e2..b89eaab5bf 100644 --- a/app/Api/V1/Controllers/AttachmentController.php +++ b/app/Api/V1/Controllers/AttachmentController.php @@ -28,7 +28,6 @@ use FireflyIII\Api\V1\Requests\AttachmentStoreRequest; use FireflyIII\Api\V1\Requests\AttachmentUpdateRequest; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; -use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Models\Attachment; use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; use FireflyIII\Transformers\AttachmentTransformer; @@ -45,13 +44,13 @@ use function strlen; /** * Class AttachmentController. - * */ class AttachmentController extends Controller { /** @var AttachmentRepositoryInterface The attachment repository */ private $repository; + /** * AccountController constructor. * diff --git a/app/Api/V1/Controllers/Autocomplete/AccountController.php b/app/Api/V1/Controllers/Autocomplete/AccountController.php new file mode 100644 index 0000000000..f0ff700644 --- /dev/null +++ b/app/Api/V1/Controllers/Autocomplete/AccountController.php @@ -0,0 +1,119 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers\Autocomplete; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Http\Api\AccountFilter; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; + +/** + * Class AccountController + */ +class AccountController extends Controller +{ + use AccountFilter; + + private array $balanceTypes; + + private AccountRepositoryInterface $repository; + + + /** + * AccountController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(AccountRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + $this->balanceTypes = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE,]; + } + + /** + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function accounts(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $types = $data['types']; + $query = $data['query']; + $date = $data['date'] ?? today(config('app.timezone')); + + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $return = []; + $result = $repository->searchAccount((string) $query, $types, $data['limit']); + $defaultCurrency = app('amount')->getDefaultCurrency(); + + /** @var Account $account */ + foreach ($result as $account) { + $nameWithBalance = $account->name; + $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; + + if (in_array($account->accountType->type, $this->balanceTypes, true)) { + $balance = app('steam')->balance($account, $date); + $nameWithBalance = sprintf('%s (%s)', $account->name, app('amount')->formatAnything($currency, $balance, false)); + } + + $return[] = [ + 'id' => $account->id, + 'name' => $account->name, + 'name_with_balance' => $nameWithBalance, + 'type' => $account->accountType->type, + 'currency_id' => $currency->id, + 'currency_name' => $currency->name, + 'currency_code' => $currency->code, + 'currency_decimal_places' => $currency->decimal_places, + ]; + } + + // custom order. + $order = [AccountType::ASSET, AccountType::REVENUE, AccountType::EXPENSE]; + + + usort($return, function ($a, $b) use ($order) { + $pos_a = array_search($a['type'], $order); + $pos_b = array_search($b['type'], $order); + return $pos_a - $pos_b; + }); + + return response()->json($return); + } +} diff --git a/app/Api/V1/Controllers/Autocomplete/BillController.php b/app/Api/V1/Controllers/Autocomplete/BillController.php new file mode 100644 index 0000000000..abdab1dee3 --- /dev/null +++ b/app/Api/V1/Controllers/Autocomplete/BillController.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers\Autocomplete; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Models\Bill; +use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; + +/** + * Class BillController + */ +class BillController extends Controller +{ + private BillRepositoryInterface $repository; + + + /** + * BillController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(BillRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * TODO add limit + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function bills(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $result = $this->repository->searchBill($data['query'], $data['limit']); + $filtered = $result->map( + static function (Bill $item) { + return [ + 'id' => $item->id, + 'name' => $item->name, + ]; + } + ); + + + return response()->json($filtered->toArray()); + } + +} diff --git a/app/Api/V1/Controllers/Autocomplete/BudgetController.php b/app/Api/V1/Controllers/Autocomplete/BudgetController.php new file mode 100644 index 0000000000..1aebb3c83a --- /dev/null +++ b/app/Api/V1/Controllers/Autocomplete/BudgetController.php @@ -0,0 +1,81 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers\Autocomplete; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Models\Budget; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; + +/** + * Class BudgetController + */ +class BudgetController extends Controller +{ + private BudgetRepositoryInterface $repository; + + + /** + * BudgetController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(BudgetRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * TODO add limit + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function budgets(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $result = $this->repository->searchBudget($data['query'], $data['limit']); + $filtered = $result->map( + static function (Budget $item) { + return [ + 'id' => $item->id, + 'name' => $item->name, + ]; + } + ); + + return response()->json($filtered); + } +} diff --git a/app/Api/V1/Controllers/Autocomplete/CategoryController.php b/app/Api/V1/Controllers/Autocomplete/CategoryController.php new file mode 100644 index 0000000000..878defc648 --- /dev/null +++ b/app/Api/V1/Controllers/Autocomplete/CategoryController.php @@ -0,0 +1,79 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers\Autocomplete; + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Models\Category; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; + +/** + * Class CategoryController + */ +class CategoryController extends Controller +{ + private CategoryRepositoryInterface $repository; + + + /** + * CategoryController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(CategoryRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function categories(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $result = $this->repository->searchCategory($data['query'], $data['limit']); + $filtered = $result->map( + static function (Category $item) { + return [ + 'id' => $item->id, + 'name' => $item->name, + ]; + } + ); + + return response()->json($filtered); + } +} diff --git a/app/Api/V1/Controllers/Autocomplete/CurrencyController.php b/app/Api/V1/Controllers/Autocomplete/CurrencyController.php new file mode 100644 index 0000000000..1e8f67827d --- /dev/null +++ b/app/Api/V1/Controllers/Autocomplete/CurrencyController.php @@ -0,0 +1,109 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers\Autocomplete; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; + +/** + * Class CurrencyController + */ +class CurrencyController extends Controller +{ + private CurrencyRepositoryInterface $repository; + + + /** + * CurrencyController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(CurrencyRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function currenciesWithCode(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $collection = $this->repository->searchCurrency($data['query'], $data['limit']); + $result = []; + + /** @var TransactionCurrency $currency */ + foreach ($collection as $currency) { + $result[] = [ + 'id' => $currency->id, + 'name' => sprintf('%s (%s)', $currency->name, $currency->code), + 'code' => $currency->code, + 'symbol' => $currency->symbol, + 'decimal_places' => $currency->decimal_places, + ]; + } + + return response()->json($result); + } + + /** + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function currencies(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $collection = $this->repository->searchCurrency($data['query'], $data['limit']); + $result = []; + + /** @var TransactionCurrency $currency */ + foreach ($collection as $currency) { + $result[] = [ + 'id' => $currency->id, + 'name' => $currency->name, + 'code' => $currency->code, + 'symbol' => $currency->symbol, + 'decimal_places' => $currency->decimal_places, + ]; + } + + return response()->json($result); + } +} diff --git a/app/Api/V1/Controllers/Autocomplete/ObjectGroupController.php b/app/Api/V1/Controllers/Autocomplete/ObjectGroupController.php new file mode 100644 index 0000000000..4ab1885c4f --- /dev/null +++ b/app/Api/V1/Controllers/Autocomplete/ObjectGroupController.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers\Autocomplete; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Models\ObjectGroup; +use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; + +/** + * Class ObjectGroupController + */ +class ObjectGroupController extends Controller +{ + private ObjectGroupRepositoryInterface $repository; + + + /** + * CurrencyController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(ObjectGroupRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function objectGroups(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $return = []; + $result = $this->repository->search($data['query'], $data['limit']); + + /** @var ObjectGroup $account */ + foreach ($result as $objectGroup) { + $return[] = [ + 'id' => $objectGroup->id, + 'name' => $objectGroup->title, + 'title' => $objectGroup->title, + ]; + } + + return response()->json($return); + } + +} diff --git a/app/Api/V1/Controllers/Autocomplete/PiggyBankController.php b/app/Api/V1/Controllers/Autocomplete/PiggyBankController.php new file mode 100644 index 0000000000..47ef2259c9 --- /dev/null +++ b/app/Api/V1/Controllers/Autocomplete/PiggyBankController.php @@ -0,0 +1,125 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers\Autocomplete; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Models\PiggyBank; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; + +/** + * Class PiggyBankController + */ +class PiggyBankController extends Controller +{ + private PiggyBankRepositoryInterface $piggyRepository; + + private AccountRepositoryInterface $accountRepository; + + + /** + * PiggyBankController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->piggyRepository = app(PiggyBankRepositoryInterface::class); + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->piggyRepository->setUser($user); + $this->accountRepository->setUser($user); + + return $next($request); + } + ); + } + + /** + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function piggyBanks(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $piggies = $this->piggyRepository->searchPiggyBank($data['query'], $data['limit']); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $response = []; + + /** @var PiggyBank $piggy */ + foreach ($piggies as $piggy) { + $currency = $this->accountRepository->getAccountCurrency($piggy->account) ?? $defaultCurrency; + $response[] = [ + 'id' => $piggy->id, + 'name' => $piggy->name, + 'currency_id' => $currency->id, + 'currency_name' => $currency->name, + 'currency_code' => $currency->code, + 'currency_decimal_places' => $currency->decimal_places, + ]; + } + + return response()->json($response); + } + + /** + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function piggyBanksWithBalance(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $piggies = $this->piggyRepository->searchPiggyBank($data['query'], $data['limit']); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $response = []; + /** @var PiggyBank $piggy */ + foreach ($piggies as $piggy) { + $currency = $this->accountRepository->getAccountCurrency($piggy->account) ?? $defaultCurrency; + $currentAmount = $this->piggyRepository->getRepetition($piggy)->currentamount ?? '0'; + $response[] = [ + 'id' => $piggy->id, + 'name' => $piggy->name, + 'name_with_balance' => sprintf( + '%s (%s / %s)', $piggy->name, app('amount')->formatAnything($currency, $currentAmount, false), + app('amount')->formatAnything($currency, $piggy->targetamount, false), + ), + 'currency_id' => $currency->id, + 'currency_name' => $currency->name, + 'currency_code' => $currency->code, + 'currency_decimal_places' => $currency->decimal_places, + ]; + } + + return response()->json($response); + } + +} diff --git a/tests/Object/FakeApiContext.php b/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php similarity index 65% rename from tests/Object/FakeApiContext.php rename to app/Api/V1/Controllers/Autocomplete/RecurrenceController.php index 84e3575039..75785eec08 100644 --- a/tests/Object/FakeApiContext.php +++ b/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php @@ -1,7 +1,7 @@ 'b']); - } } diff --git a/database/factories/AccountFactory.php b/app/Api/V1/Controllers/Autocomplete/RuleController.php similarity index 57% rename from database/factories/AccountFactory.php rename to app/Api/V1/Controllers/Autocomplete/RuleController.php index a9399edda1..fd7453d05a 100644 --- a/database/factories/AccountFactory.php +++ b/app/Api/V1/Controllers/Autocomplete/RuleController.php @@ -1,7 +1,7 @@ . */ + declare(strict_types=1); -use Carbon\Carbon; +namespace FireflyIII\Api\V1\Controllers\Autocomplete; -$factory->define( - FireflyIII\Models\Account::class, - static function (Faker\Generator $faker) { - return [ - 'id' => $faker->unique()->numberBetween(1000, 10000), - 'user_id' => 1, - 'created_at' => new Carbon, - 'updated_at' => new Carbon, - 'name' => $faker->words(3, true), - 'account_type_id' => random_int(2, 5), - 'active' => true, - ]; - } -); + +use FireflyIII\Api\V1\Controllers\Controller; + +/** + * Class RuleController + */ +class RuleController extends Controller +{ + +} diff --git a/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php b/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php new file mode 100644 index 0000000000..bbc598e40b --- /dev/null +++ b/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php @@ -0,0 +1,35 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers\Autocomplete; + + +use FireflyIII\Api\V1\Controllers\Controller; + +/** + * Class RuleGroupController + */ +class RuleGroupController extends Controller +{ + +} diff --git a/app/Api/V1/Controllers/Autocomplete/TagController.php b/app/Api/V1/Controllers/Autocomplete/TagController.php new file mode 100644 index 0000000000..5b2aeabc67 --- /dev/null +++ b/app/Api/V1/Controllers/Autocomplete/TagController.php @@ -0,0 +1,82 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers\Autocomplete; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Models\Tag; +use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; + +/** + * Class TagController + */ +class TagController extends Controller +{ + private TagRepositoryInterface $repository; + + + /** + * TagController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(TagRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function tags(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + + $result = $this->repository->searchTags($data['query'], $data['limit']); + $array = []; + /** @var Tag $tag */ + foreach ($result as $tag) { + $array[] = [ + 'id' => $tag->id, + 'name' => $tag->tag, + 'tag' => $tag->tag, + ]; + } + + return response()->json($array); + } +} diff --git a/app/Api/V1/Controllers/Autocomplete/TransactionController.php b/app/Api/V1/Controllers/Autocomplete/TransactionController.php new file mode 100644 index 0000000000..7a0a9b480b --- /dev/null +++ b/app/Api/V1/Controllers/Autocomplete/TransactionController.php @@ -0,0 +1,132 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers\Autocomplete; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Support\Collection; + +/** + * Class TransactionController + */ +class TransactionController extends Controller +{ + private JournalRepositoryInterface $repository; + + private TransactionGroupRepositoryInterface $groupRepository; + + + /** + * TransactionController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(JournalRepositoryInterface::class); + $this->groupRepository = app(TransactionGroupRepositoryInterface::class); + $this->repository->setUser($user); + $this->groupRepository->setUser($user); + + return $next($request); + } + ); + } + + + /** + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function transactions(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $result = $this->repository->searchJournalDescriptions($data['query'], $data['limit']); + + // limit and unique + $filtered = $result->unique('description'); + $array = []; + + /** @var TransactionJournal $journal */ + foreach ($filtered as $journal) { + $array[] = [ + 'id' => $journal->id, + 'transaction_group_id' => $journal->transaction_group_id, + 'name' => $journal->description, + 'description' => $journal->description, + ]; + } + + return response()->json($array); + } + + /** + * @param AutocompleteRequest $request + * + * @return JsonResponse + */ + public function transactionsWithID(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $result = new Collection; + if (is_numeric($data['query'])) { + // search for group, not journal. + $firstResult = $this->groupRepository->find((int) $data['query']); + if (null !== $firstResult) { + // group may contain multiple journals, each a result: + foreach ($firstResult->transactionJournals as $journal) { + $result->push($journal); + } + } + } + if (!is_numeric($data['query'])) { + $result = $this->repository->searchJournalDescriptions($data['query'], $data['limit']); + } + + // limit and unique + $array = []; + + /** @var TransactionJournal $journal */ + foreach ($result as $journal) { + $array[] = [ + 'id' => $journal->id, + 'transaction_group_id' => $journal->transaction_group_id, + 'name' => sprintf('#%d: %s', $journal->transaction_group_id, $journal->description), + 'description' => sprintf('#%d: %s', $journal->transaction_group_id, $journal->description), + ]; + } + + return response()->json($array); + } +} diff --git a/app/Api/V1/Controllers/Autocomplete/TransactionTypeController.php b/app/Api/V1/Controllers/Autocomplete/TransactionTypeController.php new file mode 100644 index 0000000000..5dcee6c308 --- /dev/null +++ b/app/Api/V1/Controllers/Autocomplete/TransactionTypeController.php @@ -0,0 +1,80 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers\Autocomplete; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface; +use Illuminate\Http\JsonResponse; + +/** + * Class TransactionTypeController + */ +class TransactionTypeController extends Controller +{ + private TransactionTypeRepositoryInterface $repository; + + + /** + * TransactionTypeController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + $this->repository = app(TransactionTypeRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * @param AutocompleteRequest $request + * + * @return JsonResponse + * @codeCoverageIgnore + */ + public function transactionTypes(AutocompleteRequest $request): JsonResponse + { + $data = $request->getData(); + $types = $this->repository->searchTypes($data['query'], $data['limit']); + $array = []; + + /** @var TransactionType $type */ + foreach ($types as $type) { + // different key for consistency. + $array[] = [ + 'id' => $type->id, + 'name' => $type->type, + 'type' => $type->type, + ]; + } + + return response()->json($array); + } +} diff --git a/app/Api/V1/Controllers/AvailableBudgetController.php b/app/Api/V1/Controllers/AvailableBudgetController.php index d937bc1043..c6807e08f9 100644 --- a/app/Api/V1/Controllers/AvailableBudgetController.php +++ b/app/Api/V1/Controllers/AvailableBudgetController.php @@ -38,12 +38,10 @@ use League\Fractal\Resource\Item; /** * Class AvailableBudgetController. - * */ class AvailableBudgetController extends Controller { - /** @var AvailableBudgetRepositoryInterface */ - private $abRepository; + private AvailableBudgetRepositoryInterface $abRepository; /** * AvailableBudgetController constructor. diff --git a/app/Api/V1/Controllers/BillController.php b/app/Api/V1/Controllers/BillController.php index 009685d3ec..560713d202 100644 --- a/app/Api/V1/Controllers/BillController.php +++ b/app/Api/V1/Controllers/BillController.php @@ -44,14 +44,15 @@ use League\Fractal\Resource\Item; /** * Class BillController. - * */ class BillController extends Controller { use TransactionFilter; + /** @var BillRepositoryInterface The bill repository */ private $repository; + /** * BillController constructor. * diff --git a/app/Api/V1/Controllers/BudgetController.php b/app/Api/V1/Controllers/BudgetController.php index 592f01a725..8b144d7bf4 100644 --- a/app/Api/V1/Controllers/BudgetController.php +++ b/app/Api/V1/Controllers/BudgetController.php @@ -47,16 +47,18 @@ use League\Fractal\Resource\Item; /** * Class BudgetController. - * */ class BudgetController extends Controller { use TransactionFilter; + /** @var BudgetLimitRepositoryInterface */ private $blRepository; + /** @var BudgetRepositoryInterface The budget repository */ private $repository; + /** * BudgetController constructor. * @@ -230,31 +232,6 @@ class BudgetController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } - /** - * Store a newly created resource in storage. - * - * @param BudgetLimitRequest $request - * @param Budget $budget - * - * @throws Exception - * @return JsonResponse - */ - public function storeBudgetLimit(BudgetLimitRequest $request, Budget $budget): JsonResponse - { - $data = $request->getAll(); - $data['budget'] = $budget; - $budgetLimit = $this->blRepository->storeBudgetLimit($data); - $manager = $this->getManager(); - - /** @var BudgetLimitTransformer $transformer */ - $transformer = app(BudgetLimitTransformer::class); - $transformer->setParameters($this->parameters); - - $resource = new Item($budgetLimit, $transformer, 'budget_limits'); - - return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); - } - /** * Show all transactions. * diff --git a/app/Api/V1/Controllers/BudgetLimitController.php b/app/Api/V1/Controllers/BudgetLimitController.php index 6fd81263ca..7973104360 100644 --- a/app/Api/V1/Controllers/BudgetLimitController.php +++ b/app/Api/V1/Controllers/BudgetLimitController.php @@ -44,16 +44,18 @@ use League\Fractal\Resource\Item; /** * Class BudgetLimitController. - * */ class BudgetLimitController extends Controller { use TransactionFilter; + /** @var BudgetLimitRepositoryInterface */ private $blRepository; + /** @var BudgetRepositoryInterface The budget repository */ private $repository; + /** * BudgetLimitController constructor. * @@ -156,20 +158,19 @@ class BudgetLimitController extends Controller * * @param BudgetLimitRequest $request * + * @return JsonResponse * @throws FireflyException * - * @return JsonResponse */ public function store(BudgetLimitRequest $request): JsonResponse { - $data = $request->getAll(); - $budget = $this->repository->findNull($data['budget_id']); - if (null === $budget) { - throw new FireflyException('200004: Budget does not exist.'); // @codeCoverageIgnore - } - $data['budget'] = $budget; - $budgetLimit = $this->blRepository->storeBudgetLimit($data); - $manager = $this->getManager(); + $data = $request->getAll(); + $data['start_date'] = $data['start']; + $data['end_date'] = $data['end']; + + $budgetLimit = $this->blRepository->store($data); + $manager = $this->getManager(); + /** @var BudgetLimitTransformer $transformer */ $transformer = app(BudgetLimitTransformer::class); @@ -244,8 +245,7 @@ class BudgetLimitController extends Controller public function update(BudgetLimitRequest $request, BudgetLimit $budgetLimit): JsonResponse { $data = $request->getAll(); - $data['budget'] = $budgetLimit->budget; - $budgetLimit = $this->blRepository->updateBudgetLimit($budgetLimit, $data); + $budgetLimit = $this->blRepository->update($budgetLimit, $data); $manager = $this->getManager(); /** @var BudgetLimitTransformer $transformer */ diff --git a/app/Api/V1/Controllers/CategoryController.php b/app/Api/V1/Controllers/CategoryController.php index 2f35fdbe9e..9f242de7e4 100644 --- a/app/Api/V1/Controllers/CategoryController.php +++ b/app/Api/V1/Controllers/CategoryController.php @@ -42,14 +42,15 @@ use League\Fractal\Resource\Item; /** * Class CategoryController. - * */ class CategoryController extends Controller { use TransactionFilter; + /** @var CategoryRepositoryInterface The category repository */ private $repository; + /** * CategoryController constructor. * diff --git a/app/Api/V1/Controllers/Chart/AccountController.php b/app/Api/V1/Controllers/Chart/AccountController.php index 0da7d09c7d..edebaaf010 100644 --- a/app/Api/V1/Controllers/Chart/AccountController.php +++ b/app/Api/V1/Controllers/Chart/AccountController.php @@ -42,9 +42,12 @@ use Illuminate\Http\JsonResponse; class AccountController extends Controller { use ApiSupport; + private CurrencyRepositoryInterface $currencyRepository; + private AccountRepositoryInterface $repository; + /** * AccountController constructor. * @@ -207,7 +210,7 @@ class AccountController extends Controller while ($currentStart <= $end) { $format = $currentStart->format('Y-m-d'); $label = $currentStart->format('Y-m-d'); - $balance = isset($range[$format]) ? round($range[$format], 12) : $previous; + $balance = array_key_exists($format, $range) ? round($range[$format], 12) : $previous; $previous = $balance; $currentStart->addDay(); $currentSet['entries'][$label] = $balance; diff --git a/app/Api/V1/Controllers/Chart/AvailableBudgetController.php b/app/Api/V1/Controllers/Chart/AvailableBudgetController.php index aa83e7bf4c..dc05304d6e 100644 --- a/app/Api/V1/Controllers/Chart/AvailableBudgetController.php +++ b/app/Api/V1/Controllers/Chart/AvailableBudgetController.php @@ -38,10 +38,8 @@ use Illuminate\Support\Collection; */ class AvailableBudgetController extends Controller { - /** @var OperationsRepositoryInterface */ - private $opsRepository; - /** @var BudgetRepositoryInterface */ - private $repository; + private OperationsRepositoryInterface $opsRepository; + private BudgetRepositoryInterface $repository; /** * AvailableBudgetController constructor. @@ -72,18 +70,18 @@ class AvailableBudgetController extends Controller */ public function overview(AvailableBudget $availableBudget): JsonResponse { - $currency = $availableBudget->transactionCurrency; - $budgets = $this->repository->getActiveBudgets(); - $budgetInformation = $this->opsRepository->spentInPeriodMc($budgets, new Collection, $availableBudget->start_date, $availableBudget->end_date); - $spent = 0.0; + $currency = $availableBudget->transactionCurrency; + $budgets = $this->repository->getActiveBudgets(); + $newBudgetInformation = $this->opsRepository->sumExpenses($availableBudget->start_date, $availableBudget->end_date, null, $budgets); + $spent = '0'; - // get for current currency - foreach ($budgetInformation as $spentInfo) { - if ($spentInfo['currency_id'] === $availableBudget->transaction_currency_id) { - $spent = $spentInfo['amount']; + foreach ($newBudgetInformation as $currencyId => $info) { + if ($currencyId === (int) $availableBudget->transaction_currency_id) { + $spent = $info['sum']; } } - $left = bcadd($availableBudget->amount, (string) $spent); + + $left = bcadd($availableBudget->amount, $spent); // left less than zero? Set to zero. if (-1 === bccomp($left, '0')) { $left = '0'; diff --git a/app/Api/V1/Controllers/Chart/BudgetController.php b/app/Api/V1/Controllers/Chart/BudgetController.php index a5300e3830..6f68502c8d 100644 --- a/app/Api/V1/Controllers/Chart/BudgetController.php +++ b/app/Api/V1/Controllers/Chart/BudgetController.php @@ -41,9 +41,12 @@ use Illuminate\Support\Collection; class BudgetController extends Controller { private BudgetLimitRepositoryInterface $blRepository; + private OperationsRepositoryInterface $opsRepository; + private BudgetRepositoryInterface $repository; + /** * BudgetController constructor. * @@ -211,7 +214,7 @@ class BudgetController extends Controller $return = []; /** @var array|null $arr */ foreach ($expenses as $arr) { - if (null !== $arr) { + if ([] !== $arr) { $return[] = $arr; } } @@ -260,9 +263,9 @@ class BudgetController extends Controller /** * @param BudgetLimit $limit * - * @return array|null + * @return array */ - private function getExpensesForLimit(BudgetLimit $limit): ?array + private function getExpensesForLimit(BudgetLimit $limit): array { $budget = $limit->budget; $spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $limit->transactionCurrency); @@ -270,7 +273,7 @@ class BudgetController extends Controller // when limited to a currency, the count is always one. Or it's empty. $set = array_shift($spent); if (null === $set) { - return null; + return []; } $return = [ 'label' => sprintf('%s (%s)', $budget->name, $set['currency_name']), diff --git a/app/Api/V1/Controllers/Chart/CategoryController.php b/app/Api/V1/Controllers/Chart/CategoryController.php index 36a32bb0ed..6b88eb1ad6 100644 --- a/app/Api/V1/Controllers/Chart/CategoryController.php +++ b/app/Api/V1/Controllers/Chart/CategoryController.php @@ -40,11 +40,14 @@ class CategoryController extends Controller { /** @var CategoryRepositoryInterface */ private $categoryRepository; + /** @var NoCategoryRepositoryInterface */ private $noCatRepository; + /** @var OperationsRepositoryInterface */ private $opsRepository; + /** * AccountController constructor. * @@ -91,7 +94,7 @@ class CategoryController extends Controller $categories = []; - foreach ([$spentWith, $spentWithout,] as $set) { + foreach ([$spentWith, $spentWithout, ] as $set) { foreach ($set as $currency) { foreach ($currency['categories'] as $category) { $categories[] = $category['name']; diff --git a/app/Api/V1/Controllers/ConfigurationController.php b/app/Api/V1/Controllers/ConfigurationController.php index bd81857431..c9bf085b99 100644 --- a/app/Api/V1/Controllers/ConfigurationController.php +++ b/app/Api/V1/Controllers/ConfigurationController.php @@ -37,14 +37,12 @@ use Illuminate\Http\JsonResponse; */ class ConfigurationController extends Controller { - - /** @var UserRepositoryInterface The user repository */ private $repository; + /** * ConfigurationController constructor. - * */ public function __construct() { diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php index 018afbd46e..4d3247506d 100644 --- a/app/Api/V1/Controllers/Controller.php +++ b/app/Api/V1/Controllers/Controller.php @@ -39,18 +39,16 @@ use Symfony\Component\HttpFoundation\ParameterBag; * Class Controller. * * @codeCoverageIgnore - * */ -class Controller extends BaseController +abstract class Controller extends BaseController { use AuthorizesRequests, DispatchesJobs, ValidatesRequests; - /** @var ParameterBag Parameters from the URI are stored here. */ - protected $parameters; + protected ParameterBag $parameters; + /** * Controller constructor. - * */ public function __construct() { @@ -62,9 +60,8 @@ class Controller extends BaseController * Method to help build URI's. * * @return string - * */ - protected function buildParams(): string + final protected function buildParams(): string { $return = '?'; $params = []; @@ -86,7 +83,7 @@ class Controller extends BaseController /** * @return Manager */ - protected function getManager(): Manager + final protected function getManager(): Manager { // create some objects: $manager = new Manager; diff --git a/app/Api/V1/Controllers/CurrencyController.php b/app/Api/V1/Controllers/CurrencyController.php index b6dae5fcb1..ec6acf1aa5 100644 --- a/app/Api/V1/Controllers/CurrencyController.php +++ b/app/Api/V1/Controllers/CurrencyController.php @@ -21,7 +21,6 @@ declare(strict_types=1); - namespace FireflyIII\Api\V1\Controllers; use FireflyIII\Api\V1\Requests\CurrencyRequest; @@ -64,16 +63,18 @@ use League\Fractal\Resource\Item; /** * Class CurrencyController. - * */ class CurrencyController extends Controller { use AccountFilter, TransactionFilter; + /** @var CurrencyRepositoryInterface The currency repository */ private $repository; + /** @var UserRepositoryInterface The user repository */ private $userRepository; + /** * CurrencyRepository constructor. * @@ -578,7 +579,7 @@ class CurrencyController extends Controller $manager = $this->getManager(); $currency = app('amount')->getDefaultCurrencyByUser(auth()->user()); $this->parameters->set('defaultCurrency', $currency); - + /** @var CurrencyTransformer $transformer */ $transformer = app(CurrencyTransformer::class); $transformer->setParameters($this->parameters); diff --git a/app/Api/V1/Controllers/CurrencyExchangeRateController.php b/app/Api/V1/Controllers/CurrencyExchangeRateController.php index 2cdb40ae6a..af3ab1ae04 100644 --- a/app/Api/V1/Controllers/CurrencyExchangeRateController.php +++ b/app/Api/V1/Controllers/CurrencyExchangeRateController.php @@ -43,9 +43,9 @@ class CurrencyExchangeRateController extends Controller /** @var CurrencyRepositoryInterface The currency repository */ private $repository; + /** * CurrencyExchangeRateController constructor. - * */ public function __construct() { diff --git a/app/Api/V1/Controllers/LinkTypeController.php b/app/Api/V1/Controllers/LinkTypeController.php index ff23e1b463..cbbea428ec 100644 --- a/app/Api/V1/Controllers/LinkTypeController.php +++ b/app/Api/V1/Controllers/LinkTypeController.php @@ -42,17 +42,18 @@ use League\Fractal\Resource\Item; /** * Class LinkTypeController. - * */ class LinkTypeController extends Controller { use TransactionFilter; + /** @var LinkTypeRepositoryInterface The link type repository */ private $repository; /** @var UserRepositoryInterface The user repository */ private $userRepository; + /** * LinkTypeController constructor. * diff --git a/app/Api/V1/Controllers/ObjectGroupController.php b/app/Api/V1/Controllers/ObjectGroupController.php index a078c61e93..2e68c799d7 100644 --- a/app/Api/V1/Controllers/ObjectGroupController.php +++ b/app/Api/V1/Controllers/ObjectGroupController.php @@ -39,12 +39,12 @@ use League\Fractal\Resource\Item; /** * Class GroupController. - * */ class ObjectGroupController extends Controller { private ObjectGroupRepositoryInterface $repository; + /** * ObjectGroupController constructor. * @@ -122,7 +122,6 @@ class ObjectGroupController extends Controller * * @return JsonResponse * @codeCoverageIgnore - * */ public function piggyBanks(ObjectGroup $objectGroup): JsonResponse { diff --git a/app/Api/V1/Controllers/PiggyBankController.php b/app/Api/V1/Controllers/PiggyBankController.php index b9b115d140..6f03988f79 100644 --- a/app/Api/V1/Controllers/PiggyBankController.php +++ b/app/Api/V1/Controllers/PiggyBankController.php @@ -40,14 +40,13 @@ use League\Fractal\Resource\Item; /** * Class PiggyBankController. - * */ class PiggyBankController extends Controller { - /** @var PiggyBankRepositoryInterface The piggy bank repository */ private $repository; + /** * PiggyBankController constructor. * diff --git a/app/Api/V1/Controllers/PreferenceController.php b/app/Api/V1/Controllers/PreferenceController.php index 2210e5810f..3fcb35b165 100644 --- a/app/Api/V1/Controllers/PreferenceController.php +++ b/app/Api/V1/Controllers/PreferenceController.php @@ -35,7 +35,6 @@ use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; /** - * * Class PreferenceController */ class PreferenceController extends Controller diff --git a/app/Api/V1/Controllers/RecurrenceController.php b/app/Api/V1/Controllers/RecurrenceController.php index f2a0f1fbf9..6878712c67 100644 --- a/app/Api/V1/Controllers/RecurrenceController.php +++ b/app/Api/V1/Controllers/RecurrenceController.php @@ -48,9 +48,11 @@ use Log; class RecurrenceController extends Controller { use TransactionFilter; + /** @var RecurringRepositoryInterface The recurring transaction repository */ private $repository; + /** * RecurrenceController constructor. * @@ -236,7 +238,7 @@ class RecurrenceController extends Controller $result = $recurring->fire(); } catch (FireflyException $e) { Log::error($e->getMessage()); - throw new FireflyException('200022: Error in cron job.'); + throw new FireflyException('200022: Error in cron job.',0, $e); } if (false === $result) { return response()->json([], 204); diff --git a/app/Api/V1/Controllers/RuleController.php b/app/Api/V1/Controllers/RuleController.php index e1ef261a33..fc9b73fc5d 100644 --- a/app/Api/V1/Controllers/RuleController.php +++ b/app/Api/V1/Controllers/RuleController.php @@ -46,15 +46,16 @@ use Log; /** * Class RuleController - * */ class RuleController extends Controller { /** @var AccountRepositoryInterface Account repository */ private $accountRepository; + /** @var RuleRepositoryInterface The rule repository */ private $ruleRepository; + /** * RuleController constructor. * diff --git a/app/Api/V1/Controllers/RuleGroupController.php b/app/Api/V1/Controllers/RuleGroupController.php index 8e8212fde5..59a20b3c4b 100644 --- a/app/Api/V1/Controllers/RuleGroupController.php +++ b/app/Api/V1/Controllers/RuleGroupController.php @@ -54,9 +54,11 @@ class RuleGroupController extends Controller { /** @var AccountRepositoryInterface Account repository */ private $accountRepository; + /** @var RuleGroupRepositoryInterface The rule group repository */ private $ruleGroupRepository; + /** * RuleGroupController constructor. * diff --git a/app/Api/V1/Controllers/Search/AccountController.php b/app/Api/V1/Controllers/Search/AccountController.php index 64c20a9cde..3c52be2688 100644 --- a/app/Api/V1/Controllers/Search/AccountController.php +++ b/app/Api/V1/Controllers/Search/AccountController.php @@ -45,6 +45,7 @@ class AccountController extends Controller private array $validFields; + public function __construct() { parent::__construct(); diff --git a/app/Api/V1/Controllers/SummaryController.php b/app/Api/V1/Controllers/SummaryController.php index a401ef1fe1..fd24fbc9d2 100644 --- a/app/Api/V1/Controllers/SummaryController.php +++ b/app/Api/V1/Controllers/SummaryController.php @@ -49,18 +49,23 @@ class SummaryController extends Controller { /** @var AvailableBudgetRepositoryInterface */ private $abRepository; + /** @var AccountRepositoryInterface */ private $accountRepository; + /** @var BillRepositoryInterface */ private $billRepository; + /** @var BudgetRepositoryInterface */ private $budgetRepository; + /** @var CurrencyRepositoryInterface */ private $currencyRepos; /** @var OperationsRepositoryInterface */ private $opsRepository; + /** * SummaryController constructor. * diff --git a/app/Api/V1/Controllers/TagController.php b/app/Api/V1/Controllers/TagController.php index 2ef58d6d1a..0ba1bc2698 100644 --- a/app/Api/V1/Controllers/TagController.php +++ b/app/Api/V1/Controllers/TagController.php @@ -23,8 +23,6 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; -use Carbon\Carbon; -use FireflyIII\Api\V1\Requests\DateRequest; use FireflyIII\Api\V1\Requests\TagStoreRequest; use FireflyIII\Api\V1\Requests\TagUpdateRequest; use FireflyIII\Helpers\Collector\GroupCollectorInterface; @@ -38,7 +36,6 @@ use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Support\Collection; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; @@ -53,6 +50,7 @@ class TagController extends Controller /** @var TagRepositoryInterface The tag repository */ private $repository; + /** * TagController constructor. * @@ -74,25 +72,6 @@ class TagController extends Controller ); } - /** - * @param DateRequest $request - * - * @return JsonResponse - */ - public function cloud(DateRequest $request): JsonResponse - { - // parameters for boxes: - $dates = $request->getAll(); - $start = $dates['start']; - $end = $dates['end']; - - // get all tags: - $tags = $this->repository->get(); - $cloud = $this->getTagCloud($tags, $start, $end); - - return response()->json($cloud); - } - /** * Delete the resource. * @@ -284,56 +263,4 @@ class TagController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } - - /** - * @param array $cloud - * @param float $min - * @param float $max - * - * @return array - */ - private function analyseTagCloud(array $cloud, float $min, float $max): array - { - foreach (array_keys($cloud['tags']) as $index) { - $cloud['tags'][$index]['relative'] = round($cloud['tags'][$index]['size'] / $max, 4); - } - $cloud['min'] = $min; - $cloud['max'] = $max; - - return $cloud; - } - - /** - * @param Collection $tags - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - private function getTagCloud(Collection $tags, Carbon $start, Carbon $end): array - { - $min = null; - $max = 0; - $cloud = [ - 'tags' => [], - ]; - /** @var Tag $tag */ - foreach ($tags as $tag) { - $earned = (float) $this->repository->earnedInPeriod($tag, $start, $end); - $spent = (float) $this->repository->spentInPeriod($tag, $start, $end); - $size = ($spent * -1) + $earned; - $min = $min ?? $size; - if ($size > 0) { - $max = $size > $max ? $size : $max; - $cloud['tags'][] = [ - 'tag' => $tag->tag, - 'id' => $tag->id, - 'size' => $size, - ]; - } - } - $cloud = $this->analyseTagCloud($cloud, $min, $max); - - return $cloud; - } } diff --git a/app/Api/V1/Controllers/TransactionController.php b/app/Api/V1/Controllers/TransactionController.php index becaa0af87..a9918f774b 100644 --- a/app/Api/V1/Controllers/TransactionController.php +++ b/app/Api/V1/Controllers/TransactionController.php @@ -40,6 +40,7 @@ use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\AttachmentTransformer; use FireflyIII\Transformers\PiggyBankEventTransformer; use FireflyIII\Transformers\TransactionGroupTransformer; +use FireflyIII\Transformers\TransactionLinkTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -57,12 +58,12 @@ class TransactionController extends Controller { use TransactionFilter; - /** @var TransactionGroupRepositoryInterface Group repository. */ - private $groupRepository; - /** @var JournalAPIRepositoryInterface Journal API repos */ - private $journalAPIRepository; - /** @var JournalRepositoryInterface The journal repository */ - private $repository; + private TransactionGroupRepositoryInterface $groupRepository; + + private JournalAPIRepositoryInterface $journalAPIRepository; + + private JournalRepositoryInterface $repository; + /** * TransactionController constructor. @@ -112,6 +113,26 @@ class TransactionController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } + /** + * @param TransactionJournal $transactionJournal + * + * @return JsonResponse + * @codeCoverageIgnore + */ + public function transactionLinks(TransactionJournal $transactionJournal): JsonResponse + { + $manager = $this->getManager(); + $journalLinks = $this->journalAPIRepository->getJournalLinks($transactionJournal); + + /** @var TransactionLinkTransformer $transformer */ + $transformer = app(TransactionLinkTransformer::class); + $transformer->setParameters($this->parameters); + + $resource = new FractalCollection($journalLinks, $transformer, 'transaction_links'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + /** * Remove the specified resource from storage. * @@ -269,8 +290,8 @@ class TransactionController extends Controller * * @param TransactionStoreRequest $request * - * @throws FireflyException * @return JsonResponse + * @throws FireflyException */ public function store(TransactionStoreRequest $request): JsonResponse { diff --git a/app/Api/V1/Controllers/TransactionLinkController.php b/app/Api/V1/Controllers/TransactionLinkController.php index d5079548b1..7ed1c6d8cd 100644 --- a/app/Api/V1/Controllers/TransactionLinkController.php +++ b/app/Api/V1/Controllers/TransactionLinkController.php @@ -47,9 +47,11 @@ class TransactionLinkController extends Controller /** @var JournalRepositoryInterface The journal repository */ private $journalRepository; + /** @var LinkTypeRepositoryInterface The link type repository */ private $repository; + /** * TransactionLinkController constructor. * diff --git a/app/Api/V1/Controllers/UserController.php b/app/Api/V1/Controllers/UserController.php index 5fc610c7f3..bb87e1d4d4 100644 --- a/app/Api/V1/Controllers/UserController.php +++ b/app/Api/V1/Controllers/UserController.php @@ -39,13 +39,11 @@ use League\Fractal\Resource\Item; /** * Class UserController. - * */ class UserController extends Controller { + private UserRepositoryInterface $repository; - /** @var UserRepositoryInterface The user repository */ - private $repository; /** * UserController constructor. diff --git a/app/Api/V1/Middleware/ApiDemoUser.php b/app/Api/V1/Middleware/ApiDemoUser.php index 5323877208..14fc2ac314 100644 --- a/app/Api/V1/Middleware/ApiDemoUser.php +++ b/app/Api/V1/Middleware/ApiDemoUser.php @@ -48,12 +48,9 @@ class ApiDemoUser if (null === $user) { return $next($request); } - - /** @var UserRepositoryInterface $repository */ - $repository = app(UserRepositoryInterface::class); - - if ($repository->hasRole($user, 'demo')) { + if ($user->hasRole('demo')) { return response('', 403); + } return $next($request); diff --git a/app/Api/V1/Requests/AccountStoreRequest.php b/app/Api/V1/Requests/AccountStoreRequest.php index 1809dd95c6..1b476f4ee3 100644 --- a/app/Api/V1/Requests/AccountStoreRequest.php +++ b/app/Api/V1/Requests/AccountStoreRequest.php @@ -26,14 +26,18 @@ namespace FireflyIII\Api\V1\Requests; use FireflyIII\Models\Location; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\AppendsLocationData; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class AccountStoreRequest * * @codeCoverageIgnore */ -class AccountStoreRequest extends Request +class AccountStoreRequest extends FormRequest { + use ConvertsDataTypes, AppendsLocationData; /** * Authorize logged in users. @@ -66,6 +70,7 @@ class AccountStoreRequest extends Request 'account_type' => $this->string('type'), 'account_type_id' => null, 'currency_id' => $this->integer('currency_id'), + 'order' => $this->integer('order'), 'currency_code' => $this->string('currency_code'), 'virtual_balance' => $this->string('virtual_balance'), 'iban' => $this->string('iban'), @@ -112,6 +117,7 @@ class AccountStoreRequest extends Request 'opening_balance' => 'numeric|required_with:opening_balance_date|nullable', 'opening_balance_date' => 'date|required_with:opening_balance|nullable', 'virtual_balance' => 'numeric|nullable', + 'order' => 'numeric|nullable', 'currency_id' => 'numeric|exists:transaction_currencies,id', 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', 'active' => [new IsBoolean], diff --git a/app/Api/V1/Requests/AccountUpdateRequest.php b/app/Api/V1/Requests/AccountUpdateRequest.php index 2c52b8c2c2..c0976a5f38 100644 --- a/app/Api/V1/Requests/AccountUpdateRequest.php +++ b/app/Api/V1/Requests/AccountUpdateRequest.php @@ -26,15 +26,18 @@ namespace FireflyIII\Api\V1\Requests; use FireflyIII\Models\Location; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\AppendsLocationData; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class AccountUpdateRequest * * @codeCoverageIgnore */ -class AccountUpdateRequest extends Request +class AccountUpdateRequest extends FormRequest { - + use ConvertsDataTypes, AppendsLocationData; /** * Authorize logged in users. * @@ -66,6 +69,7 @@ class AccountUpdateRequest extends Request 'account_type' => $this->nullableString('type'), 'account_type_id' => null, 'currency_id' => $this->nullableInteger('currency_id'), + 'order' => $this->integer('order'), 'currency_code' => $this->nullableString('currency_code'), 'virtual_balance' => $this->nullableString('virtual_balance'), 'iban' => $this->nullableString('iban'), @@ -114,6 +118,7 @@ class AccountUpdateRequest extends Request 'opening_balance' => 'numeric|required_with:opening_balance_date|nullable', 'opening_balance_date' => 'date|required_with:opening_balance|nullable', 'virtual_balance' => 'numeric|nullable', + 'order' => 'numeric|nullable', 'currency_id' => 'numeric|exists:transaction_currencies,id', 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', 'active' => [new IsBoolean], diff --git a/app/Api/V1/Requests/AttachmentStoreRequest.php b/app/Api/V1/Requests/AttachmentStoreRequest.php index ab74872dd2..7b0b901daf 100644 --- a/app/Api/V1/Requests/AttachmentStoreRequest.php +++ b/app/Api/V1/Requests/AttachmentStoreRequest.php @@ -24,14 +24,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Rules\IsValidAttachmentModel; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class AttachmentStoreRequest * * @codeCoverageIgnore */ -class AttachmentStoreRequest extends Request +class AttachmentStoreRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/AttachmentUpdateRequest.php b/app/Api/V1/Requests/AttachmentUpdateRequest.php index 60a69c612b..910d07d53d 100644 --- a/app/Api/V1/Requests/AttachmentUpdateRequest.php +++ b/app/Api/V1/Requests/AttachmentUpdateRequest.php @@ -23,13 +23,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** * ClassAttachmentUpdateRequest * * @codeCoverageIgnore */ -class AttachmentUpdateRequest extends Request +class AttachmentUpdateRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/Search/TransferRequest.php b/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php similarity index 58% rename from app/Api/V1/Requests/Search/TransferRequest.php rename to app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php index be910f9c39..c886037dc6 100644 --- a/app/Api/V1/Requests/Search/TransferRequest.php +++ b/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php @@ -1,7 +1,6 @@ check(); } + /** + * @return array + */ + public function getData(): array + { + $types = $this->string('types'); + $array = []; + if ('' !== $types) { + $array = explode(',', $types); + } + $limit = $this->integer('limit'); + $limit = 0 === $limit ? 10 : $limit; + + return [ + 'types' => $array, + 'query' => $this->string('query'), + 'date' => $this->date('date'), + 'limit' => $limit, + ]; + } + /** * @return array */ public function rules(): array { return [ - 'source' => ['required', new IsTransferAccount], - 'destination' => ['required', new IsTransferAccount], - 'amount' => 'required|numeric|gt:0', - 'description' => 'required|min:1', - 'date' => 'required|date', + 'limit' => 'min:0|max:1337', ]; } } diff --git a/app/Api/V1/Requests/AvailableBudgetRequest.php b/app/Api/V1/Requests/AvailableBudgetRequest.php index 93bac0676a..01b09cd798 100644 --- a/app/Api/V1/Requests/AvailableBudgetRequest.php +++ b/app/Api/V1/Requests/AvailableBudgetRequest.php @@ -23,13 +23,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** * Class AvailableBudgetRequest * * @codeCoverageIgnore */ -class AvailableBudgetRequest extends Request +class AvailableBudgetRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/BillRequest.php b/app/Api/V1/Requests/BillRequest.php index 27f014a4ca..79d4d89548 100644 --- a/app/Api/V1/Requests/BillRequest.php +++ b/app/Api/V1/Requests/BillRequest.php @@ -25,6 +25,8 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; /** @@ -34,9 +36,9 @@ use Illuminate\Validation\Validator; * * @codeCoverageIgnore */ -class BillRequest extends Request +class BillRequest extends FormRequest { - + use ConvertsDataTypes; /** * Authorize logged in users. * @@ -79,7 +81,6 @@ class BillRequest extends Request * The rules that the incoming request must be matched against. * * @return array - * */ public function rules(): array { diff --git a/app/Api/V1/Requests/BudgetLimitRequest.php b/app/Api/V1/Requests/BudgetLimitRequest.php index 85ab848b1a..dd93beb0ae 100644 --- a/app/Api/V1/Requests/BudgetLimitRequest.php +++ b/app/Api/V1/Requests/BudgetLimitRequest.php @@ -23,14 +23,19 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** * Class BudgetLimitRequest * * @codeCoverageIgnore * TODO AFTER 4.8,0: split this into two request classes. */ -class BudgetLimitRequest extends Request +class BudgetLimitRequest extends FormRequest { + use ConvertsDataTypes; + /** * Authorize logged in users. * @@ -49,7 +54,7 @@ class BudgetLimitRequest extends Request */ public function getAll(): array { - return [ + $data = [ 'budget_id' => $this->integer('budget_id'), 'start' => $this->date('start'), 'end' => $this->date('end'), @@ -57,6 +62,12 @@ class BudgetLimitRequest extends Request 'currency_id' => $this->integer('currency_id'), 'currency_code' => $this->string('currency_code'), ]; + // if request has a budget already, drop the rule. + $budget = $this->route()->parameter('budget'); + if (null !== $budget) { + $data['budget_id'] = $budget->id; + } + return $data; } /** diff --git a/app/Api/V1/Requests/BudgetStoreRequest.php b/app/Api/V1/Requests/BudgetStoreRequest.php index 21799684ac..812d7f4f8e 100644 --- a/app/Api/V1/Requests/BudgetStoreRequest.php +++ b/app/Api/V1/Requests/BudgetStoreRequest.php @@ -24,6 +24,9 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\ConvertsDataTypes; +use FireflyIII\Validation\AutoBudget\ValidatesAutoBudgetRequest; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; /** @@ -31,8 +34,9 @@ use Illuminate\Validation\Validator; * * @codeCoverageIgnore */ -class BudgetStoreRequest extends Request +class BudgetStoreRequest extends FormRequest { + use ConvertsDataTypes, ValidatesAutoBudgetRequest; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/BudgetUpdateRequest.php b/app/Api/V1/Requests/BudgetUpdateRequest.php index 0861c5be25..e7bf86c17f 100644 --- a/app/Api/V1/Requests/BudgetUpdateRequest.php +++ b/app/Api/V1/Requests/BudgetUpdateRequest.php @@ -24,6 +24,9 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\ConvertsDataTypes; +use FireflyIII\Validation\AutoBudget\ValidatesAutoBudgetRequest; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; /** @@ -31,8 +34,9 @@ use Illuminate\Validation\Validator; * * @codeCoverageIgnore */ -class BudgetUpdateRequest extends Request +class BudgetUpdateRequest extends FormRequest { + use ConvertsDataTypes, ValidatesAutoBudgetRequest; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/CategoryRequest.php b/app/Api/V1/Requests/CategoryRequest.php index ea9715aecf..ebeae6a766 100644 --- a/app/Api/V1/Requests/CategoryRequest.php +++ b/app/Api/V1/Requests/CategoryRequest.php @@ -24,6 +24,8 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Models\Category; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class CategoryRequest @@ -31,8 +33,9 @@ use FireflyIII\Models\Category; * @codeCoverageIgnore * TODO AFTER 4.8,0: split this into two request classes. */ -class CategoryRequest extends Request +class CategoryRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/ConfigurationRequest.php b/app/Api/V1/Requests/ConfigurationRequest.php index cb10f7ef1e..7a601ed6a5 100644 --- a/app/Api/V1/Requests/ConfigurationRequest.php +++ b/app/Api/V1/Requests/ConfigurationRequest.php @@ -25,15 +25,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class ConfigurationRequest * * @codeCoverageIgnore */ -class ConfigurationRequest extends Request +class ConfigurationRequest extends FormRequest { - + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/CurrencyRequest.php b/app/Api/V1/Requests/CurrencyRequest.php index 8d1b0c04d4..7d50a94ed7 100644 --- a/app/Api/V1/Requests/CurrencyRequest.php +++ b/app/Api/V1/Requests/CurrencyRequest.php @@ -24,6 +24,8 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** @@ -32,8 +34,9 @@ use FireflyIII\Rules\IsBoolean; * @codeCoverageIgnore * TODO AFTER 4.8,0: split this into two request classes. */ -class CurrencyRequest extends Request +class CurrencyRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/DataDestroyRequest.php b/app/Api/V1/Requests/DataDestroyRequest.php index 2b14817c3d..5d5fe3a31f 100644 --- a/app/Api/V1/Requests/DataDestroyRequest.php +++ b/app/Api/V1/Requests/DataDestroyRequest.php @@ -23,11 +23,15 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** * Class DataDestroyRequest */ -class DataDestroyRequest extends Request +class DataDestroyRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/DateRequest.php b/app/Api/V1/Requests/DateRequest.php index 71fa03ed53..63cd80b69c 100644 --- a/app/Api/V1/Requests/DateRequest.php +++ b/app/Api/V1/Requests/DateRequest.php @@ -25,13 +25,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** * Request class for end points that require date parameters. * * Class DateRequest */ -class DateRequest extends Request +class DateRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/LinkTypeRequest.php b/app/Api/V1/Requests/LinkTypeRequest.php index a4cf478832..c0d42b3e74 100644 --- a/app/Api/V1/Requests/LinkTypeRequest.php +++ b/app/Api/V1/Requests/LinkTypeRequest.php @@ -24,17 +24,19 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Models\LinkType; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; /** - * * Class LinkTypeRequest * * @codeCoverageIgnore * TODO AFTER 4.8,0: split this into two request classes. */ -class LinkTypeRequest extends Request +class LinkTypeRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/ObjectGroupUpdateRequest.php b/app/Api/V1/Requests/ObjectGroupUpdateRequest.php index 57cabc7fe8..cc306ed14e 100644 --- a/app/Api/V1/Requests/ObjectGroupUpdateRequest.php +++ b/app/Api/V1/Requests/ObjectGroupUpdateRequest.php @@ -24,14 +24,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** * Class AccountObjectGroupUpdateRequestUpdateRequest * * @codeCoverageIgnore */ -class ObjectGroupUpdateRequest extends Request +class ObjectGroupUpdateRequest extends FormRequest { - + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/PiggyBankRequest.php b/app/Api/V1/Requests/PiggyBankRequest.php index 68934d4475..75d636e2de 100644 --- a/app/Api/V1/Requests/PiggyBankRequest.php +++ b/app/Api/V1/Requests/PiggyBankRequest.php @@ -27,16 +27,19 @@ use FireflyIII\Models\PiggyBank; use FireflyIII\Rules\IsAssetAccountId; use FireflyIII\Rules\LessThanPiggyTarget; use FireflyIII\Rules\ZeroOrMore; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** - * * Class PiggyBankRequest * * @codeCoverageIgnore * TODO AFTER 4.8,0: split this into two request classes. */ -class PiggyBankRequest extends Request +class PiggyBankRequest extends FormRequest { + use ConvertsDataTypes; + /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/PiggyBankStoreRequest.php b/app/Api/V1/Requests/PiggyBankStoreRequest.php index 8d32203aa9..a6d627cccc 100644 --- a/app/Api/V1/Requests/PiggyBankStoreRequest.php +++ b/app/Api/V1/Requests/PiggyBankStoreRequest.php @@ -24,15 +24,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Rules\ZeroOrMore; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** - * * Class PiggyBankStoreRequest * * @codeCoverageIgnore */ -class PiggyBankStoreRequest extends Request +class PiggyBankStoreRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/PreferenceRequest.php b/app/Api/V1/Requests/PreferenceRequest.php index 75ef675d5a..18852c8740 100644 --- a/app/Api/V1/Requests/PreferenceRequest.php +++ b/app/Api/V1/Requests/PreferenceRequest.php @@ -23,15 +23,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** - * * Class PreferenceRequest * * @codeCoverageIgnore */ -class PreferenceRequest extends Request +class PreferenceRequest extends FormRequest { - + use ConvertsDataTypes; /** * Authorize logged in users. diff --git a/app/Api/V1/Requests/RecurrenceStoreRequest.php b/app/Api/V1/Requests/RecurrenceStoreRequest.php index 3e739976ab..40da7f863b 100644 --- a/app/Api/V1/Requests/RecurrenceStoreRequest.php +++ b/app/Api/V1/Requests/RecurrenceStoreRequest.php @@ -26,18 +26,20 @@ namespace FireflyIII\Api\V1\Requests; use Carbon\Carbon; use FireflyIII\Rules\BelongsUser; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\ConvertsDataTypes; +use FireflyIII\Support\Request\GetRecurrenceData; use FireflyIII\Validation\CurrencyValidation; use FireflyIII\Validation\RecurrenceValidation; use FireflyIII\Validation\TransactionValidation; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; /** * Class RecurrenceStoreRequest */ -class RecurrenceStoreRequest extends Request +class RecurrenceStoreRequest extends FormRequest { - use RecurrenceValidation, TransactionValidation, CurrencyValidation; - + use ConvertsDataTypes, RecurrenceValidation, TransactionValidation, CurrencyValidation, GetRecurrenceData; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/RecurrenceUpdateRequest.php b/app/Api/V1/Requests/RecurrenceUpdateRequest.php index aaa2b1e766..b39f9718fe 100644 --- a/app/Api/V1/Requests/RecurrenceUpdateRequest.php +++ b/app/Api/V1/Requests/RecurrenceUpdateRequest.php @@ -26,17 +26,20 @@ namespace FireflyIII\Api\V1\Requests; use FireflyIII\Models\Recurrence; use FireflyIII\Rules\BelongsUser; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\ConvertsDataTypes; +use FireflyIII\Support\Request\GetRecurrenceData; use FireflyIII\Validation\CurrencyValidation; use FireflyIII\Validation\RecurrenceValidation; use FireflyIII\Validation\TransactionValidation; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; /** * Class RecurrenceUpdateRequest */ -class RecurrenceUpdateRequest extends Request +class RecurrenceUpdateRequest extends FormRequest { - use RecurrenceValidation, TransactionValidation, CurrencyValidation; + use ConvertsDataTypes, RecurrenceValidation, TransactionValidation, CurrencyValidation, GetRecurrenceData; /** * Authorize logged in users. @@ -154,16 +157,16 @@ class RecurrenceUpdateRequest extends Request /** * Returns the repetition data as it is found in the submitted data. * - * @return array|null + * @return array */ - private function getRepetitionData(): ?array + private function getRepetitionData(): array { $return = []; // repetition data: /** @var array $repetitions */ $repetitions = $this->get('repetitions'); if (null === $repetitions) { - return null; + return []; } /** @var array $repetition */ foreach ($repetitions as $repetition) { @@ -182,16 +185,16 @@ class RecurrenceUpdateRequest extends Request * Returns the transaction data as it is found in the submitted data. It's a complex method according to code * standards but it just has a lot of ??-statements because of the fields that may or may not exist. * - * @return array|null + * @return array */ - private function getTransactionData(): ?array + private function getTransactionData(): array { $return = []; // transaction data: /** @var array $transactions */ $transactions = $this->get('transactions'); if (null === $transactions) { - return null; + return []; } /** @var array $transaction */ foreach ($transactions as $transaction) { diff --git a/app/Api/V1/Requests/RuleGroupRequest.php b/app/Api/V1/Requests/RuleGroupRequest.php index 4bee1a7aff..53374a6a88 100644 --- a/app/Api/V1/Requests/RuleGroupRequest.php +++ b/app/Api/V1/Requests/RuleGroupRequest.php @@ -25,6 +25,8 @@ namespace FireflyIII\Api\V1\Requests; use FireflyIII\Models\RuleGroup; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** @@ -32,8 +34,9 @@ use FireflyIII\Rules\IsBoolean; * Class RuleGroupRequest * TODO AFTER 4.8,0: split this into two request classes. */ -class RuleGroupRequest extends Request +class RuleGroupRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/RuleGroupTestRequest.php b/app/Api/V1/Requests/RuleGroupTestRequest.php index bdd01747c4..2d922f140a 100644 --- a/app/Api/V1/Requests/RuleGroupTestRequest.php +++ b/app/Api/V1/Requests/RuleGroupTestRequest.php @@ -22,7 +22,6 @@ declare(strict_types=1); - namespace FireflyIII\Api\V1\Requests; @@ -30,14 +29,17 @@ use Carbon\Carbon; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Collection; use Log; /** * Class RuleGroupTestRequest */ -class RuleGroupTestRequest extends Request +class RuleGroupTestRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/RuleGroupTriggerRequest.php b/app/Api/V1/Requests/RuleGroupTriggerRequest.php index 6672fcc35e..f4118b78b5 100644 --- a/app/Api/V1/Requests/RuleGroupTriggerRequest.php +++ b/app/Api/V1/Requests/RuleGroupTriggerRequest.php @@ -22,7 +22,6 @@ declare(strict_types=1); - namespace FireflyIII\Api\V1\Requests; @@ -30,14 +29,17 @@ use Carbon\Carbon; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Collection; use Log; /** * Class RuleGroupTriggerRequest */ -class RuleGroupTriggerRequest extends Request +class RuleGroupTriggerRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/RuleStoreRequest.php b/app/Api/V1/Requests/RuleStoreRequest.php index bdbac61824..26648dd18d 100644 --- a/app/Api/V1/Requests/RuleStoreRequest.php +++ b/app/Api/V1/Requests/RuleStoreRequest.php @@ -24,16 +24,18 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; use function is_array; /** * Class RuleStoreRequest - * */ -class RuleStoreRequest extends Request +class RuleStoreRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/RuleTestRequest.php b/app/Api/V1/Requests/RuleTestRequest.php index e506ce9d81..f693ac5779 100644 --- a/app/Api/V1/Requests/RuleTestRequest.php +++ b/app/Api/V1/Requests/RuleTestRequest.php @@ -22,7 +22,6 @@ declare(strict_types=1); - namespace FireflyIII\Api\V1\Requests; @@ -30,14 +29,17 @@ use Carbon\Carbon; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Collection; use Log; /** * Class RuleTestRequest */ -class RuleTestRequest extends Request +class RuleTestRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/RuleTriggerRequest.php b/app/Api/V1/Requests/RuleTriggerRequest.php index d05f4f7dfe..e2954dadaf 100644 --- a/app/Api/V1/Requests/RuleTriggerRequest.php +++ b/app/Api/V1/Requests/RuleTriggerRequest.php @@ -29,14 +29,17 @@ use Carbon\Carbon; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Collection; use Log; /** * Class RuleTriggerRequest */ -class RuleTriggerRequest extends Request +class RuleTriggerRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/RuleUpdateRequest.php b/app/Api/V1/Requests/RuleUpdateRequest.php index e1f8186883..51d285d6fc 100644 --- a/app/Api/V1/Requests/RuleUpdateRequest.php +++ b/app/Api/V1/Requests/RuleUpdateRequest.php @@ -24,16 +24,18 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; use function is_array; /** * Class RuleUpdateRequest - * */ -class RuleUpdateRequest extends Request +class RuleUpdateRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/TagStoreRequest.php b/app/Api/V1/Requests/TagStoreRequest.php index d942b7ac4e..c269357078 100644 --- a/app/Api/V1/Requests/TagStoreRequest.php +++ b/app/Api/V1/Requests/TagStoreRequest.php @@ -24,16 +24,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Models\Location; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class TagStoreRequest * * @codeCoverageIgnore - * */ -class TagStoreRequest extends Request +class TagStoreRequest extends FormRequest { - + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/TagUpdateRequest.php b/app/Api/V1/Requests/TagUpdateRequest.php index 6173e5aa1b..08d87d5261 100644 --- a/app/Api/V1/Requests/TagUpdateRequest.php +++ b/app/Api/V1/Requests/TagUpdateRequest.php @@ -25,16 +25,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; use FireflyIII\Models\Location; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class TagUpdateRequest * * @codeCoverageIgnore - * */ -class TagUpdateRequest extends Request +class TagUpdateRequest extends FormRequest { - + use ConvertsDataTypes; /** * Authorize logged in users. * diff --git a/app/Api/V1/Requests/TransactionLinkRequest.php b/app/Api/V1/Requests/TransactionLinkRequest.php index 9086c2cdd5..02f260e8e9 100644 --- a/app/Api/V1/Requests/TransactionLinkRequest.php +++ b/app/Api/V1/Requests/TransactionLinkRequest.php @@ -25,15 +25,17 @@ namespace FireflyIII\Api\V1\Requests; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; +use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\User; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; /** - * * Class TransactionLinkRequest */ -class TransactionLinkRequest extends Request +class TransactionLinkRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * @@ -62,7 +64,6 @@ class TransactionLinkRequest extends Request } /** - * * The rules that the incoming request must be matched against. * * @return array diff --git a/app/Api/V1/Requests/TransactionStoreRequest.php b/app/Api/V1/Requests/TransactionStoreRequest.php index 5355928588..2ed203bc07 100644 --- a/app/Api/V1/Requests/TransactionStoreRequest.php +++ b/app/Api/V1/Requests/TransactionStoreRequest.php @@ -28,18 +28,20 @@ use FireflyIII\Rules\BelongsUser; use FireflyIII\Rules\IsBoolean; use FireflyIII\Rules\IsDateOrTime; use FireflyIII\Support\NullArrayObject; +use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Validation\CurrencyValidation; use FireflyIII\Validation\GroupValidation; use FireflyIII\Validation\TransactionValidation; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; use Log; /** * Class TransactionStoreRequest */ -class TransactionStoreRequest extends Request +class TransactionStoreRequest extends FormRequest { - use TransactionValidation, GroupValidation, CurrencyValidation; + use TransactionValidation, GroupValidation, CurrencyValidation, ConvertsDataTypes; /** * Authorize logged in users. @@ -138,6 +140,7 @@ class TransactionStoreRequest extends Request 'transactions.*.external_id' => 'min:1,max:255|nullable', 'transactions.*.recurrence_id' => 'min:1,max:255|nullable', 'transactions.*.bunq_payment_id' => 'min:1,max:255|nullable', + 'transactions.*.external_uri' => 'min:1,max:255|nullable|url', // SEPA fields: 'transactions.*.sepa_cc' => 'min:1,max:255|nullable', @@ -271,6 +274,7 @@ class TransactionStoreRequest extends Request 'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')), 'recurrence_id' => $this->integerFromValue($object['recurrence_id']), 'bunq_payment_id' => $this->stringFromValue((string) $object['bunq_payment_id']), + 'external_uri' => $this->stringFromValue((string) $object['external_uri']), 'sepa_cc' => $this->stringFromValue($object['sepa_cc']), 'sepa_ct_op' => $this->stringFromValue($object['sepa_ct_op']), diff --git a/app/Api/V1/Requests/TransactionUpdateRequest.php b/app/Api/V1/Requests/TransactionUpdateRequest.php index 88d44b13af..302f8f3700 100644 --- a/app/Api/V1/Requests/TransactionUpdateRequest.php +++ b/app/Api/V1/Requests/TransactionUpdateRequest.php @@ -28,31 +28,39 @@ use FireflyIII\Models\TransactionGroup; use FireflyIII\Rules\BelongsUser; use FireflyIII\Rules\IsBoolean; use FireflyIII\Rules\IsDateOrTime; +use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Validation\GroupValidation; use FireflyIII\Validation\TransactionValidation; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; use Log; /** * Class TransactionUpdateRequest */ -class TransactionUpdateRequest extends Request +class TransactionUpdateRequest extends FormRequest { - use TransactionValidation, GroupValidation; + use TransactionValidation, GroupValidation, ConvertsDataTypes; /** @var array Array values. */ private $arrayFields; + /** @var array Boolean values. */ private $booleanFields; + /** @var array Fields that contain date values. */ private $dateFields; + /** @var array Fields that contain integer values. */ private $integerFields; + /** @var array Fields that contain string values. */ private $stringFields; + /** @var array Fields that contain text (with newlines) */ private $textareaFields; + /** * Authorize logged in users. * @@ -127,6 +135,7 @@ class TransactionUpdateRequest extends Request 'sepa_ep', 'sepa_ci', 'sepa_batch_id', + 'external_uri', ]; $this->booleanFields = [ 'reconciled', @@ -204,6 +213,7 @@ class TransactionUpdateRequest extends Request 'transactions.*.external_id' => 'min:1,max:255|nullable', 'transactions.*.recurrence_id' => 'min:1,max:255|nullable', 'transactions.*.bunq_payment_id' => 'min:1,max:255|nullable', + 'transactions.*.external_uri' => 'min:1,max:255|nullable|url', // SEPA fields: 'transactions.*.sepa_cc' => 'min:1,max:255|nullable', diff --git a/app/Api/V1/Requests/UserStoreRequest.php b/app/Api/V1/Requests/UserStoreRequest.php index e9b55980fe..cff3d97e47 100644 --- a/app/Api/V1/Requests/UserStoreRequest.php +++ b/app/Api/V1/Requests/UserStoreRequest.php @@ -24,16 +24,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; -use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Rules\IsBoolean; -use FireflyIII\User; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class UserStoreRequest */ -class UserStoreRequest extends Request +class UserStoreRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * @@ -41,21 +42,7 @@ class UserStoreRequest extends Request */ public function authorize(): bool { - $result = false; - // Only allow authenticated users - if (auth()->check()) { - /** @var User $user */ - $user = auth()->user(); - - /** @var UserRepositoryInterface $repository */ - $repository = app(UserRepositoryInterface::class); - - if ($repository->hasRole($user, 'owner')) { - $result = true; // @codeCoverageIgnore - } - } - - return $result; + return auth()->check() && auth()->user()->hasRole('owner'); } /** diff --git a/app/Api/V1/Requests/UserUpdateRequest.php b/app/Api/V1/Requests/UserUpdateRequest.php index 7dc41b7806..5fee8f3e24 100644 --- a/app/Api/V1/Requests/UserUpdateRequest.php +++ b/app/Api/V1/Requests/UserUpdateRequest.php @@ -24,16 +24,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; -use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Rules\IsBoolean; -use FireflyIII\User; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class UserUpdateRequest */ -class UserUpdateRequest extends Request +class UserUpdateRequest extends FormRequest { + use ConvertsDataTypes; /** * Authorize logged in users. * @@ -41,21 +42,7 @@ class UserUpdateRequest extends Request */ public function authorize(): bool { - $result = false; - // Only allow authenticated users - if (auth()->check()) { - /** @var User $user */ - $user = auth()->user(); - - /** @var UserRepositoryInterface $repository */ - $repository = app(UserRepositoryInterface::class); - - if ($repository->hasRole($user, 'owner')) { - $result = true; // @codeCoverageIgnore - } - } - - return $result; + return auth()->check() && auth()->user()->hasRole('owner'); } /** diff --git a/app/Console/Commands/Correction/CorrectDatabase.php b/app/Console/Commands/Correction/CorrectDatabase.php index d2e88f4725..2cc972a861 100644 --- a/app/Console/Commands/Correction/CorrectDatabase.php +++ b/app/Console/Commands/Correction/CorrectDatabase.php @@ -48,6 +48,7 @@ class CorrectDatabase extends Command */ protected $signature = 'firefly-iii:correct-database'; + /** * Execute the console command. */ @@ -70,11 +71,13 @@ class CorrectDatabase extends Command 'firefly-iii:delete-empty-journals', 'firefly-iii:delete-empty-groups', 'firefly-iii:fix-account-types', + 'firefly-iii:fix-account-order', 'firefly-iii:rename-meta-fields', 'firefly-iii:fix-ob-currencies', 'firefly-iii:fix-long-descriptions', 'firefly-iii:fix-recurring-transactions', 'firefly-iii:restore-oauth-keys', + 'firefly-iii:fix-transaction-types', ]; foreach ($commands as $command) { $this->line(sprintf('Now executing %s', $command)); diff --git a/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php b/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php index 1063d47aa1..d0556c7080 100644 --- a/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php +++ b/app/Console/Commands/Correction/CorrectOpeningBalanceCurrencies.php @@ -52,6 +52,7 @@ class CorrectOpeningBalanceCurrencies extends Command */ protected $signature = 'firefly-iii:fix-ob-currencies'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/CreateAccessTokens.php b/app/Console/Commands/Correction/CreateAccessTokens.php index 9dd515c17a..7314645cf8 100644 --- a/app/Console/Commands/Correction/CreateAccessTokens.php +++ b/app/Console/Commands/Correction/CreateAccessTokens.php @@ -21,7 +21,6 @@ declare(strict_types=1); - namespace FireflyIII\Console\Commands\Correction; use Exception; @@ -47,6 +46,7 @@ class CreateAccessTokens extends Command */ protected $signature = 'firefly-iii:create-access-tokens'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/CreateLinkTypes.php b/app/Console/Commands/Correction/CreateLinkTypes.php index 0bdf1d924d..6118c9920d 100644 --- a/app/Console/Commands/Correction/CreateLinkTypes.php +++ b/app/Console/Commands/Correction/CreateLinkTypes.php @@ -44,6 +44,7 @@ class CreateLinkTypes extends Command */ protected $signature = 'firefly-iii:create-link-types'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/DeleteEmptyGroups.php b/app/Console/Commands/Correction/DeleteEmptyGroups.php index 41e05013d3..f8c4d49c9f 100644 --- a/app/Console/Commands/Correction/DeleteEmptyGroups.php +++ b/app/Console/Commands/Correction/DeleteEmptyGroups.php @@ -46,6 +46,7 @@ class DeleteEmptyGroups extends Command */ protected $signature = 'firefly-iii:delete-empty-groups'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/DeleteEmptyJournals.php b/app/Console/Commands/Correction/DeleteEmptyJournals.php index 93d77eb81e..5cdef1cc89 100644 --- a/app/Console/Commands/Correction/DeleteEmptyJournals.php +++ b/app/Console/Commands/Correction/DeleteEmptyJournals.php @@ -48,6 +48,7 @@ class DeleteEmptyJournals extends Command */ protected $signature = 'firefly-iii:delete-empty-journals'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/DeleteOrphanedTransactions.php b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php index c95007b60f..8752694e12 100644 --- a/app/Console/Commands/Correction/DeleteOrphanedTransactions.php +++ b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php @@ -48,6 +48,7 @@ class DeleteOrphanedTransactions extends Command */ protected $signature = 'firefly-iii:delete-orphaned-transactions'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/DeleteZeroAmount.php b/app/Console/Commands/Correction/DeleteZeroAmount.php index f96b426eee..e374905cac 100644 --- a/app/Console/Commands/Correction/DeleteZeroAmount.php +++ b/app/Console/Commands/Correction/DeleteZeroAmount.php @@ -47,6 +47,7 @@ class DeleteZeroAmount extends Command */ protected $signature = 'firefly-iii:delete-zero-amount'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/EnableCurrencies.php b/app/Console/Commands/Correction/EnableCurrencies.php index 502d1be977..076cacd868 100644 --- a/app/Console/Commands/Correction/EnableCurrencies.php +++ b/app/Console/Commands/Correction/EnableCurrencies.php @@ -49,6 +49,7 @@ class EnableCurrencies extends Command */ protected $signature = 'firefly-iii:enable-currencies'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/FixAccountOrder.php b/app/Console/Commands/Correction/FixAccountOrder.php new file mode 100644 index 0000000000..cb3c56dc4d --- /dev/null +++ b/app/Console/Commands/Correction/FixAccountOrder.php @@ -0,0 +1,95 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Console\Commands\Correction; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\User; +use Illuminate\Console\Command; + +/** + * Class FixAccountOrder + */ +class FixAccountOrder extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Make sure account order is correct.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:fix-account-order'; + + private AccountRepositoryInterface $repository; + + + /** + * Execute the console command. + * + * @throws FireflyException + * @return int + */ + public function handle(): int + { + $this->stupidLaravel(); + $start = microtime(true); + + $users = User::get(); + foreach ($users as $user) { + $this->repository->setUser($user); + $sets = [ + [AccountType::DEFAULT, AccountType::ASSET], + [AccountType::EXPENSE, AccountType::BENEFICIARY], + [AccountType::REVENUE], + [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], + ]; + foreach ($sets as $set) { + $this->repository->resetAccountOrder($set); + } + } + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verifying account order took %s seconds', $end)); + + return 0; + } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + * @codeCoverageIgnore + */ + private function stupidLaravel(): void + { + $this->repository = app(AccountRepositoryInterface::class); + } +} diff --git a/app/Console/Commands/Correction/FixAccountTypes.php b/app/Console/Commands/Correction/FixAccountTypes.php index 8b6bd26d8e..cf7441d792 100644 --- a/app/Console/Commands/Correction/FixAccountTypes.php +++ b/app/Console/Commands/Correction/FixAccountTypes.php @@ -39,30 +39,27 @@ class FixAccountTypes extends Command { /** * The console command description. - * * @var string */ protected $description = 'Make sure all journals have the correct from/to account types.'; /** * The name and signature of the console command. - * * @var string */ protected $signature = 'firefly-iii:fix-account-types'; /** @var int */ - private $count; - /** @var array */ - private $expected; + private $count; + private array $expected; /** @var AccountFactory */ private $factory; /** @var array */ private $fixable; + /** * Execute the console command. - * - * @throws FireflyException * @return int + * @throws FireflyException */ public function handle(): int { @@ -71,21 +68,14 @@ class FixAccountTypes extends Command $start = microtime(true); $this->factory = app(AccountFactory::class); // some combinations can be fixed by this script: - $this->fixable = [ - // transfers from asset to liability and vice versa - sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::LOAN), - sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::DEBT), - sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::MORTGAGE), - sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::LOAN, AccountType::ASSET), - sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::DEBT, AccountType::ASSET), - sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::MORTGAGE, AccountType::ASSET), + $this->fixable = [// transfers from asset to liability and vice versa + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::LOAN), sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::DEBT), sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::MORTGAGE), sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::LOAN, AccountType::ASSET), sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::DEBT, AccountType::ASSET), sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::MORTGAGE, AccountType::ASSET), - // withdrawals with a revenue account as destination instead of an expense account. - sprintf('%s%s%s', TransactionType::WITHDRAWAL, AccountType::ASSET, AccountType::REVENUE), + // withdrawals with a revenue account as destination instead of an expense account. + sprintf('%s%s%s', TransactionType::WITHDRAWAL, AccountType::ASSET, AccountType::REVENUE), - // deposits with an expense account as source instead of a revenue account. - sprintf('%s%s%s', TransactionType::DEPOSIT, AccountType::EXPENSE, AccountType::ASSET), - ]; + // deposits with an expense account as source instead of a revenue account. + sprintf('%s%s%s', TransactionType::DEPOSIT, AccountType::EXPENSE, AccountType::ASSET),]; $this->expected = config('firefly.source_dests'); @@ -114,7 +104,6 @@ class FixAccountTypes extends Command * @param string $type * @param Transaction $source * @param Transaction $dest - * * @throws FireflyException */ private function fixJournal(TransactionJournal $journal, string $type, Transaction $source, Transaction $dest): void @@ -154,16 +143,7 @@ class FixAccountTypes extends Command $result = $this->factory->findOrCreate($dest->account->name, AccountType::EXPENSE); $dest->account()->associate($result); $dest->save(); - $this->info( - sprintf( - 'Transaction journal #%d, destination account changed from #%d ("%s") to #%d ("%s").', - $journal->id, - $oldDest->id, - $oldDest->name, - $result->id, - $result->name - ) - ); + $this->info(sprintf('Transaction journal #%d, destination account changed from #%d ("%s") to #%d ("%s").', $journal->id, $oldDest->id, $oldDest->name, $result->id, $result->name)); $this->inspectJournal($journal); break; case sprintf('%s%s%s', TransactionType::DEPOSIT, AccountType::EXPENSE, AccountType::ASSET): @@ -174,16 +154,7 @@ class FixAccountTypes extends Command $oldSource = $dest->account; $source->account()->associate($result); $source->save(); - $this->info( - sprintf( - 'Transaction journal #%d, source account changed from #%d ("%s") to #%d ("%s").', - $journal->id, - $oldSource->id, - $oldSource->name, - $result->id, - $result->name - ) - ); + $this->info(sprintf('Transaction journal #%d, source account changed from #%d ("%s") to #%d ("%s").', $journal->id, $oldSource->id, $oldSource->name, $result->id, $result->name)); $this->inspectJournal($journal); break; default: @@ -197,7 +168,6 @@ class FixAccountTypes extends Command /** * @param TransactionJournal $journal - * * @return Transaction */ private function getDestinationTransaction(TransactionJournal $journal): Transaction @@ -207,7 +177,6 @@ class FixAccountTypes extends Command /** * @param TransactionJournal $journal - * * @return Transaction */ private function getSourceTransaction(TransactionJournal $journal): Transaction @@ -217,12 +186,11 @@ class FixAccountTypes extends Command /** * @param TransactionJournal $journal - * * @throws FireflyException */ private function inspectJournal(TransactionJournal $journal): void { - Log::debug(sprintf('Now trying to fix journal #%d', $journal->id)); + //Log::debug(sprintf('Now trying to fix journal #%d', $journal->id)); $count = $journal->transactions()->count(); if (2 !== $count) { Log::debug(sprintf('Journal has %d transactions, so cant fix.', $count)); @@ -249,7 +217,7 @@ class FixAccountTypes extends Command $destAccount = $destTransaction->account; $destAccountType = $destAccount->accountType->type; - if (!isset($this->expected[$type])) { + if (!array_key_exists($type, $this->expected)) { // @codeCoverageIgnoreStart Log::info(sprintf('No source/destination info for transaction type %s.', $type)); $this->info(sprintf('No source/destination info for transaction type %s.', $type)); @@ -257,7 +225,7 @@ class FixAccountTypes extends Command return; // @codeCoverageIgnoreEnd } - if (!isset($this->expected[$type][$sourceAccountType])) { + if (!array_key_exists($sourceAccountType, $this->expected[$type])) { $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction); return; @@ -272,7 +240,6 @@ class FixAccountTypes extends Command * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should * be called from the handle method instead of using the constructor to initialize the command. - * * @codeCoverageIgnore */ private function stupidLaravel(): void diff --git a/app/Console/Commands/Correction/FixGroupAccounts.php b/app/Console/Commands/Correction/FixGroupAccounts.php index 9d22b06522..eff6f7d5a1 100644 --- a/app/Console/Commands/Correction/FixGroupAccounts.php +++ b/app/Console/Commands/Correction/FixGroupAccounts.php @@ -49,6 +49,7 @@ class FixGroupAccounts extends Command */ protected $signature = 'firefly-iii:unify-group-accounts'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/FixLongDescriptions.php b/app/Console/Commands/Correction/FixLongDescriptions.php index 28ad7ea15f..e05e957ccc 100644 --- a/app/Console/Commands/Correction/FixLongDescriptions.php +++ b/app/Console/Commands/Correction/FixLongDescriptions.php @@ -47,6 +47,7 @@ class FixLongDescriptions extends Command */ protected $signature = 'firefly-iii:fix-long-descriptions'; + /** * Execute the console command. * @@ -68,7 +69,7 @@ class FixLongDescriptions extends Command $groups = TransactionGroup::get(['id', 'title']); /** @var TransactionGroup $group */ foreach ($groups as $group) { - if (strlen((string)$group->title) > self::MAX_LENGTH) { + if (strlen((string) $group->title) > self::MAX_LENGTH) { $group->title = substr($group->title, 0, self::MAX_LENGTH); $group->save(); $this->line(sprintf('Truncated description of transaction group #%d', $group->id)); diff --git a/app/Console/Commands/Correction/FixPiggies.php b/app/Console/Commands/Correction/FixPiggies.php index 14243e1aa2..8cca164de8 100644 --- a/app/Console/Commands/Correction/FixPiggies.php +++ b/app/Console/Commands/Correction/FixPiggies.php @@ -51,6 +51,7 @@ class FixPiggies extends Command /** @var int */ private $count; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/FixRecurringTransactions.php b/app/Console/Commands/Correction/FixRecurringTransactions.php index 6391a32d84..01b5f9cdb6 100644 --- a/app/Console/Commands/Correction/FixRecurringTransactions.php +++ b/app/Console/Commands/Correction/FixRecurringTransactions.php @@ -54,6 +54,7 @@ class FixRecurringTransactions extends Command /** @var UserRepositoryInterface */ private $userRepos; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/FixTransactionTypes.php b/app/Console/Commands/Correction/FixTransactionTypes.php new file mode 100644 index 0000000000..de083aa82a --- /dev/null +++ b/app/Console/Commands/Correction/FixTransactionTypes.php @@ -0,0 +1,170 @@ +collectJournals(); + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $fixed = $this->fixJournal($journal); + if (true === $fixed) { + $count++; + } + } + $end = round(microtime(true) - $start, 2); + if ($count > 0) { + $this->info(sprintf('Corrected transaction type of %d transaction journals in %s seconds.', $count, $end)); + + return 0; + } + $this->line(sprintf('All transaction journals are of the correct transaction type (in %s seconds).', $end)); + + return 0; + } + + /** + * @param TransactionJournal $journal + * @param string $expectedType + */ + private function changeJournal(TransactionJournal $journal, string $expectedType): void + { + $type = TransactionType::whereType($expectedType)->first(); + if (null !== $type) { + $journal->transaction_type_id = $type->id; + $journal->save(); + } + } + + /** + * Collect all transaction journals. + * + * @return Collection + */ + private function collectJournals(): Collection + { + return TransactionJournal + ::with(['transactionType', 'transactions', 'transactions.account', 'transactions.account.accountType']) + ->get(); + } + + /** + * @param TransactionJournal $journal + * + * @return bool + */ + private function fixJournal(TransactionJournal $journal): bool + { + $type = $journal->transactionType->type; + try { + $source = $this->getSourceAccount($journal); + $destination = $this->getDestinationAccount($journal); + } catch (FireflyException $e) { + $this->error($e->getMessage()); + + return false; + } + $expectedType = (string) config(sprintf('firefly.account_to_transaction.%s.%s', $source->accountType->type, $destination->accountType->type)); + if ($expectedType !== $type) { + $this->line(sprintf('Transaction journal #%d was of type "%s" but is corrected to "%s"', $journal->id, $type, $expectedType)); + $this->changeJournal($journal, $expectedType); + + return true; + } + + return false; + } + + /** + * @param TransactionJournal $journal + * + * @throws FireflyException + * @return Account + */ + private function getDestinationAccount(TransactionJournal $journal): Account + { + $collection = $journal->transactions->filter( + static function (Transaction $transaction) { + return $transaction->amount > 0; + } + ); + if (0 === $collection->count()) { + throw new FireflyException(sprintf('Journal #%d has no destination transaction.', $journal->id)); + } + if (1 !== $collection->count()) { + throw new FireflyException(sprintf('Journal #%d has multiple destination transactions.', $journal->id)); + } + /** @var Transaction $transaction */ + $transaction = $collection->first(); + $account = $transaction->account; + if (null === $account) { + throw new FireflyException(sprintf('Journal #%d, transaction #%d has no destination account.', $journal->id, $transaction->id)); + } + + return $account; + } + + /** + * @param TransactionJournal $journal + * + * @throws FireflyException + * @return Account + */ + private function getSourceAccount(TransactionJournal $journal): Account + { + $collection = $journal->transactions->filter( + static function (Transaction $transaction) { + return $transaction->amount < 0; + } + ); + if (0 === $collection->count()) { + throw new FireflyException(sprintf('Journal #%d has no source transaction.', $journal->id)); + } + if (1 !== $collection->count()) { + throw new FireflyException(sprintf('Journal #%d has multiple source transactions.', $journal->id)); + } + /** @var Transaction $transaction */ + $transaction = $collection->first(); + $account = $transaction->account; + if (null === $account) { + throw new FireflyException(sprintf('Journal #%d, transaction #%d has no source account.', $journal->id, $transaction->id)); + } + + return $account; + } +} diff --git a/app/Console/Commands/Correction/FixUnevenAmount.php b/app/Console/Commands/Correction/FixUnevenAmount.php index d545d2f11e..eac3982598 100644 --- a/app/Console/Commands/Correction/FixUnevenAmount.php +++ b/app/Console/Commands/Correction/FixUnevenAmount.php @@ -47,6 +47,7 @@ class FixUnevenAmount extends Command */ protected $signature = 'firefly-iii:fix-uneven-amount'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/RemoveBills.php b/app/Console/Commands/Correction/RemoveBills.php index 4966c1887a..fb85b507af 100644 --- a/app/Console/Commands/Correction/RemoveBills.php +++ b/app/Console/Commands/Correction/RemoveBills.php @@ -45,6 +45,7 @@ class RemoveBills extends Command */ protected $signature = 'firefly-iii:remove-bills'; + /** * Execute the console command. * @@ -55,6 +56,9 @@ class RemoveBills extends Command $start = microtime(true); /** @var TransactionType $withdrawal */ $withdrawal = TransactionType::where('type', TransactionType::WITHDRAWAL)->first(); + if(null === $withdrawal) { + return 0; + } $journals = TransactionJournal::whereNotNull('bill_id')->where('transaction_type_id', '!=', $withdrawal->id)->get(); /** @var TransactionJournal $journal */ foreach ($journals as $journal) { diff --git a/app/Console/Commands/Correction/RenameMetaFields.php b/app/Console/Commands/Correction/RenameMetaFields.php index 0b1adad4f5..30ef3ad15e 100644 --- a/app/Console/Commands/Correction/RenameMetaFields.php +++ b/app/Console/Commands/Correction/RenameMetaFields.php @@ -47,6 +47,7 @@ class RenameMetaFields extends Command /** @var int */ private $count; + /** * Execute the console command. * diff --git a/app/Console/Commands/Correction/TransferBudgets.php b/app/Console/Commands/Correction/TransferBudgets.php index 38b8a5684e..231454d514 100644 --- a/app/Console/Commands/Correction/TransferBudgets.php +++ b/app/Console/Commands/Correction/TransferBudgets.php @@ -45,6 +45,7 @@ class TransferBudgets extends Command */ protected $signature = 'firefly-iii:fix-transfer-budgets'; + /** * Execute the console command. * diff --git a/app/Console/Commands/CreateDatabase.php b/app/Console/Commands/CreateDatabase.php index 1c95d9d0e0..df1efce742 100644 --- a/app/Console/Commands/CreateDatabase.php +++ b/app/Console/Commands/CreateDatabase.php @@ -47,6 +47,7 @@ class CreateDatabase extends Command */ protected $signature = 'firefly-iii:create-database'; + /** * Execute the console command. * diff --git a/app/Console/Commands/DecryptDatabase.php b/app/Console/Commands/DecryptDatabase.php index d4457a64a7..5995e0cb99 100644 --- a/app/Console/Commands/DecryptDatabase.php +++ b/app/Console/Commands/DecryptDatabase.php @@ -34,7 +34,6 @@ use JsonException; use Log; /** - * * Class DecryptDatabase */ class DecryptDatabase extends Command @@ -52,6 +51,7 @@ class DecryptDatabase extends Command */ protected $signature = 'firefly-iii:decrypt-all'; + /** * Execute the console command. * @@ -98,7 +98,7 @@ class DecryptDatabase extends Command } catch(JsonException $e) { Log::error($e->getMessage()); } - Log::debug(sprintf('Decrypted field "%s" "%s" to "%s" in table "%s" (row #%d)', $field, $original, print_r($value, true), $table, $id)); + //Log::debug(sprintf('Decrypted field "%s" "%s" to "%s" in table "%s" (row #%d)', $field, $original, print_r($value, true), $table, $id)); /** @var Preference $object */ $object = Preference::find((int) $id); @@ -110,7 +110,7 @@ class DecryptDatabase extends Command } if ($value !== $original) { - Log::debug(sprintf('Decrypted field "%s" "%s" to "%s" in table "%s" (row #%d)', $field, $original, $value, $table, $id)); + //Log::debug(sprintf('Decrypted field "%s" "%s" to "%s" in table "%s" (row #%d)', $field, $original, $value, $table, $id)); DB::table($table)->where('id', $id)->update([$field => $value]); } } diff --git a/app/Console/Commands/Export/ExportData.php b/app/Console/Commands/Export/ExportData.php index 590791e537..859a493443 100644 --- a/app/Console/Commands/Export/ExportData.php +++ b/app/Console/Commands/Export/ExportData.php @@ -81,6 +81,7 @@ class ExportData extends Command /** @var User */ private $user; + /** * Execute the console command. * diff --git a/app/Console/Commands/Integrity/ReportEmptyObjects.php b/app/Console/Commands/Integrity/ReportEmptyObjects.php index 4cca37288b..0e11400d30 100644 --- a/app/Console/Commands/Integrity/ReportEmptyObjects.php +++ b/app/Console/Commands/Integrity/ReportEmptyObjects.php @@ -48,6 +48,7 @@ class ReportEmptyObjects extends Command */ protected $signature = 'firefly-iii:report-empty-objects'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Integrity/ReportIntegrity.php b/app/Console/Commands/Integrity/ReportIntegrity.php index 62e45ed110..ed63e7c54b 100644 --- a/app/Console/Commands/Integrity/ReportIntegrity.php +++ b/app/Console/Commands/Integrity/ReportIntegrity.php @@ -49,6 +49,7 @@ class ReportIntegrity extends Command */ protected $signature = 'firefly-iii:report-integrity'; + /** * Execute the console command. */ diff --git a/app/Console/Commands/Integrity/ReportSum.php b/app/Console/Commands/Integrity/ReportSum.php index 2f24e1f00a..8b6f7f6cff 100644 --- a/app/Console/Commands/Integrity/ReportSum.php +++ b/app/Console/Commands/Integrity/ReportSum.php @@ -45,6 +45,7 @@ class ReportSum extends Command */ protected $signature = 'firefly-iii:report-sum'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Integrity/RestoreOAuthKeys.php b/app/Console/Commands/Integrity/RestoreOAuthKeys.php index 14655a5f58..1d2913c108 100644 --- a/app/Console/Commands/Integrity/RestoreOAuthKeys.php +++ b/app/Console/Commands/Integrity/RestoreOAuthKeys.php @@ -46,6 +46,7 @@ class RestoreOAuthKeys extends Command */ protected $signature = 'firefly-iii:restore-oauth-keys'; + /** * Execute the console command. * diff --git a/app/Console/Commands/ScanAttachments.php b/app/Console/Commands/ScanAttachments.php index 21af60f99d..abe7b209f2 100644 --- a/app/Console/Commands/ScanAttachments.php +++ b/app/Console/Commands/ScanAttachments.php @@ -54,6 +54,7 @@ class ScanAttachments extends Command */ protected $signature = 'firefly-iii:scan-attachments'; + /** * Execute the console command. */ diff --git a/app/Console/Commands/SetLatestVersion.php b/app/Console/Commands/SetLatestVersion.php index b961e755d7..310cc2d19f 100644 --- a/app/Console/Commands/SetLatestVersion.php +++ b/app/Console/Commands/SetLatestVersion.php @@ -44,6 +44,7 @@ class SetLatestVersion extends Command */ protected $signature = 'firefly-iii:set-latest-version {--james-is-cool}'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Tools/ApplyRules.php b/app/Console/Commands/Tools/ApplyRules.php index 612b2e0f41..359a91f97f 100644 --- a/app/Console/Commands/Tools/ApplyRules.php +++ b/app/Console/Commands/Tools/ApplyRules.php @@ -89,6 +89,7 @@ class ApplyRules extends Command /** @var Carbon */ private $startDate; + /** * Execute the console command. * diff --git a/app/Console/Commands/Tools/Cron.php b/app/Console/Commands/Tools/Cron.php index e37676718a..e5d2a89b5e 100644 --- a/app/Console/Commands/Tools/Cron.php +++ b/app/Console/Commands/Tools/Cron.php @@ -57,6 +57,7 @@ class Cron extends Command {--date= : Set the date in YYYY-MM-DD to make Firefly III think that\'s the current date.} '; + /** * @return int */ @@ -173,7 +174,7 @@ class Cron extends Command // if not configured to do anything with telemetry, do nothing. return; } - $telemetry = new TelemetryCronJob; + $telemetry = new TelemetryCronjob; $telemetry->setForce($force); // set date in cron job: diff --git a/app/Console/Commands/Upgrade/AccountCurrencies.php b/app/Console/Commands/Upgrade/AccountCurrencies.php index f8bfc1410d..27a14030a6 100644 --- a/app/Console/Commands/Upgrade/AccountCurrencies.php +++ b/app/Console/Commands/Upgrade/AccountCurrencies.php @@ -59,6 +59,7 @@ class AccountCurrencies extends Command /** @var UserRepositoryInterface */ private $userRepos; + /** * Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account. * diff --git a/app/Console/Commands/Upgrade/BackToJournals.php b/app/Console/Commands/Upgrade/BackToJournals.php index bc6a2aead2..6681fc030f 100644 --- a/app/Console/Commands/Upgrade/BackToJournals.php +++ b/app/Console/Commands/Upgrade/BackToJournals.php @@ -51,6 +51,7 @@ class BackToJournals extends Command */ protected $signature = 'firefly-iii:back-to-journals {--F|force : Force the execution of this command.}'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Upgrade/BudgetLimitCurrency.php b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php index 990b02bbf8..37ed9ff665 100644 --- a/app/Console/Commands/Upgrade/BudgetLimitCurrency.php +++ b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php @@ -47,6 +47,7 @@ class BudgetLimitCurrency extends Command */ protected $signature = 'firefly-iii:bl-currency {--F|force : Force the execution of this command.}'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Upgrade/CCLiabilities.php b/app/Console/Commands/Upgrade/CCLiabilities.php index e5904a7198..36a23a96a8 100644 --- a/app/Console/Commands/Upgrade/CCLiabilities.php +++ b/app/Console/Commands/Upgrade/CCLiabilities.php @@ -50,6 +50,7 @@ class CCLiabilities extends Command */ protected $signature = 'firefly-iii:cc-liabilities {--F|force : Force the execution of this command.}'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Upgrade/MigrateAttachments.php b/app/Console/Commands/Upgrade/MigrateAttachments.php index 9e2270d7d2..6120a2a4de 100644 --- a/app/Console/Commands/Upgrade/MigrateAttachments.php +++ b/app/Console/Commands/Upgrade/MigrateAttachments.php @@ -48,6 +48,7 @@ class MigrateAttachments extends Command */ protected $signature = 'firefly-iii:migrate-attachments {--F|force : Force the execution of this command.}'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Upgrade/MigrateJournalNotes.php b/app/Console/Commands/Upgrade/MigrateJournalNotes.php index a234c2948f..72760471d8 100644 --- a/app/Console/Commands/Upgrade/MigrateJournalNotes.php +++ b/app/Console/Commands/Upgrade/MigrateJournalNotes.php @@ -49,6 +49,7 @@ class MigrateJournalNotes extends Command */ protected $signature = 'firefly-iii:migrate-notes {--F|force : Force the execution of this command.}'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php b/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php index 30eca6aec4..e02495bd89 100644 --- a/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php +++ b/app/Console/Commands/Upgrade/MigrateRecurrenceMeta.php @@ -47,6 +47,7 @@ class MigrateRecurrenceMeta extends Command */ protected $signature = 'firefly-iii:migrate-recurrence-meta {--F|force : Force the execution of this command.}'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Upgrade/MigrateTagLocations.php b/app/Console/Commands/Upgrade/MigrateTagLocations.php index bc0f209a55..b1693b9f40 100644 --- a/app/Console/Commands/Upgrade/MigrateTagLocations.php +++ b/app/Console/Commands/Upgrade/MigrateTagLocations.php @@ -48,6 +48,7 @@ class MigrateTagLocations extends Command */ protected $signature = 'firefly-iii:migrate-tag-locations {--F|force : Force the execution of this command.}'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Upgrade/MigrateToGroups.php b/app/Console/Commands/Upgrade/MigrateToGroups.php index 29a922cad3..fbcd53f001 100644 --- a/app/Console/Commands/Upgrade/MigrateToGroups.php +++ b/app/Console/Commands/Upgrade/MigrateToGroups.php @@ -69,6 +69,7 @@ class MigrateToGroups extends Command /** @var JournalDestroyService */ private $service; + /** * Execute the console command. * @@ -289,7 +290,6 @@ class MigrateToGroups extends Command * @param TransactionJournal $journal * * @throws Exception - * */ private function makeMultiGroup(TransactionJournal $journal): void { diff --git a/app/Console/Commands/Upgrade/MigrateToRules.php b/app/Console/Commands/Upgrade/MigrateToRules.php index 36c40affab..5029dbd70d 100644 --- a/app/Console/Commands/Upgrade/MigrateToRules.php +++ b/app/Console/Commands/Upgrade/MigrateToRules.php @@ -63,6 +63,7 @@ class MigrateToRules extends Command /** @var UserRepositoryInterface */ private $userRepository; + /** * Execute the console command. * diff --git a/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php index d021e088ff..cefd9bca1b 100644 --- a/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php +++ b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php @@ -43,13 +43,11 @@ class OtherCurrenciesCorrections extends Command public const CONFIG_NAME = '480_other_currencies'; /** * The console command description. - * * @var string */ protected $description = 'Update all journal currency information.'; /** * The name and signature of the console command. - * * @var string */ protected $signature = 'firefly-iii:other-currencies {--F|force : Force the execution of this command.}'; @@ -66,9 +64,9 @@ class OtherCurrenciesCorrections extends Command /** @var JournalRepositoryInterface */ private $journalRepos; + /** * Execute the console command. - * * @return int */ public function handle(): int @@ -95,16 +93,15 @@ class OtherCurrenciesCorrections extends Command /** * @param Account $account - * * @return TransactionCurrency|null */ private function getCurrency(Account $account): ?TransactionCurrency { $accountId = $account->id; - if (isset($this->accountCurrencies[$accountId]) && 0 === $this->accountCurrencies[$accountId]) { + if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) { return null; // @codeCoverageIgnore } - if (isset($this->accountCurrencies[$accountId]) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { + if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { return $this->accountCurrencies[$accountId]; // @codeCoverageIgnore } $currency = $this->accountRepos->getAccountCurrency($account); @@ -123,9 +120,7 @@ class OtherCurrenciesCorrections extends Command /** * Gets the transaction that determines the transaction that "leads" and will determine * the currency to be used by all transactions, and the journal itself. - * * @param TransactionJournal $journal - * * @return Transaction|null */ private function getLeadTransaction(TransactionJournal $journal): ?Transaction @@ -141,19 +136,11 @@ class OtherCurrenciesCorrections extends Command break; case TransactionType::OPENING_BALANCE: // whichever isn't an initial balance account: - $lead = $journal->transactions() - ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') - ->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id') - ->where('account_types.type', '!=', AccountType::INITIAL_BALANCE) - ->first(['transactions.*']); + $lead = $journal->transactions()->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id')->where('account_types.type', '!=', AccountType::INITIAL_BALANCE)->first(['transactions.*']); break; case TransactionType::RECONCILIATION: // whichever isn't the reconciliation account: - $lead = $journal->transactions() - ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') - ->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id') - ->where('account_types.type', '!=', AccountType::RECONCILIATION) - ->first(['transactions.*']); + $lead = $journal->transactions()->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id')->where('account_types.type', '!=', AccountType::RECONCILIATION)->first(['transactions.*']); break; } @@ -167,7 +154,7 @@ class OtherCurrenciesCorrections extends Command { $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); if (null !== $configVar) { - return (bool) $configVar->data; + return (bool)$configVar->data; } return false; // @codeCoverageIgnore @@ -185,7 +172,6 @@ class OtherCurrenciesCorrections extends Command * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should * be called from the handle method instead of using the constructor to initialize the command. - * * @codeCoverageIgnore */ private function stupidLaravel(): void @@ -223,36 +209,27 @@ class OtherCurrenciesCorrections extends Command $currency = $this->getCurrency($account); if (null === $currency) { // @codeCoverageIgnoreStart - $this->error( - sprintf( - 'Account #%d ("%s") has no currency preference, so transaction journal #%d can\'t be corrected', - $account->id, - $account->name, - $journal->id - ) - ); + $this->error(sprintf('Account #%d ("%s") has no currency preference, so transaction journal #%d can\'t be corrected', $account->id, $account->name, $journal->id)); $this->count++; return; // @codeCoverageIgnoreEnd } // fix each transaction: - $journal->transactions->each( - static function (Transaction $transaction) use ($currency) { - if (null === $transaction->transaction_currency_id) { - $transaction->transaction_currency_id = $currency->id; - $transaction->save(); - } - - // when mismatch in transaction: - if (!((int) $transaction->transaction_currency_id === (int) $currency->id)) { - $transaction->foreign_currency_id = (int) $transaction->transaction_currency_id; - $transaction->foreign_amount = $transaction->amount; - $transaction->transaction_currency_id = $currency->id; - $transaction->save(); - } + $journal->transactions->each(static function (Transaction $transaction) use ($currency) { + if (null === $transaction->transaction_currency_id) { + $transaction->transaction_currency_id = $currency->id; + $transaction->save(); } - ); + + // when mismatch in transaction: + if (!((int)$transaction->transaction_currency_id === (int)$currency->id)) { + $transaction->foreign_currency_id = (int)$transaction->transaction_currency_id; + $transaction->foreign_amount = $transaction->amount; + $transaction->transaction_currency_id = $currency->id; + $transaction->save(); + } + }); // also update the journal, of course: $journal->transaction_currency_id = $currency->id; $this->count++; @@ -262,22 +239,12 @@ class OtherCurrenciesCorrections extends Command /** * This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for * the accounts they are linked to. - * * Both source and destination must match the respective currency preference of the related asset account. * So FF3 must verify all transactions. - * */ private function updateOtherJournalsCurrencies(): void { - $set - = $this->cliRepos->getAllJournals( - [ - TransactionType::WITHDRAWAL, - TransactionType::DEPOSIT, - TransactionType::OPENING_BALANCE, - TransactionType::RECONCILIATION, - ] - ); + $set = $this->cliRepos->getAllJournals([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,]); /** @var TransactionJournal $journal */ foreach ($set as $journal) { diff --git a/app/Console/Commands/Upgrade/RenameAccountMeta.php b/app/Console/Commands/Upgrade/RenameAccountMeta.php index 6a6e369a8d..01aaa1b6fd 100644 --- a/app/Console/Commands/Upgrade/RenameAccountMeta.php +++ b/app/Console/Commands/Upgrade/RenameAccountMeta.php @@ -45,6 +45,7 @@ class RenameAccountMeta extends Command */ protected $signature = 'firefly-iii:rename-account-meta {--F|force : Force the execution of this command.}'; + /** * Execute the console command. * diff --git a/app/Console/Commands/Upgrade/TransactionIdentifier.php b/app/Console/Commands/Upgrade/TransactionIdentifier.php index a60db0b924..d220a8b27e 100644 --- a/app/Console/Commands/Upgrade/TransactionIdentifier.php +++ b/app/Console/Commands/Upgrade/TransactionIdentifier.php @@ -57,6 +57,7 @@ class TransactionIdentifier extends Command /** @var JournalRepositoryInterface */ private $journalRepository; + /** * This method gives all transactions which are part of a split journal (so more than 2) a sort of "order" so they are easier * to easier to match to their counterpart. When a journal is split, it has two or three transactions: -3, -4 and -5 for example. diff --git a/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php b/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php index 995a70820c..752bac5087 100644 --- a/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php +++ b/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php @@ -78,6 +78,7 @@ class TransferCurrenciesCorrections extends Command /** @var Transaction The source transaction of the current journal. */ private $sourceTransaction; + /** * Execute the console command. * @@ -185,7 +186,6 @@ class TransferCurrenciesCorrections extends Command * If the destination account currency is the same as the source currency, * both foreign_amount and foreign_currency_id fields must be NULL * for both transactions (because foreign currency info would not make sense) - * */ private function fixInvalidForeignCurrency(): void { @@ -335,10 +335,10 @@ class TransferCurrenciesCorrections extends Command private function getCurrency(Account $account): ?TransactionCurrency { $accountId = $account->id; - if (isset($this->accountCurrencies[$accountId]) && 0 === $this->accountCurrencies[$accountId]) { + if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) { return null; // @codeCoverageIgnore } - if (isset($this->accountCurrencies[$accountId]) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { + if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { return $this->accountCurrencies[$accountId]; // @codeCoverageIgnore } $currency = $this->accountRepos->getAccountCurrency($account); diff --git a/app/Console/Commands/Upgrade/UpgradeDatabase.php b/app/Console/Commands/Upgrade/UpgradeDatabase.php index 9107846e69..0167595dad 100644 --- a/app/Console/Commands/Upgrade/UpgradeDatabase.php +++ b/app/Console/Commands/Upgrade/UpgradeDatabase.php @@ -48,6 +48,7 @@ class UpgradeDatabase extends Command */ protected $signature = 'firefly-iii:upgrade-database {--F|force : Force all upgrades.}'; + /** * Execute the console command. * @@ -76,7 +77,7 @@ class UpgradeDatabase extends Command 'firefly-iii:migrate-recurrence-meta', 'firefly-iii:migrate-tag-locations', - // there are 15 verify commands. + // there are 16 verify commands. 'firefly-iii:fix-piggies', 'firefly-iii:create-link-types', 'firefly-iii:create-access-tokens', @@ -89,11 +90,13 @@ class UpgradeDatabase extends Command 'firefly-iii:delete-empty-journals', 'firefly-iii:delete-empty-groups', 'firefly-iii:fix-account-types', + 'firefly-iii:fix-account-order', 'firefly-iii:rename-meta-fields', 'firefly-iii:fix-ob-currencies', 'firefly-iii:fix-long-descriptions', 'firefly-iii:fix-recurring-transactions', 'firefly-iii:unify-group-accounts', + 'firefly-iii:fix-transaction-types', // two report commands 'firefly-iii:report-empty-objects', diff --git a/app/Console/Commands/UpgradeFireflyInstructions.php b/app/Console/Commands/UpgradeFireflyInstructions.php index 5a70f5936d..67cd1db0f0 100644 --- a/app/Console/Commands/UpgradeFireflyInstructions.php +++ b/app/Console/Commands/UpgradeFireflyInstructions.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Console\Commands; use FireflyIII\Support\System\GeneratesInstallationId; +use FireflyIII\User; use Illuminate\Console\Command; /** @@ -48,6 +49,7 @@ class UpgradeFireflyInstructions extends Command */ protected $signature = 'firefly:instructions {task}'; + /** * Execute the console command. */ @@ -68,6 +70,7 @@ class UpgradeFireflyInstructions extends Command app('telemetry')->feature('system.database.driver', env('DB_CONNECTION', '(unknown)')); app('telemetry')->feature('system.os.is_docker', $isDocker); app('telemetry')->feature('system.command.executed', $this->signature); + app('telemetry')->feature('system.users.count', (string) User::count()); return 0; } diff --git a/app/Events/AdminRequestedTestMessage.php b/app/Events/AdminRequestedTestMessage.php index 78f90858c8..87b3652aa2 100644 --- a/app/Events/AdminRequestedTestMessage.php +++ b/app/Events/AdminRequestedTestMessage.php @@ -42,6 +42,7 @@ class AdminRequestedTestMessage extends Event /** @var User The user */ public $user; + /** * Create a new event instance. * diff --git a/app/Events/RegisteredUser.php b/app/Events/RegisteredUser.php index a49d099f88..2518f8214e 100644 --- a/app/Events/RegisteredUser.php +++ b/app/Events/RegisteredUser.php @@ -41,6 +41,7 @@ class RegisteredUser extends Event /** @var User The user */ public $user; + /** * Create a new event instance. This event is triggered when a new user registers. * diff --git a/app/Events/RequestedNewPassword.php b/app/Events/RequestedNewPassword.php index 1706faa15c..2532ccc837 100644 --- a/app/Events/RequestedNewPassword.php +++ b/app/Events/RequestedNewPassword.php @@ -43,6 +43,7 @@ class RequestedNewPassword extends Event /** @var User The user */ public $user; + /** * Create a new event instance. This event is triggered when a users tries to reset his or her password. * diff --git a/app/Events/RequestedReportOnJournals.php b/app/Events/RequestedReportOnJournals.php index 0e29b88ea7..e16e0d205c 100644 --- a/app/Events/RequestedReportOnJournals.php +++ b/app/Events/RequestedReportOnJournals.php @@ -46,6 +46,7 @@ class RequestedReportOnJournals /** @var int The ID of the user. */ public $userId; + /** * Create a new event instance. * diff --git a/app/Events/RequestedVersionCheckStatus.php b/app/Events/RequestedVersionCheckStatus.php index 692f825d24..45f26a92c9 100644 --- a/app/Events/RequestedVersionCheckStatus.php +++ b/app/Events/RequestedVersionCheckStatus.php @@ -40,6 +40,7 @@ class RequestedVersionCheckStatus extends Event /** @var User The user */ public $user; + /** * Create a new event instance. This event is triggered when Firefly III wants to know * what the deal is with the version checker. diff --git a/app/Events/StoredTransactionGroup.php b/app/Events/StoredTransactionGroup.php index 13e9341eb3..391610e0ef 100644 --- a/app/Events/StoredTransactionGroup.php +++ b/app/Events/StoredTransactionGroup.php @@ -41,6 +41,7 @@ class StoredTransactionGroup extends Event /** @var TransactionGroup The group that was stored. */ public $transactionGroup; + /** * Create a new event instance. * diff --git a/app/Events/UpdatedTransactionGroup.php b/app/Events/UpdatedTransactionGroup.php index cd1ded0b2c..bd3f14e589 100644 --- a/app/Events/UpdatedTransactionGroup.php +++ b/app/Events/UpdatedTransactionGroup.php @@ -31,7 +31,6 @@ use Illuminate\Queue\SerializesModels; * Class UpdatedTransactionGroup. * * @codeCoverageIgnore - * */ class UpdatedTransactionGroup extends Event { @@ -42,6 +41,7 @@ class UpdatedTransactionGroup extends Event /** @var TransactionGroup The group that was stored. */ public $transactionGroup; + /** * Create a new event instance. * diff --git a/app/Events/UserChangedEmail.php b/app/Events/UserChangedEmail.php index 8f4f70090d..d293b4ed15 100644 --- a/app/Events/UserChangedEmail.php +++ b/app/Events/UserChangedEmail.php @@ -45,6 +45,7 @@ class UserChangedEmail extends Event /** @var User The user itself */ public $user; + /** * UserChangedEmail constructor. * diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index c5e4254c2f..d780e47191 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -31,10 +31,10 @@ use Exception; use FireflyIII\Jobs\MailError; use Illuminate\Auth\AuthenticationException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Http\Request; use Illuminate\Validation\ValidationException as LaravelValidationException; use League\OAuth2\Server\Exception\OAuthServerException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Illuminate\Http\Request; use Throwable; /** * Class Handler diff --git a/app/Factory/AccountFactory.php b/app/Factory/AccountFactory.php index c5a1d4bdff..2b29ab5fb3 100644 --- a/app/Factory/AccountFactory.php +++ b/app/Factory/AccountFactory.php @@ -42,18 +42,12 @@ class AccountFactory { use AccountServiceTrait, LocationServiceTrait; - /** @var AccountRepositoryInterface */ - protected $accountRepository; - /** @var array */ - protected $validAssetFields = ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; - /** @var array */ - protected $validCCFields = ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; - /** @var array */ - protected $validFields = ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth']; - /** @var array */ - private $canHaveVirtual; - /** @var User */ - private $user; + protected AccountRepositoryInterface $accountRepository; + protected array $validAssetFields; + protected array $validCCFields; + protected array $validFields; + private array $canHaveVirtual; + private User $user; /** * AccountFactory constructor. @@ -67,22 +61,24 @@ class AccountFactory } $this->canHaveVirtual = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]; $this->accountRepository = app(AccountRepositoryInterface::class); + $this->validAssetFields = ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; + $this->validCCFields = ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; + $this->validFields = ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth']; + } /** * @param array $data * - * @throws FireflyException * @return Account + * @throws FireflyException */ public function create(array $data): Account { - $type = $this->getAccountType($data['account_type_id'], $data['account_type']); + $type = $this->getAccountType($data['account_type_id'] ?? null, $data['account_type'] ?? null); if (null === $type) { - throw new FireflyException( - sprintf('AccountFactory::create() was unable to find account type #%d ("%s").', $data['account_type_id'], $data['account_type']) - ); + throw new FireflyException(sprintf('AccountFactory::create() was unable to find account type #%d ("%s").', $data['account_type_id'] ?? null, $data['account_type'] ?? null)); } $data['iban'] = $this->filterIban($data['iban'] ?? null); @@ -93,17 +89,9 @@ class AccountFactory if (null === $return) { // create it: - $databaseData - = [ - 'user_id' => $this->user->id, - 'account_type_id' => $type->id, - 'name' => $data['name'], - 'virtual_balance' => $data['virtual_balance'] ?? null, - 'active' => true === $data['active'], - 'iban' => $data['iban'], - ]; + $databaseData = ['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => $data['name'], 'order' => $data['order'] ?? 0, 'virtual_balance' => $data['virtual_balance'] ?? null, 'active' => true === $data['active'], 'iban' => $data['iban'],]; - $currency = $this->getCurrency((int) ($data['currency_id'] ?? null), (string) ($data['currency_code'] ?? null)); + $currency = $this->getCurrency((int)($data['currency_id'] ?? null), (string)($data['currency_code'] ?? null)); unset($data['currency_code']); $data['currency_id'] = $currency->id; @@ -152,34 +140,22 @@ class AccountFactory } /** - * * @param string $accountName * @param string $accountType * - * @throws FireflyException * @return Account + * @throws FireflyException */ public function findOrCreate(string $accountName, string $accountType): Account { Log::debug(sprintf('Searching for "%s" of type "%s"', $accountName, $accountType)); /** @var AccountType $type */ $type = AccountType::whereType($accountType)->first(); - $return = $this->user->accounts->where('account_type_id', $type->id) - ->where('name', $accountName)->first(); + $return = $this->user->accounts->where('account_type_id', $type->id)->where('name', $accountName)->first(); if (null === $return) { Log::debug('Found nothing. Will create a new one.'); - $return = $this->create( - [ - 'user_id' => $this->user->id, - 'name' => $accountName, - 'account_type_id' => $type->id, - 'account_type' => null, - 'virtual_balance' => '0', - 'iban' => null, - 'active' => true, - ] - ); + $return = $this->create(['user_id' => $this->user->id, 'name' => $accountName, 'account_type_id' => $type->id, 'account_type' => null, 'virtual_balance' => '0', 'iban' => null, 'active' => true,]); } return $return; @@ -198,11 +174,10 @@ class AccountFactory * @param null|string $accountType * * @return AccountType|null - * */ protected function getAccountType(?int $accountTypeId, ?string $accountType): ?AccountType { - $accountTypeId = (int) $accountTypeId; + $accountTypeId = (int)$accountTypeId; $result = null; if ($accountTypeId > 0) { $result = AccountType::find($accountTypeId); diff --git a/app/Factory/AttachmentFactory.php b/app/Factory/AttachmentFactory.php index 22c1d66e15..37c28d4ae1 100644 --- a/app/Factory/AttachmentFactory.php +++ b/app/Factory/AttachmentFactory.php @@ -39,6 +39,7 @@ class AttachmentFactory /** @var User */ private $user; + /** * Constructor. * diff --git a/app/Factory/BillFactory.php b/app/Factory/BillFactory.php index fadcf629b0..b4dfc6b4b7 100644 --- a/app/Factory/BillFactory.php +++ b/app/Factory/BillFactory.php @@ -43,6 +43,7 @@ class BillFactory /** @var User */ private $user; + /** * Constructor. * @@ -58,15 +59,15 @@ class BillFactory /** * @param array $data * - * @throws FireflyException * @return Bill|null + * @throws FireflyException */ public function create(array $data): ?Bill { /** @var TransactionCurrencyFactory $factory */ $factory = app(TransactionCurrencyFactory::class); /** @var TransactionCurrency $currency */ - $currency = $factory->find((int) ($data['currency_id'] ?? null), (string) ($data['currency_code'] ?? null)); + $currency = $factory->find((int)($data['currency_id'] ?? null), (string)($data['currency_code'] ?? null)); if (null === $currency) { $currency = app('amount')->getDefaultCurrencyByUser($this->user); @@ -94,8 +95,8 @@ class BillFactory throw new FireflyException('400000: Could not store bill.'); } - if (isset($data['notes'])) { - $this->updateNote($bill, $data['notes']); + if (array_key_exists('notes', $data)) { + $this->updateNote($bill, (string)$data['notes']); } $objectGroupTitle = $data['object_group'] ?? ''; @@ -107,7 +108,7 @@ class BillFactory } } // try also with ID: - $objectGroupId = (int) ($data['object_group_id'] ?? 0); + $objectGroupId = (int)($data['object_group_id'] ?? 0); if (0 !== $objectGroupId) { $objectGroup = $this->findObjectGroupById($objectGroupId); if (null !== $objectGroup) { @@ -127,8 +128,8 @@ class BillFactory */ public function find(?int $billId, ?string $billName): ?Bill { - $billId = (int) $billId; - $billName = (string) $billName; + $billId = (int)$billId; + $billName = (string)$billName; $bill = null; // first find by ID: if ($billId > 0) { diff --git a/app/Factory/BudgetFactory.php b/app/Factory/BudgetFactory.php index ce6f9923d1..d28e677f6c 100644 --- a/app/Factory/BudgetFactory.php +++ b/app/Factory/BudgetFactory.php @@ -21,7 +21,6 @@ /** @noinspection MultipleReturnStatementsInspection */ declare(strict_types=1); - namespace FireflyIII\Factory; use FireflyIII\Models\Budget; @@ -36,6 +35,7 @@ class BudgetFactory /** @var User */ private $user; + /** * Constructor. * @@ -53,7 +53,6 @@ class BudgetFactory * @param null|string $budgetName * * @return Budget|null - * */ public function find(?int $budgetId, ?string $budgetName): ?Budget { diff --git a/app/Factory/CategoryFactory.php b/app/Factory/CategoryFactory.php index da55ff6cde..dac41a622c 100644 --- a/app/Factory/CategoryFactory.php +++ b/app/Factory/CategoryFactory.php @@ -38,6 +38,7 @@ class CategoryFactory /** @var User */ private $user; + /** * Constructor. * diff --git a/app/Factory/PiggyBankEventFactory.php b/app/Factory/PiggyBankEventFactory.php index 3eee4e3019..6f52157185 100644 --- a/app/Factory/PiggyBankEventFactory.php +++ b/app/Factory/PiggyBankEventFactory.php @@ -54,7 +54,6 @@ class PiggyBankEventFactory * @param PiggyBank|null $piggyBank * * @return PiggyBankEvent|null - * */ public function create(TransactionJournal $journal, ?PiggyBank $piggyBank): ?PiggyBankEvent { diff --git a/app/Factory/PiggyBankFactory.php b/app/Factory/PiggyBankFactory.php index 0a67e45240..3275582667 100644 --- a/app/Factory/PiggyBankFactory.php +++ b/app/Factory/PiggyBankFactory.php @@ -36,6 +36,7 @@ class PiggyBankFactory /** @var User */ private $user; + /** * Constructor. * @@ -53,7 +54,6 @@ class PiggyBankFactory * @param null|string $piggyBankName * * @return PiggyBank|null - * */ public function find(?int $piggyBankId, ?string $piggyBankName): ?PiggyBank { diff --git a/app/Factory/RecurrenceFactory.php b/app/Factory/RecurrenceFactory.php index c406845313..b3582200f3 100644 --- a/app/Factory/RecurrenceFactory.php +++ b/app/Factory/RecurrenceFactory.php @@ -39,12 +39,13 @@ use Log; */ class RecurrenceFactory { + + use TransactionTypeTrait, RecurringTransactionTrait; /** @var MessageBag */ private $errors; /** @var User */ private $user; - use TransactionTypeTrait, RecurringTransactionTrait; /** * Constructor. diff --git a/app/Factory/TagFactory.php b/app/Factory/TagFactory.php index 8e8cdaf409..a277211d92 100644 --- a/app/Factory/TagFactory.php +++ b/app/Factory/TagFactory.php @@ -40,6 +40,7 @@ class TagFactory /** @var User */ private $user; + /** * Constructor. * diff --git a/app/Factory/TransactionCurrencyFactory.php b/app/Factory/TransactionCurrencyFactory.php index bd6a74ef73..ab82ebffd3 100644 --- a/app/Factory/TransactionCurrencyFactory.php +++ b/app/Factory/TransactionCurrencyFactory.php @@ -82,7 +82,6 @@ class TransactionCurrencyFactory * @param null|string $currencyCode * * @return TransactionCurrency|null - * */ public function find(?int $currencyId, ?string $currencyCode): ?TransactionCurrency { diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index 4ede4f858d..9521838dc5 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -51,6 +51,7 @@ class TransactionFactory /** @var User */ private $user; + /** * Constructor. * diff --git a/app/Factory/TransactionGroupFactory.php b/app/Factory/TransactionGroupFactory.php index 34d5a2db8d..4363edad84 100644 --- a/app/Factory/TransactionGroupFactory.php +++ b/app/Factory/TransactionGroupFactory.php @@ -40,6 +40,7 @@ class TransactionGroupFactory /** @var User The user */ private $user; + /** * TransactionGroupFactory constructor. */ diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index 6fbd5b8b72..92f18e7dd0 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -56,12 +56,9 @@ class TransactionJournalFactory { use JournalServiceTrait; - /** @var AccountRepositoryInterface */ - private $accountRepository; - /** @var AccountValidator */ - private $accountValidator; - /** @var BillRepositoryInterface */ - private $billRepository; + private AccountRepositoryInterface $accountRepository; + private AccountValidator $accountValidator; + private BillRepositoryInterface $billRepository; /** @var CurrencyRepositoryInterface */ private $currencyRepository; /** @var bool */ @@ -79,6 +76,7 @@ class TransactionJournalFactory /** @var User The user */ private $user; + /** * Constructor. * @@ -88,11 +86,12 @@ class TransactionJournalFactory public function __construct() { $this->errorOnHash = false; - $this->fields = [ + // TODO move valid meta fields to config. + $this->fields = [ // sepa 'sepa_cc', 'sepa_ct_op', 'sepa_ct_id', 'sepa_db', 'sepa_country', 'sepa_ep', - 'sepa_ci', 'sepa_batch_id', + 'sepa_ci', 'sepa_batch_id', 'external_uri', // dates 'interest_date', 'book_date', 'process_date', @@ -100,7 +99,11 @@ class TransactionJournalFactory // others 'recurrence_id', 'internal_reference', 'bunq_payment_id', - 'import_hash', 'import_hash_v2', 'external_id', 'original_source']; + 'import_hash', 'import_hash_v2', 'external_id', 'original_source', + + // recurring transactions + 'recurrence_total', 'recurrence_count', + ]; if ('testing' === config('app.env')) { @@ -125,9 +128,9 @@ class TransactionJournalFactory * * @param array $data * - * @throws DuplicateTransactionException - * @throws FireflyException * @return Collection + * @throws FireflyException + * @throws DuplicateTransactionException */ public function create(array $data): Collection { @@ -243,9 +246,9 @@ class TransactionJournalFactory /** * @param NullArrayObject $row * - * @throws FireflyException - * @throws DuplicateTransactionException * @return TransactionJournal|null + * @throws DuplicateTransactionException + * @throws FireflyException */ private function createJournal(NullArrayObject $row): ?TransactionJournal { @@ -303,10 +306,10 @@ class TransactionJournalFactory Log::debug('Now calling getAccount for the destination.'); $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo); Log::debug('Done with getAccount(2x)'); - $currency = $this->getCurrencyByAccount($type->type, $currency, $sourceAccount, $destinationAccount); - $foreignCurrency = $this->compareCurrencies($currency, $foreignCurrency); - $foreignCurrency = $this->getForeignByAccount($type->type, $foreignCurrency, $destinationAccount); - $description = $this->getDescription($description); + $currency = $this->getCurrencyByAccount($type->type, $currency, $sourceAccount, $destinationAccount); + $foreignCurrency = $this->compareCurrencies($currency, $foreignCurrency); + $foreignCurrency = $this->getForeignByAccount($type->type, $foreignCurrency, $destinationAccount); + $description = $this->getDescription($description); /** Create a basic journal. */ $journal = TransactionJournal::create( @@ -593,8 +596,8 @@ class TransactionJournalFactory $this->accountValidator->setTransactionType($transactionType); // validate source account. - $sourceId = isset($data['source_id']) ? (int) $data['source_id'] : null; - $sourceName = isset($data['source_name']) ? (string) $data['source_name'] : null; + $sourceId = $data['source_id'] ? (int) $data['source_id'] : null; + $sourceName = $data['source_name'] ? (string) $data['source_name'] : null; $validSource = $this->accountValidator->validateSource($sourceId, $sourceName, null); // do something with result: @@ -603,8 +606,8 @@ class TransactionJournalFactory } Log::debug('Source seems valid.'); // validate destination account - $destinationId = isset($data['destination_id']) ? (int) $data['destination_id'] : null; - $destinationName = isset($data['destination_name']) ? (string) $data['destination_name'] : null; + $destinationId = $data['destination_id'] ? (int) $data['destination_id'] : null; + $destinationName = $data['destination_name'] ? (string) $data['destination_name'] : null; $validDestination = $this->accountValidator->validateDestination($destinationId, $destinationName, null); // do something with result: if (false === $validDestination) { diff --git a/app/Http/Controllers/Account/IndexController.php b/app/Http/Controllers/Account/IndexController.php index c4e8254f0c..e9cdb74072 100644 --- a/app/Http/Controllers/Account/IndexController.php +++ b/app/Http/Controllers/Account/IndexController.php @@ -32,6 +32,7 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\View\View; +use Exception; /** * @@ -129,6 +130,7 @@ class IndexController extends Controller * @param Request $request * @param string $objectType * + * @throws Exception * @return Factory|View */ public function index(Request $request, string $objectType) @@ -138,18 +140,27 @@ class IndexController extends Controller return $this->emptyIndex($objectType); } - $objectType = $objectType ?? 'asset'; - $subTitle = (string) trans(sprintf('firefly.%s_accounts', $objectType)); - $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); - $types = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType)); + // reset account order: + + $objectType = $objectType ?? 'asset'; + $subTitle = (string) trans(sprintf('firefly.%s_accounts', $objectType)); + $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); + $types = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType)); + + if (1 === random_int(0, 20)) { + $this->repository->resetAccountOrder($types); + } + $collection = $this->repository->getActiveAccountsByType($types); + + + $total = $collection->count(); $page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page'); $pageSize = (int) app('preferences')->get('listPageSize', 50)->data; $accounts = $collection->slice(($page - 1) * $pageSize, $pageSize); $inactiveCount = $this->repository->getInactiveAccountsByType($types)->count(); - unset($collection); /** @var Carbon $start */ $start = clone session('start', Carbon::now()->startOfMonth()); @@ -164,6 +175,7 @@ class IndexController extends Controller $accounts->each( function (Account $account) use ($activities, $startBalances, $endBalances) { + // TODO lots of queries executed in this block. $account->lastActivityDate = $this->isInArray($activities, $account->id); $account->startBalance = $this->isInArray($startBalances, $account->id); $account->endBalance = $this->isInArray($endBalances, $account->id); @@ -174,7 +186,6 @@ class IndexController extends Controller $account->location = $this->repository->getLocation($account); } ); - // make paginator: $accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page); $accounts->setPath(route('accounts.index', [$objectType])); diff --git a/app/Http/Controllers/Auth/ConfirmPasswordController.php b/app/Http/Controllers/Auth/ConfirmPasswordController.php index 73c1c4d13b..8aa2052f19 100644 --- a/app/Http/Controllers/Auth/ConfirmPasswordController.php +++ b/app/Http/Controllers/Auth/ConfirmPasswordController.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Auth; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Providers\RouteServiceProvider; use Illuminate\Foundation\Auth\ConfirmsPasswords; @@ -62,5 +63,12 @@ class ConfirmPasswordController extends Controller { parent::__construct(); $this->middleware('auth'); + + $loginProvider = config('firefly.login_provider'); + $authGuard = config('firefly.authentication_guard'); + + if ('eloquent' !== $loginProvider || 'web' !== $authGuard) { + throw new FireflyException('Using external identity provider. Cannot continue.'); + } } } diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index a325c9181a..0d07994a36 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Auth; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\User; @@ -51,6 +52,13 @@ class ForgotPasswordController extends Controller { parent::__construct(); $this->middleware('guest'); + + $loginProvider = config('firefly.login_provider'); + $authGuard = config('firefly.authentication_guard'); + + if ('eloquent' !== $loginProvider || 'web' !== $authGuard) { + throw new FireflyException('Using external identity provider. Cannot continue.'); + } } /** diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 5c359e40d2..c6d59aa30e 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -24,6 +24,7 @@ namespace FireflyIII\Http\Controllers\Auth; use Adldap; use DB; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Providers\RouteServiceProvider; use Illuminate\Contracts\View\Factory; @@ -65,6 +66,13 @@ class LoginController extends Controller { parent::__construct(); $this->middleware('guest')->except('logout'); + + $loginProvider = config('firefly.login_provider'); + $authGuard = config('firefly.authentication_guard'); + + if ('eloquent' !== $loginProvider || 'web' !== $authGuard) { + throw new FireflyException('Using external identity provider. Cannot continue.'); + } } @@ -73,9 +81,9 @@ class LoginController extends Controller * * @param Request $request * - * @throws ValidationException * @return RedirectResponse|\Illuminate\Http\Response|JsonResponse * + * @throws ValidationException */ public function login(Request $request) { @@ -112,7 +120,7 @@ class LoginController extends Controller // to login and redirect the user back to the login form. Of course, when this // user surpasses their maximum number of attempts they will get locked out. $this->incrementLoginAttempts($request); - Log::channel('audit')->info(sprintf('Login attempt for user "%s" failed.', $request->get('email'))); + Log::channel('audit')->info(sprintf('Login failed. Attempt for user "%s" failed.', $request->get('email'))); return $this->sendFailedLoginResponse($request); } @@ -124,6 +132,8 @@ class LoginController extends Controller */ public function showLoginForm(Request $request) { + Log::channel('audit')->info('Show login form (1.0).'); + $count = DB::table('users')->count(); $loginProvider = config('firefly.login_provider'); $title = (string) trans('firefly.login_page_title'); @@ -131,7 +141,6 @@ class LoginController extends Controller return redirect(route('register')); // @codeCoverageIgnore } - // is allowed to? $singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; $allowRegistration = true; @@ -160,9 +169,9 @@ class LoginController extends Controller * * @param Request $request * - * @throws ValidationException * @return Response * + * @throws ValidationException */ protected function sendFailedLoginResponse(Request $request) { diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index a471c65156..422e57073e 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Auth; use FireflyIII\Events\RegisteredUser; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Support\Http\Controllers\CreateStuff; use FireflyIII\Support\Http\Controllers\RequestInformation; @@ -63,6 +64,13 @@ class RegisterController extends Controller { parent::__construct(); $this->middleware('guest'); + + $loginProvider = config('firefly.login_provider'); + $authGuard = config('firefly.authentication_guard'); + + if ('eloquent' !== $loginProvider || 'web' !== $authGuard) { + throw new FireflyException('Using external identity provider. Cannot continue.'); + } } /** @@ -105,7 +113,7 @@ class RegisterController extends Controller $this->registered($request, $user); // telemetry - \Telemetry::feature('system.users.count', User::count()); + app('telemetry')->feature('system.users.count', (string)User::count()); return redirect($this->redirectPath()); } diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index bd7149a1e2..53a95040f6 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Auth; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\User; use Illuminate\Contracts\View\Factory; @@ -60,6 +61,13 @@ class ResetPasswordController extends Controller { parent::__construct(); $this->middleware('guest'); + + $loginProvider = config('firefly.login_provider'); + $authGuard = config('firefly.authentication_guard'); + + if ('eloquent' !== $loginProvider || 'web' !== $authGuard) { + throw new FireflyException('Using external identity provider. Cannot continue.'); + } } /** @@ -67,9 +75,9 @@ class ResetPasswordController extends Controller * * @param Request $request * + * @return Factory|JsonResponse|RedirectResponse|View * @throws \Illuminate\Validation\ValidationException * - * @return Factory|JsonResponse|RedirectResponse|View */ public function reset(Request $request) { diff --git a/app/Http/Controllers/Auth/TwoFactorController.php b/app/Http/Controllers/Auth/TwoFactorController.php index a0ed675119..c92049e828 100644 --- a/app/Http/Controllers/Auth/TwoFactorController.php +++ b/app/Http/Controllers/Auth/TwoFactorController.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Auth; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\User; use Illuminate\Http\RedirectResponse; @@ -35,6 +36,21 @@ use Preferences; */ class TwoFactorController extends Controller { + /** + * Create a new controller instance. + */ + public function __construct() + { + parent::__construct(); + + $loginProvider = config('firefly.login_provider'); + $authGuard = config('firefly.authentication_guard'); + + if ('eloquent' !== $loginProvider || 'web' !== $authGuard) { + throw new FireflyException('Using external identity provider. Cannot continue.'); + } + } + /** * What to do if 2FA lost? * diff --git a/app/Http/Controllers/Budget/IndexController.php b/app/Http/Controllers/Budget/IndexController.php index 8889a9677e..5d80e1432b 100644 --- a/app/Http/Controllers/Budget/IndexController.php +++ b/app/Http/Controllers/Budget/IndexController.php @@ -98,138 +98,56 @@ class IndexController extends Controller public function index(Request $request, Carbon $start = null, Carbon $end = null) { Log::debug('Start of IndexController::index()'); + // collect some basic vars: $range = app('preferences')->get('viewRange', '1M')->data; $start = $start ?? session('start', Carbon::now()->startOfMonth()); $end = $end ?? app('navigation')->endOfPeriod($start, $range); $defaultCurrency = app('amount')->getDefaultCurrency(); + $currencies = $this->currencyRepository->getEnabled(); $budgeted = '0'; $spent = '0'; - Log::debug(sprintf('1) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); // new period stuff: $periodTitle = app('navigation')->periodShow($start, $range); $prevLoop = $this->getPreviousPeriods($start, $range); $nextLoop = $this->getNextPeriods($start, $range); - Log::debug(sprintf('2) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); - // get all available budgets. - $ab = $this->abRepository->get($start, $end); - $availableBudgets = []; - Log::debug(sprintf('3) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); - // for each, complement with spent amount: - /** @var AvailableBudget $entry */ - foreach ($ab as $entry) { - $array = $entry->toArray(); - $array['start_date'] = $entry->start_date; - $array['end_date'] = $entry->end_date; + // get all available budgets: + $availableBudgets = $this->getAllAvailableBudgets($start, $end); - // spent in period: - $spentArr = $this->opsRepository->sumExpenses($entry->start_date, $entry->end_date, null, null, $entry->transactionCurrency); - $array['spent'] = $spentArr[$entry->transaction_currency_id]['sum'] ?? '0'; - - // budgeted in period: - $budgeted = $this->blRepository->budgeted($entry->start_date, $entry->end_date, $entry->transactionCurrency, ); - $array['budgeted'] = $budgeted; - $availableBudgets[] = $array; - unset($spentArr); - Log::debug(sprintf('4) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); - } + // get all active budgets: + $budgets = $this->getAllBudgets($start, $end, $currencies, $defaultCurrency); + $sums = $this->getSums($budgets); + // get budgeted for default currency: if (0 === count($availableBudgets)) { - // get budgeted for default currency: - $budgeted = $this->blRepository->budgeted($start, $end, $defaultCurrency, ); + $budgeted = $this->blRepository->budgeted($start, $end, $defaultCurrency,); $spentArr = $this->opsRepository->sumExpenses($start, $end, null, null, $defaultCurrency); $spent = $spentArr[$defaultCurrency->id]['sum'] ?? '0'; unset($spentArr); - Log::debug(sprintf('5) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); } // count the number of enabled currencies. This determines if we display a "+" button. - $currencies = $this->currencyRepository->getEnabled(); $enableAddButton = $currencies->count() > count($availableBudgets); // number of days for consistent budgeting. $activeDaysPassed = $this->activeDaysPassed($start, $end); // see method description. $activeDaysLeft = $this->activeDaysLeft($start, $end); // see method description. - Log::debug(sprintf('6) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); - - // get all budgets, and paginate them into $budgets. - $collection = $this->repository->getActiveBudgets(); - $budgets = []; - Log::debug(sprintf('7) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); - - // complement budget with budget limits in range, and expenses in currency X in range. - /** @var Budget $current */ - foreach ($collection as $current) { - $array = $current->toArray(); - $array['spent'] = []; - $array['budgeted'] = []; - $array['attachments'] = $this->repository->getAttachments($current); - $array['auto_budget'] = $this->repository->getAutoBudget($current); - $budgetLimits = $this->blRepository->getBudgetLimits($current, $start, $end); - Log::debug(sprintf('8) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); - /** @var BudgetLimit $limit */ - foreach ($budgetLimits as $limit) { - $currency = $limit->transactionCurrency ?? $defaultCurrency; - $array['budgeted'][] = [ - 'id' => $limit->id, - 'amount' => number_format((float) $limit->amount, $currency->decimal_places, '.', ''), - 'start_date' => $limit->start_date->formatLocalized($this->monthAndDayFormat), - 'end_date' => $limit->end_date->formatLocalized($this->monthAndDayFormat), - 'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end), - 'currency_id' => $currency->id, - 'currency_symbol' => $currency->symbol, - 'currency_name' => $currency->name, - 'currency_decimal_places' => $currency->decimal_places, - ]; - Log::debug(sprintf('9) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); - } - - /** @var TransactionCurrency $currency */ - foreach ($currencies as $currency) { - $spentArr = $this->opsRepository->sumExpenses($start, $end, null, new Collection([$current]), $currency); - if (isset($spentArr[$currency->id]['sum'])) { - $array['spent'][$currency->id]['spent'] = $spentArr[$currency->id]['sum']; - $array['spent'][$currency->id]['currency_id'] = $currency->id; - $array['spent'][$currency->id]['currency_symbol'] = $currency->symbol; - $array['spent'][$currency->id]['currency_decimal_places'] = $currency->decimal_places; - Log::debug(sprintf('10) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); - } - } - $budgets[] = $array; - } // get all inactive budgets, and simply list them: $inactive = $this->repository->getInactiveBudgets(); - Log::debug(sprintf('11) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); return view( - 'budgets.index', - compact( - 'availableBudgets', - 'budgeted', - 'spent', - 'prevLoop', - 'nextLoop', - 'budgets', - 'currencies', - 'enableAddButton', - 'periodTitle', - 'defaultCurrency', - 'activeDaysPassed', - 'activeDaysLeft', - 'inactive', - 'budgets', - 'start', - 'end' - ) + 'budgets.index', compact( + 'availableBudgets', 'budgeted', 'spent', 'prevLoop', 'nextLoop', 'budgets', 'currencies', 'enableAddButton', 'periodTitle', + 'defaultCurrency', 'activeDaysPassed', 'activeDaysLeft', 'inactive', 'budgets', 'start', 'end', 'sums' + ) ); } /** * @param Request $request - * * @param BudgetRepositoryInterface $repository * * @return JsonResponse @@ -250,4 +168,154 @@ class IndexController extends Controller return response()->json(['OK']); } + + /** + * @param array $budgets + * + * @return array + */ + private function getSums(array $budgets): array + { + $sums = [ + 'budgeted' => [], + 'spent' => [], + 'left' => [], + ]; + + /** @var array $budget */ + foreach ($budgets as $budget) { + /** @var array $spent */ + foreach ($budget['spent'] as $spent) { + $currencyId = $spent['currency_id']; + $sums['spent'][$currencyId] + = $sums['spent'][$currencyId] + ?? [ + 'amount' => '0', + 'currency_id' => $spent['currency_id'], + 'currency_symbol' => $spent['currency_symbol'], + 'currency_decimal_places' => $spent['currency_decimal_places'], + ]; + $sums['spent'][$currencyId]['amount'] = bcadd($sums['spent'][$currencyId]['amount'], $spent['spent']); + } + + /** @var array $budgeted */ + foreach ($budget['budgeted'] as $budgeted) { + $currencyId = $budgeted['currency_id']; + $sums['budgeted'][$currencyId] + = $sums['budgeted'][$currencyId] + ?? [ + 'amount' => '0', + 'currency_id' => $budgeted['currency_id'], + 'currency_symbol' => $budgeted['currency_symbol'], + 'currency_decimal_places' => $budgeted['currency_decimal_places'], + ]; + $sums['budgeted'][$currencyId]['amount'] = bcadd($sums['budgeted'][$currencyId]['amount'], $budgeted['amount']); + + // also calculate how much left from budgeted: + $sums['left'][$currencyId] = $sums['left'][$currencyId] + ?? [ + 'amount' => '0', + 'currency_id' => $budgeted['currency_id'], + 'currency_symbol' => $budgeted['currency_symbol'], + 'currency_decimal_places' => $budgeted['currency_decimal_places'], + ]; + } + } + // final calculation for 'left': + foreach ($sums['budgeted'] as $currencyId => $info) { + $spent = $sums['spent'][$currencyId]['amount'] ?? '0'; + $budgeted = $sums['budgeted'][$currencyId]['amount'] ?? '0'; + $sums['left'][$currencyId]['amount'] = bcadd($spent, $budgeted); + } + + return $sums; + } + + /** + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + private function getAllAvailableBudgets(Carbon $start, Carbon $end): array + { + // get all available budgets. + $ab = $this->abRepository->get($start, $end); + $availableBudgets = []; + // for each, complement with spent amount: + /** @var AvailableBudget $entry */ + foreach ($ab as $entry) { + $array = $entry->toArray(); + $array['start_date'] = $entry->start_date; + $array['end_date'] = $entry->end_date; + + // spent in period: + $spentArr = $this->opsRepository->sumExpenses($entry->start_date, $entry->end_date, null, null, $entry->transactionCurrency); + $array['spent'] = $spentArr[$entry->transaction_currency_id]['sum'] ?? '0'; + + // budgeted in period: + $budgeted = $this->blRepository->budgeted($entry->start_date, $entry->end_date, $entry->transactionCurrency,); + $array['budgeted'] = $budgeted; + $availableBudgets[] = $array; + unset($spentArr); + } + + return $availableBudgets; + } + + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $currencies + * @param TransactionCurrency $defaultCurrency + * + * @return array + */ + private function getAllBudgets(Carbon $start, Carbon $end, Collection $currencies, TransactionCurrency $defaultCurrency): array + { + // get all budgets, and paginate them into $budgets. + $collection = $this->repository->getActiveBudgets(); + $budgets = []; + Log::debug(sprintf('7) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); + + // complement budget with budget limits in range, and expenses in currency X in range. + /** @var Budget $current */ + foreach ($collection as $current) { + $array = $current->toArray(); + $array['spent'] = []; + $array['budgeted'] = []; + $array['attachments'] = $this->repository->getAttachments($current); + $array['auto_budget'] = $this->repository->getAutoBudget($current); + $budgetLimits = $this->blRepository->getBudgetLimits($current, $start, $end); + /** @var BudgetLimit $limit */ + foreach ($budgetLimits as $limit) { + $currency = $limit->transactionCurrency ?? $defaultCurrency; + $array['budgeted'][] = [ + 'id' => $limit->id, + 'amount' => number_format((float) $limit->amount, $currency->decimal_places, '.', ''), + 'start_date' => $limit->start_date->formatLocalized($this->monthAndDayFormat), + 'end_date' => $limit->end_date->formatLocalized($this->monthAndDayFormat), + 'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end), + 'currency_id' => $currency->id, + 'currency_symbol' => $currency->symbol, + 'currency_name' => $currency->name, + 'currency_decimal_places' => $currency->decimal_places, + ]; + } + + /** @var TransactionCurrency $currency */ + foreach ($currencies as $currency) { + $spentArr = $this->opsRepository->sumExpenses($start, $end, null, new Collection([$current]), $currency); + if (isset($spentArr[$currency->id]['sum'])) { + $array['spent'][$currency->id]['spent'] = $spentArr[$currency->id]['sum']; + $array['spent'][$currency->id]['currency_id'] = $currency->id; + $array['spent'][$currency->id]['currency_symbol'] = $currency->symbol; + $array['spent'][$currency->id]['currency_decimal_places'] = $currency->decimal_places; + } + } + $budgets[] = $array; + } + + return $budgets; + } } diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 3abf3d7399..ea508d1cba 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -154,6 +154,7 @@ class AccountController extends Controller 'label' => (string) trans('firefly.spent'), 'type' => 'bar', 'currency_symbol' => $currency->symbol, + 'currency_code' => $currency->code, 'entries' => $this->expandNames($tempData), ]; $chartData[$currencyId] = $dataSet; @@ -209,6 +210,7 @@ class AccountController extends Controller 'budget_id' => $budgetId, 'currency_name' => $journal['currency_name'], 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], ]; } $result[$key]['total'] = bcadd($journal['amount'], $result[$key]['total']); @@ -220,7 +222,7 @@ class AccountController extends Controller $budgetId = $row['budget_id']; $name = $names[$budgetId]; $label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]); - $chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol']]; + $chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']]; } $data = $this->generator->multiCurrencyPieChart($chartData); @@ -281,6 +283,7 @@ class AccountController extends Controller 'category_id' => (int) $journal['category_id'], 'currency_name' => $journal['currency_name'], 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], ]; } $result[$key]['total'] = bcadd($journal['amount'], $result[$key]['total']); @@ -291,7 +294,7 @@ class AccountController extends Controller $categoryId = $row['category_id']; $name = $names[$categoryId] ?? '(unknown)'; $label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]); - $chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol']]; + $chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']]; } $data = $this->generator->multiCurrencyPieChart($chartData); @@ -379,6 +382,7 @@ class AccountController extends Controller 'category_id' => $journal['category_id'], 'currency_name' => $journal['currency_name'], 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], ]; } $result[$key]['total'] = bcadd($journal['amount'], $result[$key]['total']); @@ -389,7 +393,7 @@ class AccountController extends Controller $categoryId = $row['category_id']; $name = $names[$categoryId] ?? '(unknown)'; $label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]); - $chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol']]; + $chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']]; } $data = $this->generator->multiCurrencyPieChart($chartData); $cache->store($data); @@ -530,7 +534,7 @@ class AccountController extends Controller // sort temp array by amount. $amounts = array_column($tempData, 'diff_float'); - array_multisort($amounts, SORT_DESC, $tempData); + array_multisort($amounts, SORT_ASC, $tempData); // loop all found currencies and build the data array for the chart. /** @@ -543,6 +547,7 @@ class AccountController extends Controller 'label' => (string) trans('firefly.earned'), 'type' => 'bar', 'currency_symbol' => $currency->symbol, + 'currency_code' => $currency->code, 'entries' => $this->expandNames($tempData), ]; $chartData[$currencyId] = $dataSet; @@ -576,6 +581,7 @@ class AccountController extends Controller $result = [ 'label' => sprintf('%s (%s)', $account->name, $currency->symbol), 'currency_symbol' => $currency->symbol, + 'currency_code' => $currency->code, 'entries' => [], ]; $entries = []; diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php index 1e7182b3f9..760e4b1b69 100644 --- a/app/Http/Controllers/Chart/BillController.php +++ b/app/Http/Controllers/Chart/BillController.php @@ -81,12 +81,12 @@ class BillController extends Controller foreach ($paid as $currencyId => $amount) { $currencies[$currencyId] = $currencies[$currencyId] ?? $currencyRepository->findNull($currencyId); $label = (string) trans('firefly.paid_in_currency', ['currency' => $currencies[$currencyId]->name]); - $chartData[$label] = ['amount' => $amount, 'currency_symbol' => $currencies[$currencyId]->symbol]; + $chartData[$label] = ['amount' => $amount, 'currency_symbol' => $currencies[$currencyId]->symbol, 'currency_code' => $currencies[$currencyId]->code]; } foreach ($unpaid as $currencyId => $amount) { $currencies[$currencyId] = $currencies[$currencyId] ?? $currencyRepository->findNull($currencyId); $label = (string) trans('firefly.unpaid_in_currency', ['currency' => $currencies[$currencyId]->name]); - $chartData[$label] = ['amount' => $amount, 'currency_symbol' => $currencies[$currencyId]->symbol]; + $chartData[$label] = ['amount' => $amount, 'currency_symbol' => $currencies[$currencyId]->symbol, 'currency_code' => $currencies[$currencyId]->code]; } $data = $this->generator->multiCurrencyPieChart($chartData); @@ -126,9 +126,9 @@ class BillController extends Controller ); $chartData = [ - ['type' => 'line', 'label' => (string) trans('firefly.min-amount'), 'currency_symbol' => $bill->transactionCurrency->symbol, 'entries' => []], - ['type' => 'line', 'label' => (string) trans('firefly.max-amount'), 'currency_symbol' => $bill->transactionCurrency->symbol, 'entries' => []], - ['type' => 'bar', 'label' => (string) trans('firefly.journal-amount'), 'currency_symbol' => $bill->transactionCurrency->symbol, 'entries' => []], + ['type' => 'line', 'label' => (string) trans('firefly.min-amount'), 'currency_symbol' => $bill->transactionCurrency->symbol, 'currency_code' => $bill->transactionCurrency->code, 'entries' => []], + ['type' => 'line', 'label' => (string) trans('firefly.max-amount'), 'currency_symbol' => $bill->transactionCurrency->symbol, 'currency_code' => $bill->transactionCurrency->code, 'entries' => []], + ['type' => 'bar', 'label' => (string) trans('firefly.journal-amount'), 'currency_symbol' => $bill->transactionCurrency->symbol, 'currency_code' => $bill->transactionCurrency->code, 'entries' => []], ]; foreach ($journals as $journal) { diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 7eb7540949..12a898f5a5 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -137,6 +137,7 @@ class BudgetController extends Controller 'label' => count($currencies) > 1 ? sprintf('%s (%s)', $budget->name, $currency['currency_name']) : $budget->name, 'type' => 'bar', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], 'entries' => $defaultEntries, ]; foreach ($currency['spent'] as $label => $spent) { @@ -193,6 +194,7 @@ class BudgetController extends Controller $data = $this->generator->singleSet((string)trans('firefly.left'), $entries); // add currency symbol from budget limit: $data['datasets'][0]['currency_symbol'] = $budgetLimit->transactionCurrency->symbol; + $data['datasets'][0]['currency_code'] = $budgetLimit->transactionCurrency->code; $cache->store($data); return response()->json($data); @@ -241,6 +243,7 @@ class BudgetController extends Controller $result[$key] = $result[$key] ?? [ 'amount' => '0', 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], 'currency_name' => $journal['currency_name'], ]; $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']); @@ -255,6 +258,7 @@ class BudgetController extends Controller = [ 'amount' => $info['amount'], 'currency_symbol' => $info['currency_symbol'], + 'currency_code' => $info['currency_code'], ]; } @@ -305,6 +309,8 @@ class BudgetController extends Controller $result[$key] = $result[$key] ?? [ 'amount' => '0', 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], + 'currency_symbol' => $journal['currency_symbol'], 'currency_name' => $journal['currency_name'], ]; $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']); @@ -318,6 +324,7 @@ class BudgetController extends Controller $chartData[$title] = [ 'amount' => $info['amount'], 'currency_symbol' => $info['currency_symbol'], + 'currency_code' => $info['currency_code'], ]; } $data = $this->generator->multiCurrencyPieChart($chartData); @@ -369,6 +376,7 @@ class BudgetController extends Controller $result[$key] = $result[$key] ?? [ 'amount' => '0', 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], 'currency_name' => $journal['currency_name'], ]; $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']); @@ -383,6 +391,7 @@ class BudgetController extends Controller $chartData[$title] = [ 'amount' => $info['amount'], 'currency_symbol' => $info['currency_symbol'], + 'currency_code' => $info['currency_code'], ]; } @@ -501,15 +510,17 @@ class BudgetController extends Controller $preferredRange = app('navigation')->preferredRangeFormat($start, $end); $chartData = [ [ - 'label' => (string)trans('firefly.box_spent_in_currency', ['currency' => $currency->name]), + 'label' => (string) trans('firefly.box_spent_in_currency', ['currency' => $currency->name]), 'type' => 'bar', 'entries' => [], 'currency_symbol' => $currency->symbol, + 'currency_code' => $currency->code, ], [ - 'label' => (string)trans('firefly.box_budgeted_in_currency', ['currency' => $currency->name]), + 'label' => (string) trans('firefly.box_budgeted_in_currency', ['currency' => $currency->name]), 'type' => 'bar', 'currency_symbol' => $currency->symbol, + 'currency_code' => $currency->code, 'entries' => [], ], ]; diff --git a/app/Http/Controllers/Chart/BudgetReportController.php b/app/Http/Controllers/Chart/BudgetReportController.php index 5d16a52981..0ab328e656 100644 --- a/app/Http/Controllers/Chart/BudgetReportController.php +++ b/app/Http/Controllers/Chart/BudgetReportController.php @@ -88,6 +88,7 @@ class BudgetReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; foreach ($budget['transaction_journals'] as $journal) { $amount = app('steam')->positive($journal['amount']); @@ -124,6 +125,7 @@ class BudgetReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); @@ -160,6 +162,7 @@ class BudgetReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); @@ -201,6 +204,7 @@ class BudgetReportController extends Controller ), 'type' => 'bar', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], 'currency_id' => $currency['currency_id'], 'entries' => $this->makeEntries($start, $end), ]; @@ -243,6 +247,7 @@ class BudgetReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); diff --git a/app/Http/Controllers/Chart/CategoryReportController.php b/app/Http/Controllers/Chart/CategoryReportController.php index 2752d6c994..28d155a5fe 100644 --- a/app/Http/Controllers/Chart/CategoryReportController.php +++ b/app/Http/Controllers/Chart/CategoryReportController.php @@ -87,6 +87,7 @@ class CategoryReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -120,6 +121,7 @@ class CategoryReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; foreach ($category['transaction_journals'] as $journal) { $amount = app('steam')->positive($journal['amount']); @@ -156,6 +158,7 @@ class CategoryReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; foreach ($category['transaction_journals'] as $journal) { $amount = app('steam')->positive($journal['amount']); @@ -192,6 +195,7 @@ class CategoryReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -227,6 +231,7 @@ class CategoryReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -267,6 +272,7 @@ class CategoryReportController extends Controller ), 'type' => 'bar', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], 'currency_id' => $currency['currency_id'], 'entries' => $this->makeEntries($start, $end), ]; @@ -293,6 +299,7 @@ class CategoryReportController extends Controller ), 'type' => 'bar', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], 'currency_id' => $currency['currency_id'], 'entries' => $this->makeEntries($start, $end), ]; @@ -335,6 +342,7 @@ class CategoryReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -370,6 +378,7 @@ class CategoryReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); diff --git a/app/Http/Controllers/Chart/DoubleReportController.php b/app/Http/Controllers/Chart/DoubleReportController.php index 287a2ff551..87c62cea35 100644 --- a/app/Http/Controllers/Chart/DoubleReportController.php +++ b/app/Http/Controllers/Chart/DoubleReportController.php @@ -89,6 +89,7 @@ class DoubleReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -123,6 +124,7 @@ class DoubleReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -157,6 +159,7 @@ class DoubleReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -201,6 +204,7 @@ class DoubleReportController extends Controller ), 'type' => 'bar', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], 'currency_id' => $currency['currency_id'], 'entries' => $this->makeEntries($start, $end), ]; @@ -226,6 +230,7 @@ class DoubleReportController extends Controller ), 'type' => 'bar', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], 'currency_id' => $currency['currency_id'], 'entries' => $this->makeEntries($start, $end), ]; @@ -273,6 +278,7 @@ class DoubleReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -290,6 +296,7 @@ class DoubleReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -331,6 +338,7 @@ class DoubleReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -348,6 +356,7 @@ class DoubleReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); diff --git a/app/Http/Controllers/Chart/ReportController.php b/app/Http/Controllers/Chart/ReportController.php index e9a9c4d1e8..c6332d14b2 100644 --- a/app/Http/Controllers/Chart/ReportController.php +++ b/app/Http/Controllers/Chart/ReportController.php @@ -117,6 +117,7 @@ class ReportController extends Controller 'label' => 'Net worth in ' . $netWorthItem['currency']->name, 'type' => 'line', 'currency_symbol' => $netWorthItem['currency']->symbol, + 'currency_code' => $netWorthItem['currency']->code, 'entries' => [], ]; } @@ -217,6 +218,7 @@ class ReportController extends Controller 'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green 'currency_id' => $currency['currency_id'], 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], 'entries' => [], ]; $expense = [ @@ -225,6 +227,7 @@ class ReportController extends Controller 'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red 'currency_id' => $currency['currency_id'], 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], 'entries' => [], ]; diff --git a/app/Http/Controllers/Chart/TagReportController.php b/app/Http/Controllers/Chart/TagReportController.php index ff8a8b1732..a187e74b55 100644 --- a/app/Http/Controllers/Chart/TagReportController.php +++ b/app/Http/Controllers/Chart/TagReportController.php @@ -87,6 +87,7 @@ class TagReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -122,6 +123,7 @@ class TagReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -157,6 +159,7 @@ class TagReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -193,6 +196,7 @@ class TagReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -228,6 +232,7 @@ class TagReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -270,6 +275,7 @@ class TagReportController extends Controller ), 'type' => 'bar', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], 'currency_id' => $currency['currency_id'], 'entries' => $this->makeEntries($start, $end), ]; @@ -296,6 +302,7 @@ class TagReportController extends Controller ), 'type' => 'bar', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], 'currency_id' => $currency['currency_id'], 'entries' => $this->makeEntries($start, $end), ]; @@ -338,6 +345,7 @@ class TagReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -373,6 +381,7 @@ class TagReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; $amount = app('steam')->positive($journal['amount']); $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); @@ -406,6 +415,7 @@ class TagReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; foreach ($tag['transaction_journals'] as $journal) { $amount = app('steam')->positive($journal['amount']); @@ -440,6 +450,7 @@ class TagReportController extends Controller $result[$title] = $result[$title] ?? [ 'amount' => '0', 'currency_symbol' => $currency['currency_symbol'], + 'currency_code' => $currency['currency_code'], ]; foreach ($tag['transaction_journals'] as $journal) { $amount = app('steam')->positive($journal['amount']); diff --git a/app/Http/Controllers/Chart/TransactionController.php b/app/Http/Controllers/Chart/TransactionController.php index 8516fb60ce..2f8061e919 100644 --- a/app/Http/Controllers/Chart/TransactionController.php +++ b/app/Http/Controllers/Chart/TransactionController.php @@ -87,6 +87,7 @@ class TransactionController extends Controller $data[$title] = $data[$title] ?? [ 'amount' => '0', 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], ]; $data[$title]['amount'] = bcadd($data[$title]['amount'], $journal['amount']); } @@ -145,6 +146,7 @@ class TransactionController extends Controller $data[$title] = $data[$title] ?? [ 'amount' => '0', 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], ]; $data[$title]['amount'] = bcadd($data[$title]['amount'], $journal['amount']); @@ -204,6 +206,7 @@ class TransactionController extends Controller $data[$title] = $data[$title] ?? [ 'amount' => '0', 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], ]; $data[$title]['amount'] = bcadd($data[$title]['amount'], $journal['amount']); } @@ -262,6 +265,7 @@ class TransactionController extends Controller $data[$title] = $data[$title] ?? [ 'amount' => '0', 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], ]; $data[$title]['amount'] = bcadd($data[$title]['amount'], $journal['amount']); diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 79434b738c..e54531f614 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -34,7 +34,7 @@ use Route; * Class Controller. * */ -class Controller extends BaseController +abstract class Controller extends BaseController { use AuthorizesRequests, DispatchesJobs, ValidatesRequests, UserNavigation, RequestInformation; diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 7c315ac633..60661c61c6 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -63,8 +63,8 @@ class HomeController extends Controller * * @param Request $request * - * @throws Exception * @return JsonResponse + * @throws Exception */ public function dateRange(Request $request): JsonResponse { @@ -105,8 +105,8 @@ class HomeController extends Controller * * @param AccountRepositoryInterface $repository * - * @throws Exception * @return Factory|RedirectResponse|Redirector|View + * @throws Exception */ public function index(AccountRepositoryInterface $repository) { @@ -119,10 +119,7 @@ class HomeController extends Controller } $subTitle = (string) trans('firefly.welcome_back'); $transactions = []; - $frontPage = app('preferences')->get( - 'frontPageAccounts', - $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray() - ); + $frontPage = app('preferences')->get('frontPageAccounts', $repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray()); /** @var Carbon $start */ $start = session('start', Carbon::now()->startOfMonth()); /** @var Carbon $end */ @@ -134,14 +131,15 @@ class HomeController extends Controller /** @var BillRepositoryInterface $billRepository */ $billRepository = app(BillRepositoryInterface::class); $billCount = $billRepository->getBills()->count(); + + + // collect groups for each transaction. foreach ($accounts as $account) { /** @var GroupCollectorInterface $collector */ $collector = app(GroupCollectorInterface::class); - $collector->setAccounts(new Collection([$account])) - ->withAccountInformation() - ->setRange($start, $end)->setLimit(10)->setPage(1); - $set = $collector->getGroups(); - $transactions[] = [$set, $account]; + $collector->setAccounts(new Collection([$account]))->withAccountInformation()->setRange($start, $end)->setLimit(10)->setPage(1); + $set = $collector->getExtractedJournals(); + $transactions[] = ['transactions' => $set, 'account' => $account]; } /** @var User $user */ diff --git a/app/Http/Controllers/JavascriptController.php b/app/Http/Controllers/JavascriptController.php index ba293e76c7..58f68882f5 100644 --- a/app/Http/Controllers/JavascriptController.php +++ b/app/Http/Controllers/JavascriptController.php @@ -132,21 +132,13 @@ class JavascriptController extends Controller public function variables(Request $request, AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository): Response { $account = $repository->findNull((int) $request->get('account')); - $currencyId = 0; - if (null !== $account) { - // TODO we can use getAccountCurrency() instead - $currencyId = (int) $repository->getMetaValue($account, 'currency_id'); + $currency = app('amount')->getDefaultCurrency(); + if(null !== $account) { + $currency = $repository->getAccountCurrency($account) ?? $currency; } - /** @var TransactionCurrency $currency */ - $currency = $currencyRepository->findNull($currencyId); - if (null === $currency) { - /** @var TransactionCurrency $currency */ - $currency = app('amount')->getDefaultCurrency(); - } - - $localeconv = app('amount')->getLocaleInfo(); - $accounting = app('amount')->getJsConfig($localeconv); - $localeconv['frac_digits'] = $currency->decimal_places; + $locale = app('steam')->getLocale(); + $accounting = app('amount')->getJsConfig(); + $accounting['frac_digits'] = $currency->decimal_places; $pref = app('preferences')->get('language', config('firefly.default_language', 'en_US')); /** @noinspection NullPointerExceptionInspection */ $lang = $pref->data; @@ -154,14 +146,14 @@ class JavascriptController extends Controller $uid = substr(hash('sha256', sprintf('%s-%s-%s', (string) config('app.key'), auth()->user()->id, auth()->user()->email)), 0, 12); $data = [ - 'currencyCode' => $currency->code, - 'currencySymbol' => $currency->symbol, - 'accounting' => $accounting, - 'localeconv' => $localeconv, - 'language' => $lang, - 'dateRangeTitle' => $dateRange['title'], - 'dateRangeConfig' => $dateRange['configuration'], - 'uid' => $uid, + 'currencyCode' => $currency->code, + 'currencySymbol' => $currency->symbol, + 'accountingLocaleInfo' => $accounting, + 'language' => $lang, + 'dateRangeTitle' => $dateRange['title'], + 'locale' => $locale, + 'dateRangeConfig' => $dateRange['configuration'], + 'uid' => $uid, ]; $request->session()->keep(['two-factor-secret']); diff --git a/app/Http/Controllers/Json/AutoCompleteController.php b/app/Http/Controllers/Json/AutoCompleteController.php index 2eb39034bf..2a65cd7cab 100644 --- a/app/Http/Controllers/Json/AutoCompleteController.php +++ b/app/Http/Controllers/Json/AutoCompleteController.php @@ -23,18 +23,10 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Json; use Amount; -use Carbon\Carbon; use FireflyIII\Http\Controllers\Controller; -use FireflyIII\Models\Account; -use FireflyIII\Models\AccountType; use FireflyIII\Models\ObjectGroup; use FireflyIII\Models\PiggyBank; -use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Bill\BillRepositoryInterface; -use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Repositories\Category\CategoryRepositoryInterface; -use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; @@ -43,7 +35,6 @@ use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Log; /** * Class AutoCompleteController. @@ -54,93 +45,6 @@ use Log; class AutoCompleteController extends Controller { - /** - * @param Request $request - * - * @return JsonResponse - */ - public function accounts(Request $request): JsonResponse - { - $accountTypes = explode(',', $request->get('types') ?? ''); - $search = $request->get('search'); - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - - // filter the account types: - $allowedAccountTypes = [AccountType::ASSET, AccountType::EXPENSE, AccountType::REVENUE, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE,]; - $balanceTypes = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE,]; - $filteredAccountTypes = []; - foreach ($accountTypes as $type) { - if (in_array($type, $allowedAccountTypes, true)) { - $filteredAccountTypes[] = $type; - } - } - if (0 === count($filteredAccountTypes)) { - $filteredAccountTypes = $allowedAccountTypes; - } - Log::debug(sprintf('Now in accounts("%s"). Filtering results.', $search), $filteredAccountTypes); - - $return = []; - $result = $repository->searchAccount((string) $search, $filteredAccountTypes); - $defaultCurrency = app('amount')->getDefaultCurrency(); - - /** @var Account $account */ - foreach ($result as $account) { - $nameWithBalance = $account->name; - $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; - - if (in_array($account->accountType->type, $balanceTypes, true)) { - $balance = app('steam')->balance($account, new Carbon); - $nameWithBalance = sprintf('%s (%s)', $account->name, app('amount')->formatAnything($currency, $balance, false)); - } - - $return[] = [ - 'id' => $account->id, - 'name' => $account->name, - 'name_with_balance' => $nameWithBalance, - 'type' => $account->accountType->type, - 'currency_id' => $currency->id, - 'currency_name' => $currency->name, - 'currency_code' => $currency->code, - 'currency_decimal_places' => $currency->decimal_places, - ]; - } - - - return response()->json($return); - } - - /** - * Searches in the titles of all transaction journals. - * The result is limited to the top 15 unique results. - * - * @param Request $request - * - * @return JsonResponse - */ - public function allJournals(Request $request): JsonResponse - { - $search = (string) $request->get('search'); - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $result = $repository->searchJournalDescriptions($search); - - // limit and unique - $filtered = $result->unique('description'); - $limited = $filtered->slice(0, 15); - $array = $limited->toArray(); - // duplicate 'description' value into 'name': - $array = array_map( - static function (array $journal) { - $journal['name'] = $journal['description']; - - return $journal; - }, - $array - ); - - return response()->json(array_values($array)); - } /** * Searches in the titles of all transaction journals. @@ -187,294 +91,4 @@ class AutoCompleteController extends Controller return response()->json($array); } - /** - * An auto-complete specifically for asset accounts and liabilities, used when mass updating and for rules mostly. - * - * @param Request $request - * - * @return JsonResponse - */ - public function assetAccounts(Request $request): JsonResponse - { - $search = $request->get('search'); - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - - // filter the account types: - $allowedAccountTypes = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]; - Log::debug(sprintf('Now in expenseAccounts(%s). Filtering results.', $search), $allowedAccountTypes); - - $return = []; - $result = $repository->searchAccount((string) $search, $allowedAccountTypes); - - /** @var Account $account */ - foreach ($result as $account) { - $return[] = [ - 'id' => $account->id, - 'name' => $account->name, - 'type' => $account->accountType->type, - ]; - } - - return response()->json($return); - } - - /** - * @param Request $request - * - * @return JsonResponse - * @codeCoverageIgnore - */ - public function bills(Request $request): JsonResponse - { - $query = (string) $request->get('search'); - /** @var BillRepositoryInterface $repository */ - $repository = app(BillRepositoryInterface::class); - $result = $repository->searchBill($query); - - return response()->json($result->toArray()); - } - - /** - * @param Request $request - * - * @return JsonResponse - * @codeCoverageIgnore - */ - public function budgets(Request $request): JsonResponse - { - $search = (string) $request->get('search'); - /** @var BudgetRepositoryInterface $repository */ - $repository = app(BudgetRepositoryInterface::class); - $result = $repository->searchBudget($search); - - return response()->json($result->toArray()); - } - - /** - * @param Request $request - * - * @return JsonResponse - * @codeCoverageIgnore - */ - public function categories(Request $request): JsonResponse - { - $query = (string) $request->get('search'); - /** @var CategoryRepositoryInterface $repository */ - $repository = app(CategoryRepositoryInterface::class); - $result = $repository->searchCategory($query); - - return response()->json($result->toArray()); - } - - /** - * @return JsonResponse - * @codeCoverageIgnore - */ - public function currencies(): JsonResponse - { - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $return = []; - $collection = $repository->getAll(); - - /** @var TransactionCurrency $currency */ - foreach ($collection as $currency) { - $return[] = [ - 'id' => $currency->id, - 'name' => $currency->name, - 'code' => $currency->code, - 'symbol' => $currency->symbol, - 'enabled' => $currency->enabled, - 'decimal_places' => $currency->decimal_places, - ]; - } - - return response()->json($return); - } - - /** - * @param Request $request - * - * @return JsonResponse - * @codeCoverageIgnore - */ - public function currencyNames(Request $request): JsonResponse - { - $query = (string) $request->get('search'); - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $result = $repository->searchCurrency($query)->toArray(); - foreach ($result as $index => $item) { - $result[$index]['name'] = sprintf('%s (%s)', $item['name'], $item['code']); - } - - return response()->json($result); - } - - /** - * An auto-complete specifically for expense accounts, used when mass updating mostly. - * - * @param Request $request - * - * @return JsonResponse - */ - public function expenseAccounts(Request $request): JsonResponse - { - $search = $request->get('search'); - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - - // filter the account types: - $allowedAccountTypes = [AccountType::EXPENSE]; - Log::debug(sprintf('Now in expenseAccounts(%s). Filtering results.', $search), $allowedAccountTypes); - - $return = []; - $result = $repository->searchAccount((string) $search, $allowedAccountTypes); - - /** @var Account $account */ - foreach ($result as $account) { - $return[] = [ - 'id' => $account->id, - 'name' => $account->name, - 'type' => $account->accountType->type, - ]; - } - - return response()->json($return); - } - - /** - * An auto-complete specifically for expense accounts, used when mass updating mostly. - * - * @param Request $request - * - * @return JsonResponse - */ - public function objectGroups(Request $request): JsonResponse - { - $search = $request->get('search'); - - /** @var ObjectGroupRepositoryInterface $repository */ - $repository = app(ObjectGroupRepositoryInterface::class); - - $return = []; - $result = $repository->search((string) $search); - - /** @var ObjectGroup $account */ - foreach ($result as $objectGroup) { - $return[] = [ - 'id' => $objectGroup->id, - 'title' => $objectGroup->title, - ]; - } - - return response()->json($return); - } - - /** - * @return JsonResponse - * @codeCoverageIgnore - */ - public function piggyBanks(): JsonResponse - { - /** @var PiggyBankRepositoryInterface $repository */ - $repository = app(PiggyBankRepositoryInterface::class); - - /** @var AccountRepositoryInterface $accountRepos */ - $accountRepos = app(AccountRepositoryInterface::class); - - $piggies = $repository->getPiggyBanks(); - $defaultCurrency = Amount::getDefaultCurrency(); - $response = []; - /** @var PiggyBank $piggy */ - foreach ($piggies as $piggy) { - $currency = $accountRepos->getAccountCurrency($piggy->account) ?? $defaultCurrency; - $currentAmount = $repository->getRepetition($piggy)->currentamount ?? '0'; - $piggy->objectGroup = $piggy->objectGroups->first(); - $piggy->name_with_amount = sprintf( - '%s (%s / %s)', - $piggy->name, - app('amount')->formatAnything($currency, $currentAmount, false), - app('amount')->formatAnything($currency, $piggy->targetamount, false), - ); - $response[] = $piggy->toArray(); - } - - return response()->json($response); - } - - /** - * An auto-complete specifically for revenue accounts, used when converting transactions mostly. - * - * @param Request $request - * - * @return JsonResponse - */ - public function revenueAccounts(Request $request): JsonResponse - { - $search = $request->get('search'); - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - - // filter the account types: - $allowedAccountTypes = [AccountType::REVENUE]; - Log::debug('Now in revenueAccounts(). Filtering results.', $allowedAccountTypes); - - $return = []; - $result = $repository->searchAccount((string) $search, $allowedAccountTypes); - - /** @var Account $account */ - foreach ($result as $account) { - $return[] = [ - 'id' => $account->id, - 'name' => $account->name, - 'type' => $account->accountType->type, - ]; - } - - return response()->json($return); - } - - /** - * @param Request $request - * - * @return JsonResponse - * @codeCoverageIgnore - */ - public function tags(Request $request): JsonResponse - { - $search = (string) $request->get('search'); - /** @var TagRepositoryInterface $repository */ - $repository = app(TagRepositoryInterface::class); - $result = $repository->searchTags($search); - $array = $result->toArray(); - foreach ($array as $index => $item) { - // rename field for consistency. - $array[$index]['name'] = $item['tag']; - } - - return response()->json($array); - } - - /** - * @param Request $request - * - * @return JsonResponse - * @codeCoverageIgnore - */ - public function transactionTypes(Request $request): JsonResponse - { - $query = (string) $request->get('search'); - /** @var TransactionTypeRepositoryInterface $repository */ - $repository = app(TransactionTypeRepositoryInterface::class); - $array = $repository->searchTypes($query)->toArray(); - - foreach ($array as $index => $item) { - // different key for consistency. - $array[$index]['name'] = $item['type']; - } - - return response()->json($array); - } } diff --git a/app/Http/Controllers/Json/BoxController.php b/app/Http/Controllers/Json/BoxController.php index 6fcd63bab4..20b45bee97 100644 --- a/app/Http/Controllers/Json/BoxController.php +++ b/app/Http/Controllers/Json/BoxController.php @@ -215,47 +215,6 @@ class BoxController extends Controller return response()->json($response); } - - /** - * Bills to pay and paid. - * - * @param BillRepositoryInterface $repository - * - * @return JsonResponse - */ - public function bills(BillRepositoryInterface $repository): JsonResponse - { - /** @var Carbon $start */ - $start = session('start', Carbon::now()->startOfMonth()); - /** @var Carbon $end */ - $end = session('end', Carbon::now()->endOfMonth()); - - $cache = new CacheProperties; - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty('box-bills'); - if ($cache->has()) { - return response()->json($cache->get()); // @codeCoverageIgnore - } - - /* - * Since both this method and the chart use the exact same data, we can suffice - * with calling the one method in the bill repository that will get this amount. - */ - $paidAmount = bcmul($repository->getBillsPaidInRange($start, $end), '-1'); - $unpaidAmount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount. - $currency = app('amount')->getDefaultCurrency(); - - $return = [ - 'paid' => app('amount')->formatAnything($currency, $paidAmount, false), - 'unpaid' => app('amount')->formatAnything($currency, $unpaidAmount, false), - ]; - $cache->store($return); - - return response()->json($return); - } - - /** * Total user net worth. * diff --git a/app/Http/Controllers/PiggyBank/IndexController.php b/app/Http/Controllers/PiggyBank/IndexController.php index 0687afa383..e7b0c8551e 100644 --- a/app/Http/Controllers/PiggyBank/IndexController.php +++ b/app/Http/Controllers/PiggyBank/IndexController.php @@ -89,13 +89,7 @@ class IndexController extends Controller $parameters->set('end', $end); // make piggy bank groups: - $piggyBanks = [ - 0 => [ // the index is the order, not the ID. - 'object_group_id' => 0, - 'object_group_title' => (string) trans('firefly.default_group_title_name'), - 'piggy_banks' => [], - ], - ]; + $piggyBanks = []; /** @var PiggyBankTransformer $transformer */ $transformer = app(PiggyBankTransformer::class); @@ -110,8 +104,8 @@ class IndexController extends Controller $groupOrder = (int) $array['object_group_order']; // make group array if necessary: $piggyBanks[$groupOrder] = $piggyBanks[$groupOrder] ?? [ - 'object_group_id' => $array['object_group_id'], - 'object_group_title' => $array['object_group_title'], + 'object_group_id' => $array['object_group_id'] ?? 0, + 'object_group_title' => $array['object_group_title'] ?? trans('firefly.default_group_title_name'), 'piggy_banks' => [], ]; diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php index 709a868e59..c5c8a1b9f8 100644 --- a/app/Http/Controllers/PreferencesController.php +++ b/app/Http/Controllers/PreferencesController.php @@ -68,7 +68,7 @@ class PreferencesController extends Controller { $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]); $isDocker = env('IS_DOCKER', false); - + // group accounts $groupedAccounts = []; /** @var Account $account */ @@ -206,6 +206,7 @@ class PreferencesController extends Controller 'internal_reference' => isset($setOptions['internal_reference']), 'notes' => isset($setOptions['notes']), 'attachments' => isset($setOptions['attachments']), + 'external_uri' => isset($setOptions['external_uri']), ]; app('preferences')->set('transaction_journal_optional_fields', $optionalTj); diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index cd952dcb1e..8c26c76665 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -82,6 +82,7 @@ class ProfileController extends Controller $loginProvider = config('firefly.login_provider'); $authGuard = config('firefly.authentication_guard'); $this->externalIdentity = 'eloquent' !== $loginProvider || 'web' !== $authGuard; + $this->externalIdentity = true; $this->middleware(IsDemoUser::class)->except(['index']); } @@ -222,9 +223,9 @@ class ProfileController extends Controller * @param UserRepositoryInterface $repository * @param string $token * - * @throws FireflyException * @return RedirectResponse|Redirector * + * @throws FireflyException */ public function confirmEmailChange(UserRepositoryInterface $repository, string $token) { @@ -338,10 +339,13 @@ class ProfileController extends Controller public function index() { /** @var User $user */ - $user = auth()->user(); - $loginProvider = config('firefly.login_provider'); - // check if client token thing exists (default one) - $count = DB::table('oauth_clients')->where('personal_access_client', 1)->whereNull('user_id')->count(); + $user = auth()->user(); + $externalIdentity = $this->externalIdentity; + $count = DB::table('oauth_clients')->where('personal_access_client', 1)->whereNull('user_id')->count(); + $subTitle = $user->email; + $userId = $user->id; + $enabled2FA = null !== $user->mfa_secret; + $mfaBackupCount = count(app('preferences')->get('mfa_recovery', [])->data); $this->createOAuthKeys(); @@ -350,19 +354,14 @@ class ProfileController extends Controller $repository = app(ClientRepository::class); $repository->createPersonalAccessClient(null, config('app.name') . ' Personal Access Client', 'http://localhost'); } - $subTitle = $user->email; - $userId = $user->id; - $enabled2FA = null !== $user->mfa_secret; - $mfaBackupCount = count(app('preferences')->get('mfa_recovery', [])->data); - // get access token or create one. $accessToken = app('preferences')->get('access_token', null); if (null === $accessToken) { $token = $user->generateAccessToken(); $accessToken = app('preferences')->set('access_token', $token); } - return view('profile.index', compact('subTitle', 'mfaBackupCount', 'userId', 'accessToken', 'enabled2FA', 'loginProvider')); + return view('profile.index', compact('subTitle', 'mfaBackupCount', 'userId', 'accessToken', 'enabled2FA', 'externalIdentity')); } /** @@ -381,7 +380,7 @@ class ProfileController extends Controller $recoveryCodes = $recovery->lowercase() ->setCount(8) // Generate 8 codes ->setBlocks(2) // Every code must have 7 blocks - ->setChars(6) // Each block must have 16 chars + ->setChars(6) // Each block must have 16 chars ->toArray(); $codes = implode("\r\n", $recoveryCodes); @@ -583,9 +582,9 @@ class ProfileController extends Controller * @param string $token * @param string $hash * - * @throws FireflyException * @return RedirectResponse|Redirector * + * @throws FireflyException */ public function undoEmailChange(UserRepositoryInterface $repository, string $token, string $hash) { diff --git a/app/Http/Controllers/Recurring/CreateController.php b/app/Http/Controllers/Recurring/CreateController.php index 37d7325c40..e91ba1fbc0 100644 --- a/app/Http/Controllers/Recurring/CreateController.php +++ b/app/Http/Controllers/Recurring/CreateController.php @@ -29,6 +29,8 @@ use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\RecurrenceFormRequest; use FireflyIII\Models\RecurrenceRepetition; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; use Illuminate\Contracts\View\Factory; @@ -75,6 +77,97 @@ class CreateController extends Controller ); } + /** + * @param Request $request + * @param TransactionJournal $journal + */ + public function createFromJournal(Request $request, TransactionJournal $journal) + { + $budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets()); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $tomorrow = new Carbon; + $oldRepetitionType = $request->old('repetition_type'); + $tomorrow->addDay(); + + // put previous url in session if not redirect from store (not "create another"). + if (true !== session('recurring.create.fromStore')) { + $this->rememberPreviousUri('recurring.create.uri'); + } + $request->session()->forget('recurring.create.fromStore'); + $repetitionEnds = [ + 'forever' => (string) trans('firefly.repeat_forever'), + 'until_date' => (string) trans('firefly.repeat_until_date'), + 'times' => (string) trans('firefly.repeat_times'), + ]; + $weekendResponses = [ + RecurrenceRepetition::WEEKEND_DO_NOTHING => (string) trans('firefly.do_nothing'), + RecurrenceRepetition::WEEKEND_SKIP_CREATION => (string) trans('firefly.skip_transaction'), + RecurrenceRepetition::WEEKEND_TO_FRIDAY => (string) trans('firefly.jump_to_friday'), + RecurrenceRepetition::WEEKEND_TO_MONDAY => (string) trans('firefly.jump_to_monday'), + ]; + + /** @var Transaction $source */ + /** @var Transaction $dest */ + + // fill prefilled with journal info + $type = strtolower($journal->transactionType->type); + + $source = $journal->transactions()->where('amount', '<', 0)->first(); + $dest = $journal->transactions()->where('amount', '>', 0)->first(); + $category = $journal->categories()->first() ? $journal->categories()->first()->name : ''; + $budget = $journal->budgets()->first() ? $journal->budgets()->first()->id : 0; + $hasOldInput = null !== $request->old('_token'); // flash some data + $preFilled = []; + if (true === $hasOldInput) { + $preFilled = [ + 'title' => $request->old('title'), + 'transaction_description' => $request->old('description'), + 'transaction_currency_id' => $request->old('transaction_currency_id'), + 'amount' => $request->old('amount'), + 'foreign_currency_id' => $request->old('foreign_currency_id'), + 'foreign_amount' => $request->old('foreign_amount'), + 'source_id' => $request->old('source_id'), + 'deposit_source_id' => $request->old('deposit_source_id'), + 'destination_id' => $request->old('destination_id'), + 'withdrawal_destination_id' => $request->old('withdrawal_destination_id'), + 'first_date' => $request->old('first_date'), + 'transaction_type' => $request->old('transaction_type'), + 'category' => $request->old('category'), + 'budget_id' => $request->old('budget_id'), + 'active' => (bool) $request->old('active'), + 'apply_rules' => (bool) $request->old('apply_rules'), + ]; + } + if (false === $hasOldInput) { + $preFilled = [ + 'title' => $journal->description, + 'transaction_description' => $journal->description, + 'transaction_currency_id' => $journal->transaction_currency_id, + 'amount' => $dest->amount, + 'foreign_currency_id' => $dest->foreign_currency_id, + 'foreign_amount' => $dest->foreign_amount, + 'source_id' => $source->account_id, + 'deposit_source_id' => $source->account_id, + 'destination_id' => $dest->account_id, + 'withdrawal_destination_id' => $dest->account_id, + 'first_date' => $tomorrow->format('Y-m-d'), + 'transaction_type' => $type, + 'category' => $category, + 'budget_id' => $budget, + 'active' => true, + 'apply_rules' => true, + ]; + } + + + $request->session()->flash('preFilled', $preFilled); + + return view( + 'recurring.create', + compact('tomorrow', 'oldRepetitionType', 'weekendResponses', 'preFilled', 'repetitionEnds', 'defaultCurrency', 'budgets') + ); + } + /** * Create a new recurring transaction. * diff --git a/app/Http/Controllers/Report/CategoryController.php b/app/Http/Controllers/Report/CategoryController.php index 8219a43d44..c3e633f188 100644 --- a/app/Http/Controllers/Report/CategoryController.php +++ b/app/Http/Controllers/Report/CategoryController.php @@ -677,12 +677,9 @@ class CategoryController extends Controller $cache->addProperty('category-report'); $cache->addProperty($accounts->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore + // return $cache->get(); // @codeCoverageIgnore } - /** @var CategoryRepositoryInterface $repository */ - $repository = app(CategoryRepositoryInterface::class); - /** @var OperationsRepositoryInterface $opsRepository */ $opsRepository = app(OperationsRepositoryInterface::class); diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 992579db8a..d7983a5c71 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -88,7 +88,6 @@ class SearchController extends Controller $searcher->parseQuery($fullQuery); $searcher->setPage($page); - $searcher->setLimit((int) config('firefly.search_result_limit')); $groups = $searcher->searchTransactions(); $hasPages = $groups->hasPages(); $searchTime = round($searcher->searchTime(), 3); // in seconds diff --git a/app/Http/Controllers/System/InstallController.php b/app/Http/Controllers/System/InstallController.php index 5117900a1c..028196155a 100644 --- a/app/Http/Controllers/System/InstallController.php +++ b/app/Http/Controllers/System/InstallController.php @@ -71,7 +71,7 @@ class InstallController extends Controller 'firefly-iii:restore-oauth-keys' => [], 'generate-keys' => [], // an exception :( - // there are 14 upgrade commands. + // upgrade commands 'firefly-iii:transaction-identifiers' => [], 'firefly-iii:migrate-to-groups' => [], 'firefly-iii:account-currencies' => [], @@ -87,7 +87,7 @@ class InstallController extends Controller 'firefly-iii:migrate-recurrence-meta' => [], 'firefly-iii:migrate-tag-locations' => [], - // there are 16 verify commands. + // verify commands 'firefly-iii:fix-piggies' => [], 'firefly-iii:create-link-types' => [], 'firefly-iii:create-access-tokens' => [], @@ -100,11 +100,13 @@ class InstallController extends Controller 'firefly-iii:delete-empty-journals' => [], 'firefly-iii:delete-empty-groups' => [], 'firefly-iii:fix-account-types' => [], + 'firefly-iii:fix-account-order' => [], 'firefly-iii:rename-meta-fields' => [], 'firefly-iii:fix-ob-currencies' => [], 'firefly-iii:fix-long-descriptions' => [], 'firefly-iii:fix-recurring-transactions' => [], 'firefly-iii:unify-group-accounts' => [], + 'firefly-iii:fix-transaction-types' => [], // final command to set latest version in DB 'firefly-iii:set-latest-version' => ['--james-is-cool' => true], diff --git a/app/Http/Controllers/Transaction/BulkController.php b/app/Http/Controllers/Transaction/BulkController.php index bbf72cc08c..75c4052111 100644 --- a/app/Http/Controllers/Transaction/BulkController.php +++ b/app/Http/Controllers/Transaction/BulkController.php @@ -55,7 +55,7 @@ class BulkController extends Controller $this->middleware( function ($request, $next) { $this->repository = app(JournalRepositoryInterface::class); - app('view')->share('title', (string) trans('firefly.transactions')); + app('view')->share('title', (string)trans('firefly.transactions')); app('view')->share('mainTitleIcon', 'fa-exchange'); return $next($request); @@ -74,7 +74,7 @@ class BulkController extends Controller */ public function edit(array $journals) { - $subTitle = (string) trans('firefly.mass_bulk_journals'); + $subTitle = (string)trans('firefly.mass_bulk_journals'); $this->rememberPreviousUri('transactions.bulk-edit.uri'); @@ -100,17 +100,18 @@ class BulkController extends Controller { $journalIds = $request->get('journals'); $journalIds = is_array($journalIds) ? $journalIds : []; - $ignoreCategory = 1 === (int) $request->get('ignore_category'); - $ignoreBudget = 1 === (int) $request->get('ignore_budget'); - $ignoreTags = 1 === (int) $request->get('ignore_tags'); - $count = 0; + $ignoreCategory = 1 === (int)$request->get('ignore_category'); + $ignoreBudget = 1 === (int)$request->get('ignore_budget'); + $tagsAction = $request->get('tags_action'); + + $count = 0; foreach ($journalIds as $journalId) { - $journalId = (int) $journalId; + $journalId = (int)$journalId; $journal = $this->repository->findNull($journalId); if (null !== $journal) { $resultA = $this->updateJournalBudget($journal, $ignoreBudget, $request->integer('budget_id')); - $resultB = $this->updateJournalTags($journal, $ignoreTags, explode(',', $request->string('tags'))); + $resultB = $this->updateJournalTags($journal, $tagsAction, explode(',', $request->string('tags'))); $resultC = $this->updateJournalCategory($journal, $ignoreCategory, $request->string('category')); if ($resultA || $resultB || $resultC) { $count++; @@ -118,7 +119,7 @@ class BulkController extends Controller } } app('preferences')->mark(); - $request->session()->flash('success', (string) trans_choice('firefly.mass_edited_transactions_success', $count)); + $request->session()->flash('success', (string)trans_choice('firefly.mass_edited_transactions_success', $count)); // redirect to previous URL: return redirect($this->getPreviousUri('transactions.bulk-edit.uri')); @@ -162,20 +163,22 @@ class BulkController extends Controller /** * @param TransactionJournal $journal - * @param bool $ignoreUpdate + * @param string $action * @param array $tags * * @return bool */ - private function updateJournalTags(TransactionJournal $journal, bool $ignoreUpdate, array $tags): bool + private function updateJournalTags(TransactionJournal $journal, string $action, array $tags): bool { - - if (true === $ignoreUpdate) { - return false; + if ('do_replace' === $action) { + Log::debug(sprintf('Set tags to %s', implode(',', $tags))); + $this->repository->updateTags($journal, $tags); + } + if ('do_append' === $action) { + $existing = $journal->tags->pluck('tag')->toArray(); + $new = array_unique(array_merge($tags, $existing)); + $this->repository->updateTags($journal, $new); } - Log::debug(sprintf('Set tags to %s', implode(',', $tags))); - $this->repository->updateTags($journal, $tags); - return true; } } diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index 42c0cee6a2..ae6556038a 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -260,7 +260,7 @@ class ConvertController extends Controller // group accounts: /** @var Account $account */ foreach ($accountList as $account) { - $balance = app('steam')->balance($account, new Carbon); + $balance = app('steam')->balance($account); $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; $role = (string) $repository->getMetaValue($account, 'account_role'); if ('' === $role) { @@ -289,7 +289,7 @@ class ConvertController extends Controller // group accounts: /** @var Account $account */ foreach ($accountList as $account) { - $balance = app('steam')->balance($account, new Carbon); + $balance = app('steam')->balance($account); $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; $role = 'l_' . $account->accountType->type; $key = (string) trans('firefly.opt_group_' . $role); diff --git a/app/Http/Controllers/Transaction/EditController.php b/app/Http/Controllers/Transaction/EditController.php index cf9a413515..fc4fdea4dc 100644 --- a/app/Http/Controllers/Transaction/EditController.php +++ b/app/Http/Controllers/Transaction/EditController.php @@ -77,15 +77,25 @@ class EditController extends Controller $repository = app(AccountRepositoryInterface::class); $allowedOpposingTypes = config('firefly.allowed_opposing_types'); $accountToTypes = config('firefly.account_to_transaction'); - $defaultCurrency = app('amount')->getDefaultCurrency(); - $cash = $repository->getCashAccount(); - $previousUri = $this->rememberPreviousUri('transactions.edit.uri'); - $parts = parse_url($previousUri); - $search = sprintf('?%s', $parts['query'] ?? ''); - $previousUri = str_replace($search, '', $previousUri); + $expectedSourceTypes = config('firefly.expected_source_types'); + $allowedSourceDests = config('firefly.source_dests'); + // + + $defaultCurrency = app('amount')->getDefaultCurrency(); + $cash = $repository->getCashAccount(); + $previousUri = $this->rememberPreviousUri('transactions.edit.uri'); + $parts = parse_url($previousUri); + $search = sprintf('?%s', $parts['query'] ?? ''); + $previousUri = str_replace($search, '', $previousUri); - return view('transactions.edit', compact('cash', 'transactionGroup', 'allowedOpposingTypes', 'accountToTypes', 'defaultCurrency', 'previousUri')); + return view( + 'transactions.edit', + compact( + 'cash', 'allowedSourceDests', 'expectedSourceTypes', 'transactionGroup', 'allowedOpposingTypes', 'accountToTypes', 'defaultCurrency', + 'previousUri' + ) + ); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b225877efd..5450788a8a 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -90,8 +90,8 @@ class Kernel extends HttpKernel StartFireflySession::class, ShareErrorsFromSession::class, VerifyCsrfToken::class, - CreateFreshApiToken::class, AuthenticateSession::class, + CreateFreshApiToken::class, ], // only the basic variable binders. @@ -155,8 +155,8 @@ class Kernel extends HttpKernel MFAMiddleware::class, Range::class, Binder::class, - CreateFreshApiToken::class, InterestingMessage::class, + CreateFreshApiToken::class, ], // MUST be logged in // MUST have 2fa diff --git a/app/Http/Requests/AccountFormRequest.php b/app/Http/Requests/AccountFormRequest.php index 0ad9dd03e2..3dab35d4c3 100644 --- a/app/Http/Requests/AccountFormRequest.php +++ b/app/Http/Requests/AccountFormRequest.php @@ -25,12 +25,16 @@ namespace FireflyIII\Http\Requests; use FireflyIII\Models\Account; use FireflyIII\Models\Location; use FireflyIII\Rules\UniqueIban; +use FireflyIII\Support\Request\AppendsLocationData; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class AccountFormRequest. */ -class AccountFormRequest extends Request +class AccountFormRequest extends FormRequest { + use ConvertsDataTypes, AppendsLocationData; /** * Verify the request. * diff --git a/app/Http/Requests/AttachmentFormRequest.php b/app/Http/Requests/AttachmentFormRequest.php index 73fa661d8a..5b62e3bf48 100644 --- a/app/Http/Requests/AttachmentFormRequest.php +++ b/app/Http/Requests/AttachmentFormRequest.php @@ -22,13 +22,18 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** * Class AttachmentFormRequest. * * @codeCoverageIgnore */ -class AttachmentFormRequest extends Request +class AttachmentFormRequest extends FormRequest { + use ConvertsDataTypes; + /** * Verify the request. * diff --git a/app/Http/Requests/BillStoreRequest.php b/app/Http/Requests/BillStoreRequest.php index b727f8e1ac..4db4af0805 100644 --- a/app/Http/Requests/BillStoreRequest.php +++ b/app/Http/Requests/BillStoreRequest.php @@ -22,11 +22,15 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** * Class BillStoreRequest. */ -class BillStoreRequest extends Request +class BillStoreRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/BillUpdateRequest.php b/app/Http/Requests/BillUpdateRequest.php index ef1a683269..e0e713bb35 100644 --- a/app/Http/Requests/BillUpdateRequest.php +++ b/app/Http/Requests/BillUpdateRequest.php @@ -23,12 +23,15 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; use FireflyIII\Models\Bill; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class BillUpdateRequest. */ -class BillUpdateRequest extends Request +class BillUpdateRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/BudgetFormStoreRequest.php b/app/Http/Requests/BudgetFormStoreRequest.php index 9565726f72..22aa0733b9 100644 --- a/app/Http/Requests/BudgetFormStoreRequest.php +++ b/app/Http/Requests/BudgetFormStoreRequest.php @@ -22,14 +22,18 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use FireflyIII\Validation\AutoBudget\ValidatesAutoBudgetRequest; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; /** * @codeCoverageIgnore * Class BudgetFormStoreRequest */ -class BudgetFormStoreRequest extends Request +class BudgetFormStoreRequest extends FormRequest { + use ConvertsDataTypes, ValidatesAutoBudgetRequest; /** * Verify the request. * diff --git a/app/Http/Requests/BudgetFormUpdateRequest.php b/app/Http/Requests/BudgetFormUpdateRequest.php index 13b7defcf9..64dac08cf4 100644 --- a/app/Http/Requests/BudgetFormUpdateRequest.php +++ b/app/Http/Requests/BudgetFormUpdateRequest.php @@ -23,14 +23,18 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; use FireflyIII\Models\Budget; +use FireflyIII\Support\Request\ConvertsDataTypes; +use FireflyIII\Validation\AutoBudget\ValidatesAutoBudgetRequest; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; /** * @codeCoverageIgnore * Class BudgetFormUpdateRequest */ -class BudgetFormUpdateRequest extends Request +class BudgetFormUpdateRequest extends FormRequest { + use ConvertsDataTypes, ValidatesAutoBudgetRequest; /** * Verify the request. * diff --git a/app/Http/Requests/BudgetIncomeRequest.php b/app/Http/Requests/BudgetIncomeRequest.php index 93f1821125..eae41b6006 100644 --- a/app/Http/Requests/BudgetIncomeRequest.php +++ b/app/Http/Requests/BudgetIncomeRequest.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class BudgetIncomeRequest. * * @codeCoverageIgnore */ -class BudgetIncomeRequest extends Request +class BudgetIncomeRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/BulkEditJournalRequest.php b/app/Http/Requests/BulkEditJournalRequest.php index 6fe81cd072..0c674d49e3 100644 --- a/app/Http/Requests/BulkEditJournalRequest.php +++ b/app/Http/Requests/BulkEditJournalRequest.php @@ -22,11 +22,15 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** * Class MassEditBulkJournalRequest. */ -class BulkEditJournalRequest extends Request +class BulkEditJournalRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * @@ -48,7 +52,8 @@ class BulkEditJournalRequest extends Request // fixed return [ - 'journals.*' => 'required|belongsToUser:transaction_journals,id', + 'journals.*' => 'required|belongsToUser:transaction_journals,id', + 'tags_action' => 'in:no_nothing,do_replace,do_append', ]; } } diff --git a/app/Http/Requests/CategoryFormRequest.php b/app/Http/Requests/CategoryFormRequest.php index d6bb06d611..54b92655c2 100644 --- a/app/Http/Requests/CategoryFormRequest.php +++ b/app/Http/Requests/CategoryFormRequest.php @@ -23,12 +23,15 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; use FireflyIII\Models\Category; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class CategoryFormRequest. */ -class CategoryFormRequest extends Request +class CategoryFormRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/ConfigurationRequest.php b/app/Http/Requests/ConfigurationRequest.php index e72bc3548f..166170378b 100644 --- a/app/Http/Requests/ConfigurationRequest.php +++ b/app/Http/Requests/ConfigurationRequest.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class ConfigurationRequest. * * @codeCoverageIgnore */ -class ConfigurationRequest extends Request +class ConfigurationRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/CurrencyFormRequest.php b/app/Http/Requests/CurrencyFormRequest.php index f763efdf08..fce35b6705 100644 --- a/app/Http/Requests/CurrencyFormRequest.php +++ b/app/Http/Requests/CurrencyFormRequest.php @@ -23,12 +23,15 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class CurrencyFormRequest. */ -class CurrencyFormRequest extends Request +class CurrencyFormRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/DeleteAccountFormRequest.php b/app/Http/Requests/DeleteAccountFormRequest.php index a110200f1e..0b3ffd7e4b 100644 --- a/app/Http/Requests/DeleteAccountFormRequest.php +++ b/app/Http/Requests/DeleteAccountFormRequest.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class DeleteAccountFormRequest. * * @codeCoverageIgnore */ -class DeleteAccountFormRequest extends Request +class DeleteAccountFormRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/EmailFormRequest.php b/app/Http/Requests/EmailFormRequest.php index b0e04e164e..c10d4a2c8c 100644 --- a/app/Http/Requests/EmailFormRequest.php +++ b/app/Http/Requests/EmailFormRequest.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class EmailFormRequest. * * @codeCoverageIgnore */ -class EmailFormRequest extends Request +class EmailFormRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/JournalLinkRequest.php b/app/Http/Requests/JournalLinkRequest.php index 7d5de5438a..3536814439 100644 --- a/app/Http/Requests/JournalLinkRequest.php +++ b/app/Http/Requests/JournalLinkRequest.php @@ -24,12 +24,15 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; use FireflyIII\Models\LinkType; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class JournalLink. */ -class JournalLinkRequest extends Request +class JournalLinkRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/LinkTypeFormRequest.php b/app/Http/Requests/LinkTypeFormRequest.php index 03229dae86..89a50fd037 100644 --- a/app/Http/Requests/LinkTypeFormRequest.php +++ b/app/Http/Requests/LinkTypeFormRequest.php @@ -22,10 +22,12 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class LinkTypeFormRequest. */ -class LinkTypeFormRequest extends Request +class LinkTypeFormRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/MassDeleteJournalRequest.php b/app/Http/Requests/MassDeleteJournalRequest.php index 7d026a009e..640ea76d8d 100644 --- a/app/Http/Requests/MassDeleteJournalRequest.php +++ b/app/Http/Requests/MassDeleteJournalRequest.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class MassDeleteJournalRequest. * * @codeCoverageIgnore */ -class MassDeleteJournalRequest extends Request +class MassDeleteJournalRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/MassEditJournalRequest.php b/app/Http/Requests/MassEditJournalRequest.php index c1ac043adc..c3c54ea8eb 100644 --- a/app/Http/Requests/MassEditJournalRequest.php +++ b/app/Http/Requests/MassEditJournalRequest.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class MassEditJournalRequest. * * @codeCoverageIgnore */ -class MassEditJournalRequest extends Request +class MassEditJournalRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/NewUserFormRequest.php b/app/Http/Requests/NewUserFormRequest.php index df31e5beb7..9c75c72628 100644 --- a/app/Http/Requests/NewUserFormRequest.php +++ b/app/Http/Requests/NewUserFormRequest.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class NewUserFormRequest. * * @codeCoverageIgnore */ -class NewUserFormRequest extends Request +class NewUserFormRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/ObjectGroupFormRequest.php b/app/Http/Requests/ObjectGroupFormRequest.php index 959bc1afaf..63d11e1575 100644 --- a/app/Http/Requests/ObjectGroupFormRequest.php +++ b/app/Http/Requests/ObjectGroupFormRequest.php @@ -23,11 +23,12 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; use FireflyIII\Models\ObjectGroup; +use Illuminate\Foundation\Http\FormRequest; /** * Class ObjectGroupFormRequest. */ -class ObjectGroupFormRequest extends Request +class ObjectGroupFormRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/PiggyBankStoreRequest.php b/app/Http/Requests/PiggyBankStoreRequest.php index 89af1191ee..0df3c35fb2 100644 --- a/app/Http/Requests/PiggyBankStoreRequest.php +++ b/app/Http/Requests/PiggyBankStoreRequest.php @@ -22,11 +22,15 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** * Class PiggyBankStoreRequest. */ -class PiggyBankStoreRequest extends Request +class PiggyBankStoreRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/PiggyBankUpdateRequest.php b/app/Http/Requests/PiggyBankUpdateRequest.php index c3dc8e9be0..b606787d62 100644 --- a/app/Http/Requests/PiggyBankUpdateRequest.php +++ b/app/Http/Requests/PiggyBankUpdateRequest.php @@ -23,12 +23,15 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; use FireflyIII\Models\PiggyBank; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class PiggyBankFormRequest. */ -class PiggyBankUpdateRequest extends Request +class PiggyBankUpdateRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/ProfileFormRequest.php b/app/Http/Requests/ProfileFormRequest.php index bc0ef0f7dc..4bb971a6f4 100644 --- a/app/Http/Requests/ProfileFormRequest.php +++ b/app/Http/Requests/ProfileFormRequest.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class ProfileFormRequest. * * @codeCoverageIgnore */ -class ProfileFormRequest extends Request +class ProfileFormRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/ReconciliationStoreRequest.php b/app/Http/Requests/ReconciliationStoreRequest.php index c2dead3e4e..47b3df082f 100644 --- a/app/Http/Requests/ReconciliationStoreRequest.php +++ b/app/Http/Requests/ReconciliationStoreRequest.php @@ -24,13 +24,16 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; use FireflyIII\Rules\ValidJournals; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; use Log; /** * Class ReconciliationStoreRequest */ -class ReconciliationStoreRequest extends Request +class ReconciliationStoreRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/RecurrenceFormRequest.php b/app/Http/Requests/RecurrenceFormRequest.php index 40b729e24e..180fe2fe86 100644 --- a/app/Http/Requests/RecurrenceFormRequest.php +++ b/app/Http/Requests/RecurrenceFormRequest.php @@ -29,16 +29,18 @@ use FireflyIII\Models\Recurrence; use FireflyIII\Models\TransactionType; use FireflyIII\Rules\ValidRecurrenceRepetitionType; use FireflyIII\Rules\ValidRecurrenceRepetitionValue; +use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Validation\AccountValidator; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Validator; use Log; /** * Class RecurrenceFormRequest */ -class RecurrenceFormRequest extends Request +class RecurrenceFormRequest extends FormRequest { - + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/ReportFormRequest.php b/app/Http/Requests/ReportFormRequest.php index 150698d1e2..ef13ed9048 100644 --- a/app/Http/Requests/ReportFormRequest.php +++ b/app/Http/Requests/ReportFormRequest.php @@ -29,13 +29,14 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Collection; use Log; /** * Class CategoryFormRequest. */ -class ReportFormRequest extends Request +class ReportFormRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php deleted file mode 100644 index ca6e9227c1..0000000000 --- a/app/Http/Requests/Request.php +++ /dev/null @@ -1,443 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Http\Requests; - -use Carbon\Carbon; -use Carbon\Exceptions\InvalidDateException; -use Exception; -use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Validator; -use Log; - -/** - * Class Request. - * - * @codeCoverageIgnore - * - */ -class Request extends FormRequest -{ - /** - * @param $array - * - * @return array|null - */ - public function arrayFromValue($array): ?array - { - if (is_array($array)) { - return $array; - } - if (null === $array) { - return null; - } - if (is_string($array)) { - return explode(',', $array); - } - - return null; - } - - /** - * @param string $value - * - * @return bool - */ - public function convertBoolean(?string $value): bool - { - if (null === $value) { - return false; - } - if ('true' === $value) { - return true; - } - if ('yes' === $value) { - return true; - } - if (1 === $value) { - return true; - } - if ('1' === $value) { - return true; - } - if (true === $value) { - return true; - } - - return false; - } - - /** - * @param string|null $string - * - * @return Carbon|null - */ - public function dateFromValue(?string $string): ?Carbon - { - if (null === $string) { - return null; - } - if ('' === $string) { - return null; - } - try { - $carbon = new Carbon($string); - } catch (Exception $e) { - Log::debug(sprintf('Invalid date: %s: %s', $string, $e->getMessage())); - - return null; - } - - return $carbon; - } - - /** - * Return floating value. - * - * @param string $field - * - * @return float|null - */ - public function float(string $field): ?float - { - $res = $this->get($field); - if (null === $res) { - return null; - } - - return (float) $res; - } - - /** - * Return integer value. - * - * @param string $field - * - * @return int - */ - public function integer(string $field): int - { - return (int) $this->get($field); - } - - /** - * Parse to integer - * - * @param string|null $string - * - * @return int|null - */ - public function integerFromValue(?string $string): ?int - { - if (null === $string) { - return null; - } - if ('' === $string) { - return null; - } - - return (int) $string; - } - - /** - * Return string value, but keep newlines. - * - * @param string $field - * - * @return string - */ - public function nlString(string $field): string - { - return app('steam')->nlCleanString((string) ($this->get($field) ?? '')); - } - - /** - * Parse and clean a string, but keep the newlines. - * - * @param string|null $string - * - * @return string|null - */ - public function nlStringFromValue(?string $string): ?string - { - if (null === $string) { - return null; - } - $result = app('steam')->nlCleanString($string); - - return '' === $result ? null : $result; - - } - - /** - * Return integer value, or NULL when it's not set. - * - * @param string $field - * - * @return int|null - */ - public function nullableInteger(string $field): ?int - { - if (!$this->has($field)) { - return null; - } - - $value = (string) $this->get($field); - if ('' === $value) { - return null; - } - - return (int) $value; - } - - /** - * Return string value, but keep newlines, or NULL if empty. - * - * @param string $field - * - * @return string - */ - public function nullableNlString(string $field): ?string - { - if (!$this->has($field)) { - return null; - } - - return app('steam')->nlCleanString((string) ($this->get($field) ?? '')); - } - - /** - * Return string value, or NULL if empty. - * - * @param string $field - * - * @return string|null - */ - public function nullableString(string $field): ?string - { - if (!$this->has($field)) { - return null; - } - $res = trim(app('steam')->cleanString((string) ($this->get($field) ?? ''))); - if ('' === $res) { - return null; - } - - return $res; - } - - /** - * Return string value. - * - * @param string $field - * - * @return string - */ - public function string(string $field): string - { - return app('steam')->cleanString((string) ($this->get($field) ?? '')); - } - - /** - * Parse and clean a string. - * - * @param string|null $string - * - * @return string|null - */ - public function stringFromValue(?string $string): ?string - { - if (null === $string) { - return null; - } - $result = app('steam')->cleanString($string); - - return '' === $result ? null : $result; - - } - - /** - * Read the submitted Request data and add new or updated Location data to the array. - * - * @param array $data - * - * @param string|null $prefix - * - * @return array - */ - protected function appendLocationData(array $data, ?string $prefix): array - { - Log::debug(sprintf('Now in appendLocationData("%s")', $prefix), $data); - $data['store_location'] = false; - $data['update_location'] = false; - $data['longitude'] = null; - $data['latitude'] = null; - $data['zoom_level'] = null; - - $longitudeKey = $this->getLocationKey($prefix, 'longitude'); - $latitudeKey = $this->getLocationKey($prefix, 'latitude'); - $zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level'); - $hasLocationKey = $this->getLocationKey($prefix, 'has_location'); - $hasLocation = $this->boolean($hasLocationKey); - - // for a POST (store), all fields must be present and accounted for: - if ( - ('POST' === $this->method() && $this->routeIs('*.store')) - && ($this->has($longitudeKey) && $this->has($latitudeKey) && $this->has($zoomLevelKey)) - ) { - Log::debug('Method is POST and all fields present.'); - $data['store_location'] = $this->boolean($hasLocationKey); - $data['longitude'] = $this->nullableString($longitudeKey); - $data['latitude'] = $this->nullableString($latitudeKey); - $data['zoom_level'] = $this->nullableString($zoomLevelKey); - } - if ( - ($this->has($longitudeKey) && $this->has($latitudeKey) && $this->has($zoomLevelKey)) - && ( - ('PUT' === $this->method() && $this->routeIs('*.update')) - || ('POST' === $this->method() && $this->routeIs('*.update')) - ) - ) { - Log::debug('Method is PUT and all fields present.'); - $data['update_location'] = true; - $data['longitude'] = $this->nullableString($longitudeKey); - $data['latitude'] = $this->nullableString($latitudeKey); - $data['zoom_level'] = $this->nullableString($zoomLevelKey); - } - if (false === $hasLocation || null === $data['longitude'] || null === $data['latitude'] || null === $data['zoom_level']) { - Log::debug('One of the fields is NULL or hasLocation is false, wont save.'); - $data['store_location'] = false; - $data['update_location'] = true; // update is always true, but the values are null: - $data['longitude'] = null; - $data['latitude'] = null; - $data['zoom_level'] = null; - } - Log::debug(sprintf('Returning longitude: "%s", latitude: "%s", zoom level: "%s"', $data['longitude'], $data['latitude'], $data['zoom_level'])); - - return $data; - } - - /** - * Return date or NULL. - * - * @param string $field - * - * @return Carbon|null - */ - protected function date(string $field): ?Carbon - { - $result = null; - try { - $result = $this->get($field) ? new Carbon($this->get($field)) : null; - } catch (Exception $e) { - Log::debug(sprintf('Exception when parsing date. Not interesting: %s', $e->getMessage())); - } - - return $result; - } - - /** - * Return date time or NULL. - * - * @param string $field - * - * @return Carbon|null - */ - protected function dateTime(string $field): ?Carbon - { - if (null === $this->get($field)) { - return null; - } - $value = (string) $this->get($field); - if (10 === strlen($value)) { - // probably a date format. - try { - $result = Carbon::createFromFormat('Y-m-d', $value); - } catch (InvalidDateException $e) { - Log::error(sprintf('"%s" is not a valid date: %s', $value, $e->getMessage())); - - return null; - } - - return $result; - } - // is an atom string, I hope? - try { - $result = Carbon::parse($value); - } catch (InvalidDateException $e) { - Log::error(sprintf('"%s" is not a valid date or time: %s', $value, $e->getMessage())); - - return null; - } - - return $result; - } - - /** - * @param Validator $validator - */ - protected function validateAutoBudgetAmount(Validator $validator): void - { - $data = $validator->getData(); - $type = $data['auto_budget_type'] ?? ''; - $amount = $data['auto_budget_amount'] ?? ''; - $period = (string) ($data['auto_budget_period'] ?? ''); - $currencyId = $data['auto_budget_currency_id'] ?? ''; - $currencyCode = $data['auto_budget_currency_code'] ?? ''; - if (is_numeric($type)) { - $type = (int) $type; - } - if (0 === $type || 'none' === $type || '' === $type) { - return; - } - // basic float check: - if ('' === $amount) { - $validator->errors()->add('auto_budget_amount', (string) trans('validation.amount_required_for_auto_budget')); - } - if (1 !== bccomp((string) $amount, '0')) { - $validator->errors()->add('auto_budget_amount', (string) trans('validation.auto_budget_amount_positive')); - } - if ('' === $period) { - $validator->errors()->add('auto_budget_period', (string) trans('validation.auto_budget_period_mandatory')); - } - if ('' === $currencyCode && '' === $currencyId) { - $validator->errors()->add('auto_budget_amount', (string) trans('validation.require_currency_info')); - } - } - - /** - * @param string|null $prefix - * @param string $key - * - * @return string - */ - private function getLocationKey(?string $prefix, string $key): string - { - if (null === $prefix) { - return $key; - } - - return sprintf('%s_%s', $prefix, $key); - } - - -} diff --git a/app/Http/Requests/RuleFormRequest.php b/app/Http/Requests/RuleFormRequest.php index ad0ee86e06..ba37873a10 100644 --- a/app/Http/Requests/RuleFormRequest.php +++ b/app/Http/Requests/RuleFormRequest.php @@ -23,12 +23,15 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; use FireflyIII\Models\Rule; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class RuleFormRequest. */ -class RuleFormRequest extends Request +class RuleFormRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/RuleGroupFormRequest.php b/app/Http/Requests/RuleGroupFormRequest.php index 2d79cc0b4b..9aaf2095b8 100644 --- a/app/Http/Requests/RuleGroupFormRequest.php +++ b/app/Http/Requests/RuleGroupFormRequest.php @@ -24,12 +24,15 @@ namespace FireflyIII\Http\Requests; use FireflyIII\Models\RuleGroup; use FireflyIII\Rules\IsBoolean; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class RuleGroupFormRequest. */ -class RuleGroupFormRequest extends Request +class RuleGroupFormRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/SelectTransactionsRequest.php b/app/Http/Requests/SelectTransactionsRequest.php index 59ef94d4fe..c172b2ed7b 100644 --- a/app/Http/Requests/SelectTransactionsRequest.php +++ b/app/Http/Requests/SelectTransactionsRequest.php @@ -23,13 +23,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; use Carbon\Carbon; +use Illuminate\Foundation\Http\FormRequest; /** * Class SelectTransactionsRequest. * * @codeCoverageIgnore */ -class SelectTransactionsRequest extends Request +class SelectTransactionsRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/TagFormRequest.php b/app/Http/Requests/TagFormRequest.php index e6d1180b97..91de7bb05f 100644 --- a/app/Http/Requests/TagFormRequest.php +++ b/app/Http/Requests/TagFormRequest.php @@ -24,12 +24,16 @@ namespace FireflyIII\Http\Requests; use FireflyIII\Models\Location; use FireflyIII\Models\Tag; +use FireflyIII\Support\Request\AppendsLocationData; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; /** * Class TagFormRequest. */ -class TagFormRequest extends Request +class TagFormRequest extends FormRequest { + use ConvertsDataTypes, AppendsLocationData; /** * Verify the request. * diff --git a/app/Http/Requests/TestRuleFormRequest.php b/app/Http/Requests/TestRuleFormRequest.php index 600c7c57b5..ed3786e719 100644 --- a/app/Http/Requests/TestRuleFormRequest.php +++ b/app/Http/Requests/TestRuleFormRequest.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class TestRuleFormRequest. * * @codeCoverageIgnore */ -class TestRuleFormRequest extends Request +class TestRuleFormRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/TokenFormRequest.php b/app/Http/Requests/TokenFormRequest.php index fd1f2987a2..9b09546552 100644 --- a/app/Http/Requests/TokenFormRequest.php +++ b/app/Http/Requests/TokenFormRequest.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class TokenFormRequest. * * @codeCoverageIgnore */ -class TokenFormRequest extends Request +class TokenFormRequest extends FormRequest { /** * Verify the request. diff --git a/app/Http/Requests/UserFormRequest.php b/app/Http/Requests/UserFormRequest.php index abab3af725..1a34382cff 100644 --- a/app/Http/Requests/UserFormRequest.php +++ b/app/Http/Requests/UserFormRequest.php @@ -22,13 +22,17 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + /** * Class UserFormRequest. * * @codeCoverageIgnore */ -class UserFormRequest extends Request +class UserFormRequest extends FormRequest { + use ConvertsDataTypes; /** * Verify the request. * diff --git a/app/Http/Requests/UserRegistrationRequest.php b/app/Http/Requests/UserRegistrationRequest.php index 8e46d6f2cd..5dead1b879 100644 --- a/app/Http/Requests/UserRegistrationRequest.php +++ b/app/Http/Requests/UserRegistrationRequest.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use Illuminate\Foundation\Http\FormRequest; + /** * Class UserRegistrationRequest. * * @codeCoverageIgnore */ -class UserRegistrationRequest extends Request +class UserRegistrationRequest extends FormRequest { /** * Verify the request. diff --git a/app/Jobs/CreateAutoBudgetLimits.php b/app/Jobs/CreateAutoBudgetLimits.php index fc33cf2ba9..b2cd983b75 100644 --- a/app/Jobs/CreateAutoBudgetLimits.php +++ b/app/Jobs/CreateAutoBudgetLimits.php @@ -151,7 +151,7 @@ class CreateAutoBudgetLimits implements ShouldQueue $repository = app(OperationsRepositoryInterface::class); $repository->setUser($autoBudget->budget->user); $spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection([$autoBudget->budget]), $autoBudget->transactionCurrency); - $currencyId = (int) $autoBudget->transaction_currency_id; + $currencyId = (int)$autoBudget->transaction_currency_id; $spentAmount = $spent[$currencyId]['sum'] ?? '0'; Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount)); @@ -200,6 +200,11 @@ class CreateAutoBudgetLimits implements ShouldQueue */ private function handleAutoBudget(AutoBudget $autoBudget): void { + if (null === $autoBudget->budget) { + Log::info(sprintf('Auto budget #%d is associated with a deleted budget.', $autoBudget->id)); + $autoBudget->delete(); + return; + } if (!$this->isMagicDay($autoBudget)) { Log::info( sprintf( @@ -255,8 +260,8 @@ class CreateAutoBudgetLimits implements ShouldQueue /** * @param AutoBudget $autoBudget * - * @throws FireflyException * @return bool + * @throws FireflyException */ private function isMagicDay(AutoBudget $autoBudget): bool { diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 967df4c2f2..374e016530 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -53,15 +53,15 @@ class CreateRecurringTransactions implements ShouldQueue use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** @var int Transaction groups created */ - public $created; + public int $created; /** @var int Number of recurrences actually fired */ - public $executed; + public int $executed; /** @var int Number of recurrences submitted */ - public $submitted; + public int $submitted; /** @var Carbon The current date */ - private $date; + private Carbon $date; /** @var bool Force the transaction to be created no matter what. */ - private $force; + private bool $force; /** @var TransactionGroupRepositoryInterface */ private $groupRepository; /** @var JournalRepositoryInterface Journal repository */ @@ -219,19 +219,23 @@ class CreateRecurringTransactions implements ShouldQueue /** * Get transaction information from a recurring transaction. * - * @param Recurrence $recurrence - * @param Carbon $date + * @param Recurrence $recurrence + * @param RecurrenceRepetition $repetition + * @param Carbon $date * * @return array * */ - private function getTransactionData(Recurrence $recurrence, Carbon $date): array + private function getTransactionData(Recurrence $recurrence, RecurrenceRepetition $repetition, Carbon $date): array { + // total transactions expected for this recurrence: + $total = $this->repository->totalTransactions($recurrence, $repetition); + $count = $this->repository->getJournalCount($recurrence) + 1; $transactions = $recurrence->recurrenceTransactions()->get(); $return = []; /** @var RecurrenceTransaction $transaction */ foreach ($transactions as $index => $transaction) { - $single = [ + $single = [ 'type' => strtolower($recurrence->transactionType->type), 'date' => $date, 'user' => $recurrence->user_id, @@ -260,6 +264,8 @@ class CreateRecurringTransactions implements ShouldQueue 'piggy_bank_name' => null, 'bill_id' => null, 'bill_name' => null, + 'recurrence_total' => $total, + 'recurrence_count' => $count, ]; $return[] = $single; } @@ -268,17 +274,18 @@ class CreateRecurringTransactions implements ShouldQueue } /** - * @param Recurrence $recurrence - * @param Carbon $date + * @param Recurrence $recurrence + * @param RecurrenceRepetition $repetition + * @param Carbon $date * * @return TransactionGroup|null */ - private function handleOccurrence(Recurrence $recurrence, Carbon $date): ?TransactionGroup + private function handleOccurrence(Recurrence $recurrence, RecurrenceRepetition $repetition, Carbon $date): ?TransactionGroup { - Log::debug(sprintf('Now at date %s.', $date->format('Y-m-d'))); + #Log::debug(sprintf('Now at date %s.', $date->format('Y-m-d'))); $date->startOfDay(); if ($date->ne($this->date)) { - Log::debug(sprintf('%s is not today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d'))); + #Log::debug(sprintf('%s is not today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d'))); return null; } @@ -305,10 +312,11 @@ class CreateRecurringTransactions implements ShouldQueue $groupTitle = $first->description; // @codeCoverageIgnoreEnd } + $array = [ 'user' => $recurrence->user_id, 'group_title' => $groupTitle, - 'transactions' => $this->getTransactionData($recurrence, $date), + 'transactions' => $this->getTransactionData($recurrence, $repetition, $date), ]; /** @var TransactionGroup $group */ $group = $this->groupRepository->store($array); @@ -328,17 +336,18 @@ class CreateRecurringTransactions implements ShouldQueue /** * Check if the occurences should be executed. * - * @param Recurrence $recurrence - * @param array $occurrences + * @param Recurrence $recurrence + * @param RecurrenceRepetition $repetition + * @param array $occurrences * * @return Collection */ - private function handleOccurrences(Recurrence $recurrence, array $occurrences): Collection + private function handleOccurrences(Recurrence $recurrence, RecurrenceRepetition $repetition, array $occurrences): Collection { $collection = new Collection; /** @var Carbon $date */ foreach ($occurrences as $date) { - $result = $this->handleOccurrence($recurrence, $date); + $result = $this->handleOccurrence($recurrence, $repetition, $date); if (null !== $result) { $collection->push($result); } @@ -374,6 +383,7 @@ class CreateRecurringTransactions implements ShouldQueue $includeWeekend = clone $this->date; $includeWeekend->addDays(2); $occurrences = $this->repository->getOccurrencesInRange($repetition, $recurrence->first_date, $includeWeekend); + /* Log::debug( sprintf( 'Calculated %d occurrences between %s and %s', @@ -383,9 +393,10 @@ class CreateRecurringTransactions implements ShouldQueue ), $this->debugArray($occurrences) ); + */ unset($includeWeekend); - $result = $this->handleOccurrences($recurrence, $occurrences); + $result = $this->handleOccurrences($recurrence, $repetition, $occurrences); $collection = $collection->merge($result); } diff --git a/app/Models/Account.php b/app/Models/Account.php index 1b92e96505..6028d5920b 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -143,6 +143,13 @@ class Account extends Model throw new NotFoundHttpException; } + /** + * Get all of the tags for the post. + */ + public function objectGroups() + { + return $this->morphToMany(ObjectGroup::class, 'object_groupable'); + } /** * @codeCoverageIgnore diff --git a/app/Models/ObjectGroup.php b/app/Models/ObjectGroup.php index 0505a63e1f..89a425c2f6 100644 --- a/app/Models/ObjectGroup.php +++ b/app/Models/ObjectGroup.php @@ -85,6 +85,14 @@ class ObjectGroup extends Model return $this->morphedByMany(Bill::class, 'object_groupable'); } + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + public function accounts() + { + return $this->morphedByMany(Account::class, 'object_groupable'); + } + /** * Route binder. Converts the key in the URL to the specified object (or throw 404). * @@ -97,6 +105,7 @@ class ObjectGroup extends Model { if (auth()->check()) { $objectGroupId = (int) $value; + /** @var ObjectGroup $objectGroup */ $objectGroup = self::where('object_groups.id', $objectGroupId) ->where('object_groups.user_id', auth()->user()->id)->first(); if (null !== $objectGroup) { diff --git a/app/Providers/SearchServiceProvider.php b/app/Providers/SearchServiceProvider.php index 1dcc9b8acc..cecd94c00e 100644 --- a/app/Providers/SearchServiceProvider.php +++ b/app/Providers/SearchServiceProvider.php @@ -22,7 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Providers; -use FireflyIII\Support\Search\Search; +use FireflyIII\Support\Search\BetterQuerySearch; use FireflyIII\Support\Search\SearchInterface; use Illuminate\Foundation\Application; use Illuminate\Support\ServiceProvider; @@ -48,8 +48,8 @@ class SearchServiceProvider extends ServiceProvider $this->app->bind( SearchInterface::class, function (Application $app) { - /** @var Search $search */ - $search = app(Search::class); + /** @var BetterQuerySearch $search */ + $search = app(BetterQuerySearch::class); if ($app->auth->check()) { $search->setUser(auth()->user()); } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 32d02df306..2c21024f88 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -231,7 +231,7 @@ class AccountRepository implements AccountRepositoryInterface */ public function getAccountCurrency(Account $account): ?TransactionCurrency { - $currencyId = (int)$this->getMetaValue($account, 'currency_id'); + $currencyId = (int) $this->getMetaValue($account, 'currency_id'); if ($currencyId > 0) { return TransactionCurrency::find($currencyId); } @@ -274,12 +274,11 @@ class AccountRepository implements AccountRepositoryInterface if (count($accountIds) > 0) { $query->whereIn('accounts.id', $accountIds); } + $query->orderBy('accounts.order', 'ASC'); $query->orderBy('accounts.active', 'DESC'); $query->orderBy('accounts.name', 'ASC'); - $result = $query->get(['accounts.*']); - - return $result; + return $query->get(['accounts.*']); } /** @@ -294,12 +293,12 @@ class AccountRepository implements AccountRepositoryInterface if (count($types) > 0) { $query->accountTypeIn($types); } + $query->orderBy('accounts.order', 'ASC'); $query->orderBy('accounts.active', 'DESC'); $query->orderBy('accounts.name', 'ASC'); - $result = $query->get(['accounts.*']); + return $query->get(['accounts.*']); - return $result; } /** @@ -320,6 +319,7 @@ class AccountRepository implements AccountRepositoryInterface } $query->where('active', 1); $query->orderBy('accounts.account_type_id', 'ASC'); + $query->orderBy('accounts.order', 'ASC'); $query->orderBy('accounts.name', 'ASC'); return $query->get(['accounts.*']); @@ -351,13 +351,16 @@ class AccountRepository implements AccountRepositoryInterface */ public function getMetaValue(Account $account, string $field): ?string { - /** @var AccountMeta $meta */ - $meta = $account->accountMeta()->where('name', $field)->first(); - if (null === $meta) { + $result = $account->accountMeta->filter(function (AccountMeta $meta) use ($field) { + return strtolower($meta->name) === strtolower($field); + }); + if (0 === $result->count()) { return null; } - - return (string)$meta->data; + if (1 === $result->count()) { + return (string)$result->first()->data; + } + return null; } /** @@ -414,7 +417,7 @@ class AccountRepository implements AccountRepositoryInterface return null; } - return (string)$transaction->amount; + return (string) $transaction->amount; } /** @@ -476,13 +479,13 @@ class AccountRepository implements AccountRepositoryInterface throw new FireflyException(sprintf('%s is not an asset account.', $account->name)); } $currency = $this->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); - $name = trans('firefly.reconciliation_account_name', ['name' => $account->name, 'currency' => $currency->code]); + $name = trans('firefly.reconciliation_account_name', ['name' => $account->name, 'currency' => $currency->code]); /** @var AccountType $type */ $type = AccountType::where('type', AccountType::RECONCILIATION)->first(); $current = $this->user->accounts()->where('account_type_id', $type->id) - ->where('name', $name) - ->first(); + ->where('name', $name) + ->first(); /** @var Account $current */ if (null !== $current) { @@ -532,7 +535,7 @@ class AccountRepository implements AccountRepositoryInterface ->orderBy('transaction_journals.id', 'ASC') ->first(['transaction_journals.id']); if (null !== $first) { - return TransactionJournal::find((int)$first->id); + return TransactionJournal::find((int) $first->id); } return null; @@ -559,19 +562,22 @@ class AccountRepository implements AccountRepositoryInterface /** * @param string $query * @param array $types + * @param int $limit * * @return Collection */ - public function searchAccount(string $query, array $types): Collection + public function searchAccount(string $query, array $types, int $limit): Collection { $dbQuery = $this->user->accounts() ->where('active', 1) + ->orderBy('accounts.order', 'ASC') + ->orderBy('accounts.account_type_id', 'ASC') ->orderBy('accounts.name', 'ASC') ->with(['accountType']); if ('' !== $query) { // split query on spaces just in case: $parts = explode(' ', $query); - foreach($parts as $part) { + foreach ($parts as $part) { $search = sprintf('%%%s%%', $part); $dbQuery->where('name', 'LIKE', $search); } @@ -582,7 +588,7 @@ class AccountRepository implements AccountRepositoryInterface $dbQuery->whereIn('account_types.type', $types); } - return $dbQuery->get(['accounts.*']); + return $dbQuery->take($limit)->get(['accounts.*']); } /** @@ -642,6 +648,7 @@ class AccountRepository implements AccountRepositoryInterface } $query->where('active', 0); $query->orderBy('accounts.account_type_id', 'ASC'); + $query->orderBy('accounts.order', 'ASC'); $query->orderBy('accounts.name', 'ASC'); return $query->get(['accounts.*']); @@ -660,7 +667,7 @@ class AccountRepository implements AccountRepositoryInterface */ public function getAttachments(Account $account): Collection { - $set = $account->attachments()->get(); + $set = $account->attachments()->get(); /** @var Storage $disk */ $disk = Storage::disk('upload'); @@ -693,4 +700,20 @@ class AccountRepository implements AccountRepositoryInterface return TransactionCurrency::whereIn('id', $currencyIds)->get(); } + + /** + * @inheritDoc + */ + public function resetAccountOrder(array $types): void + { + $list = $this->getAccountsByType($types); + /** + * @var int $index + * @var Account $account + */ + foreach ($list as $index => $account) { + $account->order = $index + 1; + $account->save(); + } + } } diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index 176df182fc..999cb6c5dd 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -47,6 +47,13 @@ interface AccountRepositoryInterface */ public function count(array $types): int; + /** + * Reset order types of the mentioned accounts. + * + * @param array $types + */ + public function resetAccountOrder(array $types): void; + /** * @param Account $account * @@ -276,10 +283,11 @@ interface AccountRepositoryInterface /** * @param string $query * @param array $types + * @param int $limit * * @return Collection */ - public function searchAccount(string $query, array $types): Collection; + public function searchAccount(string $query, array $types, int $limit): Collection; /** * @param User $user diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index c2163ae597..da61f7e503 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -50,6 +50,7 @@ use Storage; class BillRepository implements BillRepositoryInterface { use CreatesObjectGroups; + private User $user; /** @@ -101,7 +102,7 @@ class BillRepository implements BillRepositoryInterface public function findBill(?int $billId, ?string $billName): ?Bill { if (null !== $billId) { - $searchResult = $this->find((int)$billId); + $searchResult = $this->find((int) $billId); if (null !== $searchResult) { Log::debug(sprintf('Found bill based on #%d, will return it.', $billId)); @@ -109,7 +110,7 @@ class BillRepository implements BillRepositoryInterface } } if (null !== $billName) { - $searchResult = $this->findByName((string)$billName); + $searchResult = $this->findByName((string) $billName); if (null !== $searchResult) { Log::debug(sprintf('Found bill based on "%s", will return it.', $billName)); @@ -249,7 +250,7 @@ class BillRepository implements BillRepositoryInterface $set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']); if ($set->count() > 0) { $journalIds = $set->pluck('id')->toArray(); - $amount = (string)Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount'); + $amount = (string) Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount'); $sum = bcadd($sum, $amount); Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $amount, $sum)); } @@ -275,10 +276,10 @@ class BillRepository implements BillRepositoryInterface foreach ($bills as $bill) { /** @var Collection $set */ $set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']); - $currencyId = (int)$bill->transaction_currency_id; + $currencyId = (int) $bill->transaction_currency_id; if ($set->count() > 0) { $journalIds = $set->pluck('id')->toArray(); - $amount = (string)Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount'); + $amount = (string) Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount'); $return[$currencyId] = $return[$currencyId] ?? '0'; $return[$currencyId] = bcadd($amount, $return[$currencyId]); Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (currency %d)', $amount, $return[$currencyId], $currencyId)); @@ -311,7 +312,7 @@ class BillRepository implements BillRepositoryInterface if ($total > 0) { $average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2'); - $multi = bcmul($average, (string)$total); + $multi = bcmul($average, (string) $total); $sum = bcadd($sum, $multi); Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $multi, $sum)); } @@ -338,13 +339,13 @@ class BillRepository implements BillRepositoryInterface $dates = $this->getPayDatesInRange($bill, $start, $end); $count = $bill->transactionJournals()->after($start)->before($end)->count(); $total = $dates->count() - $count; - $currencyId = (int)$bill->transaction_currency_id; + $currencyId = (int) $bill->transaction_currency_id; Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total)); if ($total > 0) { $average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2'); - $multi = bcmul($average, (string)$total); + $multi = bcmul($average, (string) $total); $return[$currencyId] = $return[$currencyId] ?? '0'; $return[$currencyId] = bcadd($return[$currencyId], $multi); Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (for currency %d)', $multi, $return[$currencyId], $currencyId)); @@ -378,7 +379,7 @@ class BillRepository implements BillRepositoryInterface /** @var Note $note */ $note = $bill->notes()->first(); if (null !== $note) { - return (string)$note->text; + return (string) $note->text; } return ''; @@ -387,26 +388,46 @@ class BillRepository implements BillRepositoryInterface /** * @param Bill $bill * - * @return string + * @return array */ - public function getOverallAverage(Bill $bill): string + public function getOverallAverage(Bill $bill): array { /** @var JournalRepositoryInterface $repos */ $repos = app(JournalRepositoryInterface::class); $repos->setUser($this->user); + + // get and sort on currency + $result = []; $journals = $bill->transactionJournals()->get(); - $sum = '0'; - $count = (string)$journals->count(); + /** @var TransactionJournal $journal */ foreach ($journals as $journal) { - $sum = bcadd($sum, $repos->getJournalTotal($journal)); - } - $avg = '0'; - if ($journals->count() > 0) { - $avg = bcdiv($sum, $count); + /** @var Transaction $transaction */ + $transaction = $journal->transactions()->where('amount', '<', 0)->first(); + $currencyId = (int) $journal->transaction_currency_id; + $currency = $journal->transactionCurrency; + $result[$currencyId] = $result[$currencyId] ?? [ + 'sum' => '0', + 'count' => 0, + 'avg' => '0', + 'currency_id' => $currency->id, + 'currency_code' => $currency->code, + 'currency_symbol' => $currency->symbol, + 'currency_decimal_places' => $currency->decimal_places, + ]; + $result[$currencyId]['sum'] = bcadd($result[$currencyId]['sum'], $transaction->amount); + $result[$currencyId]['count']++; } - return $avg; + // after loop, re-loop for avg. + /** + * @var int $currencyId + * @var array $arr + */ + foreach ($result as $currencyId => $arr) { + $result[$currencyId]['avg'] = bcdiv($arr['sum'], (string) $arr['count']); + } + return $result; } /** @@ -535,30 +556,50 @@ class BillRepository implements BillRepositoryInterface * @param Bill $bill * @param Carbon $date * - * @return string + * @return array */ - public function getYearAverage(Bill $bill, Carbon $date): string + public function getYearAverage(Bill $bill, Carbon $date): array { /** @var JournalRepositoryInterface $repos */ $repos = app(JournalRepositoryInterface::class); $repos->setUser($this->user); + // get and sort on currency + $result = []; + $journals = $bill->transactionJournals() ->where('date', '>=', $date->year . '-01-01 00:00:00') ->where('date', '<=', $date->year . '-12-31 23:59:59') ->get(); - $sum = '0'; - $count = (string)$journals->count(); + /** @var TransactionJournal $journal */ foreach ($journals as $journal) { - $sum = bcadd($sum, $repos->getJournalTotal($journal)); - } - $avg = '0'; - if ($journals->count() > 0) { - $avg = bcdiv($sum, $count); + /** @var Transaction $transaction */ + $transaction = $journal->transactions()->where('amount', '<', 0)->first(); + $currencyId = (int) $journal->transaction_currency_id; + $currency = $journal->transactionCurrency; + $result[$currencyId] = $result[$currencyId] ?? [ + 'sum' => '0', + 'count' => 0, + 'avg' => '0', + 'currency_id' => $currency->id, + 'currency_code' => $currency->code, + 'currency_symbol' => $currency->symbol, + 'currency_decimal_places' => $currency->decimal_places, + ]; + $result[$currencyId]['sum'] = bcadd($result[$currencyId]['sum'], $transaction->amount); + $result[$currencyId]['count']++; } - return $avg; + // after loop, re-loop for avg. + /** + * @var int $currencyId + * @var array $arr + */ + foreach ($result as $currencyId => $arr) { + $result[$currencyId]['avg'] = bcdiv($arr['sum'], (string) $arr['count']); + } + return $result; } /** @@ -571,7 +612,7 @@ class BillRepository implements BillRepositoryInterface { /** @var Transaction $transaction */ foreach ($transactions as $transaction) { - $journal = $bill->user->transactionJournals()->find((int)$transaction['transaction_journal_id']); + $journal = $bill->user->transactionJournals()->find((int) $transaction['transaction_journal_id']); $journal->bill_id = $bill->id; $journal->save(); Log::debug(sprintf('Linked journal #%d to bill #%d', $journal->id, $bill->id)); @@ -661,14 +702,15 @@ class BillRepository implements BillRepositoryInterface /** * @param string $query + * @param int $limit * * @return Collection */ - public function searchBill(string $query): Collection + public function searchBill(string $query, int $limit): Collection { $query = sprintf('%%%s%%', $query); - return $this->user->bills()->where('name', 'LIKE', $query)->get(); + return $this->user->bills()->where('name', 'LIKE', $query)->take($limit)->get(); } /** diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index 4ae68097fc..ab2884b508 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -198,9 +198,9 @@ interface BillRepositoryInterface /** * @param Bill $bill * - * @return string + * @return array */ - public function getOverallAverage(Bill $bill): string; + public function getOverallAverage(Bill $bill): array; /** * @param int $size @@ -254,14 +254,14 @@ interface BillRepositoryInterface * @param Bill $bill * @param Carbon $date * - * @return string + * @return array */ - public function getYearAverage(Bill $bill, Carbon $date): string; + public function getYearAverage(Bill $bill, Carbon $date): array; /** * Link a set of journals to a bill. * - * @param Bill $bill + * @param Bill $bill * @param array $transactions */ public function linkCollectionToBill(Bill $bill, array $transactions): void; @@ -287,10 +287,11 @@ interface BillRepositoryInterface /** * @param string $query + * @param int $limit * * @return Collection */ - public function searchBill(string $query): Collection; + public function searchBill(string $query, int $limit): Collection; /** * @param User $user diff --git a/app/Repositories/Budget/BudgetLimitRepository.php b/app/Repositories/Budget/BudgetLimitRepository.php index b725c943b8..c204a55b44 100644 --- a/app/Repositories/Budget/BudgetLimitRepository.php +++ b/app/Repositories/Budget/BudgetLimitRepository.php @@ -26,6 +26,7 @@ namespace FireflyIII\Repositories\Budget; use Carbon\Carbon; use Exception; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\TransactionCurrencyFactory; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; @@ -284,23 +285,10 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface * @param array $data * * @return BudgetLimit + * @throws FireflyException */ public function store(array $data): BudgetLimit { - return BudgetLimit::create($data); - } - - /** - * @param array $data - * - * @return BudgetLimit - * @deprecated - */ - public function storeBudgetLimit(array $data): BudgetLimit - { - /** @var Budget $budget */ - $budget = $data['budget']; - // if no currency has been provided, use the user's default currency: /** @var TransactionCurrencyFactory $factory */ $factory = app(TransactionCurrencyFactory::class); @@ -308,16 +296,23 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface if (null === $currency) { $currency = app('amount')->getDefaultCurrencyByUser($this->user); } + $currency->enabled = true; + $currency->save(); - // find limit with same date range. - // if it exists, return that one. + // find the budget: + $budget = $this->user->budgets()->find((int) $data['budget_id']); + if (null === $budget) { + throw new FireflyException('200004: Budget does not exist.'); // @codeCoverageIgnore + } + + // find limit with same date range and currency. $limit = $budget->budgetlimits() ->where('budget_limits.start_date', $data['start']->format('Y-m-d 00:00:00')) ->where('budget_limits.end_date', $data['end']->format('Y-m-d 00:00:00')) ->where('budget_limits.transaction_currency_id', $currency->id) ->get(['budget_limits.*'])->first(); if (null !== $limit) { - return $limit; + throw new FireflyException('200027: Budget limit already exists.'); // @codeCoverageIgnore } Log::debug('No existing budget limit, create a new one'); @@ -342,29 +337,11 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface */ public function update(BudgetLimit $budgetLimit, array $data): BudgetLimit { - $budgetLimit->amount = $data['amount'] ?? $budgetLimit->amount; - $budgetLimit->save(); - - return $budgetLimit; - } - - /** - * @param BudgetLimit $budgetLimit - * @param array $data - * - * @return BudgetLimit - * @throws Exception - * @deprecated - */ - public function updateBudgetLimit(BudgetLimit $budgetLimit, array $data): BudgetLimit - { - /** @var Budget $budget */ - $budget = $data['budget']; - - $budgetLimit->budget()->associate($budget); - $budgetLimit->start_date = $data['start']->format('Y-m-d 00:00:00'); - $budgetLimit->end_date = $data['end']->format('Y-m-d 00:00:00'); - $budgetLimit->amount = $data['amount']; + $budgetLimit->amount = $data['amount'] ?? $budgetLimit->amount; + $budgetLimit->budget_id = $data['budget_id'] ?? $budgetLimit->id; + $budgetLimit->budget_id = $data['budget'] ? $data['budget']->id : $budgetLimit->budget_id; + $budgetLimit->start_date = $data['start'] ? $data['start']->format('Y-m-d 00:00:00') : $budgetLimit->start_date; + $budgetLimit->end_date = $data['end'] ? $data['end']->format('Y-m-d 00:00:00') : $budgetLimit->end_date; // if no currency has been provided, use the user's default currency: /** @var TransactionCurrencyFactory $factory */ @@ -375,9 +352,10 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface } $currency->enabled = true; $currency->save(); - $budgetLimit->transaction_currency_id = $currency->id; + $budgetLimit->transaction_currency_id = $currency->id; $budgetLimit->save(); + Log::debug(sprintf('Updated budget limit with ID #%d and amount %s', $budgetLimit->id, $data['amount'])); return $budgetLimit; diff --git a/app/Repositories/Budget/BudgetLimitRepositoryInterface.php b/app/Repositories/Budget/BudgetLimitRepositoryInterface.php index c9813fe39a..4fc293f5d5 100644 --- a/app/Repositories/Budget/BudgetLimitRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetLimitRepositoryInterface.php @@ -111,14 +111,6 @@ interface BudgetLimitRepositoryInterface */ public function store(array $data): BudgetLimit; - /** - * @param array $data - * - * @return BudgetLimit - * @deprecated - */ - public function storeBudgetLimit(array $data): BudgetLimit; - /** * @param BudgetLimit $budgetLimit * @param array $data @@ -127,15 +119,6 @@ interface BudgetLimitRepositoryInterface */ public function update(BudgetLimit $budgetLimit, array $data): BudgetLimit; - /** - * @param BudgetLimit $budgetLimit - * @param array $data - * - * @return BudgetLimit - * @deprecated - */ - public function updateBudgetLimit(BudgetLimit $budgetLimit, array $data): BudgetLimit; - /** * @param Budget $budget * @param Carbon $start diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index da41d696e0..939ca1dc8b 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -50,17 +50,6 @@ class BudgetRepository implements BudgetRepositoryInterface /** @var User */ private $user; - /** - * Constructor. - */ - public function __construct() - { - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); - die(get_class($this)); - } - } - /** * @return bool */ @@ -171,19 +160,9 @@ class BudgetRepository implements BudgetRepositoryInterface $oldest = null; $journal = $budget->transactionJournals()->orderBy('date', 'ASC')->first(); if (null !== $journal) { - $oldest = $journal->date < $oldest ? $journal->date : $oldest; + return $journal->date; } - - $transaction = $budget - ->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.id') - ->orderBy('transaction_journals.date', 'ASC')->first(['transactions.*', 'transaction_journals.date']); - if (null !== $transaction) { - $carbon = new Carbon($transaction->date); - $oldest = $carbon < $oldest ? $carbon : $oldest; - } - - return $oldest; + return null; } /** @@ -238,10 +217,11 @@ class BudgetRepository implements BudgetRepositoryInterface /** * @param string $query + * @param int $limit * * @return Collection */ - public function searchBudget(string $query): Collection + public function searchBudget(string $query, int $limit): Collection { $search = $this->user->budgets(); @@ -251,7 +231,7 @@ class BudgetRepository implements BudgetRepositoryInterface $search->orderBy('order', 'ASC') ->orderBy('name', 'ASC')->where('active', 1); - return $search->get(); + return $search->take($limit)->get(); } /** diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 9da1aa922b..bf58a3f739 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -138,10 +138,10 @@ interface BudgetRepositoryInterface /** * @param string $query - * + * @param int $limit * @return Collection */ - public function searchBudget(string $query): Collection; + public function searchBudget(string $query, int $limit): Collection; /** * @param Budget $budget diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index e4e8736586..324834988b 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -42,19 +42,7 @@ use Storage; */ class CategoryRepository implements CategoryRepositoryInterface { - /** @var User */ - private $user; - - /** - * Constructor. - */ - public function __construct() - { - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); - die(__METHOD__); - } - } + private User $user; /** * @param Category $category @@ -217,17 +205,18 @@ class CategoryRepository implements CategoryRepositoryInterface /** * @param string $query + * @param int $limit * * @return Collection */ - public function searchCategory(string $query): Collection + public function searchCategory(string $query, int $limit): Collection { $search = $this->user->categories(); if ('' !== $query) { $search->where('name', 'LIKE', sprintf('%%%s%%', $query)); } - return $search->get(); + return $search->take($limit)->get(); } /** diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index 2b7dfddc5d..0b5e7fa9be 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -113,10 +113,11 @@ interface CategoryRepositoryInterface /** * @param string $query + * @param int $limit * * @return Collection */ - public function searchCategory(string $query): Collection; + public function searchCategory(string $query, int $limit): Collection; /** * @param User $user diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index 4ca89ddc3f..52b7db555c 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -476,17 +476,18 @@ class CurrencyRepository implements CurrencyRepositoryInterface /** * @param string $search + * @param int $limit * * @return Collection */ - public function searchCurrency(string $search): Collection + public function searchCurrency(string $search, int $limit): Collection { $query = TransactionCurrency::where('enabled', 1); if ('' !== $search) { $query->where('name', 'LIKE', sprintf('%%%s%%', $search)); } - return $query->get(); + return $query->take($limit)->get(); } /** diff --git a/app/Repositories/Currency/CurrencyRepositoryInterface.php b/app/Repositories/Currency/CurrencyRepositoryInterface.php index 5a37050ecc..440257fa34 100644 --- a/app/Repositories/Currency/CurrencyRepositoryInterface.php +++ b/app/Repositories/Currency/CurrencyRepositoryInterface.php @@ -230,10 +230,11 @@ interface CurrencyRepositoryInterface /** * @param string $search + * @param int $limit * * @return Collection */ - public function searchCurrency(string $search): Collection; + public function searchCurrency(string $search, int $limit): Collection; /** * @param User $user diff --git a/app/Repositories/Journal/JournalAPIRepository.php b/app/Repositories/Journal/JournalAPIRepository.php index 9b51937fb3..5dd293af98 100644 --- a/app/Repositories/Journal/JournalAPIRepository.php +++ b/app/Repositories/Journal/JournalAPIRepository.php @@ -121,4 +121,14 @@ class JournalAPIRepository implements JournalAPIRepositoryInterface { $this->user = $user; } + + /** + * @inheritDoc + */ + public function getJournalLinks(TransactionJournal $journal): Collection + { + $collection = $journal->destJournalLinks()->get(); + + return $journal->sourceJournalLinks()->get()->merge($collection); + } } diff --git a/app/Repositories/Journal/JournalAPIRepositoryInterface.php b/app/Repositories/Journal/JournalAPIRepositoryInterface.php index bfc6c10283..ad6e372987 100644 --- a/app/Repositories/Journal/JournalAPIRepositoryInterface.php +++ b/app/Repositories/Journal/JournalAPIRepositoryInterface.php @@ -51,6 +51,15 @@ interface JournalAPIRepositoryInterface */ public function getAttachments(TransactionJournal $journal): Collection; + /** + * Return all journal links for journal. + * + * @param TransactionJournal $journal + * + * @return Collection + */ + public function getJournalLinks(TransactionJournal $journal): Collection; + /** * Get all piggy bank events for a journal. * diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 793f1b21a3..0fad9319f9 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -63,9 +63,10 @@ class JournalRepository implements JournalRepositoryInterface * Search in journal descriptions. * * @param string $search + * @param int $limit * @return Collection */ - public function searchJournalDescriptions(string $search): Collection + public function searchJournalDescriptions(string $search, int $limit): Collection { $query = $this->user->transactionJournals() ->orderBy('date', 'DESC'); @@ -73,7 +74,7 @@ class JournalRepository implements JournalRepositoryInterface $query->where('description', 'LIKE', sprintf('%%%s%%', $search)); } - return $query->get(); + return $query->take($limit)->get(); } /** diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index dde2dfdd62..89a1bf196a 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -50,15 +50,14 @@ interface JournalRepositoryInterface public function findByType(array $types): Collection; /** - * TODO maybe create JSON repository? - * * Search in journal descriptions. * * @param string $search + * @param int $limit * * @return Collection */ - public function searchJournalDescriptions(string $search): Collection; + public function searchJournalDescriptions(string $search, int $limit): Collection; /** * Deletes a transaction group. diff --git a/app/Repositories/ObjectGroup/ObjectGroupRepository.php b/app/Repositories/ObjectGroup/ObjectGroupRepository.php index 2f71683a36..763bfc3ba9 100644 --- a/app/Repositories/ObjectGroup/ObjectGroupRepository.php +++ b/app/Repositories/ObjectGroup/ObjectGroupRepository.php @@ -61,10 +61,11 @@ class ObjectGroupRepository implements ObjectGroupRepositoryInterface /** * @param string $query + * @param int $limit * * @return Collection */ - public function search(string $query): Collection + public function search(string $query, int $limit): Collection { $dbQuery = $this->user->objectGroups()->orderBy('order', 'ASC')->orderBy('title', 'ASC'); if ('' !== $query) { @@ -77,7 +78,7 @@ class ObjectGroupRepository implements ObjectGroupRepositoryInterface } - return $dbQuery->get(['object_groups.*']); + return $dbQuery->take($limit)->get(['object_groups.*']); } /** diff --git a/app/Repositories/ObjectGroup/ObjectGroupRepositoryInterface.php b/app/Repositories/ObjectGroup/ObjectGroupRepositoryInterface.php index 7e63681c98..0a8d8b2763 100644 --- a/app/Repositories/ObjectGroup/ObjectGroupRepositoryInterface.php +++ b/app/Repositories/ObjectGroup/ObjectGroupRepositoryInterface.php @@ -39,10 +39,11 @@ interface ObjectGroupRepositoryInterface /** * @param string $query + * @param int $limit * * @return Collection */ - public function search(string $query): Collection; + public function search(string $query, int $limit): Collection; /** * Delete empty ones. diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 4ec0ca2351..65a5118419 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -239,7 +239,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface */ public function getMaxOrder(): int { - return (int)$this->user->piggyBanks()->max('order'); + return (int)$this->user->piggyBanks()->max('piggy_banks.order'); } /** @@ -403,4 +403,19 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface { $this->user->piggyBanks()->delete(); } + + /** + * @inheritDoc + */ + public function searchPiggyBank(string $query, int $limit): Collection + { + $search = $this->user->piggyBanks(); + if ('' !== $query) { + $search->where('piggy_banks.name', 'LIKE', sprintf('%%%s%%', $query)); + } + $search->orderBy('piggy_banks.order', 'ASC') + ->orderBy('piggy_banks.name', 'ASC'); + + return $search->take($limit)->get(); + } } diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index 942e104f18..e8848c6912 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -145,6 +145,16 @@ interface PiggyBankRepositoryInterface */ public function findByName(string $name): ?PiggyBank; + /** + * Search for piggy banks. + * + * @param string $query + * @param int $limit + * + * @return Collection + */ + public function searchPiggyBank(string $query, int $limit): Collection; + /** * @param int $piggyBankId * diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index d44a7cd9a3..f5f53a3d02 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -569,4 +569,28 @@ class RecurringRepository implements RecurringRepositoryInterface { $this->user->recurrences()->delete(); } + + /** + * @inheritDoc + */ + public function totalTransactions(Recurrence $recurrence, RecurrenceRepetition $repetition): int + { + // if repeat = null just return 0. + if (null === $recurrence->repeat_until && 0 === (int) $recurrence->repetitions) { + return 0; + } + // expect X transactions then stop. Return that number + if (null === $recurrence->repeat_until && 0 !== (int) $recurrence->repetitions) { + return (int) $recurrence->repetitions; + } + + // need to calculate, this depends on the repetition: + if (null !== $recurrence->repeat_until && 0 === (int) $recurrence->repetitions) { + $occurrences = $this->getOccurrencesInRange($repetition, $recurrence->first_date ?? today(), $recurrence->repeat_until); + + return count($occurrences); + } + + return 0; + } } diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index 751bceec01..ddca5c2b21 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -44,6 +44,15 @@ interface RecurringRepositoryInterface */ public function destroyAll(): void; + /** + * Calculate how many transactions are to be expected from this recurrence. + * + * @param Recurrence $recurrence + * @param RecurrenceRepetition $repetition + * @return int + */ + public function totalTransactions(Recurrence $recurrence, RecurrenceRepetition $repetition): int; + /** * Destroy a recurring transaction. * diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index 970978a746..71f07f2cf0 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -287,10 +287,11 @@ class TagRepository implements TagRepositoryInterface * Search the users tags. * * @param string $query + * @param int $limit * * @return Collection */ - public function searchTags(string $query): Collection + public function searchTags(string $query, int $limit): Collection { /** @var Collection $tags */ $tags = $this->user->tags()->orderBy('tag', 'ASC'); @@ -299,7 +300,7 @@ class TagRepository implements TagRepositoryInterface $tags->where('tag', 'LIKE', $search); } - return $tags->get(); + return $tags->take($limit)->get('tags.*'); } /** diff --git a/app/Repositories/Tag/TagRepositoryInterface.php b/app/Repositories/Tag/TagRepositoryInterface.php index 7fac389191..309b06be5d 100644 --- a/app/Repositories/Tag/TagRepositoryInterface.php +++ b/app/Repositories/Tag/TagRepositoryInterface.php @@ -165,10 +165,11 @@ interface TagRepositoryInterface * Search the users tags. * * @param string $query + * @param int $limit * * @return Collection */ - public function searchTags(string $query): Collection; + public function searchTags(string $query, int $limit): Collection; /** * @param User $user diff --git a/app/Repositories/TransactionType/TransactionTypeRepository.php b/app/Repositories/TransactionType/TransactionTypeRepository.php index 9710d46d09..5b5a319eff 100644 --- a/app/Repositories/TransactionType/TransactionTypeRepository.php +++ b/app/Repositories/TransactionType/TransactionTypeRepository.php @@ -71,14 +71,15 @@ class TransactionTypeRepository implements TransactionTypeRepositoryInterface /** * @param string $query + * @param int $limit * @return Collection */ - public function searchTypes(string $query): Collection + public function searchTypes(string $query, int $limit): Collection { if ('' === $query) { return TransactionType::get(); } - return TransactionType::where('type', 'LIKE', sprintf('%%%s%%', $query))->get(); + return TransactionType::where('type', 'LIKE', sprintf('%%%s%%', $query))->take($limit)->get(); } } diff --git a/app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php b/app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php index dac9cb18ad..65033e48da 100644 --- a/app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php +++ b/app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php @@ -48,7 +48,8 @@ interface TransactionTypeRepositoryInterface /** * @param string $query + * @param int $limit * @return Collection */ - public function searchTypes(string $query): Collection; + public function searchTypes(string $query, int $limit): Collection; } diff --git a/app/Repositories/User/UserRepository.php b/app/Repositories/User/UserRepository.php index 0182e82bde..f84ec0e2ed 100644 --- a/app/Repositories/User/UserRepository.php +++ b/app/Repositories/User/UserRepository.php @@ -28,6 +28,7 @@ use FireflyIII\User; use Illuminate\Database\QueryException; use Illuminate\Support\Collection; use Log; +use Str; /** * Class UserRepository. @@ -327,7 +328,7 @@ class UserRepository implements UserRepositoryInterface 'blocked' => $data['blocked'] ?? false, 'blocked_code' => $data['blocked_code'] ?? null, 'email' => $data['email'], - 'password' => str_random(24), + 'password' => Str::random(24), ] ); $role = $data['role'] ?? ''; diff --git a/app/Services/Internal/Support/AccountServiceTrait.php b/app/Services/Internal/Support/AccountServiceTrait.php index f91030ddc0..55f30c5c9d 100644 --- a/app/Services/Internal/Support/AccountServiceTrait.php +++ b/app/Services/Internal/Support/AccountServiceTrait.php @@ -85,7 +85,7 @@ trait AccountServiceTrait $fields = $this->validAssetFields; } if ($account->accountType->type === AccountType::ASSET && isset($data['account_role']) && 'ccAsset' === $data['account_role']) { - $fields = $this->validCCFields; + $fields = $this->validCCFields; // @codeCoverageIgnore } /** @var AccountMetaFactory $factory */ $factory = app(AccountMetaFactory::class); @@ -96,10 +96,10 @@ trait AccountServiceTrait // convert boolean value: if (is_bool($data[$field]) && false === $data[$field]) { - $data[$field] = 0; + $data[$field] = 0; // @codeCoverageIgnore } if (is_bool($data[$field]) && true === $data[$field]) { - $data[$field] = 1; + $data[$field] = 1; // @codeCoverageIgnore } $factory->crud($account, $field, (string)$data[$field]); @@ -150,7 +150,7 @@ trait AccountServiceTrait { $data['opening_balance'] = (string)($data['opening_balance'] ?? ''); if ('' !== $data['opening_balance'] && 0 === bccomp($data['opening_balance'], '0')) { - $data['opening_balance'] = ''; + $data['opening_balance'] = ''; // @codeCoverageIgnore } if ('' !== $data['opening_balance'] && isset($data['opening_balance'], $data['opening_balance_date'])) { Log::debug('Array has valid opening balance data.'); @@ -162,6 +162,33 @@ trait AccountServiceTrait return false; } + /** + * Returns true if the data in the array is submitted but empty. + * + * @param array $data + * + * @return bool + */ + public function isEmptyOBData(array $data): bool + { + if (!array_key_exists('opening_balance', $data) && + !array_key_exists('opening_balance_date', $data) + ) { + // not set, so false. + return false; + } + // if isset, but is empty: + if ( + (array_key_exists('opening_balance', $data) && '' === $data['opening_balance']) + || + (array_key_exists('opening_balance_date', $data) && '' === $data['opening_balance_date']) + ) { + return true; + } + + return false; + } + /** * @param Account $account * @param array $data @@ -190,9 +217,11 @@ trait AccountServiceTrait $sourceId = $account->id; } if (0 === bccomp($amount, '0')) { + // @codeCoverageIgnoreStart Log::debug('Amount is zero, so will not make an OB group.'); return null; + // @codeCoverageIgnoreEnd } $amount = app('steam')->positive($amount); $submission = [ @@ -306,7 +335,6 @@ trait AccountServiceTrait * @param array $data * * @return TransactionGroup|null - * @codeCoverageIgnore */ protected function updateOBGroup(Account $account, array $data): ?TransactionGroup { diff --git a/app/Services/Internal/Support/JournalServiceTrait.php b/app/Services/Internal/Support/JournalServiceTrait.php index cdcc7172bb..12cc232a61 100644 --- a/app/Services/Internal/Support/JournalServiceTrait.php +++ b/app/Services/Internal/Support/JournalServiceTrait.php @@ -44,14 +44,10 @@ use Log; */ trait JournalServiceTrait { - /** @var AccountRepositoryInterface */ - private $accountRepository; - /** @var BudgetRepositoryInterface */ - private $budgetRepository; - /** @var CategoryRepositoryInterface */ - private $categoryRepository; - /** @var TagFactory */ - private $tagFactory; + private AccountRepositoryInterface $accountRepository; + private BudgetRepositoryInterface $budgetRepository; + private CategoryRepositoryInterface $categoryRepository; + private TagFactory $tagFactory; /** diff --git a/app/Services/Internal/Update/AccountUpdateService.php b/app/Services/Internal/Update/AccountUpdateService.php index f8a88c3b87..f68b29f373 100644 --- a/app/Services/Internal/Update/AccountUpdateService.php +++ b/app/Services/Internal/Update/AccountUpdateService.php @@ -38,18 +38,12 @@ class AccountUpdateService { use AccountServiceTrait; - /** @var AccountRepositoryInterface */ - protected $accountRepository; - /** @var array */ - protected $validAssetFields = ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; - /** @var array */ - protected $validCCFields = ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; - /** @var array */ - protected $validFields = ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth']; - /** @var array */ - private $canHaveVirtual; - /** @var User */ - private $user; + protected AccountRepositoryInterface $accountRepository; + protected array $validAssetFields; + protected array $validCCFields; + protected array $validFields; + private array $canHaveVirtual; + private User $user; /** * Constructor. @@ -62,6 +56,9 @@ class AccountUpdateService // TODO move to configuration. $this->canHaveVirtual = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]; $this->accountRepository = app(AccountRepositoryInterface::class); + $this->validAssetFields = ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; + $this->validCCFields = ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; + $this->validFields = ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth']; } /** @@ -118,12 +115,14 @@ class AccountUpdateService // if it can have a virtual balance, it can also have an opening balance. if (in_array($type->type, $this->canHaveVirtual, true)) { + // check if is submitted as empty, that makes it valid: - if ($this->validOBData($data)) { + + if ($this->validOBData($data) && !$this->isEmptyOBData($data)) { $this->updateOBGroup($account, $data); } - if (!$this->validOBData($data)) { + if (!$this->validOBData($data) && $this->isEmptyOBData($data)) { $this->deleteOBGroup($account); } } @@ -174,6 +173,7 @@ class AccountUpdateService $account->name = $data['name'] ?? $account->name; $account->active = $data['active'] ?? $account->active; $account->iban = $data['iban'] ?? $account->iban; + $account->order = $data['order'] ?? $account->order; // if account type is a liability, the liability type (account type) // can be updated to another one. diff --git a/app/Services/Internal/Update/JournalUpdateService.php b/app/Services/Internal/Update/JournalUpdateService.php index a8746201c8..ea1d8d86bb 100644 --- a/app/Services/Internal/Update/JournalUpdateService.php +++ b/app/Services/Internal/Update/JournalUpdateService.php @@ -93,7 +93,7 @@ class JournalUpdateService $this->accountRepository = app(AccountRepositoryInterface::class); $this->currencyRepository = app(CurrencyRepositoryInterface::class); $this->metaString = ['sepa_cc', 'sepa_ct_op', 'sepa_ct_id', 'sepa_db', 'sepa_country', 'sepa_ep', 'sepa_ci', 'sepa_batch_id', 'recurrence_id', - 'internal_reference', 'bunq_payment_id', 'external_id',]; + 'internal_reference', 'bunq_payment_id', 'external_id', 'external_uri']; $this->metaDate = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date',]; } diff --git a/app/Support/Amount.php b/app/Support/Amount.php index 27b3856312..cb3769634d 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -28,6 +28,7 @@ use FireflyIII\User; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Support\Collection; use Log; +use NumberFormatter; /** * Class Amount. @@ -36,7 +37,6 @@ use Log; */ class Amount { - /** * bool $sepBySpace is $localeconv['n_sep_by_space'] * int $signPosn = $localeconv['n_sign_posn'] @@ -111,6 +111,58 @@ class Amount return $format; } + /** + * This method returns the correct format rules required by accounting.js, + * the library used to format amounts in charts. + * + * Used only in one place. + * + * @return array + */ + public function getJsConfig(): array + { + $config = $this->getLocaleInfo(); + $negative = self::getAmountJsConfig($config['n_sep_by_space'], $config['n_sign_posn'], $config['negative_sign'], $config['n_cs_precedes']); + $positive = self::getAmountJsConfig($config['p_sep_by_space'], $config['p_sign_posn'], $config['positive_sign'], $config['p_cs_precedes']); + + return [ + 'mon_decimal_point' => $config['mon_decimal_point'], + 'mon_thousands_sep' => $config['mon_thousands_sep'], + 'format' => [ + 'pos' => $positive, + 'neg' => $negative, + 'zero' => $positive, + ], + ]; + } + + /** + * @return array + */ + private function getLocaleInfo(): array + { + // get config from preference, not from translation: + $locale = app('steam')->getLocale(); + $array = app('steam')->getLocaleArray($locale); + + setlocale(LC_MONETARY, $array); + $info = localeconv(); + + // correct variables + $info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes'); + $info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes'); + + $info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space'); + $info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space'); + + $fmt = new NumberFormatter( $locale, NumberFormatter::CURRENCY); + + $info['mon_decimal_point'] = $fmt->getSymbol(NumberFormatter::MONETARY_SEPARATOR_SYMBOL); + $info['mon_thousands_sep'] = $fmt->getSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL); + + return $info; + } + /** * This method will properly format the given number, in color or "black and white", * as a currency, given two things: the currency required and the current locale. @@ -142,13 +194,15 @@ class Amount */ public function formatFlat(string $symbol, int $decimalPlaces, string $amount, bool $coloured = null): string { + $locale = app('steam')->getLocale(); + $coloured = $coloured ?? true; - $info = $this->getLocaleInfo(); - $formatted = number_format((float) $amount, $decimalPlaces, $info['mon_decimal_point'], $info['mon_thousands_sep']); - $precedes = $amount < 0 ? $info['n_cs_precedes'] : $info['p_cs_precedes']; - $separated = $amount < 0 ? $info['n_sep_by_space'] : $info['p_sep_by_space']; - $space = true === $separated ? ' ' : ''; - $result = false === $precedes ? $formatted . $space . $symbol : $symbol . $space . $formatted; + + $fmt = new NumberFormatter( $locale, NumberFormatter::CURRENCY ); + $fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $symbol); + $fmt->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces); + $fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces); + $result = $fmt->format($amount); if (true === $coloured) { if ($amount > 0) { @@ -215,28 +269,7 @@ class Amount } /** - * @return string - */ - public function getCurrencySymbol(): string - { - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); - } - $cache = new CacheProperties; - $cache->addProperty('getCurrencySymbol'); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - $currencyPreference = app('preferences')->get('currencyPreference', config('firefly.default_currency', 'EUR')); - $currency = TransactionCurrency::where('code', $currencyPreference->data)->first(); - - $cache->store($currency->symbol); - - return $currency->symbol; - } - - /** - * @return \FireflyIII\Models\TransactionCurrency + * @return TransactionCurrency */ public function getDefaultCurrency(): TransactionCurrency { @@ -250,7 +283,7 @@ class Amount } /** - * @return \FireflyIII\Models\TransactionCurrency + * @return TransactionCurrency */ public function getSystemCurrency(): TransactionCurrency { @@ -264,7 +297,7 @@ class Amount /** * @param User $user * - * @return \FireflyIII\Models\TransactionCurrency + * @return TransactionCurrency */ public function getDefaultCurrencyByUser(User $user): TransactionCurrency { @@ -299,47 +332,6 @@ class Amount return $currency; } - /** - * This method returns the correct format rules required by accounting.js, - * the library used to format amounts in charts. - * - * @param array $config - * - * @return array - */ - public function getJsConfig(array $config): array - { - $negative = self::getAmountJsConfig($config['n_sep_by_space'], $config['n_sign_posn'], $config['negative_sign'], $config['n_cs_precedes']); - $positive = self::getAmountJsConfig($config['p_sep_by_space'], $config['p_sign_posn'], $config['positive_sign'], $config['p_cs_precedes']); - - return [ - 'pos' => $positive, - 'neg' => $negative, - 'zero' => $positive, - ]; - } - - /** - * @return array - */ - public function getLocaleInfo(): array - { - // get config from preference, not from translation: - $locale = app('steam')->getLocale(); - $array = app('steam')->getLocaleArray($locale); - - setlocale(LC_MONETARY, $array); - $info = localeconv(); - // correct variables - $info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes'); - $info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes'); - - $info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space'); - $info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space'); - - return $info; - } - /** * @param array $info * @param string $field diff --git a/app/Support/Authentication/RemoteUserGuard.php b/app/Support/Authentication/RemoteUserGuard.php index baf2d94f73..d93b98bcfa 100644 --- a/app/Support/Authentication/RemoteUserGuard.php +++ b/app/Support/Authentication/RemoteUserGuard.php @@ -69,16 +69,13 @@ class RemoteUserGuard implements Guard return; } // Get the user identifier from $_SERVER - $userID = request()->server('REMOTE_USER') ?? null; + $header = config('auth.guard_header', 'REMOTE_USER'); + $userID = request()->server($header) ?? null; if (null === $userID) { - Log::debug('No user in REMOTE_USER.'); - throw new FireflyException('The REMOTE_USER header was unexpectedly empty.'); + Log::error(sprintf('No user in header "%s".', $header)); + throw new FireflyException('The guard header was unexpectedly empty. See the logs.'); } - - // do some basic debugging here: - // $userID = 'test@firefly'; - /** @var User $user */ $user = $this->provider->retrieveById($userID); diff --git a/app/Support/Chart/Category/FrontpageChartGenerator.php b/app/Support/Chart/Category/FrontpageChartGenerator.php index da3bee74db..39e4fcf787 100644 --- a/app/Support/Chart/Category/FrontpageChartGenerator.php +++ b/app/Support/Chart/Category/FrontpageChartGenerator.php @@ -89,18 +89,16 @@ class FrontpageChartGenerator foreach ($categories as $category) { // get expenses $collection[] = $this->collectExpenses($category, $accounts); - //$collection[] = $this->collectIncome($category, $accounts); } // collect for no-category: $collection[] = $this->collectNoCatExpenses($accounts); - //$collection[] = $this->collectNoCatIncome($accounts); $tempData = array_merge(...$collection); // sort temp array by amount. $amounts = array_column($tempData, 'sum_float'); - array_multisort($amounts, SORT_DESC, $tempData); + array_multisort($amounts, SORT_ASC, $tempData); $currencyData = $this->createCurrencyGroups($tempData); @@ -146,29 +144,6 @@ class FrontpageChartGenerator return $tempData; } - /** - * @param Category $category - * @param Collection $accounts - * - * @return array - */ - private function collectIncome(Category $category, Collection $accounts): array - { - $spent = $this->opsRepos->sumIncome($this->start, $this->end, $accounts, new Collection([$category])); - $tempData = []; - foreach ($spent as $currency) { - $this->addCurrency($currency); - $tempData[] = [ - 'name' => $category->name, - 'sum' => $currency['sum'], - 'sum_float' => round($currency['sum'], $currency['currency_decimal_places']), - 'currency_id' => (int) $currency['currency_id'], - ]; - } - - return $tempData; - } - /** * @param Collection $accounts * @@ -184,28 +159,8 @@ class FrontpageChartGenerator 'name' => trans('firefly.no_category'), 'sum' => $currency['sum'], 'sum_float' => round($currency['sum'], $currency['currency_decimal_places'] ?? 2), - 'currency_id' => (int) $currency['currency_id'],]; - } - - return $tempData; - } - - /** - * @param Collection $accounts - * - * @return array - */ - private function collectNoCatIncome(Collection $accounts): array - { - $noCatExp = $this->noCatRepos->sumIncome($this->start, $this->end, $accounts); - $tempData = []; - foreach ($noCatExp as $currency) { - $this->addCurrency($currency); - $tempData[] = [ - 'name' => trans('firefly.no_category'), - 'sum' => $currency['sum'], - 'sum_float' => round($currency['sum'], $currency['currency_decimal_places'] ?? 2), - 'currency_id' => (int) $currency['currency_id'],]; + 'currency_id' => (int) $currency['currency_id'], + ]; } return $tempData; @@ -231,14 +186,6 @@ class FrontpageChartGenerator 'currency_symbol' => $currency['currency_symbol'], 'entries' => $names, ]; - // $key = sprintf('earned-%d', $currencyId); - // $return[$key] = [ - // 'label' => sprintf('%s (%s)', (string) trans('firefly.earned'), $currency['currency_name']), - // 'type' => 'bar', - // 'currency_symbol' => $currency['currency_symbol'], - // 'data_type' => 'earned', - // 'entries' => $names, - // ]; } return $return; diff --git a/app/Support/Facades/Amount.php b/app/Support/Facades/Amount.php index 992f6fbfbc..a72db8bb45 100644 --- a/app/Support/Facades/Amount.php +++ b/app/Support/Facades/Amount.php @@ -38,7 +38,6 @@ use Illuminate\Support\Facades\Facade; * @method string getCurrencySymbol() * @method TransactionCurrency getDefaultCurrency() * @method TransactionCurrency getDefaultCurrencyByUser(User $user) - * @method array getJsConfig(array $config) */ class Amount extends Facade { diff --git a/app/Support/FireflyConfig.php b/app/Support/FireflyConfig.php index ebb8b725d3..2ce1219d79 100644 --- a/app/Support/FireflyConfig.php +++ b/app/Support/FireflyConfig.php @@ -156,7 +156,7 @@ class FireflyConfig if ('testing' === config('app.env')) { Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); } - Log::debug('Set new value for ', ['name' => $name]); + //Log::debug('Set new value for ', ['name' => $name]); /** @var Configuration $config */ try { $config = Configuration::whereName($name)->first(); @@ -168,7 +168,7 @@ class FireflyConfig return $item; } if (null === $config) { - Log::debug('Does not exist yet ', ['name' => $name]); + //Log::debug('Does not exist yet ', ['name' => $name]); /** @var Configuration $item */ $item = new Configuration; $item->name = $name; @@ -179,7 +179,7 @@ class FireflyConfig return $item; } - Log::debug('Exists already, overwrite value.', ['name' => $name]); + //Log::debug('Exists already, overwrite value.', ['name' => $name]); $config->data = $value; $config->save(); Cache::forget('ff-config-' . $name); diff --git a/app/Support/Form/AccountForm.php b/app/Support/Form/AccountForm.php index b8e4d73289..78915668db 100644 --- a/app/Support/Form/AccountForm.php +++ b/app/Support/Form/AccountForm.php @@ -26,6 +26,7 @@ namespace FireflyIII\Support\Form; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use Log; use Throwable; @@ -41,6 +42,36 @@ class AccountForm { use FormSupport; + private function getAccountsGrouped(array $types, AccountRepositoryInterface $repository = null): array + { + if (null === $repository) { + $repository = $this->getAccountRepository(); + } + $accountList = $repository->getActiveAccountsByType($types); + $liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN,]; + $grouped = []; + + /** @var Account $account */ + foreach ($accountList as $account) { + $role = (string)$repository->getMetaValue($account, 'account_role'); + if (in_array($account->accountType->type, $liabilityTypes, true)) { + $role = sprintf('l_%s', $account->accountType->type); + } elseif ('' === $role) { + if (AccountType::EXPENSE === $account->accountType->type) { + $role = 'expense_account'; + } elseif (AccountType::REVENUE === $account->accountType->type) { + $role = 'revenue_account'; + } else { + $role = 'no_account_type'; + } + } + $key = (string) trans(sprintf('firefly.opt_group_%s', $role)); + $grouped[$key][$account->id] = $account->name; + } + + return $grouped; + } + /** * Shows a "]);var Me=/<|?\w+;/;function we(e,t,n,i,r){for(var a,o,s,l,u,c,d=t.createDocumentFragment(),h=[],f=0,p=e.length;f
-1)r&&r.push(a);else if(u=oe(a),o=ye(d.appendChild(a),"script"),u&&be(o),n)for(c=0;a=o[c++];)ge.test(a.type||"")&&n.push(a);return d}var ke=/^key/,Le=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,xe=/^([^.]*)(?:\.(.+)|)/;function Te(){return!0}function De(){return!1}function Ye(e,t){return e===function(){try{return y.activeElement}catch(e){}}()==("focus"===t)}function Se(e,t,n,i,r,a){var o,s;if("object"==typeof t){for(s in"string"!=typeof n&&(i=i||n,n=void 0),t)Se(e,s,n,i,t[s],a);return e}if(null==i&&null==r?(r=n,i=n=void 0):null==r&&("string"==typeof n?(r=i,i=void 0):(r=i,i=n,n=void 0)),!1===r)r=De;else if(!r)return e;return 1===a&&(o=r,(r=function(e){return k().off(e),o.apply(this,arguments)}).guid=o.guid||(o.guid=k.guid++)),e.each((function(){k.event.add(this,t,r,i,n)}))}function Ce(e,t,n){n?(Q.set(e,t,!1),k.event.add(e,t,{namespace:!1,handler:function(e){var i,r,a=Q.get(this,t);if(1&e.isTrigger&&this[t]){if(a.length)(k.event.special[t]||{}).delegateType&&e.stopPropagation();else if(a=s.call(arguments),Q.set(this,t,a),i=n(this,t),this[t](),a!==(r=Q.get(this,t))||i?Q.set(this,t,!1):r={},a!==r)return e.stopImmediatePropagation(),e.preventDefault(),r.value}else a.length&&(Q.set(this,t,{value:k.event.trigger(k.extend(a[0],k.Event.prototype),a.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,t)&&k.event.add(e,t,Te)}k.event={global:{},add:function(e,t,n,i,r){var a,o,s,l,u,c,d,h,f,p,m,_=Q.get(e);if(G(e))for(n.handler&&(n=(a=n).handler,r=a.selector),r&&k.find.matchesSelector(ae,r),n.guid||(n.guid=k.guid++),(l=_.events)||(l=_.events=Object.create(null)),(o=_.handle)||(o=_.handle=function(t){return void 0!==k&&k.event.triggered!==t.type?k.event.dispatch.apply(e,arguments):void 0}),u=(t=(t||"").match(I)||[""]).length;u--;)f=m=(s=xe.exec(t[u])||[])[1],p=(s[2]||"").split(".").sort(),f&&(d=k.event.special[f]||{},f=(r?d.delegateType:d.bindType)||f,d=k.event.special[f]||{},c=k.extend({type:f,origType:m,data:i,handler:n,guid:n.guid,selector:r,needsContext:r&&k.expr.match.needsContext.test(r),namespace:p.join(".")},a),(h=l[f])||((h=l[f]=[]).delegateCount=0,d.setup&&!1!==d.setup.call(e,i,p,o)||e.addEventListener&&e.addEventListener(f,o)),d.add&&(d.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),r?h.splice(h.delegateCount++,0,c):h.push(c),k.event.global[f]=!0)},remove:function(e,t,n,i,r){var a,o,s,l,u,c,d,h,f,p,m,_=Q.hasData(e)&&Q.get(e);if(_&&(l=_.events)){for(u=(t=(t||"").match(I)||[""]).length;u--;)if(f=m=(s=xe.exec(t[u])||[])[1],p=(s[2]||"").split(".").sort(),f){for(d=k.event.special[f]||{},h=l[f=(i?d.delegateType:d.bindType)||f]||[],s=s[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),o=a=h.length;a--;)c=h[a],!r&&m!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||i&&i!==c.selector&&("**"!==i||!c.selector)||(h.splice(a,1),c.selector&&h.delegateCount--,d.remove&&d.remove.call(e,c));o&&!h.length&&(d.teardown&&!1!==d.teardown.call(e,p,_.handle)||k.removeEvent(e,f,_.handle),delete l[f])}else for(f in l)k.event.remove(e,f+t[u],n,i,!0);k.isEmptyObject(l)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,i,r,a,o,s=new Array(arguments.length),l=k.event.fix(e),u=(Q.get(this,"events")||Object.create(null))[l.type]||[],c=k.event.special[l.type]||{};for(s[0]=l,t=1;t
\n \n and