Compare commits

..

140 Commits

Author SHA1 Message Date
github-actions[bot] 7ddf395ea9 Merge pull request #12256 from firefly-iii/release-1778958406
🤖 Automatically merge the PR into the develop branch.
2026-05-16 21:06:52 +02:00
JC5 492c55bd76 🤖 Auto commit for release 'develop' on 2026-05-16 2026-05-16 21:06:46 +02:00
James Cole 894dea5c9c Fix https://github.com/firefly-iii/firefly-iii/issues/12254 as suggested by @imjuzcy 2026-05-16 21:01:50 +02:00
github-actions[bot] fecf12790d Merge pull request #12255 from firefly-iii/release-1778957079
🤖 Automatically merge the PR into the develop branch.
2026-05-16 20:44:49 +02:00
JC5 883c083860 🤖 Auto commit for release 'develop' on 2026-05-16 2026-05-16 20:44:39 +02:00
James Cole e059753c43 Merge branch 'main' into develop 2026-05-16 20:39:12 +02:00
James Cole 0bac0aaaee Add some debug logging. 2026-05-16 20:38:50 +02:00
James Cole 2a68c48e2a Update security reporting guidelines in security.md
Clarified instructions for reporting false security issues.

Signed-off-by: James Cole <james@firefly-iii.org>
2026-05-16 20:05:44 +02:00
James Cole c394034876 Clarify AI hallucinations in security reporting
Reworded the third point to clarify AI hallucinations in security issues.

Signed-off-by: James Cole <james@firefly-iii.org>
2026-05-16 20:05:20 +02:00
James Cole 7bd91048ea Update security.md with reporting guidelines
Clarified reporting guidelines for security issues to prevent false reports.

Signed-off-by: James Cole <james@firefly-iii.org>
2026-05-16 20:03:49 +02:00
James Cole d64bca7700 Merge branch 'main' into develop 2026-05-16 19:54:22 +02:00
James Cole 7d768cfa23 Add AI-generated security advisories section
Added a section regarding AI-generated security advisories to clarify reporting policies and potential consequences.

Signed-off-by: James Cole <james@firefly-iii.org>
2026-05-16 19:52:56 +02:00
James Cole fd50fbf193 Merge branch 'main' into develop 2026-05-12 18:48:04 +02:00
James Cole 134c232f45 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-05-12 18:47:48 +02:00
James Cole ce603f50d8 Fix https://github.com/firefly-iii/firefly-iii/issues/12243 2026-05-12 18:45:33 +02:00
github-actions[bot] 3b7bff4c57 Merge pull request #12240 from firefly-iii/release-1778473850
🤖 Automatically merge the PR into the develop branch.
2026-05-11 06:30:59 +02:00
JC5 1485f99579 🤖 Auto commit for release 'develop' on 2026-05-11 2026-05-11 06:30:51 +02:00
James Cole 7d24783c49 Merge pull request #12238 from firefly-iii/dependabot/npm_and_yarn/develop/vite-8.0.11
Bump vite from 8.0.10 to 8.0.11
2026-05-11 06:09:04 +02:00
James Cole c4ee3598e1 Merge pull request #12239 from firefly-iii/dependabot/github_actions/actions/dependency-review-action-5
Bump actions/dependency-review-action from 4 to 5
2026-05-11 06:08:49 +02:00
dependabot[bot] 8cf8e91448 Bump actions/dependency-review-action from 4 to 5
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4 to 5.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 03:56:03 +00:00
dependabot[bot] 28f2de0df7 Bump vite from 8.0.10 to 8.0.11
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.10 to 8.0.11.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.11/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 03:54:53 +00:00
github-actions[bot] 86adf11263 Merge pull request #12229 from firefly-iii/release-1778308665
🤖 Automatically merge the PR into the develop branch.
2026-05-09 08:37:52 +02:00
JC5 045f875041 🤖 Auto commit for release 'develop' on 2026-05-09 2026-05-09 08:37:45 +02:00
James Cole f781e9f2b6 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	package-lock.json
2026-05-09 08:32:43 +02:00
James Cole 5eb52812f0 Merge branch 'main' into develop
# Conflicts:
#	package-lock.json
2026-05-09 08:32:08 +02:00
James Cole 916abd8464 Merge pull request #12228 from firefly-iii/dependabot/npm_and_yarn/npm_and_yarn-3d438dea79
Bump @babel/plugin-transform-modules-systemjs from 7.29.0 to 7.29.4 in the npm_and_yarn group across 1 directory
2026-05-09 08:27:14 +02:00
dependabot[bot] 6baca9510f Bump @babel/plugin-transform-modules-systemjs
Bumps the npm_and_yarn group with 1 update in the / directory: [@babel/plugin-transform-modules-systemjs](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-modules-systemjs).


Updates `@babel/plugin-transform-modules-systemjs` from 7.29.0 to 7.29.4
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.29.4/packages/babel-plugin-transform-modules-systemjs)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-modules-systemjs"
  dependency-version: 7.29.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-09 05:54:10 +00:00
github-actions[bot] 5c22a40849 Merge pull request #12227 from firefly-iii/release-1778303410
🤖 Automatically merge the PR into the develop branch.
2026-05-09 07:10:19 +02:00
JC5 51e994056a 🤖 Auto commit for release 'develop' on 2026-05-09 2026-05-09 07:10:10 +02:00
James Cole 984e735bc1 Fix https://github.com/firefly-iii/firefly-iii/issues/12223 2026-05-09 07:02:06 +02:00
James Cole c1a271d9c4 Merge pull request #12226 from firefly-iii/dependabot/npm_and_yarn/npm_and_yarn-053c9c4054 2026-05-09 05:50:33 +02:00
dependabot[bot] 0f9a2f010c Bump fast-uri in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [fast-uri](https://github.com/fastify/fast-uri).


Updates `fast-uri` from 3.1.0 to 3.1.2
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.0...v3.1.2)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-08 23:26:04 +00:00
James Cole ed9557aaa0 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-05-05 19:52:46 +02:00
James Cole 1cc471fcc4 New guidelines. 2026-05-05 19:51:57 +02:00
github-actions[bot] 6c27424bfe Merge pull request #12219 from firefly-iii/release-1777868588
🤖 Automatically merge the PR into the develop branch.
2026-05-04 06:23:15 +02:00
JC5 38576f7fe0 🤖 Auto commit for release 'develop' on 2026-05-04 2026-05-04 06:23:09 +02:00
github-actions[bot] 5b80a5bdbe Merge pull request #12215 from firefly-iii/release-1777795578
🤖 Automatically merge the PR into the develop branch.
2026-05-03 10:06:25 +02:00
JC5 0202f4abd9 🤖 Auto commit for release 'develop' on 2026-05-03 2026-05-03 10:06:18 +02:00
James Cole 615d568479 Change sentence 2026-05-03 10:00:29 +02:00
github-actions[bot] 2ace0d3f23 Merge pull request #12214 from firefly-iii/release-1777794370
🤖 Automatically merge the PR into the develop branch.
2026-05-03 09:46:17 +02:00
JC5 42204f8dc1 🤖 Auto commit for release 'develop' on 2026-05-03 2026-05-03 09:46:10 +02:00
James Cole cfac8fa569 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-05-03 09:24:38 +02:00
James Cole 04704392f3 Fix amount display in budget overview. 2026-05-03 09:24:03 +02:00
github-actions[bot] 4b9bbc9d6a Merge pull request #12213 from firefly-iii/release-1777786936
🤖 Automatically merge the PR into the develop branch.
2026-05-03 07:42:23 +02:00
JC5 f24f535d39 🤖 Auto commit for release 'develop' on 2026-05-03 2026-05-03 07:42:17 +02:00
James Cole 3a9ac03358 Add entry in preferences. 2026-05-02 15:04:23 +02:00
github-actions[bot] 4ac7fec5f6 Merge pull request #12211 from firefly-iii/release-1777697309
🤖 Automatically merge the PR into the develop branch.
2026-05-02 06:48:38 +02:00
JC5 b6759c3fa0 🤖 Auto commit for release 'develop' on 2026-05-02 2026-05-02 06:48:29 +02:00
James Cole 4a83b1e3e5 Fix phpstan issues. 2026-05-02 06:38:25 +02:00
James Cole 4b701dfc4c Fix bad compare. 2026-05-02 06:31:25 +02:00
James Cole b2997d0a5a Remove unused method. 2026-05-01 13:51:03 +02:00
James Cole fd8722e401 Fix https://github.com/orgs/firefly-iii/discussions/12210 2026-05-01 13:46:57 +02:00
James Cole e46153330a Fix https://github.com/firefly-iii/firefly-iii/issues/12207 2026-04-30 14:29:36 +02:00
James Cole 525f0c752a Fix https://github.com/orgs/firefly-iii/discussions/11408 2026-04-30 07:55:43 +02:00
James Cole 13e4160e85 Fix https://github.com/orgs/firefly-iii/discussions/12097 2026-04-30 06:22:10 +02:00
James Cole 7806d63f91 Fix https://github.com/orgs/firefly-iii/discussions/11455 2026-04-30 06:08:07 +02:00
James Cole 8c620b6536 Fix https://github.com/firefly-iii/firefly-iii/issues/12204 2026-04-29 06:36:25 +02:00
github-actions[bot] e47ce30579 Merge pull request #12202 from firefly-iii/release-1777358720
🤖 Automatically merge the PR into the develop branch.
2026-04-28 08:45:29 +02:00
JC5 7e6eadc047 🤖 Auto commit for release 'develop' on 2026-04-28 2026-04-28 08:45:20 +02:00
Sander Dorigo dae4f6f351 Add clarity on password validation api 2026-04-28 08:38:04 +02:00
github-actions[bot] 8c8af51bc4 Merge pull request #12200 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-04-27 18:55:46 +02:00
github-actions[bot] a5e1cba39c Merge pull request #12199 from firefly-iii/release-1777308933
🤖 Automatically merge the PR into the develop branch.
2026-04-27 18:55:40 +02:00
JC5 96d56ad723 🤖 Auto commit for release 'v6.6.2' on 2026-04-27 2026-04-27 18:55:33 +02:00
James Cole e4b1c3045e Update Mago Lint command to use vendor path
Signed-off-by: James Cole <james@firefly-iii.org>
2026-04-27 18:50:27 +02:00
James Cole e974594fe3 Merge branch 'main' into develop 2026-04-27 18:46:22 +02:00
James Cole c93a2dc23a Refactor CI workflow by removing Mago setup
Removed Mago setup step and updated command path.

Signed-off-by: James Cole <james@firefly-iii.org>
2026-04-27 18:41:58 +02:00
James Cole 639efee78a Add mago. 2026-04-27 18:41:49 +02:00
James Cole eb4971fec6 Add latest version setup for Mago in release workflow
Signed-off-by: James Cole <james@firefly-iii.org>
2026-04-27 18:36:24 +02:00
James Cole 0c63a3380d Refactor Setup Mago step in release workflow
Removed working-directory input from Setup Mago step.

Signed-off-by: James Cole <james@firefly-iii.org>
2026-04-27 18:32:52 +02:00
James Cole edd30b2d42 Merge branch 'main' into develop 2026-04-27 18:28:03 +02:00
github-actions[bot] a4f6c2b748 Merge pull request #12198 from firefly-iii/release-1777307205
🤖 Automatically merge the PR into the develop branch.
2026-04-27 18:26:52 +02:00
JC5 5fc90e0f76 🤖 Auto commit for release 'develop' on 2026-04-27 2026-04-27 18:26:45 +02:00
James Cole e8ab7d8a93 Specify version for Mago setup in release workflow
Update Mago setup to use a specific version.

Signed-off-by: James Cole <james@firefly-iii.org>
2026-04-27 18:20:27 +02:00
James Cole e73d04bc0f Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-04-27 18:15:08 +02:00
James Cole c0eca4298a Updated templates. 2026-04-27 18:14:58 +02:00
github-actions[bot] dafb43393a Merge pull request #12197 from firefly-iii/release-1777306297
🤖 Automatically merge the PR into the develop branch.
2026-04-27 18:11:47 +02:00
JC5 ba8155a391 🤖 Auto commit for release 'develop' on 2026-04-27 2026-04-27 18:11:37 +02:00
James Cole 9e4329ebfc Update changelog. 2026-04-27 18:05:39 +02:00
James Cole 60e2645e54 Merge pull request #12194 from firefly-iii/dependabot/npm_and_yarn/develop/vite-8.0.10
Bump vite from 8.0.8 to 8.0.10
2026-04-27 16:18:03 +02:00
github-actions[bot] 33a9e5b3f0 Merge pull request #12196 from firefly-iii/release-1777263537
🤖 Automatically merge the PR into the develop branch.
2026-04-27 06:19:05 +02:00
JC5 cd0290475b 🤖 Auto commit for release 'develop' on 2026-04-27 2026-04-27 06:18:57 +02:00
dependabot[bot] 45528cf7d3 Bump vite from 8.0.8 to 8.0.10
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.10/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-27 03:52:35 +00:00
James Cole 7cfd21362a Fix https://github.com/firefly-iii/firefly-iii/issues/12186 2026-04-26 05:12:39 +02:00
github-actions[bot] f91063c88b Merge pull request #12184 from firefly-iii/release-1777100403
🤖 Automatically merge the PR into the develop branch.
2026-04-25 09:00:10 +02:00
JC5 30c1f4c13d 🤖 Auto commit for release 'develop' on 2026-04-25 2026-04-25 09:00:03 +02:00
James Cole 39be5075eb Clean up JS scripts. 2026-04-25 08:54:28 +02:00
James Cole 714133dad5 Merge pull request #12182 from tasnim0tantawi/bug/fix-sidebar-expanding-when-navigating
fix shrinked sidebar expanding when navigating by clicking on icons
2026-04-25 08:45:47 +02:00
James Cole 1caf639b85 Move to jQuery func 2026-04-25 08:45:20 +02:00
github-actions[bot] 33ad34a719 Merge pull request #12183 from firefly-iii/release-1777088715
🤖 Automatically merge the PR into the develop branch.
2026-04-25 05:45:23 +02:00
JC5 dca1d962af 🤖 Auto commit for release 'develop' on 2026-04-25 2026-04-25 05:45:16 +02:00
tasnim0tantawi 1ebff22785 fix delay 2026-04-24 22:42:22 +03:00
tasnim0tantawi 8b14a11969 lines 2026-04-24 22:28:06 +03:00
tasnim0tantawi 7b0e40543b all code in firefly js 2026-04-24 22:26:38 +03:00
tasnim0tantawi ea57a0a8c8 remove code from default twig 2026-04-24 21:49:37 +03:00
tasnim0tantawi 62ee37f631 implement collapse save in v1 2026-04-24 21:17:19 +03:00
tasnim0tantawi c96cc8d941 fix shrinked sidebar expanding when navigating by clicking on icons 2026-04-24 21:01:03 +03:00
James Cole 0e97b4a6b8 Add script and add exception. 2026-04-24 10:26:48 +02:00
github-actions[bot] 3de7cfd3bf Merge pull request #12181 from firefly-iii/release-1777003029
🤖 Automatically merge the PR into the develop branch.
2026-04-24 05:57:17 +02:00
JC5 a6fd805202 🤖 Auto commit for release 'develop' on 2026-04-24 2026-04-24 05:57:09 +02:00
James Cole 541e1ac7b4 And now fix them for real. 2026-04-24 05:51:44 +02:00
James Cole 1c6b8af3a3 Fix Mago issues. 2026-04-24 05:45:42 +02:00
James Cole b33b22e0b3 Let's add some authentication before I am drowned in reports. 2026-04-24 05:37:55 +02:00
James Cole 30cce327e8 Add helpers and fix missing string. 2026-04-24 05:34:40 +02:00
James Cole 994e3dae18 Merge pull request #12179 from tasnim0tantawi/feature/client-side-password-verification-on-register
implement password validation JS script
2026-04-24 05:09:19 +02:00
tasnim0tantawi b065150968 implement password validation JS script 2026-04-23 22:08:24 +03:00
James Cole d18e9ffad0 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-04-23 03:08:53 +02:00
James Cole fad5438909 Fix https://github.com/firefly-iii/firefly-iii/issues/12170 2026-04-23 03:07:03 +02:00
github-actions[bot] 4799c3c97f Merge pull request #12173 from firefly-iii/release-1776876664
🤖 Automatically merge the PR into the develop branch.
2026-04-22 18:51:12 +02:00
JC5 0a4d401f57 🤖 Auto commit for release 'develop' on 2026-04-22 2026-04-22 18:51:05 +02:00
James Cole f890f55864 Fix if-statement. 2026-04-22 18:45:45 +02:00
James Cole a2189a8577 Fix thingie. 2026-04-22 18:40:21 +02:00
James Cole e81af7e051 Fix #12169 2026-04-22 18:34:16 +02:00
James Cole 32250ddc1a Fix https://github.com/firefly-iii/firefly-iii/issues/12169 2026-04-22 07:54:53 +02:00
github-actions[bot] a9723a85d1 Merge pull request #12163 from firefly-iii/release-1776658280
🤖 Automatically merge the PR into the develop branch.
2026-04-20 06:11:26 +02:00
JC5 28fd3600b2 🤖 Auto commit for release 'develop' on 2026-04-20 2026-04-20 06:11:21 +02:00
github-actions[bot] 4df332e0d5 Merge pull request #12156 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-04-19 11:15:56 +02:00
github-actions[bot] f72e168a0d Merge pull request #12155 from firefly-iii/release-1776590142
🤖 Automatically merge the PR into the develop branch.
2026-04-19 11:15:51 +02:00
JC5 a567280d1b 🤖 Auto commit for release 'v6.6.1' on 2026-04-19 2026-04-19 11:15:43 +02:00
James Cole 420f436fb6 Update changelog. 2026-04-19 11:09:52 +02:00
James Cole 10a6d96134 Fix https://github.com/firefly-iii/firefly-iii/issues/12154 2026-04-19 11:07:10 +02:00
github-actions[bot] 9ee2d4d487 Merge pull request #12153 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-04-19 09:59:51 +02:00
github-actions[bot] 66b6e057f2 Merge pull request #12152 from firefly-iii/release-1776585580
🤖 Automatically merge the PR into the develop branch.
2026-04-19 09:59:46 +02:00
JC5 fade8cc41e 🤖 Auto commit for release 'v6.6.0' on 2026-04-19 2026-04-19 09:59:40 +02:00
James Cole 7690042ab7 Add missing fixed issue. 2026-04-19 09:54:22 +02:00
github-actions[bot] ef6ffb6f2d Merge pull request #12150 from firefly-iii/release-1776534459
🤖 Automatically merge the PR into the develop branch.
2026-04-18 19:47:47 +02:00
JC5 a158e4596c 🤖 Auto commit for release 'develop' on 2026-04-18 2026-04-18 19:47:39 +02:00
James Cole e6f616880a Fix call to header. 2026-04-18 19:42:04 +02:00
github-actions[bot] c57233a5f7 Merge pull request #12149 from firefly-iii/release-1776514174
🤖 Automatically merge the PR into the develop branch.
2026-04-18 14:09:41 +02:00
JC5 c1816e2136 🤖 Auto commit for release 'develop' on 2026-04-18 2026-04-18 14:09:34 +02:00
James Cole fdcf73b775 Fix broken oauth controller 2026-04-18 14:04:12 +02:00
github-actions[bot] 502c392331 Merge pull request #12148 from firefly-iii/release-1776513742
🤖 Automatically merge the PR into the develop branch.
2026-04-18 14:02:30 +02:00
JC5 5af66155f8 🤖 Auto commit for release 'develop' on 2026-04-18 2026-04-18 14:02:22 +02:00
James Cole fa9e4be353 Catch missing variable. 2026-04-18 13:52:43 +02:00
github-actions[bot] 7b7cc784d1 Merge pull request #12145 from firefly-iii/release-1776489136
🤖 Automatically merge the PR into the develop branch.
2026-04-18 07:12:23 +02:00
JC5 ee92f3bbe5 🤖 Auto commit for release 'develop' on 2026-04-18 2026-04-18 07:12:16 +02:00
James Cole 30c2625c6a Add warning. 2026-04-18 07:06:30 +02:00
James Cole 2a90258cdb Expand changelog. 2026-04-18 06:40:50 +02:00
github-actions[bot] 929bb0e5f3 Merge pull request #12144 from firefly-iii/release-1776486586
🤖 Automatically merge the PR into the develop branch.
2026-04-18 06:29:54 +02:00
JC5 91ca3f38eb 🤖 Auto commit for release 'develop' on 2026-04-18 2026-04-18 06:29:46 +02:00
James Cole b4b67b0759 Fix button and translations. 2026-04-18 06:24:13 +02:00
133 changed files with 1778 additions and 1173 deletions
+102 -94
View File
@@ -294,42 +294,42 @@
},
{
"name": "ergebnis/agent-detector",
"version": "1.1.1",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/ergebnis/agent-detector.git",
"reference": "5b654a9f1ff8a5d2ce6a57568df5ae8801c87f64"
"reference": "e211f17928c8b95a51e06040792d57f5462fb271"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/5b654a9f1ff8a5d2ce6a57568df5ae8801c87f64",
"reference": "5b654a9f1ff8a5d2ce6a57568df5ae8801c87f64",
"url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/e211f17928c8b95a51e06040792d57f5462fb271",
"reference": "e211f17928c8b95a51e06040792d57f5462fb271",
"shasum": ""
},
"require": {
"php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.50.0",
"ergebnis/composer-normalize": "^2.51.0",
"ergebnis/license": "^2.7.0",
"ergebnis/php-cs-fixer-config": "^6.60.2",
"ergebnis/phpstan-rules": "^2.13.1",
"ergebnis/phpunit-slow-test-detector": "^2.24.0",
"ergebnis/rector-rules": "^1.16.0",
"ergebnis/rector-rules": "^1.18.1",
"fakerphp/faker": "^1.24.1",
"infection/infection": "^0.26.6",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^2.1.46",
"phpstan/phpstan": "^2.1.54",
"phpstan/phpstan-deprecation-rules": "^2.0.4",
"phpstan/phpstan-phpunit": "^2.0.16",
"phpstan/phpstan-strict-rules": "^2.0.10",
"phpunit/phpunit": "^9.6.34",
"rector/rector": "^2.4.1"
"rector/rector": "^2.4.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.0-dev"
"dev-main": "1.2-dev"
},
"composer-normalize": {
"indent-size": 2,
@@ -359,7 +359,7 @@
"security": "https://github.com/ergebnis/agent-detector/blob/main/.github/SECURITY.md",
"source": "https://github.com/ergebnis/agent-detector"
},
"time": "2026-04-10T13:45:13+00:00"
"time": "2026-05-07T08:19:07+00:00"
},
{
"name": "evenement/evenement",
@@ -471,16 +471,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.95.1",
"version": "v3.95.2",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "a9727678fbd12997f1d9de8f4a37824ed9df1065"
"reference": "a28d88a5e172b27e78d0816992b15a9df3da20f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a9727678fbd12997f1d9de8f4a37824ed9df1065",
"reference": "a9727678fbd12997f1d9de8f4a37824ed9df1065",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a28d88a5e172b27e78d0816992b15a9df3da20f1",
"reference": "a28d88a5e172b27e78d0816992b15a9df3da20f1",
"shasum": ""
},
"require": {
@@ -512,8 +512,8 @@
"symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0"
},
"require-dev": {
"facile-it/paraunit": "^1.3.1 || ^2.8.0",
"infection/infection": "^0.32.6",
"facile-it/paraunit": "^1.3.1 || ^2.11.0",
"infection/infection": "^0.32.7",
"justinrainbow/json-schema": "^6.8.0",
"keradus/cli-executor": "^2.3",
"mikey179/vfsstream": "^1.6.12",
@@ -564,7 +564,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.1"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.2"
},
"funding": [
{
@@ -572,7 +572,7 @@
"type": "github"
}
],
"time": "2026-04-12T17:00:09+00:00"
"time": "2026-05-15T09:20:44+00:00"
},
{
"name": "psr/container",
@@ -1255,16 +1255,16 @@
},
{
"name": "sebastian/diff",
"version": "8.1.0",
"version": "8.3.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "9c957d730257f49c873f3761674559bd90098a7d"
"reference": "b36d33b6e796513de7cb7df053afb3f55eefcd47"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/9c957d730257f49c873f3761674559bd90098a7d",
"reference": "9c957d730257f49c873f3761674559bd90098a7d",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b36d33b6e796513de7cb7df053afb3f55eefcd47",
"reference": "b36d33b6e796513de7cb7df053afb3f55eefcd47",
"shasum": ""
},
"require": {
@@ -1277,7 +1277,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "8.1-dev"
"dev-main": "8.3-dev"
}
},
"autoload": {
@@ -1310,7 +1310,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"security": "https://github.com/sebastianbergmann/diff/security/policy",
"source": "https://github.com/sebastianbergmann/diff/tree/8.1.0"
"source": "https://github.com/sebastianbergmann/diff/tree/8.3.0"
},
"funding": [
{
@@ -1330,20 +1330,20 @@
"type": "tidelift"
}
],
"time": "2026-04-05T12:02:33+00:00"
"time": "2026-05-15T04:58:09+00:00"
},
{
"name": "symfony/console",
"version": "v8.0.8",
"version": "v8.0.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7"
"reference": "3156577f46a38aa1b9323aad223de7a9cd426782"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/5b66d385dc58f69652e56f78a4184615e3f2b7f7",
"reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7",
"url": "https://api.github.com/repos/symfony/console/zipball/3156577f46a38aa1b9323aad223de7a9cd426782",
"reference": "3156577f46a38aa1b9323aad223de7a9cd426782",
"shasum": ""
},
"require": {
@@ -1400,7 +1400,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v8.0.8"
"source": "https://github.com/symfony/console/tree/v8.0.11"
},
"funding": [
{
@@ -1420,20 +1420,20 @@
"type": "tidelift"
}
],
"time": "2026-03-30T15:14:47+00:00"
"time": "2026-05-13T12:07:53+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.6.0",
"version": "v3.7.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
"reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b",
"reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b",
"shasum": ""
},
"require": {
@@ -1446,7 +1446,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.6-dev"
"dev-main": "3.7-dev"
}
},
"autoload": {
@@ -1471,7 +1471,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0"
},
"funding": [
{
@@ -1482,25 +1482,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-25T14:21:43+00:00"
"time": "2026-04-13T15:52:40+00:00"
},
{
"name": "symfony/event-dispatcher",
"version": "v8.0.8",
"version": "v8.0.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6"
"reference": "0c3c1a17604c4dbbec4b93fe162c538482096e1f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f662acc6ab22a3d6d716dcb44c381c6002940df6",
"reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0c3c1a17604c4dbbec4b93fe162c538482096e1f",
"reference": "0c3c1a17604c4dbbec4b93fe162c538482096e1f",
"shasum": ""
},
"require": {
@@ -1552,7 +1556,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.8"
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.9"
},
"funding": [
{
@@ -1572,20 +1576,20 @@
"type": "tidelift"
}
],
"time": "2026-03-30T15:14:47+00:00"
"time": "2026-04-18T13:51:42+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v3.6.0",
"version": "v3.7.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "59eb412e93815df44f05f342958efa9f46b1e586"
"reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586",
"reference": "59eb412e93815df44f05f342958efa9f46b1e586",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32",
"reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32",
"shasum": ""
},
"require": {
@@ -1599,7 +1603,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.6-dev"
"dev-main": "3.7-dev"
}
},
"autoload": {
@@ -1632,7 +1636,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0"
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0"
},
"funding": [
{
@@ -1643,25 +1647,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-25T14:21:43+00:00"
"time": "2026-01-05T13:30:16+00:00"
},
{
"name": "symfony/filesystem",
"version": "v8.0.8",
"version": "v8.0.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "66b769ae743ce2d13e435528fbef4af03d623e5a"
"reference": "224db910898ce1317b892a9a1338f1f8f17eb7c7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/66b769ae743ce2d13e435528fbef4af03d623e5a",
"reference": "66b769ae743ce2d13e435528fbef4af03d623e5a",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/224db910898ce1317b892a9a1338f1f8f17eb7c7",
"reference": "224db910898ce1317b892a9a1338f1f8f17eb7c7",
"shasum": ""
},
"require": {
@@ -1698,7 +1706,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v8.0.8"
"source": "https://github.com/symfony/filesystem/tree/v8.0.11"
},
"funding": [
{
@@ -1718,7 +1726,7 @@
"type": "tidelift"
}
],
"time": "2026-03-30T15:14:47+00:00"
"time": "2026-05-11T16:39:47+00:00"
},
{
"name": "symfony/finder",
@@ -1861,7 +1869,7 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@@ -1920,7 +1928,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
},
"funding": [
{
@@ -1944,16 +1952,16 @@
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df"
"reference": "4864388bfbd3001ce88e234fab652acd91fdc57e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/ad1b7b9092976d6c948b8a187cec9faaea9ec1df",
"reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e",
"reference": "4864388bfbd3001ce88e234fab652acd91fdc57e",
"shasum": ""
},
"require": {
@@ -2002,7 +2010,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0"
},
"funding": [
{
@@ -2022,11 +2030,11 @@
"type": "tidelift"
}
],
"time": "2026-04-10T16:19:22+00:00"
"time": "2026-04-26T13:13:48+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
@@ -2087,7 +2095,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0"
},
"funding": [
{
@@ -2111,7 +2119,7 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
@@ -2172,7 +2180,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0"
},
"funding": [
{
@@ -2196,7 +2204,7 @@
},
{
"name": "symfony/polyfill-php80",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
@@ -2256,7 +2264,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
},
"funding": [
{
@@ -2280,7 +2288,7 @@
},
{
"name": "symfony/polyfill-php81",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
@@ -2336,7 +2344,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-php81/tree/v1.37.0"
},
"funding": [
{
@@ -2360,7 +2368,7 @@
},
{
"name": "symfony/polyfill-php84",
"version": "v1.36.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php84.git",
@@ -2416,7 +2424,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php84/tree/v1.36.0"
"source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0"
},
"funding": [
{
@@ -2440,16 +2448,16 @@
},
{
"name": "symfony/process",
"version": "v8.0.8",
"version": "v8.0.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc"
"reference": "26d89e459f037d2873300605d0a07e7a8ef84db0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
"url": "https://api.github.com/repos/symfony/process/zipball/26d89e459f037d2873300605d0a07e7a8ef84db0",
"reference": "26d89e459f037d2873300605d0a07e7a8ef84db0",
"shasum": ""
},
"require": {
@@ -2481,7 +2489,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v8.0.8"
"source": "https://github.com/symfony/process/tree/v8.0.11"
},
"funding": [
{
@@ -2501,20 +2509,20 @@
"type": "tidelift"
}
],
"time": "2026-03-30T15:14:47+00:00"
"time": "2026-05-11T16:56:32+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.6.1",
"version": "v3.7.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
"reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
"reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a",
"reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a",
"shasum": ""
},
"require": {
@@ -2532,7 +2540,7 @@
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.6-dev"
"dev-main": "3.7-dev"
}
},
"autoload": {
@@ -2568,7 +2576,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
"source": "https://github.com/symfony/service-contracts/tree/v3.7.0"
},
"funding": [
{
@@ -2588,7 +2596,7 @@
"type": "tidelift"
}
],
"time": "2025-07-15T11:30:57+00:00"
"time": "2026-03-28T09:44:51+00:00"
},
{
"name": "symfony/stopwatch",
@@ -2658,16 +2666,16 @@
},
{
"name": "symfony/string",
"version": "v8.0.8",
"version": "v8.0.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "ae9488f874d7603f9d2dfbf120203882b645d963"
"reference": "39be2ad058a3c0bd558edca23e65f009865d75ff"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/ae9488f874d7603f9d2dfbf120203882b645d963",
"reference": "ae9488f874d7603f9d2dfbf120203882b645d963",
"url": "https://api.github.com/repos/symfony/string/zipball/39be2ad058a3c0bd558edca23e65f009865d75ff",
"reference": "39be2ad058a3c0bd558edca23e65f009865d75ff",
"shasum": ""
},
"require": {
@@ -2724,7 +2732,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v8.0.8"
"source": "https://github.com/symfony/string/tree/v8.0.11"
},
"funding": [
{
@@ -2744,7 +2752,7 @@
"type": "tidelift"
}
],
"time": "2026-03-30T15:14:47+00:00"
"time": "2026-05-13T12:07:53+00:00"
}
],
"packages-dev": [],
+8
View File
@@ -12,6 +12,14 @@ body:
required: true
- label: I've used [the search](https://github.com/firefly-iii/firefly-iii/issues?q=is%3Aissue) and this has not been requested before.
required: true
- type: checkboxes
attributes:
label: Use of AI
description: AI agents like Claude and CoPilot are not reliable tools. Do not use them.
options:
- label: I'm a real person and wrote this bug without assistance from AI.
required: true
- type: textarea
attributes:
+4 -2
View File
@@ -1,3 +1,5 @@
# [Contributing guidelines](https://docs.firefly-iii.org/explanation/support/#contributing-code)
# Contributing guidelines
[Contributing guidelines](https://docs.firefly-iii.org/explanation/support/#contributing-code)
This repository is part of "Firefly III", by being either a main repository for code, a supporting repository with guidelines and documentation, or a repository with tools and secondary code.
The [contribution guidelines](https://docs.firefly-iii.org/explanation/contributing/) for this repository are the same as they are for ALL Firefly III related repositories, and they can be found in [the Firefly III documentation](https://docs.firefly-iii.org/explanation/contributing/).
+10 -1
View File
@@ -3,9 +3,18 @@
Firefly III is an application to manage your personal finances. As such, the developer has adopted this security
disclosure and response policy to ensure that critical issues are responsibly handled.
## AI-generated security advisories
> [!WARNING]
> Due to a large number of irrelevant, noisy and uninformed AI-generated security advisories coming my way, reporting any the following security issues may return in a permanent ban from the Firefly III organization on GitHub.
1. Any SSRF in any user provided URL field (webhooks, ntfy, SimpleFIN, Slack). It's by design that users may set-up any URL they want, be it internal, private or non-existing.
2. Any XSS issue without a viable attack tree. If you can find a spot where Firefly III or the associated tools render unescaped data, it's not a security issue unless you can show me an actual attack that gets that data into the system.
3. Any issue that is not true. AI models have already *hallucinated* security issues in Firefly III. They've referred to **non-existing** functions, templates and files. Including line numbers and code excerpts. Validate your findings before you report them to me.
## Supported versions
Only the latest Firefly III release is maintained. Applicable fixes, including security fixes, will not backported to
Only the latest Firefly III release is maintained. Applicable fixes, including security fixes, will not be backported to
older release branches. Please refer to [releases.md](https://github.com/firefly-iii/firefly-iii/blob/main/releases.md) for details.
## Reporting a vulnerability - private disclosure process
+1 -1
View File
@@ -13,4 +13,4 @@ jobs:
with:
fetch-depth: 0
- name: 'Dependency review'
uses: actions/dependency-review-action@v4
uses: actions/dependency-review-action@v5
+4 -7
View File
@@ -99,11 +99,6 @@ jobs:
env:
FIREFLY_III_ROOT: /github/workspace
GH_TOKEN: ''
- name: Setup Mago
uses: nhedger/setup-mago@v1
with:
version: "latest"
working-directory: "."
- name: Run CI
run: |
cp .env.example .env
@@ -113,17 +108,19 @@ jobs:
# format code.
echo "Will now run Mago Format"
mago format
./vendor/bin/mago format
sudo chown -R runner:docker resources/lang
echo "Will now run PHPCS"
.ci/phpcs.sh
# lint and check
echo "Will now run Mago Lint"
mago lint
./vendor/bin/mago lint
echo "Will now run PHPstan"
.ci/phpstan.sh
rm .env
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Crowdin action
uses: crowdin/github-action@v2
with:
+1
View File
@@ -4,6 +4,7 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
## 2026
- tasnim0tantawi
- Joe Longendyke
- Daniel Holøien
- Matthew Grove
@@ -179,9 +179,9 @@ final class CategoryController extends Controller
// order by amount
usort($return, static fn (array $a, array $b): int => ((float) $a['entries']['spent'] + (float) $a['entries']['earned'])
< ((float) $b['entries']['spent'] + (float) $b['entries']['earned'])
? 1
: -1);
< ((float) $b['entries']['spent'] + (float) $b['entries']['earned'])
? 1
: -1);
return response()->json($this->clean($return));
}
@@ -158,10 +158,7 @@ final class TagController extends Controller
'currency_id' => (string) $foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignKey]['difference'] = bcadd(
(string) $response[$foreignKey]['difference'],
Steam::positive($journal['foreign_amount'])
);
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference'];
}
}
@@ -155,10 +155,7 @@ final class TagController extends Controller
'currency_id' => (string) $foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignKey]['difference'] = bcadd(
(string) $response[$foreignKey]['difference'],
Steam::positive($journal['foreign_amount'])
);
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference']; // intentional float
}
}
@@ -59,8 +59,9 @@ final class DestroyController extends Controller
public function destroy(DestroyRequest $request, TransactionCurrency $from, TransactionCurrency $to): JsonResponse
{
$first = Carbon::create(1970, 1, 1);
$this->repository->deleteRates($from, $to);
event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request)));
event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request), $first));
return response()->json([], 204);
}
@@ -74,7 +75,7 @@ final class DestroyController extends Controller
if (!$exchangeRate instanceof CurrencyExchangeRate) {
throw new FireflyException('Bla');
}
event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request)));
event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request), $date));
return response()->json([], 204);
}
@@ -85,7 +86,7 @@ final class DestroyController extends Controller
$to = $exchangeRate->toCurrency;
$this->repository->deleteRate($exchangeRate);
event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request)));
event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request), $exchangeRate->date));
return response()->json([], 204);
}
@@ -95,7 +95,9 @@ final class StoreController extends Controller
$transactionGroup = $this->groupRepository->store($data);
} catch (DuplicateTransactionException $e) {
Log::warning('Caught a duplicate transaction. Return error message.');
$validator = Validator::make(['transactions' => [['description' => $e->getMessage()]]], ['transactions.0.description' => new IsDuplicateTransaction()]);
$validator = Validator::make(['transactions' => [['description' => $e->getMessage()]]], [
'transactions.0.description' => new IsDuplicateTransaction(),
]);
throw new ValidationException($validator);
} catch (FireflyException $e) {
@@ -158,7 +158,7 @@ final class ConfigurationController extends Controller
$enableExternalRates = FireflyConfig::get('enable_external_rates', false);
$allowWebhooks = FireflyConfig::get('allow_webhooks', false);
$enableBatchProcessing = FireflyConfig::get('enable_batch_processing', false);
$validUrlProtocols = FireflyConfig::get('valid_url_protocols', 'http,https');
$validUrlProtocols = FireflyConfig::get('valid_url_protocols', config('firefly.valid_url_protocols'));
return [
'is_demo_site' => $isDemoSite?->data,
@@ -171,7 +171,7 @@ final class ConfigurationController extends Controller
'enable_external_rates' => $enableExternalRates?->data,
'allow_webhooks' => $allowWebhooks?->data,
'enable_batch_processing' => $enableBatchProcessing?->data,
'valid_url_protocols' => $validUrlProtocols->data ?? 'http,https',
'valid_url_protocols' => $validUrlProtocols->data ?? config('firefly.valid_url_protocols'),
];
}
@@ -49,7 +49,7 @@ class StoreRequest extends FormRequest
*/
public function getAll(): array
{
$fields = ['order' => ['order', 'convertInteger']];
$fields = ['order' => ['order', 'convertInteger']];
$data = $this->getAllData($fields);
$data['name'] = $this->convertString('name');
$data['accounts'] = $this->parseAccounts($this->get('accounts'));
@@ -70,7 +70,7 @@ class UpdateRequest extends FormRequest
];
$reps = $this->getRepetitionData();
$transactions = $this->getTransactionData();
$return = ['recurrence' => $this->getAllData($fields)];
$return = ['recurrence' => $this->getAllData($fields)];
if (null !== $reps) {
$return['repetitions'] = $reps;
}
@@ -162,12 +162,12 @@ class StoreRequest extends FormRequest
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
// dates
'transactions.*.interest_date' => 'date|nullable',
'transactions.*.book_date' => 'date|nullable',
'transactions.*.process_date' => 'date|nullable',
'transactions.*.due_date' => 'date|nullable',
'transactions.*.payment_date' => 'date|nullable',
'transactions.*.invoice_date' => 'date|nullable',
'transactions.*.interest_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
'transactions.*.book_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
'transactions.*.process_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
'transactions.*.due_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
'transactions.*.payment_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
'transactions.*.invoice_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
];
}
@@ -209,12 +209,12 @@ class UpdateRequest extends FormRequest
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
// dates
'transactions.*.interest_date' => 'date|nullable',
'transactions.*.book_date' => 'date|nullable',
'transactions.*.process_date' => 'date|nullable',
'transactions.*.due_date' => 'date|nullable',
'transactions.*.payment_date' => 'date|nullable',
'transactions.*.invoice_date' => 'date|nullable',
'transactions.*.interest_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
'transactions.*.book_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
'transactions.*.process_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
'transactions.*.due_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
'transactions.*.payment_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
'transactions.*.invoice_date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
];
}
+7 -6
View File
@@ -57,12 +57,13 @@ class Cron extends Command
public function handle(): int
{
$doAll = !$this->option('download-cer')
&& !$this->option('create-recurring')
&& !$this->option('create-auto-budgets')
&& !$this->option('send-subscription-warnings')
&& !$this->option('check-version')
&& !$this->option('send-webhook-messages');
$doAll
= !$this->option('download-cer')
&& !$this->option('create-recurring')
&& !$this->option('create-auto-budgets')
&& !$this->option('send-subscription-warnings')
&& !$this->option('check-version')
&& !$this->option('send-webhook-messages');
$date = null;
try {
@@ -26,7 +26,9 @@ namespace FireflyIII\Console\Commands\Upgrade;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class RepairsPostgresSequences extends Command
{
@@ -72,8 +74,6 @@ class RepairsPostgresSequences extends Command
'locations',
'migrations',
'notes',
'oauth_clients',
'oauth_personal_access_clients',
'object_groups',
'permissions',
'piggy_bank_events',
@@ -106,11 +106,23 @@ class RepairsPostgresSequences extends Command
foreach ($tablesToCheck as $tableToCheck) {
$this->friendlyLine(sprintf('Checking the next id sequence for table "%s".', $tableToCheck));
$highestId = DB::table($tableToCheck)->select(DB::raw('MAX(id)'))->first();
$nextId = DB::table($tableToCheck)
->select(DB::raw(sprintf('nextval(\'%s_id_seq\')', $tableToCheck)))
->first()
;
try {
$highestId = DB::table($tableToCheck)->select(DB::raw('MAX(id)'))->first();
} catch (QueryException $e) {
Log::warning(sprintf('Could not select max, but will ignore this: %s', $e->getMessage()));
continue;
}
try {
$nextId = DB::table($tableToCheck)
->select(DB::raw(sprintf('nextval(\'%s_id_seq\')', $tableToCheck)))
->first()
;
} catch (QueryException $e) {
Log::warning(sprintf('Could not get nextval, but will ignore this: %s', $e->getMessage()));
$nextId = null;
}
if (null === $nextId) {
$this->friendlyInfo(sprintf('nextval is NULL for table "%s", go to next table.', $tableToCheck));
@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Events\Model\CurrencyExchangeRate;
use Carbon\Carbon;
use FireflyIII\Events\Event;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
@@ -37,7 +38,8 @@ class DestroyedCurrencyExchangeRate extends Event
public function __construct(
public TransactionCurrency $from,
public TransactionCurrency $to,
public UserGroup $userGroup
public UserGroup $userGroup,
public Carbon $date
) {
Log::debug(sprintf('DestroyedCurrencyExchangeRate(%s, %s) Event', $from->code, $to->code));
}
@@ -48,10 +48,12 @@ class TransactionGroupEventObjects
public function appendFromTransactionGroup(TransactionGroup $transactionGroup): void
{
Log::debug(sprintf('Appended transaction group #%d', $transactionGroup->id));
$this->transactionGroups->push($transactionGroup);
/** @var TransactionJournal $journal */
foreach ($transactionGroup->transactionJournals as $journal) {
Log::debug(sprintf('Appended transaction journal #%d', $journal->id));
$this->transactionJournals->push($journal);
$this->budgets = $this->budgets->merge($journal->budgets);
$this->categories = $this->categories->merge($journal->categories);
@@ -59,6 +61,7 @@ class TransactionGroupEventObjects
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
Log::debug(sprintf('Appended account #%d', $transaction->account->id));
$this->accounts->push($transaction->account);
}
}
@@ -69,4 +72,25 @@ class TransactionGroupEventObjects
$this->tags = $this->tags->unique('id');
$this->accounts = $this->accounts->unique('id');
}
public function collectFromCollection(Collection $collection): void
{
Log::debug('Will now collect info from collection.');
/** @var array|TransactionGroup $object */
foreach ($collection as $object) {
if ($object instanceof TransactionGroup) {
Log::debug(sprintf('Added group #%d', $object->id));
$this->appendFromTransactionGroup($object);
}
if (is_array($object) && array_key_exists('id', $object)) {
// FIXME technically speaking not sure of this is the user's transaction group.
$group = TransactionGroup::find((int) $object['id']);
if (null !== $group) {
Log::debug(sprintf('Added group #%d', $group->id));
$this->appendFromTransactionGroup($group);
}
}
}
}
}
+6 -4
View File
@@ -192,10 +192,12 @@ class TransactionFactory
return;
}
// validate info:
$validator = Validator::make(['iban' => $this->accountInformation['iban']], ['iban' => [
'required',
new UniqueIban($this->account, $this->account->accountType->type),
]]);
$validator = Validator::make(['iban' => $this->accountInformation['iban']], [
'iban' => [
'required',
new UniqueIban($this->account, $this->account->accountType->type),
],
]);
if ($validator->fails()) {
Log::debug('Invalid or non-unique IBAN, will not update.');
+1 -1
View File
@@ -79,7 +79,7 @@ class PiggyBankObserver
}
$params = new ConversionParameters();
$params->user = $piggyBank->accounts()->first()?->user;
$params->user = $piggyBank->accounts()->first()->user;
$params->model = $piggyBank;
$params->originalCurrency = $piggyBank->transactionCurrency;
$params->amountField = 'target_amount';
+105
View File
@@ -0,0 +1,105 @@
<?php
/*
* helpers.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
use FireflyIII\Exceptions\FireflyException;
use function Safe\mb_ord;
use function Safe\preg_match;
use function Safe\preg_replace_callback;
if (!function_exists('env_default_when_empty')) {
/**
* @return null|mixed
*/
function env_default_when_empty(mixed $value, bool|int|string|null $default = null): mixed
{
if (null === $value) {
return $default;
}
if ('' === $value) {
return $default;
}
return $value;
}
}
if (!function_exists('string_is_equal')) {
function string_is_equal(string $left, string $right): bool
{
return $left === $right;
}
}
if (!function_exists('blade_escape_js')) {
function blade_escape_js(string $string): string
{
// escape all non-alphanumeric characters
// into their \x or \uHHHH representations
if (0 === preg_match('//u', $string)) {
throw new FireflyException('The string to escape is not a valid UTF-8 string.');
}
return preg_replace_callback(
'#[^a-zA-Z0-9,\._]#Su',
static function ($matches) {
$char = $matches[0];
/*
* A few characters have short escape sequences in JSON and JavaScript.
* Escape sequences supported only by JavaScript, not JSON, are omitted.
* \" is also supported but omitted, because the resulting string is not HTML safe.
*/
$short = match ($char) {
'\\' => '\\\\',
'/' => '\/',
"\x08" => '\b',
"\x0C" => '\f',
"\x0A" => '\n',
"\x0D" => '\r',
"\x09" => '\t',
default => false
};
if ($short) {
return $short;
}
$codepoint = mb_ord($char, 'UTF-8');
if (0x10_000 > $codepoint) {
return \sprintf('\u%04X', $codepoint);
}
// Split characters outside the BMP into surrogate pairs
// https://tools.ietf.org/html/rfc2781.html#section-2.1
$u = $codepoint - 0x10_000;
$high = 0xD800 | ($u >> 10);
$low = 0xDC00 | ($u & 0x3FF);
return \sprintf('\u%04X\u%04X', $high, $low);
},
$string
);
}
}
+2 -3
View File
@@ -40,8 +40,7 @@ class ReportHelper implements ReportHelperInterface
/**
* ReportHelper constructor.
*/
public function __construct(
/** @var BudgetRepositoryInterface The budget repository */
public function __construct(/** @var BudgetRepositoryInterface The budget repository */
protected BudgetRepositoryInterface $budgetRepository
) {}
@@ -56,7 +55,7 @@ class ReportHelper implements ReportHelperInterface
/** @var BillRepositoryInterface $repository */
$repository = app(BillRepositoryInterface::class);
$bills = $repository->getBillsForAccounts($accounts);
$report = ['bills' => []];
$report = ['bills' => []];
/** @var Bill $bill */
foreach ($bills as $bill) {
@@ -78,12 +78,14 @@ final class CreateController extends Controller
$roles = $this->getRoles();
$liabilityTypes = $this->getLiabilityTypes();
$hasOldInput = null !== $request->old('_token');
$locations = ['location' => [
'latitude' => $hasOldInput ? old('location_latitude') : config('firefly.default_location.latitude'),
'longitude' => $hasOldInput ? old('location_longitude') : config('firefly.default_location.longitude'),
'zoom_level' => $hasOldInput ? old('location_zoom_level') : config('firefly.default_location.zoom_level'),
'has_location' => $hasOldInput && 'true' === old('location_has_location'),
]];
$locations = [
'location' => [
'latitude' => $hasOldInput ? old('location_latitude') : config('firefly.default_location.latitude'),
'longitude' => $hasOldInput ? old('location_longitude') : config('firefly.default_location.longitude'),
'zoom_level' => $hasOldInput ? old('location_zoom_level') : config('firefly.default_location.zoom_level'),
'has_location' => $hasOldInput && 'true' === old('location_has_location'),
],
];
$liabilityDirections = ['debit' => trans('firefly.liability_direction_debit'), 'credit' => trans('firefly.liability_direction_credit')];
// interest calculation periods:
@@ -96,12 +96,14 @@ final class EditController extends Controller
$zoomLevel = $location instanceof Location ? $location->zoom_level : config('firefly.default_location.zoom_level');
$canEditCurrency = 0 === $account->piggyBanks()->count();
$hasLocation = $location instanceof Location;
$locations = ['location' => [
'latitude' => old('location_latitude') ?? $latitude,
'longitude' => old('location_longitude') ?? $longitude,
'zoom_level' => old('location_zoom_level') ?? $zoomLevel,
'has_location' => $hasLocation || 'true' === old('location_has_location'),
]];
$locations = [
'location' => [
'latitude' => old('location_latitude') ?? $latitude,
'longitude' => old('location_longitude') ?? $longitude,
'zoom_level' => old('location_zoom_level') ?? $zoomLevel,
'has_location' => $hasLocation || 'true' === old('location_has_location'),
],
];
$liabilityDirections = ['debit' => trans('firefly.liability_direction_debit'), 'credit' => trans('firefly.liability_direction_credit')];
@@ -255,10 +255,7 @@ final class IndexController extends Controller
if (count($bill['paid_dates']) < count($bill['pay_dates'])) {
$count = count($bill['pay_dates']) - count($bill['paid_dates']);
if ($count > 0) {
$avg = bcdiv(
bcadd((string) $bill['amount_min'], (string) $bill['amount_max']),
'2'
);
$avg = bcdiv(bcadd((string) $bill['amount_min'], (string) $bill['amount_max']), '2');
$avg = bcmul($avg, (string) $count);
$sums[$groupOrder][$currencyId]['total_left_to_pay'] = bcadd($sums[$groupOrder][$currencyId]['total_left_to_pay'], $avg);
Log::debug(
@@ -198,13 +198,7 @@ final class BudgetLimitController extends Controller
if ($request->expectsJson()) {
$array = $limit->toArray();
// add some extra metadata:
$spentArr = $this->opsRepository->sumExpenses(
$limit->start_date,
$limit->end_date,
null,
new Collection()->push($budget),
$currency
);
$spentArr = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
$array['spent'] = $spentArr[$currency->id]['sum'] ?? '0';
$array['left_formatted'] = Amount::formatAnything($limit->transactionCurrency, bcadd($array['spent'], (string) $array['amount']));
$array['amount_formatted'] = Amount::formatAnything($limit->transactionCurrency, $limit['amount']);
@@ -246,6 +246,7 @@ final class IndexController extends Controller
$currency = $limit->transactionCurrency ?? $primaryCurrency;
$amount = Steam::bcround($limit->amount, $currency->decimal_places);
$spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
$spentAmount = $spent[$currency->id]['sum'] ?? '0';
$array['budgeted'][] = [
'id' => $limit->id,
@@ -283,10 +284,7 @@ final class IndexController extends Controller
if (array_key_exists($currency->id, $spentArr) && array_key_exists('sum', $spentArr[$currency->id])) {
$array['spent'][$currency->id]['spent'] = $spentArr[$currency->id]['sum'];
$array['spent'][$currency->id]['spent_outside'] = bcmul(
bcsub($spentInLimits[$currency->id], $spentArr[$currency->id]['sum']),
'-1'
);
$array['spent'][$currency->id]['spent_outside'] = Steam::negative(bcsub($spentInLimits[$currency->id], $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;
@@ -589,7 +589,6 @@ final class AccountController extends Controller
Log::debug('End of chart loop.');
// second loop (yes) to create nice array with info! Yay!
$chartData = [];
foreach ($return as $key => $info) {
if ('balance' !== $key && 'pc_balance' !== $key) {
// assume it's a currency:
@@ -608,6 +607,11 @@ final class AccountController extends Controller
$info['currency_code'] = $this->primaryCurrency->code;
$info['label'] = sprintf('%s (%s) (%s)', $account->name, (string) trans('firefly.sum'), $this->primaryCurrency->symbol);
}
// do not add pc_balance to the array if the account is in the primary currency anyway,
// and it has no currency balances.
if (2 === count(array_keys($return)) && 'pc_balance' === $key && $accountCurrency->id === $this->primaryCurrency->id) {
continue;
}
$chartData[] = $info;
}
@@ -539,13 +539,7 @@ final class BudgetController extends Controller
}
// get spent amount in this period for this currency.
$sum = $this->opsRepository->sumExpenses(
$currentStart,
$currentEnd,
$accounts,
new Collection()->push($budget),
$currency
);
$sum = $this->opsRepository->sumExpenses($currentStart, $currentEnd, $accounts, new Collection()->push($budget), $currency);
$amount = Steam::positive($sum[$currency->id]['sum'] ?? '0');
$chartData[0]['entries'][$title] = Steam::bcround($amount, $currency->decimal_places);
@@ -59,7 +59,7 @@ final class JavascriptController extends Controller
AccountTypeEnum::MORTGAGE->value,
AccountTypeEnum::CREDITCARD->value,
]);
$data = ['accounts' => []];
$data = ['accounts' => []];
/** @var Account $account */
foreach ($accounts as $account) {
@@ -79,7 +79,7 @@ final class JavascriptController extends Controller
public function currencies(CurrencyRepositoryInterface $repository): Response
{
$currencies = $repository->get();
$data = ['currencies' => []];
$data = ['currencies' => []];
/** @var TransactionCurrency $currency */
foreach ($currencies as $currency) {
@@ -270,10 +270,10 @@ final class PreferencesController extends Controller
$variables = ['slack_webhook_url', 'pushover_app_token', 'pushover_user_token', 'ntfy_server', 'ntfy_topic', 'ntfy_user', 'ntfy_pass'];
$all = $request->only($variables);
foreach ($variables as $variable) {
if ('' === $all[$variable]) {
if (!array_key_exists($variable, $all) || '' === $all[$variable]) {
Preferences::delete($variable);
}
if ('' !== $all[$variable]) {
if (array_key_exists($variable, $all) && '' !== $all[$variable]) {
Preferences::setEncrypted($variable, $all[$variable]);
}
}
@@ -25,7 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Profile;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\JsonResponse;
@@ -59,8 +59,6 @@ final class OAuthController extends Controller
$authGuard = config('firefly.authentication_guard');
$this->internalAuth = 'web' === $authGuard;
Log::debug(sprintf('ProfileController::__construct(). Authentication guard is "%s"', $authGuard));
$this->middleware(IsDemoUser::class)->except(['index']);
}
public function destroyClient(Request $request, string $clientId): Response
@@ -118,10 +116,22 @@ final class OAuthController extends Controller
public function listClients(): JsonResponse
{
if (!auth()->check()) {
return response()->json([]);
}
Log::debug('Now in listClients()');
// Retrieving all the OAuth app clients that belong to the user...
$clients = auth()->user()->oauthApps()->where('revoked', false)->get();
$array = [];
return response()->json($clients);
/** @var Client $client */
foreach ($clients as $client) {
$item = $client->toArray();
$item['confidential'] = $client->confidential();
$array[] = $item;
}
return response()->json($array);
}
public function listPersonalAccessTokens(): JsonResponse
@@ -135,6 +145,7 @@ final class OAuthController extends Controller
->where('expires_at', '>', Date::now())
->get()
->filter(fn (#[SensitiveParameter] Token $token) => $token->client->hasGrantType('personal_access'))
->values()
;
return response()->json($tokens);
@@ -156,9 +167,10 @@ final class OAuthController extends Controller
public function storeClient(Request $request): JsonResponse
{
$validProtocols = FireflyConfig::get('valid_url_protocols', config('firefly.valid_url_protocols'))->data;
$this->validation->make($request->only(['name', 'redirect_uris', 'confidential']), [
'name' => ['required', 'string', 'max:255'],
'redirect_uris' => ['required', 'url'],
'redirect_uris' => ['required', sprintf('url:%s', $validProtocols)],
'confidential' => 'boolean',
])->validate();
@@ -186,15 +198,15 @@ final class OAuthController extends Controller
public function updateClient(Request $request, string $clientId): Client|Response
{
$client = auth()->user()->oauthApps()->where('revoked', false)->find($clientId);
$client = auth()->user()->oauthApps()->where('revoked', false)->find($clientId);
$validProtocols = FireflyConfig::get('valid_url_protocols', config('firefly.valid_url_protocols'))->data;
if (null === $client) {
return new Response('', 404);
}
$this->validation->make($request->only(['name', 'redirect_uris']), [
'name' => ['required', 'string', 'max:255'],
'redirect_uris' => ['required', 'url'],
'redirect_uris' => ['required', sprintf('url:%s', $validProtocols)],
])->validate();
$this->clients->update($client, $request->input('name'), explode(',', $request->input('redirect_uris'))); // FIXME replace
@@ -284,7 +284,7 @@ final class BudgetController extends Controller
$cache->addProperty('budget-period-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
// return $cache->get();
}
$periods = Navigation::listOfPeriods($start, $end);
@@ -292,7 +292,6 @@ final class BudgetController extends Controller
// list expenses for budgets in account(s)
$expenses = $this->opsRepository->listExpenses($start, $end, $accounts);
$report = [];
foreach ($expenses as $currency) {
foreach ($currency['budgets'] as $budget) {
@@ -300,9 +299,12 @@ final class BudgetController extends Controller
foreach ($budget['transaction_journals'] as $journal) {
// #10678
// skip transactions between two asset / liability accounts.
// #12223
// must also be of the same type to be skipped
if (
in_array($journal['source_account_type'], config('firefly.valid_currency_account_types'), true)
&& in_array($journal['destination_account_type'], config('firefly.valid_currency_account_types'), true)
&& $journal['source_account_type'] === $journal['destination_account_type']
) {
continue;
}
+16 -12
View File
@@ -82,12 +82,14 @@ final class TagController extends Controller
// location info:
$hasOldInput = null !== $request->old('_token');
$locations = ['location' => [
'latitude' => $hasOldInput ? old('location_latitude') : config('firefly.default_location.latitude'),
'longitude' => $hasOldInput ? old('location_longitude') : config('firefly.default_location.longitude'),
'zoom_level' => $hasOldInput ? old('location_zoom_level') : config('firefly.default_location.zoom_level'),
'has_location' => $hasOldInput && 'true' === old('location_has_location'),
]];
$locations = [
'location' => [
'latitude' => $hasOldInput ? old('location_latitude') : config('firefly.default_location.latitude'),
'longitude' => $hasOldInput ? old('location_longitude') : config('firefly.default_location.longitude'),
'zoom_level' => $hasOldInput ? old('location_zoom_level') : config('firefly.default_location.zoom_level'),
'has_location' => $hasOldInput && 'true' === old('location_has_location'),
],
];
// put previous url in session if not redirect from store (not "create another").
if (true !== session('tags.create.fromStore')) {
@@ -142,12 +144,14 @@ final class TagController extends Controller
$longitude = $location instanceof Location ? $location->longitude : config('firefly.default_location.longitude');
$zoomLevel = $location instanceof Location ? $location->zoom_level : config('firefly.default_location.zoom_level');
$hasLocation = $location instanceof Location;
$locations = ['location' => [
'latitude' => old('location_latitude') ?? $latitude,
'longitude' => old('location_longitude') ?? $longitude,
'zoom_level' => old('location_zoom_level') ?? $zoomLevel,
'has_location' => $hasLocation || 'true' === old('location_has_location'),
]];
$locations = [
'location' => [
'latitude' => old('location_latitude') ?? $latitude,
'longitude' => old('location_longitude') ?? $longitude,
'zoom_level' => old('location_zoom_level') ?? $zoomLevel,
'has_location' => $hasLocation || 'true' === old('location_has_location'),
],
];
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('tags.edit.fromUpdate')) {
@@ -82,7 +82,7 @@ final class DeleteController extends Controller
if ($this->repository->currencyInUse($currency)) {
$location = $this->repository->currencyInUseAt($currency);
$message = (string) trans(sprintf('firefly.cannot_disable_currency_%s', $location), ['name' => e($currency->name)]);
$message = (string) trans(sprintf('firefly.cannot_disable_currency_%s', $location), ['name' => e($currency->name)]);
$request->session()->flash('error', $message);
Log::channel('audit')->warning(sprintf('Tried to visit page to delete currency %s but currency is in use.', $currency->code));
+3 -4
View File
@@ -40,10 +40,9 @@ class Authenticate
/**
* Create a new middleware instance.
*/
public function __construct(
/**
* The authentication factory instance.
*/
public function __construct(/**
* The authentication factory instance.
*/
protected Auth $auth
) {}
+3 -4
View File
@@ -42,10 +42,9 @@ class Binder
/**
* Binder constructor.
*/
public function __construct(
/**
* The authentication factory instance.
*/
public function __construct(/**
* The authentication factory instance.
*/
protected Auth $auth
) {
$this->binders = Domain::getBindables();
+2 -2
View File
@@ -63,7 +63,7 @@ class SecureHeaders
"base-uri 'self'",
// "form-action 'self'", // safe
"font-src 'self' data:",
sprintf("connect-src 'self' %s", $trackingScriptSrc),
sprintf("connect-src 'self' https://api.pwnedpasswords.com %s", $trackingScriptSrc),
sprintf("img-src 'self' data: 'nonce-%1s' ", $nonce),
"manifest-src 'self'",
];
@@ -129,7 +129,7 @@ class SecureHeaders
$response->header('Content-Security-Policy', implode('; ', $csp));
}
if (!method_exists($response, 'header')) {
$response->header('Content-Security-Policy', implode('; ', $csp));
$response->headers->set('Content-Security-Policy', implode('; ', $csp));
}
}
if (method_exists($response, 'header')) {
+5 -3
View File
@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Requests;
use FireflyIII\Rules\Admin\IsValidSlackOrDiscordUrl;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
@@ -65,9 +66,10 @@ class NotificationRequest extends FormRequest
*/
public function rules(): array
{
$rules = [
'slack_webhook_url' => ['nullable', 'url', 'min:1', new IsValidSlackOrDiscordUrl()],
'ntfy_server' => ['nullable', 'url', 'min:1'],
$validProtocols = FireflyConfig::get('valid_url_protocols', config('firefly.valid_url_protocols'))->data;
$rules = [
'slack_webhook_url' => ['nullable', sprintf('url:%s', $validProtocols), 'min:1', new IsValidSlackOrDiscordUrl()],
'ntfy_server' => ['nullable', sprintf('url:%s', $validProtocols), 'min:1'],
'ntfy_user' => ['required_with:ntfy_pass,ntfy_auth', 'nullable', 'string', 'min:1'],
'ntfy_pass' => ['required_with:ntfy_user,ntfy_auth', 'nullable', 'string', 'min:1'],
];
+5 -3
View File
@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Requests;
use FireflyIII\Rules\Admin\IsValidSlackOrDiscordUrl;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Request\ChecksLogin;
use Illuminate\Foundation\Http\FormRequest;
@@ -39,9 +40,10 @@ class PreferencesRequest extends FormRequest
*/
public function rules(): array
{
$rules = [
'slack_webhook_url' => ['nullable', 'url', 'min:1', new IsValidSlackOrDiscordUrl()],
'ntfy_server' => ['nullable', 'url', 'min:1'],
$validProtocols = FireflyConfig::get('valid_url_protocols', config('firefly.valid_url_protocols'))->data;
$rules = [
'slack_webhook_url' => ['nullable', sprintf('url:%s', $validProtocols), 'min:1', new IsValidSlackOrDiscordUrl()],
'ntfy_server' => ['nullable', sprintf('url:%s', $validProtocols), 'min:1'],
'ntfy_user' => ['required_with:ntfy_pass,ntfy_auth', 'nullable', 'string', 'min:1'],
'ntfy_pass' => ['required_with:ntfy_user,ntfy_auth', 'nullable', 'string', 'min:1'],
];
+2 -14
View File
@@ -122,13 +122,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
// if has one, calculate expenses and use that as a base.
$repository = app(OperationsRepositoryInterface::class);
$repository->setUser($autoBudget->budget->user);
$spent = $repository->sumExpenses(
$previousStart,
$previousEnd,
null,
new Collection()->push($autoBudget->budget),
$autoBudget->transactionCurrency
);
$spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection()->push($autoBudget->budget), $autoBudget->transactionCurrency);
$currencyId = $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));
@@ -218,13 +212,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
// if has one, calculate expenses and use that as a base.
$repository = app(OperationsRepositoryInterface::class);
$repository->setUser($autoBudget->budget->user);
$spent = $repository->sumExpenses(
$previousStart,
$previousEnd,
null,
new Collection()->push($autoBudget->budget),
$autoBudget->transactionCurrency
);
$spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection()->push($autoBudget->budget), $autoBudget->transactionCurrency);
$currencyId = $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));
@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Listeners\Model\CurrencyExchangeRate;
use Carbon\Carbon;
use FireflyIII\Events\Model\CurrencyExchangeRate\CreatedCurrencyExchangeRate;
use FireflyIII\Events\Model\CurrencyExchangeRate\DestroyedCurrencyExchangeRate;
use FireflyIII\Events\Model\CurrencyExchangeRate\UpdatedCurrencyExchangeRate;
@@ -42,20 +43,22 @@ class ProcessesExchangeRates
Preferences::mark();
Cache::clear();
if ($event instanceof DestroyedCurrencyExchangeRate) {
$this->handleCurrency($event->userGroup, $event->from);
$this->handleCurrency($event->userGroup, $event->to);
$this->handleCurrency($event->userGroup, $event->from, $event->date);
$this->handleCurrency($event->userGroup, $event->to, $event->date);
return;
}
$this->handleCurrency($event->rate->userGroup, $event->rate->fromCurrency);
$this->handleCurrency($event->rate->userGroup, $event->rate->toCurrency);
$this->handleCurrency($event->rate->userGroup, $event->rate->fromCurrency, $event->rate->date);
$this->handleCurrency($event->rate->userGroup, $event->rate->toCurrency, $event->rate->date);
}
private function handleCurrency(UserGroup $userGroup, TransactionCurrency $currency): void
private function handleCurrency(UserGroup $userGroup, TransactionCurrency $currency, Carbon $date): void
{
$calculator = new PrimaryAmountRecalculationService();
$calculator->setDate($date);
if (Amount::convertToPrimary()) {
Log::debug(sprintf('Will now convert amounts to primary currency for currency %s.', $currency->code));
$date->startOfDay();
Log::debug(sprintf('Will now convert amounts to primary currency for currency %s after %s.', $currency->code, $date->format('Y-m-d')));
$calculator->recalculateForGroupAndCurrency($userGroup, $currency);
// $calculator->recalculateForGroup($userGroup);
@@ -335,7 +335,9 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
$limit = new BudgetLimit();
$limit->budget()->associate($budget);
$limit->start_date = $data['start_date']->format('Y-m-d');
$limit->start_date_tz = $data['start_date']->format('e');
$limit->end_date = $data['end_date']->format('Y-m-d');
$limit->end_date_tz = $data['end_date']->format('e');
$limit->amount = $data['amount'];
$limit->generated = $data['generated'] ?? false;
$limit->period = $data['period'] ?? '';
@@ -98,7 +98,7 @@ trait ModifiesPiggyBanks
$maxAmount = $leftOnAccount;
Log::debug(sprintf('Left on account: %s on %s', $leftOnAccount, $today->format('Y-m-d H:i:s')));
Log::debug(sprintf('Saved so far: %s', $savedSoFar));
Log::debug(sprintf('Saved so far : %s', $savedSoFar));
if (0 !== bccomp($piggyBank->target_amount, '0')) {
$leftToSave = bcsub($piggyBank->target_amount, (string) $savedSoFar);
@@ -110,7 +110,7 @@ trait ModifiesPiggyBanks
$compare = bccomp($amount, (string) $maxAmount);
$result = $compare <= 0;
Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true)));
Log::debug(sprintf('Compare %s to %s <= 0? %d, so canAddAmount is %s', $amount, $maxAmount, $compare, var_export($result, true)));
return $result;
}
@@ -385,9 +385,9 @@ trait ModifiesPiggyBanks
$piggyBank->target_date = $data['target_date'];
$piggyBank->target_date_tz = $data['target_date']?->format('e');
}
if (array_key_exists('start_date', $data)) {
if (array_key_exists('start_date', $data) && '' !== $data['start_date']) {
$piggyBank->start_date = $data['start_date'];
$piggyBank->start_date_tz = $data['target_date']?->format('e');
$piggyBank->start_date_tz = $data['start_date']?->format('e');
}
$piggyBank->save();
@@ -342,33 +342,6 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
return $piggyBank->piggyBankRepetitions()->first();
}
/**
* Returns the suggested amount the user should save per month, or "".
*/
public function getSuggestedMonthlyAmount(PiggyBank $piggyBank): string
{
$savePerMonth = '0';
$currentAmount = $this->getCurrentAmount($piggyBank);
if (null !== $piggyBank->target_date && $currentAmount < $piggyBank->target_amount) {
$now = today(config('app.timezone'));
$startDate = null !== $piggyBank->start_date && $piggyBank->start_date->gte($now) ? $piggyBank->start_date : $now;
$diffInMonths = (int) $startDate->diffInMonths($piggyBank->target_date);
$remainingAmount = bcsub((string) $piggyBank->target_amount, $currentAmount);
// more than 1 month to go and still need money to save:
if ($diffInMonths > 0 && 1 === bccomp($remainingAmount, '0')) {
$savePerMonth = bcdiv($remainingAmount, (string) $diffInMonths);
}
// less than 1 month to go but still need money to save:
if (0 === $diffInMonths && 1 === bccomp($remainingAmount, '0')) {
$savePerMonth = $remainingAmount;
}
}
return $savePerMonth;
}
/**
* Get for piggy account what is left to put in piggies.
*/
@@ -111,11 +111,6 @@ interface PiggyBankRepositoryInterface
public function getRepetition(PiggyBank $piggyBank, bool $overrule = false): ?PiggyBankRepetition;
/**
* Returns the suggested amount the user should save per month, or "".
*/
public function getSuggestedMonthlyAmount(PiggyBank $piggyBank): string;
/**
* Get for piggy account what is left to put in piggies.
*/
@@ -166,7 +166,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
$set = TransactionJournalLink::where(static function (Builder $q) use ($journals): void {
$q->whereIn('source_id', $journals);
$q->orWhereIn('destination_id', $journals);
})->with(['source', 'destination', 'source.transactions'])->leftJoin('link_types', 'link_types.id', '=', 'journal_links.link_type_id')->get([
})->with(['source', 'notes', 'destination', 'source.transactions'])->leftJoin('link_types', 'link_types.id', '=', 'journal_links.link_type_id')->get([
'journal_links.*',
'link_types.inward',
'link_types.outward',
@@ -191,6 +191,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
'editable' => 1 === (int) $entry->editable,
'amount' => $amount,
'foreign_amount' => $foreignAmount,
'notes' => null === $entry->notes->first() ? '' : $entry->notes->first()->text,
];
}
if ($journalId === $entry->destination_id) {
@@ -204,6 +205,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
'editable' => 1 === (int) $entry->editable,
'amount' => $amount,
'foreign_amount' => $foreignAmount,
'notes' => null === $entry->notes->first() ? '' : $entry->notes->first()->text,
];
}
}
+12 -2
View File
@@ -50,7 +50,7 @@ class IsDateOrTime implements ValidationRule
if (10 === strlen($value)) {
// probably a date format.
try {
Carbon::createFromFormat('Y-m-d', $value);
$object = Carbon::createFromFormat('Y-m-d', $value);
} catch (InvalidDateException $e) {
Log::error(sprintf('"%s" is not a valid date: %s', $value, $e->getMessage()));
@@ -64,13 +64,18 @@ class IsDateOrTime implements ValidationRule
return;
}
if ($object->year < 1970) {
$fail('validation.date_or_time')->translate();
return;
}
return;
}
// is an atom string, I hope?
try {
Carbon::parse($value);
$object = Carbon::parse($value);
} catch (InvalidDateException $e) {
Log::error(sprintf('"%s" is not a valid date or time: %s', $value, $e->getMessage()));
@@ -82,6 +87,11 @@ class IsDateOrTime implements ValidationRule
$fail('validation.date_or_time')->translate();
return;
}
if ($object->year < 1970) {
$fail('validation.date_or_time')->translate();
return;
}
}
+5 -4
View File
@@ -54,8 +54,9 @@ class IsEnoughInAccounts implements ValidationRule
$accounts = $this->data['accounts'];
foreach ($accounts as $info) {
$account = $repository->find((int) $info['account_id']);
$amount = $info['current_amount'] ?? '0';
$account = $repository->find((int) $info['account_id']);
$amount = $info['current_amount'] ?? '0';
$savedSoFar = $piggyRepos->getCurrentAmount($this->piggyBank, $account);
if (null === $account) {
$fail('validation.no_asset_account')->translate();
@@ -64,8 +65,8 @@ class IsEnoughInAccounts implements ValidationRule
if ('' === $amount || 0 === bccomp($amount, '0')) {
continue;
}
$diff = bcsub($amount, $piggyRepos->getCurrentAmount($this->piggyBank, $account));
if (1 === bccomp($diff, '0') && !$piggyRepos->canAddAmount($this->piggyBank, $account, $amount)) {
$diff = bcsub($amount, $savedSoFar);
if (1 === bccomp($diff, '0') && !$piggyRepos->canAddAmount($this->piggyBank, $account, $diff)) {
$fail('validation.cannot_add_piggy_amount')->translate();
}
}
@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Services\Internal\Recalculate;
use Carbon\Carbon;
use FireflyIII\Events\Model\Account\UpdatedExistingAccount;
use FireflyIII\Handlers\Observer\TransactionObserver;
use FireflyIII\Models\Account;
@@ -52,6 +53,13 @@ use Illuminate\Support\Facades\Log;
class PrimaryAmountRecalculationService
{
private Carbon $date;
public function __construct()
{
$this->date = Carbon::createFromDate(1970, 1, 1);
}
public function recalculate(): void
{
if (false === FireflyConfig::get('enable_exchange_rates', config('cer.enabled'))->data) {
@@ -106,12 +114,18 @@ class PrimaryAmountRecalculationService
$this->calculateTransactionsForCurrency($userGroup, $currency, $limitCurrency);
}
public function setDate(?Carbon $date): void
{
$this->date = $date;
}
private function calculateTransactions(UserGroup $userGroup, TransactionCurrency $currency): void
{
// custom query because of the potential size of this update.
$set = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where('transaction_journals.date', '>=', $this->date)
->where(static function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(static function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');
@@ -147,6 +161,7 @@ class PrimaryAmountRecalculationService
$set = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where('transaction_journals.date', '>=', $this->date)
->where(static function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(static function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');
@@ -279,7 +294,15 @@ class PrimaryAmountRecalculationService
private function recalculateBudgetLimits(Budget $budget, TransactionCurrency $currency): void
{
$set = $budget->budgetlimits()->where('transaction_currency_id', '!=', $currency->id)->get();
$set = $budget
->budgetlimits()
->where(function (EloquentBuilder $q): void {
$q->where('budget_limits.start_date', '>=', $this->date);
$q->orWhere('budget_limits.end_date', '<=', $this->date);
})
->where('transaction_currency_id', '!=', $currency->id)
->get()
;
/** @var BudgetLimit $limit */
foreach ($set as $limit) {
@@ -436,6 +459,7 @@ class PrimaryAmountRecalculationService
$success = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where('transaction_journals.date', '>=', $this->date)
->where(static function (Builder $q): void {
$q->whereNotNull('native_amount')->orWhereNotNull('native_foreign_amount');
})
@@ -741,12 +741,12 @@ class JournalUpdateService
if (null === $group || null === $this->transactionJournal) {
return;
}
if (0 === bccomp($source->foreign_amount, $foreignAmount)) {
if (0 === bccomp(Steam::positive($originalSourceAmount), Steam::positive($foreignAmount))) {
Log::debug('Amount was not actually changed, return.');
return;
}
Log::debug('Amount was changed, needs audit log entry.');
Log::debug(sprintf('Amount was changed (%s -> %s), needs audit log entry.', $originalSourceAmount, $foreignAmount));
$transfer = TransactionTypeEnum::TRANSFER->value === $this->transactionJournal->transactionType->type;
// $withdrawal = TransactionTypeEnum::WITHDRAWAL->value === $this->transactionJournal->transactionType->type;
$deposit = TransactionTypeEnum::DEPOSIT->value === $this->transactionJournal->transactionType->type;
+1 -1
View File
@@ -49,7 +49,7 @@ class PiggyBankForm
$piggyBanks = $repository->getPiggyBanksWithAmount();
$title = (string) trans('firefly.default_group_title_name');
$array = [];
$subList = [0 => ['group' => ['title' => $title], 'piggies' => [(string) trans('firefly.none_in_select_list')]]];
$subList = [0 => ['group' => ['title' => $title], 'piggies' => [(string) trans('firefly.none_in_select_list')]]];
/** @var PiggyBank $piggy */
foreach ($piggyBanks as $piggy) {
@@ -222,14 +222,7 @@ trait AugumentData
$currentEnd->addMonth();
}
// primary currency amount.
$expenses = $opsRepository->sumExpenses(
$currentStart,
$currentEnd,
null,
$budgetCollection,
$entry->transactionCurrency,
$this->convertToPrimary
);
$expenses = $opsRepository->sumExpenses($currentStart, $currentEnd, null, $budgetCollection, $entry->transactionCurrency, $this->convertToPrimary);
$spent = $expenses[$currency->id]['sum'] ?? '0';
$entry->pc_spent = $spent;
+1 -1
View File
@@ -117,7 +117,7 @@ trait CreateStuff
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$savingsAccount = [
'name' => (string) trans('firefly.new_savings_account', ['bank_name' => $request->get('bank_name')], $language),
'name' => (string) trans('firefly.new_savings_account', ['bank_name' => $request->get('bank_name')], $language),
'iban' => null,
'account_type_name' => 'asset',
'account_type_id' => null,
@@ -163,6 +163,7 @@ class PiggyBankEnrichment implements EnrichmentInterface
}
// get suggested per month.
$meta['save_per_month'] = Steam::bcround(
$this->getSuggestedMonthlyAmount($this->date, $item->target_date, $meta['target_amount'], $meta['current_amount']),
$currency->decimal_places
@@ -301,23 +302,21 @@ class PiggyBankEnrichment implements EnrichmentInterface
if (null === $targetAmount || !$targetDate instanceof Carbon || !$startDate instanceof Carbon) {
return '0';
}
$savePerMonth = '0';
if (1 === bccomp($targetAmount, $currentAmount)) {
$now = today(config('app.timezone'));
$diffInMonths = (int) $startDate->diffInMonths($targetDate);
$diffInMonths = ceil($startDate->diffInMonths($targetDate));
$remainingAmount = bcsub($targetAmount, $currentAmount);
// more than 1 month to go and still need money to save:
if ($diffInMonths > 0 && 1 === bccomp($remainingAmount, '0')) {
$savePerMonth = bcdiv($remainingAmount, (string) $diffInMonths);
return bcdiv($remainingAmount, (string) $diffInMonths);
}
// less than 1 month to go but still need money to save:
if (0 === $diffInMonths && 1 === bccomp($remainingAmount, '0')) {
$savePerMonth = $remainingAmount;
if (1 === bccomp($remainingAmount, '0')) {
return $remainingAmount;
}
}
return $savePerMonth;
return '0';
}
}
@@ -354,10 +354,7 @@ class RecurringEnrichment implements EnrichmentInterface
/** @var RecurrenceRepetition $repetition */
foreach ($set as $repetition) {
$recurrence = $this->collection
->filter(static fn (Recurrence $item): bool => (int) $item->id === (int) $repetition->recurrence_id)
->first()
;
$recurrence = $this->collection->filter(static fn (Recurrence $item): bool => (int) $item->id === (int) $repetition->recurrence_id)->first();
$fromDate = clone ($recurrence->latest_date ?? $recurrence->first_date);
$recurrenceId = (int) $repetition->recurrence_id;
$repId = (int) $repetition->id;
@@ -470,10 +470,9 @@ class SubscriptionEnrichment implements EnrichmentInterface
} catch (InvalidFormatException) {
$temp2 = today(config('app.timezone'));
}
$nemDiff = trans('firefly.bill_expected_date', ['date' => $temp2->diffForHumans(
today(config('app.timezone')),
CarbonInterface::DIFF_RELATIVE_TO_NOW
)]);
$nemDiff = trans('firefly.bill_expected_date', [
'date' => $temp2->diffForHumans(today(config('app.timezone')), CarbonInterface::DIFF_RELATIVE_TO_NOW),
]);
}
unset($temp2);
+2 -2
View File
@@ -522,7 +522,7 @@ class OperatorQuerySearch implements SearchInterface
$stringMethod = 'str_contains';
}
if (StringPosition::IS === $stringPosition) {
$stringMethod = 'stringIsEqual';
$stringMethod = 'string_is_equal';
}
// get accounts:
@@ -619,7 +619,7 @@ class OperatorQuerySearch implements SearchInterface
$stringMethod = 'str_contains';
}
if (StringPosition::IS === $stringPosition) {
$stringMethod = 'stringIsEqual';
$stringMethod = 'string_is_equal';
}
// search for accounts:
+8 -8
View File
@@ -317,14 +317,14 @@ class General extends AbstractExtension
return new TwigFilter(
'mimeIcon',
static fn (string $string): string => match ($string) {
'application/pdf' => 'fa-file-pdf-o',
'application/pdf' => 'fa-file-pdf-o',
'image/webp',
'image/png',
'image/jpeg',
'image/svg+xml',
'image/heic',
'image/heic-sequence',
'application/vnd.oasis.opendocument.image' => 'fa-file-image-o',
'application/vnd.oasis.opendocument.image' => 'fa-file-image-o',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
@@ -337,7 +337,7 @@ class General extends AbstractExtension
'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.text-template',
'application/vnd.oasis.opendocument.text-web',
'application/vnd.oasis.opendocument.text-master' => 'fa-file-word-o',
'application/vnd.oasis.opendocument.text-master' => 'fa-file-word-o',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
@@ -345,7 +345,7 @@ class General extends AbstractExtension
'application/vnd.sun.xml.calc.template',
'application/vnd.stardivision.calc',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.oasis.opendocument.spreadsheet-template' => 'fa-file-excel-o',
'application/vnd.oasis.opendocument.spreadsheet-template' => 'fa-file-excel-o',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/vnd.openxmlformats-officedocument.presentationml.template',
@@ -354,18 +354,18 @@ class General extends AbstractExtension
'application/vnd.sun.xml.impress.template',
'application/vnd.stardivision.impress',
'application/vnd.oasis.opendocument.presentation',
'application/vnd.oasis.opendocument.presentation-template' => 'fa-file-powerpoint-o',
'application/vnd.oasis.opendocument.presentation-template' => 'fa-file-powerpoint-o',
'application/vnd.sun.xml.draw',
'application/vnd.sun.xml.draw.template',
'application/vnd.stardivision.draw',
'application/vnd.oasis.opendocument.chart' => 'fa-paint-brush',
'application/vnd.oasis.opendocument.chart' => 'fa-paint-brush',
'application/vnd.oasis.opendocument.graphics',
'application/vnd.oasis.opendocument.graphics-template',
'application/vnd.sun.xml.math',
'application/vnd.stardivision.math',
'application/vnd.oasis.opendocument.formula',
'application/vnd.oasis.opendocument.database' => 'fa-calculator',
default => 'fa-file-o'
'application/vnd.oasis.opendocument.database' => 'fa-calculator',
default => 'fa-file-o'
},
['is_safe' => ['html']]
);
@@ -25,6 +25,9 @@ declare(strict_types=1);
namespace FireflyIII\TransactionRules\Engine;
use Carbon\Carbon;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventObjects;
use FireflyIII\Events\Model\TransactionGroup\UpdatedSingleTransactionGroup;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Note;
use FireflyIII\Models\Rule;
@@ -399,9 +402,17 @@ class SearchRuleEngine implements RuleEngineInterface
private function fireNonStrictRule(Rule $rule): bool
{
Log::debug(sprintf('SearchRuleEngine::fireNonStrictRule(%d)!', $rule->id));
$collection = $this->findNonStrictRule($rule);
$flags = new TransactionGroupEventFlags();
$flags->applyRules = false;
$flags->fireWebhooks = false;
$objects = new TransactionGroupEventObjects();
$collection = $this->findNonStrictRule($rule);
$objects->collectFromCollection($collection);
$this->processResults($rule, $collection);
// collect from collection, again!
$objects->collectFromCollection($collection);
event(new UpdatedSingleTransactionGroup($flags, $objects));
Log::debug(sprintf('SearchRuleEngine:: Done processing non-strict rule #%d', $rule->id));
return $collection->count() > 0;
@@ -438,11 +449,23 @@ class SearchRuleEngine implements RuleEngineInterface
private function fireStrictRule(Rule $rule): bool
{
Log::debug(sprintf('SearchRuleEngine::fireStrictRule(%d)!', $rule->id));
$collection = $this->findStrictRule($rule);
$flags = new TransactionGroupEventFlags();
$flags->applyRules = false;
$flags->fireWebhooks = false;
$objects = new TransactionGroupEventObjects();
$collection = $this->findStrictRule($rule);
$objects->collectFromCollection($collection);
$this->processResults($rule, $collection);
$result = $collection->count() > 0;
// collect from collection, again!
$objects->collectFromCollection($collection);
// fire event for changed groups.
event(new UpdatedSingleTransactionGroup($flags, $objects));
$result = $collection->count() > 0;
if ($result) {
Log::debug(sprintf('SearchRuleEngine:: Done. Rule #%d was triggered (on %d transaction(s)).', $rule->id, $collection->count()));
-22
View File
@@ -63,29 +63,7 @@ use PragmaRX\Google2FALaravel\Middleware as MFAMiddleware;
bcscale(12);
if (!function_exists('envDefaultWhenEmpty')) {
/**
*
* @return mixed|null
*/
function envDefaultWhenEmpty(mixed $value, string | int | bool | null $default = null): mixed
{
if(null === $value) {
return $default;
}
if('' === $value) {
return $default;
}
return $value;
}
}
if (!function_exists('stringIsEqual')) {
function stringIsEqual(string $left, string $right): bool
{
return $left === $right;
}
}
$app = Application::configure(basePath: dirname(__DIR__))
->withRouting(
+73
View File
@@ -3,6 +3,79 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## v6.6.2 - 2026-04-28
<!-- summary: This releases fixes a security issue and some small UI issues. Please upgrade at your earliest convenience. -->
### Added
- [PR 12179](https://github.com/firefly-iii/firefly-iii/pull/12179) (implement password validation JS script) reported by @tasnim0tantawi
- [PR 12182](https://github.com/firefly-iii/firefly-iii/pull/12182) (fix shrinked sidebar expanding when navigating by clicking on icons) reported by @tasnim0tantawi
### Fixed
- [Issue 12169](https://github.com/firefly-iii/firefly-iii/issues/12169) (The 'Running balance' column is not showing the respective calculation instantly for new records that use 'Rules') reported by @jgmm81
- [Issue 12186](https://github.com/firefly-iii/firefly-iii/issues/12186) (Set a year validator accepted by the system when saving or editing a transaction) reported by @jgmm81
### Security
- Fixed an issue where oAuth tokens could be generated before you confirmed your 2FA state. This would allow access to your data when your password was stolen, despite you having MFA enabled.
## v6.6.1 - 2026-04-19
<!-- summary: This releases upgrades many dependencies and will invalidate all of your OAuth-tokens and clients. -->
> [!WARNING]
> This releases will invalidate ALL of your OAuth-tokens and clients. Integrations with Home Assistant, AI-agents or the Firefly III Data Importer must be reconfigured with newly generated tokens. Old tokens and clients will no longer work. Sorry about that.
### Changed
- Updated many dependencies, amongst which are some backwards incompatible ones. Sorry about your OAuth-tokens.
### Fixed
- [Issue 12029](https://github.com/firefly-iii/firefly-iii/issues/12029) (PrimaryAmountRecalculationService - Attempt to read property "data" on null.) reported by @MihataBG
- [Issue 12030](https://github.com/firefly-iii/firefly-iii/issues/12030) (Rule engine fires twice in some cases) reported by @Robubble
- [Issue 12034](https://github.com/firefly-iii/firefly-iii/issues/12034) (The new Piggy Banks accounts are not being displayed properly grouped in the record creation form) reported by @jgmm81
- [Issue 12035](https://github.com/firefly-iii/firefly-iii/issues/12035) (Foreign currency account value in primary currency does not update after changing exchange rates (Dashboard)) reported by @gattacus
- [Issue 12043](https://github.com/firefly-iii/firefly-iii/issues/12043) (Detail in the Running balance indicator through the Liabilities accounts (multi currencies)) reported by @jgmm81
- [Discussion 12044](https://github.com/orgs/firefly-iii/discussions/12044) (Very slow startup) started by @pelaxa
- [Issue 12056](https://github.com/firefly-iii/firefly-iii/issues/12056) (Details regarding the Note records in the Database and the "Purge data from Firefly III" function) reported by @jgmm81
- [Issue 12063](https://github.com/firefly-iii/firefly-iii/issues/12063) (Cannot set budget higher than 268435456) reported by @Permagate
- [Issue 12066](https://github.com/firefly-iii/firefly-iii/issues/12066) (Email notifications ignore user language preferences) reported by @examosa
- [Issue 12070](https://github.com/firefly-iii/firefly-iii/issues/12070) (can't add money to piggy bank) reported by @4e868df3
- [Issue 12081](https://github.com/firefly-iii/firefly-iii/issues/12081) (Discord changes webhook domain) reported by @MinDBreaK
- [Issue 12083](https://github.com/firefly-iii/firefly-iii/issues/12083) (Hide or remove the Ntfy fields if they dont work.) reported by @OrakMoya
- [Issue 12107](https://github.com/firefly-iii/firefly-iii/issues/12107) (Exception sending test email notification) reported by @antrv
- [Issue 12154](https://github.com/firefly-iii/firefly-iii/issues/12154) (Error on artisan firefly-iii:upgrade-database v6.6.0) reported by @Tealk
## v6.6.0 - 2026-04-19
<!-- summary: This releases upgrades many dependencies and will invalidate all of your OAuth-tokens and clients. -->
> [!WARNING]
> This releases will invalidate ALL of your OAuth-tokens and clients. Integrations with Home Assistant, AI-agents or the Firefly III Data Importer must be reconfigured with newly generated tokens. Old tokens and clients will no longer work. Sorry about that.
### Changed
- Updated many dependencies, amongst which are some backwards incompatible ones. Sorry about your OAuth-tokens.
### Fixed
- [Issue 12029](https://github.com/firefly-iii/firefly-iii/issues/12029) (PrimaryAmountRecalculationService - Attempt to read property "data" on null.) reported by @MihataBG
- [Issue 12030](https://github.com/firefly-iii/firefly-iii/issues/12030) (Rule engine fires twice in some cases) reported by @Robubble
- [Issue 12034](https://github.com/firefly-iii/firefly-iii/issues/12034) (The new Piggy Banks accounts are not being displayed properly grouped in the record creation form) reported by @jgmm81
- [Issue 12035](https://github.com/firefly-iii/firefly-iii/issues/12035) (Foreign currency account value in primary currency does not update after changing exchange rates (Dashboard)) reported by @gattacus
- [Issue 12043](https://github.com/firefly-iii/firefly-iii/issues/12043) (Detail in the Running balance indicator through the Liabilities accounts (multi currencies)) reported by @jgmm81
- [Discussion 12044](https://github.com/orgs/firefly-iii/discussions/12044) (Very slow startup) started by @pelaxa
- [Issue 12056](https://github.com/firefly-iii/firefly-iii/issues/12056) (Details regarding the Note records in the Database and the "Purge data from Firefly III" function) reported by @jgmm81
- [Issue 12063](https://github.com/firefly-iii/firefly-iii/issues/12063) (Cannot set budget higher than 268435456) reported by @Permagate
- [Issue 12066](https://github.com/firefly-iii/firefly-iii/issues/12066) (Email notifications ignore user language preferences) reported by @examosa
- [Issue 12070](https://github.com/firefly-iii/firefly-iii/issues/12070) (can't add money to piggy bank) reported by @4e868df3
- [Issue 12081](https://github.com/firefly-iii/firefly-iii/issues/12081) (Discord changes webhook domain) reported by @MinDBreaK
- [Issue 12083](https://github.com/firefly-iii/firefly-iii/issues/12083) (Hide or remove the Ntfy fields if they dont work.) reported by @OrakMoya
- [Issue 12107](https://github.com/firefly-iii/firefly-iii/issues/12107) (Exception sending test email notification) reported by @antrv
## v6.5.9 - 2026-03-23
<!-- summary: Bug fixes mainly, but also updated dependencies and new wording in the instructions you see when you open a PR. -->
+6 -2
View File
@@ -111,7 +111,9 @@
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^3",
"carthage-software/mago": "^1.24.0",
"driftingly/rector-laravel": "^2.0",
"ergebnis/phpstan-rules": "^2",
"fakerphp/faker": "1.*",
"filp/whoops": "2.*",
"fruitcake/laravel-debugbar": "^4.0",
@@ -124,8 +126,7 @@
"phpstan/phpstan-strict-rules": "^2",
"phpunit/phpunit": "^13",
"rector/rector": "^2.3",
"thecodingmachine/phpstan-safe-rule": "^1.4",
"ergebnis/phpstan-rules": "^2"
"thecodingmachine/phpstan-safe-rule": "^1.4"
},
"replace": {
"symfony/polyfill-php54": "*",
@@ -135,6 +136,9 @@
"suggest": {},
"autoload": {
"files": [
"app/Helpers/Functions/helpers.php"
],
"psr-4": {
"FireflyIII\\": "app/",
"Domain\\": "domain/",
Generated
+442 -285
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -36,12 +36,12 @@ use Illuminate\Support\Facades\URL;
use Spatie\Html\Facades\Html;
return [
'name' => envDefaultWhenEmpty(env('APP_NAME'), 'Firefly III'),
'env' => envDefaultWhenEmpty(env('APP_ENV'), 'production'),
'name' => env_default_when_empty(env('APP_NAME'), 'Firefly III'),
'env' => env_default_when_empty(env('APP_ENV'), 'production'),
'debug' => env('APP_DEBUG', false),
'url' => envDefaultWhenEmpty(env('APP_URL'), 'http://localhost'),
'timezone' => envDefaultWhenEmpty(env('TZ'), 'UTC'),
'locale' => envDefaultWhenEmpty(env('DEFAULT_LANGUAGE'), 'en_US'),
'url' => env_default_when_empty(env('APP_URL'), 'http://localhost'),
'timezone' => env_default_when_empty(env('TZ'), 'UTC'),
'locale' => env_default_when_empty(env('DEFAULT_LANGUAGE'), 'en_US'),
'fallback_locale' => 'en_US',
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',
+2 -2
View File
@@ -37,10 +37,10 @@ return [
*/
'defaults' => [
'guard' => envDefaultWhenEmpty(env('AUTHENTICATION_GUARD'), 'web'),
'guard' => env_default_when_empty(env('AUTHENTICATION_GUARD'), 'web'),
'passwords' => 'users',
],
'guard_header' => envDefaultWhenEmpty(env('AUTHENTICATION_GUARD_HEADER'), 'REMOTE_USER'),
'guard_header' => env_default_when_empty(env('AUTHENTICATION_GUARD_HEADER'), 'REMOTE_USER'),
'guard_email' => env('AUTHENTICATION_GUARD_EMAIL'),
/*
+1 -1
View File
@@ -36,7 +36,7 @@ return [
|
*/
'default' => envDefaultWhenEmpty(env('CACHE_DRIVER'), 'file'),
'default' => env_default_when_empty(env('CACHE_DRIVER'), 'file'),
/*
|--------------------------------------------------------------------------
+27 -26
View File
@@ -21,6 +21,7 @@
*/
declare(strict_types=1);
use Pdo\Mysql;
use function Safe\parse_url;
@@ -49,42 +50,42 @@ $mysql_ssl_ciphers = env('MYSQL_SSL_CIPHER');
$mysql_ssl_verify = env('MYSQL_SSL_VERIFY_SERVER_CERT');
$mySqlSSLOptions = [];
$useSSL = envDefaultWhenEmpty(env('MYSQL_USE_SSL'), false);
$useSSL = env_default_when_empty(env('MYSQL_USE_SSL'), false);
if (false !== $useSSL && null !== $useSSL && '' !== $useSSL) {
if (null !== $mysql_ssl_ca_dir) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_CAPATH] = $mysql_ssl_ca_dir;
$mySqlSSLOptions[Mysql::ATTR_SSL_CAPATH] = $mysql_ssl_ca_dir;
}
if (null !== $mysql_ssl_ca_file) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_CA] = $mysql_ssl_ca_file;
$mySqlSSLOptions[Mysql::ATTR_SSL_CA] = $mysql_ssl_ca_file;
}
if (null !== $mysql_ssl_cert) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_CERT] = $mysql_ssl_cert;
$mySqlSSLOptions[Mysql::ATTR_SSL_CERT] = $mysql_ssl_cert;
}
if (null !== $mysql_ssl_key) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_KEY] = $mysql_ssl_key;
$mySqlSSLOptions[Mysql::ATTR_SSL_KEY] = $mysql_ssl_key;
}
if (null !== $mysql_ssl_ciphers) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_CIPHER] = $mysql_ssl_ciphers;
$mySqlSSLOptions[Mysql::ATTR_SSL_CIPHER] = $mysql_ssl_ciphers;
}
if (null !== $mysql_ssl_verify) {
$mySqlSSLOptions[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $mysql_ssl_verify;
$mySqlSSLOptions[Mysql::ATTR_SSL_VERIFY_SERVER_CERT] = $mysql_ssl_verify;
}
}
return [
'default' => envDefaultWhenEmpty(env('DB_CONNECTION'), 'mysql'),
'default' => env_default_when_empty(env('DB_CONNECTION'), 'mysql'),
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => envDefaultWhenEmpty(env('DB_DATABASE'), storage_path('database/database.sqlite')),
'database' => env_default_when_empty(env('DB_DATABASE'), storage_path('database/database.sqlite')),
'prefix' => '',
],
'mysql' => [
'driver' => 'mysql',
'host' => envDefaultWhenEmpty(env('DB_HOST'), $host),
'port' => envDefaultWhenEmpty(env('DB_PORT'), $port),
'database' => envDefaultWhenEmpty(env('DB_DATABASE'), $database),
'username' => envDefaultWhenEmpty(env('DB_USERNAME'), $username),
'host' => env_default_when_empty(env('DB_HOST'), $host),
'port' => env_default_when_empty(env('DB_PORT'), $port),
'database' => env_default_when_empty(env('DB_DATABASE'), $database),
'username' => env_default_when_empty(env('DB_USERNAME'), $username),
'password' => env('DB_PASSWORD', $password),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
@@ -96,16 +97,16 @@ return [
],
'pgsql' => [
'driver' => 'pgsql',
'host' => envDefaultWhenEmpty(env('DB_HOST'), $host),
'port' => envDefaultWhenEmpty(env('DB_PORT'), $port),
'database' => envDefaultWhenEmpty(env('DB_DATABASE'), $database),
'username' => envDefaultWhenEmpty(env('DB_USERNAME'), $username),
'host' => env_default_when_empty(env('DB_HOST'), $host),
'port' => env_default_when_empty(env('DB_PORT'), $port),
'database' => env_default_when_empty(env('DB_DATABASE'), $database),
'username' => env_default_when_empty(env('DB_USERNAME'), $username),
'password' => env('DB_PASSWORD', $password),
'charset' => 'utf8',
'prefix' => '',
'search_path' => envDefaultWhenEmpty(env('PGSQL_SCHEMA'), 'public'),
'schema' => envDefaultWhenEmpty(env('PGSQL_SCHEMA'), 'public'),
'sslmode' => envDefaultWhenEmpty(env('PGSQL_SSL_MODE'), 'prefer'),
'search_path' => env_default_when_empty(env('PGSQL_SCHEMA'), 'public'),
'schema' => env_default_when_empty(env('PGSQL_SCHEMA'), 'public'),
'sslmode' => env_default_when_empty(env('PGSQL_SSL_MODE'), 'prefer'),
'sslcert' => env('PGSQL_SSL_CERT'),
'sslkey' => env('PGSQL_SSL_KEY'),
'sslrootcert' => env('PGSQL_SSL_ROOT_CERT'),
@@ -139,21 +140,21 @@ return [
// 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
],
'default' => [
'scheme' => envDefaultWhenEmpty(env('REDIS_SCHEME'), 'tcp'),
'scheme' => env_default_when_empty(env('REDIS_SCHEME'), 'tcp'),
'url' => env('REDIS_URL'),
'path' => env('REDIS_PATH'),
'host' => envDefaultWhenEmpty(env('REDIS_HOST'), '127.0.0.1'),
'port' => envDefaultWhenEmpty(env('REDIS_PORT'), 6379),
'host' => env_default_when_empty(env('REDIS_HOST'), '127.0.0.1'),
'port' => env_default_when_empty(env('REDIS_PORT'), 6379),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'scheme' => envDefaultWhenEmpty(env('REDIS_SCHEME'), 'tcp'),
'scheme' => env_default_when_empty(env('REDIS_SCHEME'), 'tcp'),
'url' => env('REDIS_URL'),
'path' => env('REDIS_PATH'),
'host' => envDefaultWhenEmpty(env('REDIS_HOST'), '127.0.0.1'),
'port' => envDefaultWhenEmpty(env('REDIS_PORT'), 6379),
'host' => env_default_when_empty(env('REDIS_HOST'), '127.0.0.1'),
'port' => env_default_when_empty(env('REDIS_PORT'), 6379),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'database' => env('REDIS_CACHE_DB', '1'),
+11 -10
View File
@@ -75,18 +75,18 @@ return [
'webhooks' => true,
'handle_debts' => true,
'expression_engine' => true,
'running_balance_column' => (bool)envDefaultWhenEmpty(env('USE_RUNNING_BALANCE'), true), // this is only the default value, is not used.
'running_balance_column' => (bool)env_default_when_empty(env('USE_RUNNING_BALANCE'), true), // this is only the default value, is not used.
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2026-04-18',
'build_time' => 1776485897,
'version' => 'develop/2026-05-16',
'build_time' => 1778958405,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
// Docker build info, if present:
'is_docker' => env('IS_DOCKER', false),
'base_image_build' => envDefaultWhenEmpty(env('BASE_IMAGE_BUILD'), '(unknown)'),
'base_image_date' => envDefaultWhenEmpty(env('BASE_IMAGE_DATE'), '(unknown)'),
'base_image_build' => env_default_when_empty(env('BASE_IMAGE_BUILD'), '(unknown)'),
'base_image_date' => env_default_when_empty(env('BASE_IMAGE_DATE'), '(unknown)'),
'is_local_dev' => env('IS_LOCAL_DEV', false),
// generic settings
@@ -112,8 +112,8 @@ return [
'tracker_url' => env('TRACKER_URL', ''),
// authentication settings
'authentication_guard' => envDefaultWhenEmpty(env('AUTHENTICATION_GUARD'), 'web'),
'custom_logout_url' => envDefaultWhenEmpty(env('CUSTOM_LOGOUT_URL'), ''),
'authentication_guard' => env_default_when_empty(env('AUTHENTICATION_GUARD'), 'web'),
'custom_logout_url' => env_default_when_empty(env('CUSTOM_LOGOUT_URL'), ''),
// static config (cannot be changed by user)
'update_endpoint' => 'https://version.firefly-iii.org/index.json',
@@ -194,10 +194,11 @@ return [
'convertToPrimary' => false,
],
'default_currency' => 'EUR',
'default_language' => envDefaultWhenEmpty(env('DEFAULT_LANGUAGE'), 'en_US'),
'default_locale' => envDefaultWhenEmpty(env('DEFAULT_LOCALE'), 'equal'),
'default_language' => env_default_when_empty(env('DEFAULT_LANGUAGE'), 'en_US'),
'default_locale' => env_default_when_empty(env('DEFAULT_LOCALE'), 'equal'),
// account types that may have or set a currency
'valid_currency_account_types' => [
AccountTypeEnum::ASSET->value,
AccountTypeEnum::LOAN->value,
@@ -224,7 +225,7 @@ return [
'available_dark_modes' => ['light', 'dark', 'browser'],
'bill_reminder_periods' => [90, 30, 14, 7, 0],
'valid_view_ranges' => ['1D', '1W', '1M', '3M', '6M', '1Y'],
'valid_url_protocols' => envDefaultWhenEmpty(env('VALID_URL_PROTOCOLS'), 'http,https,ftp,ftps,mailto'), // no longer used, only for default.
'valid_url_protocols' => env_default_when_empty(env('VALID_URL_PROTOCOLS'), 'http,https,ftp,ftps,mailto,abacusfiiiapp'), // no longer used, only for default.
'allowedMimes' => [
// plain files
'text/plain',
+14 -14
View File
@@ -34,7 +34,7 @@ $validChannels = ['single', 'papertrail', 'stdout', 'daily', 'syslog', 'err
$validAuditChannels = ['audit_papertrail', 'audit_stdout', 'audit_stdout', 'audit_daily', 'audit_syslog', 'audit_errorlog'];
// which settings did the user set, if any?
$defaultLogChannel = (string) envDefaultWhenEmpty(env('LOG_CHANNEL'), 'stack');
$defaultLogChannel = (string) env_default_when_empty(env('LOG_CHANNEL'), 'stack');
$auditLogChannel = (string) env('AUDIT_LOG_CHANNEL');
if ('stack' === $defaultLogChannel) {
@@ -60,8 +60,8 @@ return [
|
*/
'default' => envDefaultWhenEmpty(env('LOG_CHANNEL'), 'stack'),
'level' => envDefaultWhenEmpty(env('APP_LOG_LEVEL'), 'info'),
'default' => env_default_when_empty(env('LOG_CHANNEL'), 'stack'),
'level' => env_default_when_empty(env('APP_LOG_LEVEL'), 'info'),
/*
|--------------------------------------------------------------------------
| Log Channels
@@ -93,11 +93,11 @@ return [
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => envDefaultWhenEmpty(env('APP_LOG_LEVEL'), 'info'),
'level' => env_default_when_empty(env('APP_LOG_LEVEL'), 'info'),
],
'papertrail' => [
'driver' => 'monolog',
'level' => envDefaultWhenEmpty(env('APP_LOG_LEVEL'), 'info'),
'level' => env_default_when_empty(env('APP_LOG_LEVEL'), 'info'),
'handler' => SyslogUdpHandler::class,
'handler_with' => [
'host' => env('PAPERTRAIL_HOST'),
@@ -107,21 +107,21 @@ return [
'stdout' => [
'driver' => 'single',
'path' => 'php://stdout',
'level' => envDefaultWhenEmpty(env('APP_LOG_LEVEL'), 'info'),
'level' => env_default_when_empty(env('APP_LOG_LEVEL'), 'info'),
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/ff3-'.PHP_SAPI.'.log'),
'level' => envDefaultWhenEmpty(env('APP_LOG_LEVEL'), 'info'),
'level' => env_default_when_empty(env('APP_LOG_LEVEL'), 'info'),
'days' => 7,
],
'syslog' => [
'driver' => 'syslog',
'level' => envDefaultWhenEmpty(env('APP_LOG_LEVEL'), 'info'),
'level' => env_default_when_empty(env('APP_LOG_LEVEL'), 'info'),
],
'errorlog' => [
'driver' => 'errorlog',
'level' => envDefaultWhenEmpty(env('APP_LOG_LEVEL'), 'info'),
'level' => env_default_when_empty(env('APP_LOG_LEVEL'), 'info'),
],
/*
@@ -130,7 +130,7 @@ return [
*/
'audit_papertrail' => [
'driver' => 'monolog',
'level' => envDefaultWhenEmpty(env('AUDIT_LOG_LEVEL'), 'info'),
'level' => env_default_when_empty(env('AUDIT_LOG_LEVEL'), 'info'),
'handler' => SyslogUdpHandler::class,
'tap' => [AuditLogger::class],
'handler_with' => [
@@ -142,24 +142,24 @@ return [
'driver' => 'single',
'path' => 'php://stdout',
'tap' => [AuditLogger::class],
'level' => envDefaultWhenEmpty(env('AUDIT_LOG_LEVEL'), 'info'),
'level' => env_default_when_empty(env('AUDIT_LOG_LEVEL'), 'info'),
],
'audit_daily' => [
'driver' => 'daily',
'path' => storage_path('logs/ff3-audit.log'),
'tap' => [AuditLogger::class],
'level' => envDefaultWhenEmpty(env('AUDIT_LOG_LEVEL'), 'info'),
'level' => env_default_when_empty(env('AUDIT_LOG_LEVEL'), 'info'),
'days' => 90,
],
'audit_syslog' => [
'driver' => 'syslog',
'tap' => [AuditLogger::class],
'level' => envDefaultWhenEmpty(env('AUDIT_LOG_LEVEL'), 'info'),
'level' => env_default_when_empty(env('AUDIT_LOG_LEVEL'), 'info'),
],
'audit_errorlog' => [
'driver' => 'errorlog',
'tap' => [AuditLogger::class],
'level' => envDefaultWhenEmpty(env('AUDIT_LOG_LEVEL'), 'info'),
'level' => env_default_when_empty(env('AUDIT_LOG_LEVEL'), 'info'),
],
],
];
+7 -7
View File
@@ -34,16 +34,16 @@ return [
| and used as needed; however, this mailer will be used by default.
|
*/
'default' => envDefaultWhenEmpty(env('MAIL_MAILER'), 'log'),
'default' => env_default_when_empty(env('MAIL_MAILER'), 'log'),
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'host' => envDefaultWhenEmpty(env('MAIL_HOST'), 'smtp.mailtrap.io'),
'host' => env_default_when_empty(env('MAIL_HOST'), 'smtp.mailtrap.io'),
'port' => (int) env('MAIL_PORT', 2525),
'encryption' => envDefaultWhenEmpty(env('MAIL_ENCRYPTION'), 'tls'),
'username' => envDefaultWhenEmpty(env('MAIL_USERNAME'), 'user@example.com'),
'password' => envDefaultWhenEmpty(env('MAIL_PASSWORD'), 'password'),
'encryption' => env_default_when_empty(env('MAIL_ENCRYPTION'), 'tls'),
'username' => env_default_when_empty(env('MAIL_USERNAME'), 'user@example.com'),
'password' => env_default_when_empty(env('MAIL_PASSWORD'), 'password'),
'timeout' => null,
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'),
@@ -73,7 +73,7 @@ return [
'sendmail' => [
'transport' => 'sendmail',
'path' => envDefaultWhenEmpty(env('MAIL_SENDMAIL_COMMAND'), '/usr/sbin/sendmail -bs'),
'path' => env_default_when_empty(env('MAIL_SENDMAIL_COMMAND'), '/usr/sbin/sendmail -bs'),
],
'log' => [
'transport' => 'log',
@@ -91,7 +91,7 @@ return [
],
],
'from' => ['address' => envDefaultWhenEmpty(env('MAIL_FROM'), 'changeme@example.com'), 'name' => 'Firefly III Mailer'],
'from' => ['address' => env_default_when_empty(env('MAIL_FROM'), 'changeme@example.com'), 'name' => 'Firefly III Mailer'],
'markdown' => [
'theme' => 'default',
+16 -55
View File
@@ -1,40 +1,21 @@
<?php
/*
* passport.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
return [
/*
|--------------------------------------------------------------------------
| Passport Guard
|--------------------------------------------------------------------------
|
| Here you may specify which authentication guard Passport will use when
| authenticating users. This value should correspond with one of your
| guards that is already present in your "auth" configuration file.
|
*/
'guard' => envDefaultWhenEmpty(env('AUTHENTICATION_GUARD'), 'web'),
/*
|--------------------------------------------------------------------------
| Passport Guard
|--------------------------------------------------------------------------
|
| Here you may specify which authentication guard Passport will use when
| authenticating users. This value should correspond with one of your
| guards that is already present in your "auth" configuration file.
|
*/
'guard' => env_default_when_empty(env('AUTHENTICATION_GUARD'), 'web'),
/*
|--------------------------------------------------------------------------
@@ -51,32 +32,12 @@ return [
'public_key' => env('PASSPORT_PUBLIC_KEY'),
/*
|--------------------------------------------------------------------------
| Client UUIDs
|--------------------------------------------------------------------------
|
| By default, Passport uses auto-incrementing primary keys when assigning
| IDs to clients. However, if Passport is installed using the provided
| --uuids switch, this will be set to "true" and UUIDs will be used.
|
*/
'client_uuids' => false,
/*
|--------------------------------------------------------------------------
| Personal Access Client
|--------------------------------------------------------------------------
|
| If you enable client hashing, you should set the personal access client
| ID and unhashed secret within your environment file. The values will
| get used while issuing fresh personal access tokens to your users.
|
*/
'personal_access_client' => [
'id' => env('PASSPORT_PERSONAL_ACCESS_CLIENT_ID'),
'secret' => env('PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET'),
],
'middleware' => [],
'connection' => env('PASSPORT_CONNECTION'),
];
+1
View File
@@ -141,6 +141,7 @@ return [
'firefly' => [
'explain_pats',
'profile_oauth_clients_explain',
'regenerate_secret',
'administrations_page_title',
'administrations_index_menu',
'expires_at',
+340 -295
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@
"resources/assets/v2"
],
"devDependencies": {
"postcss": "^8.4.47"
"postcss": "^8.5.14"
},
"dependencies": {
"patch-package": "^8.0.1"
+1 -1
View File
@@ -1,3 +1,3 @@
*.LICENSE.txt
*.js
#webhooks
!ff/*/*.js
+1
View File
@@ -0,0 +1 @@
Options -Indexes
+99
View File
@@ -0,0 +1,99 @@
/*
* register.js
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
$(function () {
"use strict";
const form = document.querySelector('form[action="'+route+'"]');
const errorBox = document.getElementById('client-errors');
const errorList = document.getElementById('client-errors-list');
const submitBtn = form.querySelector('button[type="submit"]');
const originalBtnText = submitBtn.textContent;
function showErrors(errors) {
errorList.innerHTML = errors.map(function(e) { return '<li>' + e + '</li>'; }).join('');
errorBox.style.display = 'block';
errorBox.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
async function sha1Hex(str) {
const buf = await crypto.subtle.digest('SHA-1', new TextEncoder().encode(str));
return Array.from(new Uint8Array(buf))
.map(function(b) { return b.toString(16).padStart(2, '0'); })
.join('')
.toUpperCase();
}
async function isPwned(password) {
const hash = await sha1Hex(password);
const prefix = hash.slice(0, 5);
const suffix = hash.slice(5);
const res = await fetch('https://api.pwnedpasswords.com/range/' + prefix, {
headers: { 'Add-Padding': 'true' }
});
if (!res.ok) { return false; }
const text = await res.text();
return text.toUpperCase().split('\n').some(function(line) {
return line.split(':')[0] === suffix;
});
}
form.addEventListener('submit', async function (e) {
e.preventDefault();
errorBox.style.display = 'none';
const password = form.querySelector('[name="password"]').value;
const confirm = form.querySelector('[name="password_confirmation"]').value;
const verify = form.querySelector('[name="verify_password"]');
const errors = [];
if (password.length < 16) {
errors.push(passwordLengthError);
}
if (password !== confirm) {
errors.push(passwordMatchError);
}
if (errors.length > 0) {
showErrors(errors);
return;
}
if (verify && verify.checked) {
submitBtn.disabled = true;
submitBtn.textContent = waitForVerify;
try {
if (await isPwned(password)) {
errors.push(needSecurePassword);
}
} catch (_) {
// network failure — let server validate
}
submitBtn.disabled = false;
submitBtn.textContent = originalBtnText;
}
if (errors.length > 0) {
showErrors(errors);
return;
}
form.submit();
});
})();
+10 -4
View File
@@ -38,9 +38,9 @@ function parseToLocalDates() {
var obj = moment.utc(date).local();
console.log('auto convert to timezone is: "' + obj.format() + '"');
console.log('moment.js format is: "'+date_time_js+'"');
console.log('moment.js format is: "' + date_time_js + '"');
$(this).text(obj.format(date_time_js) + ' ('+ timeZone +')');
$(this).text(obj.format(date_time_js) + ' (' + timeZone + ')');
});
}
@@ -50,19 +50,25 @@ $(function () {
configAccounting(currencySymbol);
// on submit of logout button:
$('.logout-link').click(function(e) {
$('.logout-link').click(function (e) {
e.preventDefault();
document.getElementById('logout-form').submit();
return false;
});
// save sidebar collapsed state when page loads.
$('[data-toggle="push-menu"]').click(function () {
localStorage.setItem('ff3_sidebar_collapsed', (!$('body').hasClass('sidebar-collapse')).toString());
});
// on submit of form, disable any button in form:
$('form.form-horizontal:not(.nodisablebutton)').on('submit', function () {
$('button[type="submit"]').prop('disabled', true);
});
// when you click on a currency, this happens:
$('.currency-option').on('click', currencySelect);
+10 -10
View File
@@ -100,29 +100,29 @@ $(function () {
// show rule triggers
$('.rule-triggers-show').click(function (e) {
var obj = $(e.currentTarget);
$('.rule-trigger-list[data-id="' + obj.data('id') + '"]').show();
$('.rule-triggers-show[data-id="' + obj.data('id') + '"]').hide();
$('.rule-trigger-list[data-id="' + obj.data('id') + '"]').removeClass('hidden');
$('.rule-triggers-show[data-id="' + obj.data('id') + '"]').addClass('hidden');
});
$('.rule-trigger-list').each(function(i,v) {
var obj = $(v);
if(obj.data('count') > 2) {
obj.hide();
$('.rule-triggers-show[data-id="' + obj.data('id') + '"]').show();
obj.addClass('hidden');
$('.rule-triggers-show[data-id="' + obj.data('id') + '"]').removeClass('hidden');
}
});
// show rule actions
$('.rule-actions-show').click(function (e) {
var obj = $(e.currentTarget);
$('.rule-action-list[data-id="' + obj.data('id') + '"]').show();
$('.rule-actions-show[data-id="' + obj.data('id') + '"]').hide();
$('.rule-action-list[data-id="' + obj.data('id') + '"]').removeClass('hidden');
$('.rule-actions-show[data-id="' + obj.data('id') + '"]').addClass('hidden');
});
$('.rule-action-list').each(function(i,v) {
var obj = $(v);
if(obj.data('count') > 1) {
obj.hide();
$('.rule-actions-show[data-id="' + obj.data('id') + '"]').show();
obj.addClass('hidden');
$('.rule-actions-show[data-id="' + obj.data('id') + '"]').removeClass('hidden');
}
});
@@ -194,9 +194,9 @@ function testRuleTriggers(e) {
// Show warning if appropriate
if (data.warning) {
modal.find(".transaction-warning .warning-contents").text(data.warning);
modal.find(".transaction-warning").show();
modal.find(".transaction-warning").removeClass('hidden');
} else {
modal.find(".transaction-warning").hide();
modal.find(".transaction-warning").addClass('hidden');
}
// Show the modal dialog
+1
View File
@@ -36,6 +36,7 @@
"/public/v1/js/ff/accounts/show.js": "/public/v1/js/ff/accounts/show.js",
"/public/v1/js/ff/admin/update/index.js": "/public/v1/js/ff/admin/update/index.js",
"/public/v1/js/ff/admin/users.js": "/public/v1/js/ff/admin/users.js",
"/public/v1/js/ff/auth/register.js": "/public/v1/js/ff/auth/register.js",
"/public/v1/js/ff/bills/create.js": "/public/v1/js/ff/bills/create.js",
"/public/v1/js/ff/bills/edit.js": "/public/v1/js/ff/bills/edit.js",
"/public/v1/js/ff/bills/index.js": "/public/v1/js/ff/bills/index.js",
+1 -1
View File
@@ -22,7 +22,7 @@
"font-awesome": "^4.7.0",
"jquery": "^3",
"laravel-mix": "^6.0",
"postcss": "^8.4.47",
"postcss": "^8.5.14",
"uiv": "^1.4",
"vue": "^2.7",
"vue-i18n": "^8",
@@ -52,7 +52,7 @@
<tr>
<th class="width-30" scope="col">{{ $t('firefly.profile_oauth_client_id') }}</th>
<th class="width-30" scope="col">{{ $t('firefly.name') }}</th>
<th class="width-40" scope="col" style="text-align:right">{{ $t('form.buttons') }}</th>
<th class="width-40" scope="col" style="text-align:right">{{ $t('firefly.actions') }}</th>
</tr>
</thead>
@@ -71,7 +71,7 @@
<!-- Secret -->
<td style="vertical-align: middle;text-align:right">
<div class="btn-group">
<a :title="$t('firefly.regenerate_secret')" class="btn btn-default btn-sm" @click="regenerateSecret(client)">
<a v-if="client.confidential" :title="$t('firefly.regenerate_secret')" class="btn btn-default btn-sm" @click="regenerateSecret(client)">
<em :title="$t('firefly.regenerate_secret')" class="fa fa-retweet"></em>
{{ $t('firefly.regenerate_secret') }}
</a>
@@ -442,11 +442,16 @@ export default {
}
})
.catch(error => {
if (typeof error.response.data === 'object') {
form.errors = _.flatten(_.toArray(error.response.data.errors));
for (const [key, value] of Object.entries(error.response.data.errors)) {
console.log(`${key}: ${value}`);
form.errors.push(value);
}
} else {
form.errors = ['Something went wrong. Please try again.'];
}
console.log(form.errors);
});
},
@@ -213,6 +213,7 @@ export default {
* Prepare the component (Vue 1.x).
*/
ready() {
console.log('ready()');
this.prepareComponent();
},
@@ -220,6 +221,7 @@ export default {
* Prepare the component (Vue 2.x).
*/
mounted() {
console.log('mounted()');
this.prepareComponent();
},
@@ -228,6 +230,7 @@ export default {
* Prepare the component.
*/
prepareComponent() {
console.log('prepareComponent()');
this.getTokens();
// this.getScopes();
@@ -251,8 +254,10 @@ export default {
* Get all of the personal access tokens for the user.
*/
getTokens() {
console.log('getTokens()');
axios.get('./oauth/personal-access-tokens')
.then(response => {
console.log(response.data);
this.tokens = response.data;
});
},
@@ -261,6 +266,7 @@ export default {
* Get all the available scopes.
*/
getScopes() {
console.log('getScopes()');
axios.get('./oauth/scopes')
.then(response => {
this.scopes = response.data;
@@ -271,6 +277,7 @@ export default {
* Show the form for creating new tokens.
*/
showCreateTokenForm() {
console.log('showCreateTokenForm()');
$('#modal-create-token').modal('show');
},
@@ -278,12 +285,14 @@ export default {
* Create a new personal access token.
*/
store() {
console.log('store()');
this.accessToken = null;
this.form.errors = [];
axios.post('./oauth/personal-access-tokens', this.form)
.then(response => {
console.log('Successful POST new token, reset form content.');
this.form.name = '';
this.form.scopes = [];
this.form.errors = [];
@@ -293,6 +302,7 @@ export default {
})
.catch(error => {
console.warn('Bad POST new token, show error.');
if (typeof error.response.data === 'object') {
this.form.errors = _.flatten(_.toArray(error.response.data.errors));
} else {
@@ -323,6 +333,7 @@ export default {
* Show the given access token to the user.
*/
showAccessToken(accessToken) {
console.log('showAccessToken');
$('#modal-create-token').modal('hide');
this.accessToken = accessToken;
+1
View File
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "Financial administrations",
"administrations_index_menu": "Financial administrations",
"expires_at": "Expires at",
+1
View File
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "\u0627\u0644\u0625\u062f\u0627\u0631\u0627\u062a \u0627\u0644\u0645\u0627\u0644\u064a\u0629",
"administrations_index_menu": "\u0627\u0644\u0625\u062f\u0627\u0631\u0627\u062a \u0627\u0644\u0645\u0627\u0644\u064a\u0629",
"expires_at": "\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0627\u0646\u062a\u0647\u0627\u0621",
+1
View File
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "Financial administrations",
"administrations_index_menu": "Financial administrations",
"expires_at": "Expires at",
+1
View File
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "Administracions financeres",
"administrations_index_menu": "Administracions financeres",
"expires_at": "Caduca a les",
+1
View File
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "Spr\u00e1va financ\u00ed",
"administrations_index_menu": "Spr\u00e1va financ\u00ed",
"expires_at": "Expires at",
+1
View File
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "Financial administrations",
"administrations_index_menu": "Financial administrations",
"expires_at": "Expires at",
+9 -8
View File
@@ -1,7 +1,8 @@
{
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"explain_pats": "Pers\u00f6nliche Zugriffstoken sind langfristig g\u00fcltige Schl\u00fcssel (mit einer maximalen G\u00fcltigkeitsdauer von einem Jahr), die einen direkten und uneingeschr\u00e4nkten Zugriff auf Ihre Firefly III-Daten erm\u00f6glichen. Tools wie der Firefly III Importdienst und die Firefly III-Integration in Home Assistant nutzen solche Token, um eine Verbindung zu Firefly III herzustellen und ihre Aufgaben auszuf\u00fchren. Wenn Sie ein Token erstellen, wird es nur einmalig angezeigt. Das Token ist zudem sehr lang.",
"profile_oauth_clients_explain": "Ein OAuth-Client kann verwendet werden, um \u201eintelligente\u201c Anwendungen mit Firefly III zu verbinden: Anwendungen, die in der Lage sind, Sie zu Ihrem Firefly III weiterzuleiten, Ihre Berechtigung einzuholen und Sie wieder zur\u00fcckzuleiten. Der Firefly III Importdienst ist eine solche Anwendung. OAuth-Clients k\u00f6nnen mit oder ohne \u201eSecret\u201c (Zugangsschl\u00fcssel) generiert werden. Dieser Zugangsschl\u00fcssel dient zur Authentifizierung des Clients. Da nicht alle Clients in der Lage sind, den Zugangsschl\u00fcssel zu speichern, haben Sie die M\u00f6glichkeit, einen Client ohne diesen zu generieren.",
"regenerate_secret": "Geheimnis neu erzeugen",
"administrations_page_title": "Finanzverwaltungen",
"administrations_index_menu": "Finanzverwaltung",
"expires_at": "G\u00fcltig bis",
@@ -74,8 +75,8 @@
"profile_whoops": "Huch!",
"profile_something_wrong": "Ein Problem ist aufgetreten!",
"profile_try_again": "Ein Problem ist aufgetreten. Bitte versuchen Sie es erneut.",
"profile_oauth_clients": "OAuth Clients and Applications",
"profile_oauth_no_clients": "You have not created any OAuth clients or applications.",
"profile_oauth_clients": "OAuth-Clients und -Anwendungen",
"profile_oauth_no_clients": "Sie haben noch keine OAuth-Clients oder -Anwendungen erstellt.",
"profile_oauth_clients_header": "Clients",
"profile_oauth_client_id": "Client-ID",
"profile_oauth_client_name": "Name",
@@ -85,7 +86,7 @@
"profile_oauth_edit_client": "Client bearbeiten",
"profile_oauth_name_help": "Etwas das Ihre Nutzer erkennen und dem sie vertrauen.",
"profile_oauth_redirect_url": "Weiterleitungs-URL",
"profile_oauth_clients_external_auth": "Please note that if you're using an external authentication provider like Authelia, OAuth Clients will not work. You can use Personal Access Tokens only.",
"profile_oauth_clients_external_auth": "Wenn Sie einen externen Authentifizierungsanbieter wie Authelia verwenden, funktionieren OAuth-Clients nicht. Es k\u00f6nnen daher nur pers\u00f6nliche Zugriffstoken verwendet werden.",
"profile_oauth_redirect_url_help": "Die Authorisierungs-Callback-URL Ihrer Anwendung.",
"profile_authorized_apps": "Autorisierte Anwendungen",
"profile_authorized_clients": "Autorisierte Clients",
@@ -101,10 +102,10 @@
"profile_save_changes": "\u00c4nderungen speichern",
"default_group_title_name": "(ohne Gruppierung)",
"piggy_bank": "Sparschwein",
"profile_oauth_client_secret_title": "Client Secret",
"profile_oauth_client_secret_title": "Pers\u00f6nlicher Zugangsschl\u00fcssel",
"profile_oauth_client_secret_expl": "Hier ist Ihr neuer pers\u00f6nlicher Zugangsschl\u00fcssel. Dies ist das einzige Mal, dass er angezeigt wird, also verlieren Sie ihn nicht! Sie k\u00f6nnen diesen Token jetzt verwenden, um API-Anfragen zu stellen.",
"profile_oauth_confidential": "Keep a secret?",
"profile_oauth_confidential_help": "Can the application you're using this for keep a secret? The Firefly III Data Importer CANNOT keep a secret, so UNCHECK the box. In other cases, it's up to you.",
"profile_oauth_confidential": "Einen Zugangsschl\u00fcssel f\u00fcr dich behalten?",
"profile_oauth_confidential_help": "Kann die Anwendung, f\u00fcr die Sie dies verwenden, ein Geheimnis bewahren? Der Firefly III Importdienst kann ein Geheimnis NICHT bewahren, deaktivieren Sie daher das Kontrollk\u00e4stchen. In allen anderen F\u00e4llen liegt die Entscheidung bei Ihnen.",
"multi_account_warning_unknown": "Abh\u00e4ngig von der Art der Buchung, die Sie anlegen, kann das Quell- und\/oder Zielkonto nachfolgender Aufteilungen durch das \u00fcberschrieben werden, was in der ersten Aufteilung der Buchung definiert wurde.",
"multi_account_warning_withdrawal": "Bedenken Sie, dass das Quellkonto nachfolgender Aufteilungen von dem, was in der ersten Aufteilung der Abhebung definiert ist, au\u00dfer Kraft gesetzt wird.",
"multi_account_warning_deposit": "Bedenken Sie, dass das Zielkonto nachfolgender Aufteilungen von dem, was in der ersten Aufteilung der Einnahmen definiert ist, au\u00dfer Kraft gesetzt wird.",
+1
View File
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "Financial administrations",
"administrations_index_menu": "Financial administrations",
"expires_at": "Expires at",
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "Financial administrations",
"administrations_index_menu": "Financial administrations",
"expires_at": "Expires at",
+1
View File
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "Financial administrations",
"administrations_index_menu": "Financial administrations",
"expires_at": "Expires at",
+1
View File
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "Administraciones financieras",
"administrations_index_menu": "Administraciones financieras",
"expires_at": "Expira el",
+1
View File
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "Financial administrations",
"administrations_index_menu": "Financial administrations",
"expires_at": "Expires at",
+1
View File
@@ -2,6 +2,7 @@
"firefly": {
"explain_pats": "Personal Access Tokens are long lived (with a maximum of 1 year) keys that allow direct and unlimited access to your Firefly III data. Tools like the Firefly III Data Importer and the Firefly III integration in Home Assistant use such tokens to connect to Firefly III and do their thing. When you create a token, it is only visible once. The token is also very long.",
"profile_oauth_clients_explain": "An OAuth client can be used to connect \"smart\" applications to Firefly III: applications that are capable of redirecting you to your Firefly III, get your permission, and return you back. The Firefly III Data Importer is such an application. OAuth clients can be generated with or without a \"secret\". This secret is used to authenticate the client. Since not all clients are capable of storing the secret, so you have the option to generate a client without one.",
"regenerate_secret": "Regenerate secret",
"administrations_page_title": "Financial administrations",
"administrations_index_menu": "Financial administrations",
"expires_at": "Expires at",

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