Showing preview only (2,478K chars total). Download the full file or copy to clipboard to get everything.
Repository: ellite/Wallos
Branch: main
Commit: b39f0ae40ff9
Files: 345
Total size: 2.3 MB
Directory structure:
gitextract_dfd08_8w/
├── .dockerignore
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ └── build-release.yaml
├── .gitignore
├── .tmp/
│ └── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE.md
├── README.md
├── SECURITY.md
├── about.php
├── admin.php
├── api/
│ ├── admin/
│ │ ├── get_admin_settings.php
│ │ ├── get_oidc_settings.php
│ │ └── set_disable_password_login.php
│ ├── categories/
│ │ └── get_categories.php
│ ├── currencies/
│ │ └── get_currencies.php
│ ├── fixer/
│ │ └── get_fixer.php
│ ├── household/
│ │ └── get_household.php
│ ├── notifications/
│ │ └── get_notification_settings.php
│ ├── payment_methods/
│ │ └── get_payment_methods.php
│ ├── settings/
│ │ └── get_settings.php
│ ├── status/
│ │ └── version.php
│ ├── subscriptions/
│ │ ├── get_ical_feed.php
│ │ ├── get_monthly_cost.php
│ │ └── get_subscriptions.php
│ └── users/
│ └── get_user.php
├── calendar.php
├── cronjobs
├── docker-compose.yaml
├── endpoints/
│ ├── admin/
│ │ ├── adduser.php
│ │ ├── deleteunusedlogos.php
│ │ ├── deleteuser.php
│ │ ├── enableoidc.php
│ │ ├── saveoidcsettings.php
│ │ ├── saveopenregistrations.php
│ │ ├── savesecuritysettings.php
│ │ ├── savesmtpsettings.php
│ │ └── updatenotification.php
│ ├── ai/
│ │ ├── delete_recommendation.php
│ │ ├── fetch_models.php
│ │ ├── generate_recommendations.php
│ │ └── save_settings.php
│ ├── categories/
│ │ └── category.php
│ ├── cronjobs/
│ │ ├── checkforupdates.php
│ │ ├── cleanupresettokens.php
│ │ ├── createdatabase.php
│ │ ├── sendcancellationnotifications.php
│ │ ├── sendnotifications.php
│ │ ├── sendresetpasswordemails.php
│ │ ├── sendverificationemails.php
│ │ ├── settimezone.php
│ │ ├── storetotalyearlycost.php
│ │ ├── updateexchange.php
│ │ ├── updatenextpayment.php
│ │ └── validate.php
│ ├── currency/
│ │ ├── currency.php
│ │ ├── fixer_api_key.php
│ │ └── update_exchange.php
│ ├── db/
│ │ ├── backup.php
│ │ ├── import.php
│ │ ├── migrate.php
│ │ └── restore.php
│ ├── household/
│ │ └── household.php
│ ├── logos/
│ │ └── search.php
│ ├── notifications/
│ │ ├── savediscordnotifications.php
│ │ ├── saveemailnotifications.php
│ │ ├── savegotifynotifications.php
│ │ ├── savemattermostnotifications.php
│ │ ├── savenotificationsettings.php
│ │ ├── saventfynotifications.php
│ │ ├── savepushovernotifications.php
│ │ ├── savepushplusnotifications.php
│ │ ├── saveserverchannotifications.php
│ │ ├── savetelegramnotifications.php
│ │ ├── savewebhooknotifications.php
│ │ ├── testdiscordnotifications.php
│ │ ├── testemailnotifications.php
│ │ ├── testgotifynotifications.php
│ │ ├── testmattermostnotifications.php
│ │ ├── testntfynotifications.php
│ │ ├── testpushovernotifications.php
│ │ ├── testpushplusnotifications.php
│ │ ├── testserverchannotifications.php
│ │ ├── testtelegramnotifications.php
│ │ └── testwebhooknotifications.php
│ ├── payments/
│ │ ├── add.php
│ │ ├── delete.php
│ │ ├── get.php
│ │ ├── rename.php
│ │ ├── search.php
│ │ ├── sort.php
│ │ └── toggle.php
│ ├── settings/
│ │ ├── colortheme.php
│ │ ├── convert_currency.php
│ │ ├── customcss.php
│ │ ├── customtheme.php
│ │ ├── deleteaccount.php
│ │ ├── disabled_to_bottom.php
│ │ ├── hide_disabled.php
│ │ ├── mobile_navigation.php
│ │ ├── monthly_price.php
│ │ ├── remove_background.php
│ │ ├── resettheme.php
│ │ ├── show_original_price.php
│ │ ├── subscription_progress.php
│ │ └── theme.php
│ ├── subscription/
│ │ ├── add.php
│ │ ├── clone.php
│ │ ├── delete.php
│ │ ├── exportcalendar.php
│ │ ├── get.php
│ │ ├── getcalendar.php
│ │ └── renew.php
│ ├── subscriptions/
│ │ ├── export.php
│ │ └── get.php
│ └── user/
│ ├── budget.php
│ ├── delete_avatar.php
│ ├── disable_totp.php
│ ├── enable_totp.php
│ ├── regenerateapikey.php
│ └── save_user.php
├── health.php
├── images/
│ └── siteicons/
│ └── svg/
│ ├── automatic.php
│ ├── category.php
│ ├── check.php
│ ├── clone.php
│ ├── delete.php
│ ├── edit.php
│ ├── export_ical.php
│ ├── logo.php
│ ├── manual.php
│ ├── mobile-menu/
│ │ ├── about.php
│ │ ├── admin.php
│ │ ├── calendar.php
│ │ ├── clone.php
│ │ ├── delete.php
│ │ ├── edit.php
│ │ ├── home.php
│ │ ├── logout.php
│ │ ├── profile.php
│ │ ├── renew.php
│ │ ├── settings.php
│ │ ├── statistics.php
│ │ └── subscriptions.php
│ ├── notes.php
│ ├── payment.php
│ ├── renew.php
│ ├── save.php
│ ├── subscription.php
│ ├── web.php
│ └── websearch.php
├── includes/
│ ├── checkredirect.php
│ ├── checksession.php
│ ├── checkuser.php
│ ├── connect.php
│ ├── connect_endpoint.php
│ ├── connect_endpoint_crontabs.php
│ ├── currency_formatter.php
│ ├── filters_menu.php
│ ├── footer.php
│ ├── getdbkeys.php
│ ├── getsettings.php
│ ├── header.php
│ ├── i18n/
│ │ ├── ca.php
│ │ ├── cs.php
│ │ ├── da.php
│ │ ├── de.php
│ │ ├── el.php
│ │ ├── en.php
│ │ ├── es.php
│ │ ├── fr.php
│ │ ├── getlang.php
│ │ ├── id.php
│ │ ├── it.php
│ │ ├── jp.php
│ │ ├── ko.php
│ │ ├── languages.php
│ │ ├── nl.php
│ │ ├── pl.php
│ │ ├── pt.php
│ │ ├── pt_br.php
│ │ ├── ro.php
│ │ ├── ru.php
│ │ ├── sl.php
│ │ ├── sr.php
│ │ ├── sr_lat.php
│ │ ├── tr.php
│ │ ├── uk.php
│ │ ├── vi.php
│ │ ├── zh_cn.php
│ │ └── zh_tw.php
│ ├── inputvalidation.php
│ ├── list_subscriptions.php
│ ├── oidc/
│ │ ├── handle_oidc_callback.php
│ │ ├── oidc_create_user.php
│ │ └── oidc_login.php
│ ├── sort_options.php
│ ├── ssrf_helper.php
│ ├── stats_calculations.php
│ ├── validate_endpoint.php
│ ├── validate_endpoint_admin.php
│ └── version.php
├── index.php
├── libs/
│ ├── OTPHP/
│ │ ├── Factory.php
│ │ ├── FactoryInterface.php
│ │ ├── HOTP.php
│ │ ├── HOTPInterface.php
│ │ ├── InternalClock.php
│ │ ├── OTP.php
│ │ ├── OTPInterface.php
│ │ ├── ParameterTrait.php
│ │ ├── TOTP.php
│ │ ├── TOTPInterface.php
│ │ └── Url.php
│ ├── PHPMailer/
│ │ ├── Exception.php
│ │ ├── PHPMailer.php
│ │ └── SMTP.php
│ ├── Psr/
│ │ └── Clock/
│ │ └── ClockInterface.php
│ ├── constant_time_encoding/
│ │ ├── Base32.php
│ │ ├── Base32Hex.php
│ │ ├── Base64.php
│ │ ├── Base64DotSlash.php
│ │ ├── Base64DotSlashOrdered.php
│ │ ├── Base64UrlSafe.php
│ │ ├── Binary.php
│ │ ├── EncoderInterface.php
│ │ ├── Encoding.php
│ │ ├── Hex.php
│ │ └── RFC4648.php
│ └── csrf.php
├── login.php
├── logos.php
├── logout.php
├── manifest.json
├── migrations/
│ ├── 000001.php
│ ├── 000002.php
│ ├── 000003.php
│ ├── 000004.php
│ ├── 000005.php
│ ├── 000006.php
│ ├── 000007.php
│ ├── 000008.php
│ ├── 000009.php
│ ├── 000010.php
│ ├── 000011.php
│ ├── 000012.php
│ ├── 000013.php
│ ├── 000014.php
│ ├── 000015.php
│ ├── 000016.php
│ ├── 000017.php
│ ├── 000018.php
│ ├── 000019.php
│ ├── 000020.php
│ ├── 000021.php
│ ├── 000022.php
│ ├── 000023.php
│ ├── 000024.php
│ ├── 000025.php
│ ├── 000026.php
│ ├── 000027.php
│ ├── 000028.php
│ ├── 000029.php
│ ├── 000030.php
│ ├── 000031.php
│ ├── 000032.php
│ ├── 000033.php
│ ├── 000034.php
│ ├── 000035.php
│ ├── 000036.php
│ ├── 000037.php
│ ├── 000038.php
│ ├── 000039.php
│ ├── 000040.php
│ ├── 000041.php
│ ├── 000042.php
│ ├── 000043.php
│ └── 000044.php
├── nginx.conf
├── nginx.default.conf
├── passwordreset.php
├── profile.php
├── registration.php
├── robots.txt
├── scripts/
│ ├── admin.js
│ ├── all.js
│ ├── calendar.js
│ ├── common.js
│ ├── dashboard.js
│ ├── i18n/
│ │ ├── ca.js
│ │ ├── cs.js
│ │ ├── da.js
│ │ ├── de.js
│ │ ├── el.js
│ │ ├── en.js
│ │ ├── es.js
│ │ ├── fr.js
│ │ ├── getlang.js
│ │ ├── id.js
│ │ ├── it.js
│ │ ├── jp.js
│ │ ├── ko.js
│ │ ├── nl.js
│ │ ├── pl.js
│ │ ├── pt.js
│ │ ├── pt_br.js
│ │ ├── ro.js
│ │ ├── ru.js
│ │ ├── sl.js
│ │ ├── sr.js
│ │ ├── sr_lat.js
│ │ ├── tr.js
│ │ ├── uk.js
│ │ ├── vi.js
│ │ ├── zh_cn.js
│ │ └── zh_tw.js
│ ├── libs/
│ │ └── chart.js
│ ├── login.js
│ ├── notifications.js
│ ├── profile.js
│ ├── registration.js
│ ├── settings.js
│ ├── stats.js
│ ├── subscriptions.js
│ └── theme.js
├── service-worker.js
├── settings.php
├── startup.sh
├── stats.php
├── styles/
│ ├── barlow.css
│ ├── brands.css
│ ├── dark-theme.css
│ ├── login-dark-theme.css
│ ├── login.css
│ ├── styles.css
│ ├── theme.css
│ └── themes/
│ ├── green.css
│ ├── purple.css
│ ├── red.css
│ └── yellow.css
├── subscriptions.php
├── totp.php
└── verifyemail.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
# Exclude .vscode directory
.vscode/
# Exclude .git directory
.git/
.github/
*.md
.gitignore
.dockerignore
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [ellite]
custom: ['https://www.paypal.com/paypalme/miguelr']
================================================
FILE: .github/workflows/build-release.yaml
================================================
name: Build & Release
on:
push:
branches:
- "*"
pull_request:
branches:
- main
permissions:
contents: write
pull-requests: write
packages: write
env:
# login to docker hub with provided secrets
REGISTRY: docker.io
REGISTRY_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
IMAGE_NAME: ${{ vars.DOCKERHUB_TAG }}
# For release-please, see available types at https://github.com/google-github-actions/release-please-action/tree/v4/?tab=readme-ov-file#release-types-supported
PROJECT_TYPE: simple
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: rp
if: github.event_name != 'pull_request' && github.ref_name == 'main'
uses: google-github-actions/release-please-action@v4
with:
release-type: ${{ env.PROJECT_TYPE }}
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.REGISTRY_USERNAME }}
password: ${{ env.REGISTRY_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare tags for Docker meta
id: tags
env:
# When release please is skipped, these values will be empty
is_release: ${{ steps.rp.outputs.release_created }}
version: v${{ steps.rp.outputs.major }}.${{ steps.rp.outputs.minor }}.${{ steps.rp.outputs.patch }}
run: |
tags=""
if [[ "$is_release" = 'true' ]]; then
tags="type=semver,pattern={{version}},value=$version
type=ref,event=branch,value=main"
else
tags="type=ref,event=branch
type=ref,event=pr"
fi
{
echo 'tags<<EOF'
echo "$tags"
echo EOF
} >> "$GITHUB_OUTPUT"
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
ghcr.io/ellite/wallos
tags: ${{ steps.tags.outputs.tags }}
# necessary for multi-platform images
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
# necessary for multi-platform images
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Send notification to Discord
if: github.event_name != 'pull_request'
uses: Ilshidur/action-discord@master
with:
args: "A new release has been created: ${{ steps.meta.outputs.tags }}"
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
================================================
FILE: .gitignore
================================================
/db/*
!/db/wallos.empty.db
/images/uploads/logos/*
!/images/uploads/logos/wallos.png
.DS_Store
.idea/
.vscode/
================================================
FILE: .tmp/.gitignore
================================================
*
!.gitignore
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## [4.7.2](https://github.com/ellite/Wallos/compare/v4.7.1...v4.7.2) (2026-03-19)
### Bug Fixes
* password reset tokens now expire after 60 minutes ([90bb618](https://github.com/ellite/Wallos/commit/90bb6186ee4091590b6efdef824c85f2494ff2bb))
* vulnerability would allow to bypass 2fa ([#1021](https://github.com/ellite/Wallos/issues/1021)) ([90bb618](https://github.com/ellite/Wallos/commit/90bb6186ee4091590b6efdef824c85f2494ff2bb))
## [4.7.1](https://github.com/ellite/Wallos/compare/v4.7.0...v4.7.1) (2026-03-19)
### Bug Fixes
* remove extra line on languages.php causing headers already sent ([#1019](https://github.com/ellite/Wallos/issues/1019)) ([f5c9a34](https://github.com/ellite/Wallos/commit/f5c9a3498ed2df8ae6b225fc63ce01a8ed5ce348))
## [4.7.0](https://github.com/ellite/Wallos/compare/v4.6.2...v4.7.0) (2026-03-19)
### Features
* add romanian translations ([#1017](https://github.com/ellite/Wallos/issues/1017)) ([e87387f](https://github.com/ellite/Wallos/commit/e87387f0ebb540cd33e6dfda7181db9db650ecef))
* mask ai api key on the settings page ([e87387f](https://github.com/ellite/Wallos/commit/e87387f0ebb540cd33e6dfda7181db9db650ecef))
### Bug Fixes
* ai recommendation numbering when deleting a recommendation ([e87387f](https://github.com/ellite/Wallos/commit/e87387f0ebb540cd33e6dfda7181db9db650ecef))
* calendar ocurrences to respect subscriptions start date ([e87387f](https://github.com/ellite/Wallos/commit/e87387f0ebb540cd33e6dfda7181db9db650ecef))
* logo search ([e87387f](https://github.com/ellite/Wallos/commit/e87387f0ebb540cd33e6dfda7181db9db650ecef))
* retain first and last name when switching language during registration ([e87387f](https://github.com/ellite/Wallos/commit/e87387f0ebb540cd33e6dfda7181db9db650ecef))
* set login cookie to httponly ([e87387f](https://github.com/ellite/Wallos/commit/e87387f0ebb540cd33e6dfda7181db9db650ecef))
* ssrf vulnerability on several endpoints ([e87387f](https://github.com/ellite/Wallos/commit/e87387f0ebb540cd33e6dfda7181db9db650ecef))
* unicode character on the css file ([e87387f](https://github.com/ellite/Wallos/commit/e87387f0ebb540cd33e6dfda7181db9db650ecef))
* xss vulnerability on payment method rename endpoint ([e87387f](https://github.com/ellite/Wallos/commit/e87387f0ebb540cd33e6dfda7181db9db650ecef))
## [4.6.2](https://github.com/ellite/Wallos/compare/v4.6.1...v4.6.2) (2026-03-05)
### Bug Fixes
* ssrf vulnerability on all test notifications endpoint ([e8a5135](https://github.com/ellite/Wallos/commit/e8a513591dbbf885966e2ef55c38622785b9060d))
* vulnerability allowed to delete avatars from other users ([e8a5135](https://github.com/ellite/Wallos/commit/e8a513591dbbf885966e2ef55c38622785b9060d))
* xss vulnerability on password reset page ([e8a5135](https://github.com/ellite/Wallos/commit/e8a513591dbbf885966e2ef55c38622785b9060d))
## [4.6.1](https://github.com/ellite/Wallos/compare/v4.6.0...v4.6.1) (2026-02-10)
### Bug Fixes
* vulnerabily on add subscription endpoint ([#991](https://github.com/ellite/Wallos/issues/991)) ([76a53df](https://github.com/ellite/Wallos/commit/76a53df9cb4658123b8f0b7cf1826f1ba7d1c960))
## [4.6.0](https://github.com/ellite/Wallos/compare/v4.5.0...v4.6.0) (2025-12-20)
### Features
* add catalan translation ([#970](https://github.com/ellite/Wallos/issues/970)) ([f5746e7](https://github.com/ellite/Wallos/commit/f5746e76a5dd6bbda7d52b1a2229c02bb9fad94b))
* add robots.txt to disallow indexing. ([f5746e7](https://github.com/ellite/Wallos/commit/f5746e76a5dd6bbda7d52b1a2229c02bb9fad94b))
* add serverchan notifications. ([f5746e7](https://github.com/ellite/Wallos/commit/f5746e76a5dd6bbda7d52b1a2229c02bb9fad94b))
* notifications for subscription can be triggered up to 180 days before payment date. ([f5746e7](https://github.com/ellite/Wallos/commit/f5746e76a5dd6bbda7d52b1a2229c02bb9fad94b))
### Bug Fixes
* use RFC 5545 compliant date format in iCal exports ([#965](https://github.com/ellite/Wallos/issues/965)) ([b6b0abe](https://github.com/ellite/Wallos/commit/b6b0abed0d916c3ae5a31257f4c0b1a34436ad91))
* use RFC 5545 compliant date format in iCal exports. ([f5746e7](https://github.com/ellite/Wallos/commit/f5746e76a5dd6bbda7d52b1a2229c02bb9fad94b))
* use stable UID for iCal events to prevent duplicates. ([f5746e7](https://github.com/ellite/Wallos/commit/f5746e76a5dd6bbda7d52b1a2229c02bb9fad94b))
## [4.5.0](https://github.com/ellite/Wallos/compare/v4.4.1...v4.5.0) (2025-10-18)
### Features
* enforce CSRF protection and POST-only policy across endpoints ([#940](https://github.com/ellite/Wallos/issues/940)) ([3247ce2](https://github.com/ellite/Wallos/commit/3247ce2c8768d8e5910f74e5b8eba657b5b05cc1))
## [4.4.1](https://github.com/ellite/Wallos/compare/v4.4.0...v4.4.1) (2025-10-12)
### Bug Fixes
* get_subscriptions api endpoint was not returning subscriptions ([#937](https://github.com/ellite/Wallos/issues/937)) ([d6329a7](https://github.com/ellite/Wallos/commit/d6329a7af5a48f74b5f1d44a51cdc8c09dc2508b))
## [4.4.0](https://github.com/ellite/Wallos/compare/v4.3.0...v4.4.0) (2025-10-12)
### Features
* add mattermost notifications ([#923](https://github.com/ellite/Wallos/issues/923)) ([#934](https://github.com/ellite/Wallos/issues/934)) ([5629a31](https://github.com/ellite/Wallos/commit/5629a319bc5eb6cb80abfca06725aed9d2d9df88))
* add openrouter ai endpoint ([#922](https://github.com/ellite/Wallos/issues/922)) ([5629a31](https://github.com/ellite/Wallos/commit/5629a319bc5eb6cb80abfca06725aed9d2d9df88))
* enhance get_subscriptions API with admin access ([#928](https://github.com/ellite/Wallos/issues/928)) ([5629a31](https://github.com/ellite/Wallos/commit/5629a319bc5eb6cb80abfca06725aed9d2d9df88))
### Bug Fixes
* add autocomplete attribute to inputes ([#926](https://github.com/ellite/Wallos/issues/926)) ([5629a31](https://github.com/ellite/Wallos/commit/5629a319bc5eb6cb80abfca06725aed9d2d9df88))
## [4.3.0](https://github.com/ellite/Wallos/compare/v4.2.0...v4.3.0) (2025-09-15)
### Features
* add health endpoint and healthcheck to container ([#919](https://github.com/ellite/Wallos/issues/919)) ([852cb48](https://github.com/ellite/Wallos/commit/852cb485a65a58c91577b369fb9ea293d370bda8))
## [4.2.0](https://github.com/ellite/Wallos/compare/v4.1.1...v4.2.0) (2025-09-14)
### Features
* add pushplus notification service ([#911](https://github.com/ellite/Wallos/issues/911)) ([27ac805](https://github.com/ellite/Wallos/commit/27ac805141c0d170a40c2a7796a589a5ef29544f))
* make container shutdown instant & graceful ([27ac805](https://github.com/ellite/Wallos/commit/27ac805141c0d170a40c2a7796a589a5ef29544f))
* make container shutdown instant & graceful ([#916](https://github.com/ellite/Wallos/issues/916)) ([27ac805](https://github.com/ellite/Wallos/commit/27ac805141c0d170a40c2a7796a589a5ef29544f))
* option to delete ai recommendations ([27ac805](https://github.com/ellite/Wallos/commit/27ac805141c0d170a40c2a7796a589a5ef29544f))
### Bug Fixes
* parsing ai recommendations from gemini ([#909](https://github.com/ellite/Wallos/issues/909)) ([27ac805](https://github.com/ellite/Wallos/commit/27ac805141c0d170a40c2a7796a589a5ef29544f))
## [4.1.1](https://github.com/ellite/Wallos/compare/v4.1.0...v4.1.1) (2025-08-13)
### Bug Fixes
* missing apikey validation error on get_monthly_cost api endpoint ([3ecc160](https://github.com/ellite/Wallos/commit/3ecc160ccb73f22367bea427315519876de74a65))
* redirect from dashboard to subscriptions for new users ([3ecc160](https://github.com/ellite/Wallos/commit/3ecc160ccb73f22367bea427315519876de74a65))
* wrong check for disabling password login ([3ecc160](https://github.com/ellite/Wallos/commit/3ecc160ccb73f22367bea427315519876de74a65))
## [4.1.0](https://github.com/ellite/Wallos/compare/v4.0.0...v4.1.0) (2025-08-11)
### Features
* add at a glance dashboard ([ba6dddf](https://github.com/ellite/Wallos/commit/ba6dddf52601fdbeb18897731beacc48d16043c3))
* add get_oidc_settings endpoint to the api ([ba6dddf](https://github.com/ellite/Wallos/commit/ba6dddf52601fdbeb18897731beacc48d16043c3))
* ai recommendations with chatgpt, gemini or ollama ([ba6dddf](https://github.com/ellite/Wallos/commit/ba6dddf52601fdbeb18897731beacc48d16043c3))
* allow to disable password login when oidc is enabled ([ba6dddf](https://github.com/ellite/Wallos/commit/ba6dddf52601fdbeb18897731beacc48d16043c3))
* display ai recommendations on the dashboard ([ba6dddf](https://github.com/ellite/Wallos/commit/ba6dddf52601fdbeb18897731beacc48d16043c3))
* refactor css colors ([ba6dddf](https://github.com/ellite/Wallos/commit/ba6dddf52601fdbeb18897731beacc48d16043c3))
### Bug Fixes
* accept both api_key and apiKey as parameter on the api ([ba6dddf](https://github.com/ellite/Wallos/commit/ba6dddf52601fdbeb18897731beacc48d16043c3))
## [4.0.0](https://github.com/ellite/Wallos/compare/v3.3.1...v4.0.0) (2025-07-21)
### ⚠ BREAKING CHANGES
* add oauth / oidc support ([#875](https://github.com/ellite/Wallos/issues/875))
### Features
* add oauth / oidc support ([#875](https://github.com/ellite/Wallos/issues/875)) ([805e688](https://github.com/ellite/Wallos/commit/805e688ec0fac1dbb362e847ed8a4e3e301ee113))
* add oauth/oidc support ([#873](https://github.com/ellite/Wallos/issues/873)) ([c0d53e4](https://github.com/ellite/Wallos/commit/c0d53e4423996595e5c82404af92e077c00eae47))
## [3.3.1](https://github.com/ellite/Wallos/compare/v3.3.0...v3.3.1) (2025-07-19)
### Bug Fixes
* code of new taiwan dollar ([596cbc4](https://github.com/ellite/Wallos/commit/596cbc42464100dc8c6db5d07c090dab4b767268))
* decoding of header from database on the webhook notifications ([596cbc4](https://github.com/ellite/Wallos/commit/596cbc42464100dc8c6db5d07c090dab4b767268))
* unicode issue on telegram notifications ([#871](https://github.com/ellite/Wallos/issues/871)) ([596cbc4](https://github.com/ellite/Wallos/commit/596cbc42464100dc8c6db5d07c090dab4b767268))
## [3.3.0](https://github.com/ellite/Wallos/compare/v3.2.0...v3.3.0) (2025-06-09)
### Features
* set todays date on start subscription field for new subscriptions by default ([#848](https://github.com/ellite/Wallos/issues/848)) ([d3fd938](https://github.com/ellite/Wallos/commit/d3fd9387d34f430adb84ef553193b4ad3080c009))
### Bug Fixes
* visual issue with date fields on ios ([#846](https://github.com/ellite/Wallos/issues/846)) ([e2df8f7](https://github.com/ellite/Wallos/commit/e2df8f7e24678f9d62f36f68c94de838fc741913))
## [3.2.0](https://github.com/ellite/Wallos/compare/v3.1.1...v3.2.0) (2025-06-08)
### Features
* add button to auto fill the next payment date ([48db4e3](https://github.com/ellite/Wallos/commit/48db4e300df6128b7cc0b4e0c86271bfb3159545))
* add first and last names to the user profile ([48db4e3](https://github.com/ellite/Wallos/commit/48db4e300df6128b7cc0b4e0c86271bfb3159545))
* add indonesian language ([#842](https://github.com/ellite/Wallos/issues/842)) ([48db4e3](https://github.com/ellite/Wallos/commit/48db4e300df6128b7cc0b4e0c86271bfb3159545))
* add new currency ([48db4e3](https://github.com/ellite/Wallos/commit/48db4e300df6128b7cc0b4e0c86271bfb3159545))
* Add new currency ([#829](https://github.com/ellite/Wallos/issues/829)) ([288ad45](https://github.com/ellite/Wallos/commit/288ad456564c307018541a09df447898e1d62d26))
* enable IPv6 environments by configuring a dual-stack listen in nginx ([48db4e3](https://github.com/ellite/Wallos/commit/48db4e300df6128b7cc0b4e0c86271bfb3159545))
### Bug Fixes
* vulnerability on test webhook endpoint ([48db4e3](https://github.com/ellite/Wallos/commit/48db4e300df6128b7cc0b4e0c86271bfb3159545))
## [3.1.1](https://github.com/ellite/Wallos/compare/v3.1.0...v3.1.1) (2025-05-15)
### Bug Fixes
* issue listing prices when uah was added to the list of currencies ([#823](https://github.com/ellite/Wallos/issues/823)) ([bd20b56](https://github.com/ellite/Wallos/commit/bd20b5697659fc6117113205a3995d7e5f9026c9))
## [3.1.0](https://github.com/ellite/Wallos/compare/v3.0.2...v3.1.0) (2025-05-08)
### Features
* add danish translation ([0cfefc7](https://github.com/ellite/Wallos/commit/0cfefc7f07056d59ad911f926cc56ff3e6c8e261))
### Bug Fixes
* disable totp with backup code ([0cfefc7](https://github.com/ellite/Wallos/commit/0cfefc7f07056d59ad911f926cc56ff3e6c8e261))
* gotify settings test ([0cfefc7](https://github.com/ellite/Wallos/commit/0cfefc7f07056d59ad911f926cc56ff3e6c8e261))
* vulnerability adding logos from url ([0cfefc7](https://github.com/ellite/Wallos/commit/0cfefc7f07056d59ad911f926cc56ff3e6c8e261))
## [3.0.2](https://github.com/ellite/Wallos/compare/v3.0.1...v3.0.2) (2025-05-03)
### Bug Fixes
* delete avatar would not work if wallos is on a subfolder ([69c7d52](https://github.com/ellite/Wallos/commit/69c7d52cf8d708bcb046343faa663209c8d36779))
* some strings not using translations on the calendar page ([69c7d52](https://github.com/ellite/Wallos/commit/69c7d52cf8d708bcb046343faa663209c8d36779))
* vulnerability on delete avatar ([69c7d52](https://github.com/ellite/Wallos/commit/69c7d52cf8d708bcb046343faa663209c8d36779))
## [3.0.1](https://github.com/ellite/Wallos/compare/v3.0.0...v3.0.1) (2025-04-30)
### Bug Fixes
* allow to clear the budget field ([f6b8fb9](https://github.com/ellite/Wallos/commit/f6b8fb9162c5fb4fefa1fbd9cde65c201e96be6c))
* don't show budget alert when budget is 0 ([f6b8fb9](https://github.com/ellite/Wallos/commit/f6b8fb9162c5fb4fefa1fbd9cde65c201e96be6c))
## [3.0.0](https://github.com/ellite/Wallos/compare/v2.52.2...v3.0.0) (2025-04-27)
### ⚠ BREAKING CHANGES
* simplified webhook notifications without iterator (might break your current webhook settings)
### Features
* simplified webhook notifications without iterator (might break your current webhook settings) ([e0f2048](https://github.com/ellite/Wallos/commit/e0f204803e635400c404529d87e5057c579c8531))
* use mobile style toggles instead of checkboxes ([e0f2048](https://github.com/ellite/Wallos/commit/e0f204803e635400c404529d87e5057c579c8531))
* webhooks can now be used for cancelation notifications ([e0f2048](https://github.com/ellite/Wallos/commit/e0f204803e635400c404529d87e5057c579c8531))
### Bug Fixes
* barely readable placeholder text on textarea on dark the ([e0f2048](https://github.com/ellite/Wallos/commit/e0f204803e635400c404529d87e5057c579c8531))
## [2.52.2](https://github.com/ellite/Wallos/compare/v2.52.1...v2.52.2) (2025-04-26)
### Bug Fixes
* incorrect headers on the api ([#802](https://github.com/ellite/Wallos/issues/802)) ([af68c11](https://github.com/ellite/Wallos/commit/af68c11abf5d5a64fd7136e1d2e37323d170c77e))
## [2.52.1](https://github.com/ellite/Wallos/compare/v2.52.0...v2.52.1) (2025-04-26)
### Bug Fixes
* error on statistics page when budget = 0 ([#800](https://github.com/ellite/Wallos/issues/800)) ([b7712dc](https://github.com/ellite/Wallos/commit/b7712dc80d6642a6a33a28adc641f9a4b3263ae6))
## [2.52.0](https://github.com/ellite/Wallos/compare/v2.51.1...v2.52.0) (2025-04-19)
### Features
* new graph cost vs budget on statistics ([#793](https://github.com/ellite/Wallos/issues/793)) ([6d67319](https://github.com/ellite/Wallos/commit/6d673195ba39f1a52e9ea16bad21221768690e7a))
## [2.51.1](https://github.com/ellite/Wallos/compare/v2.51.0...v2.51.1) (2025-04-19)
### Bug Fixes
* timezone for cronjobs now comes from TZ env var first ([#791](https://github.com/ellite/Wallos/issues/791)) ([66a1a45](https://github.com/ellite/Wallos/commit/66a1a45f2dc1df99f8292cbb531d569f706eca6d))
## [2.51.0](https://github.com/ellite/Wallos/compare/v2.50.1...v2.51.0) (2025-04-18)
### Features
* add over budget warnings on the calendar ([88eae10](https://github.com/ellite/Wallos/commit/88eae1002f0cc29a847e95b7698ab713779ec4f4))
### Bug Fixes
* force correct timezone on the cronjobs ([88eae10](https://github.com/ellite/Wallos/commit/88eae1002f0cc29a847e95b7698ab713779ec4f4))
## [2.50.1](https://github.com/ellite/Wallos/compare/v2.50.0...v2.50.1) (2025-04-16)
### Bug Fixes
* localization on date on browsers not in english ([c7b3fb4](https://github.com/ellite/Wallos/commit/c7b3fb445182e19bc464ac987977bac266628757))
## [2.50.0](https://github.com/ellite/Wallos/compare/v2.49.1...v2.50.0) (2025-04-16)
### Features
* shorten date displayed on the list of subscriptions ([68f1d47](https://github.com/ellite/Wallos/commit/68f1d4757737de50622bb4b2aeb8f291dec62972))
* use user defined language for the date on the list of subscriptions ([68f1d47](https://github.com/ellite/Wallos/commit/68f1d4757737de50622bb4b2aeb8f291dec62972))
### Bug Fixes
* limit name display, when sub has no logo to two lines ([68f1d47](https://github.com/ellite/Wallos/commit/68f1d4757737de50622bb4b2aeb8f291dec62972))
* use translations on the mobile menu ([68f1d47](https://github.com/ellite/Wallos/commit/68f1d4757737de50622bb4b2aeb8f291dec62972))
## [2.49.1](https://github.com/ellite/Wallos/compare/v2.49.0...v2.49.1) (2025-04-13)
### Bug Fixes
* version number ([eade2d9](https://github.com/ellite/Wallos/commit/eade2d9919e5d30e7be279f53e278fb746095762))
## [2.49.0](https://github.com/ellite/Wallos/compare/v2.48.1...v2.49.0) (2025-04-13)
### Features
* show name on mobile view when subscription has no logo ([9eb2907](https://github.com/ellite/Wallos/commit/9eb2907145297e3b7aac54dd5b51451d961f549a))
* show timezone on sendnotification cronjob on admin page ([9eb2907](https://github.com/ellite/Wallos/commit/9eb2907145297e3b7aac54dd5b51451d961f549a))
* use currencyConverter for notifications as well ([9eb2907](https://github.com/ellite/Wallos/commit/9eb2907145297e3b7aac54dd5b51451d961f549a))
* use symbol from db when currencyFormatter does not support the currency ([9eb2907](https://github.com/ellite/Wallos/commit/9eb2907145297e3b7aac54dd5b51451d961f549a))
### Bug Fixes
* date comparison check on sendnotifications cronjob ([9eb2907](https://github.com/ellite/Wallos/commit/9eb2907145297e3b7aac54dd5b51451d961f549a))
* emails with encryption set to none not working without ssl ([9eb2907](https://github.com/ellite/Wallos/commit/9eb2907145297e3b7aac54dd5b51451d961f549a))
* error when not setting custom headers for ntfy ([9eb2907](https://github.com/ellite/Wallos/commit/9eb2907145297e3b7aac54dd5b51451d961f549a))
## [2.48.1](https://github.com/ellite/Wallos/compare/v2.48.0...v2.48.1) (2025-03-27)
### Bug Fixes
* notifications would also be sent x days after subscription was due in some cases ([ba912a3](https://github.com/ellite/Wallos/commit/ba912a37d1a0d95401a38dabe8f98f29a6aa49db))
## [2.48.0](https://github.com/ellite/Wallos/compare/v2.47.1...v2.48.0) (2025-03-20)
### Features
* add update notification and release notes to the about page ([3e0e88d](https://github.com/ellite/Wallos/commit/3e0e88d6a2adc46c17773b09dd8684618c979711))
* increase privacy by not sending referrer to external urls ([3e0e88d](https://github.com/ellite/Wallos/commit/3e0e88d6a2adc46c17773b09dd8684618c979711))
* small layout change on the about page ([3e0e88d](https://github.com/ellite/Wallos/commit/3e0e88d6a2adc46c17773b09dd8684618c979711))
## [2.47.1](https://github.com/ellite/Wallos/compare/v2.47.0...v2.47.1) (2025-03-19)
### Bug Fixes
* small layout inconsistencies on the dashboard ([19d3067](https://github.com/ellite/Wallos/commit/19d30672b2635b6e79eaa6eb5c49100d7a27a63a))
## [2.47.0](https://github.com/ellite/Wallos/compare/v2.46.1...v2.47.0) (2025-03-19)
### Features
* add filter by renew type ([1bec973](https://github.com/ellite/Wallos/commit/1bec973803e0b3c00d2765bbf80447439127574d))
* add sort by renew type ([1bec973](https://github.com/ellite/Wallos/commit/1bec973803e0b3c00d2765bbf80447439127574d))
* add ukranian translation ([#756](https://github.com/ellite/Wallos/issues/756)) ([1bec973](https://github.com/ellite/Wallos/commit/1bec973803e0b3c00d2765bbf80447439127574d))
* remove "Wallos" text from calendar export ([1bec973](https://github.com/ellite/Wallos/commit/1bec973803e0b3c00d2765bbf80447439127574d))
### Bug Fixes
* ical trigger to spec RFC5545 ([1bec973](https://github.com/ellite/Wallos/commit/1bec973803e0b3c00d2765bbf80447439127574d))
* special chars on calendar exports ([1bec973](https://github.com/ellite/Wallos/commit/1bec973803e0b3c00d2765bbf80447439127574d))
* special chars on notifications ([1bec973](https://github.com/ellite/Wallos/commit/1bec973803e0b3c00d2765bbf80447439127574d))
* state filter not cleared by clear button ([1bec973](https://github.com/ellite/Wallos/commit/1bec973803e0b3c00d2765bbf80447439127574d))
## [2.46.1](https://github.com/ellite/Wallos/compare/v2.46.0...v2.46.1) (2025-03-06)
### Bug Fixes
* calculation of monthly cost progress graph ([#747](https://github.com/ellite/Wallos/issues/747)) ([77486ec](https://github.com/ellite/Wallos/commit/77486ec92c44b71f69e85b1eafb7f3a98c4a44c1))
## [2.46.0](https://github.com/ellite/Wallos/compare/v2.45.2...v2.46.0) (2025-02-22)
### Features
* sorting by category or payment method respects order from the settings page ([51b2272](https://github.com/ellite/Wallos/commit/51b22727bf5656a4a263519b5b56adfe6a2d12be))
### Bug Fixes
* access to tmp folder by www-data ([51b2272](https://github.com/ellite/Wallos/commit/51b22727bf5656a4a263519b5b56adfe6a2d12be))
## [2.45.2](https://github.com/ellite/Wallos/compare/v2.45.1...v2.45.2) (2025-02-05)
### Bug Fixes
* bug setting main currency for the first registered user ([c43b08a](https://github.com/ellite/Wallos/commit/c43b08aa4c45c907f82eb6afe37fd46aa5103654))
* deprecation message ([c43b08a](https://github.com/ellite/Wallos/commit/c43b08aa4c45c907f82eb6afe37fd46aa5103654))
* subscription progress above 100% for disabled subscriptions ([c43b08a](https://github.com/ellite/Wallos/commit/c43b08aa4c45c907f82eb6afe37fd46aa5103654))
* typo on czech translation ([c43b08a](https://github.com/ellite/Wallos/commit/c43b08aa4c45c907f82eb6afe37fd46aa5103654))
* use first currency on the list of currencies if user has not selected a main currency ([c43b08a](https://github.com/ellite/Wallos/commit/c43b08aa4c45c907f82eb6afe37fd46aa5103654))
* use gd if imagick is not available ([c43b08a](https://github.com/ellite/Wallos/commit/c43b08aa4c45c907f82eb6afe37fd46aa5103654))
## [2.45.1](https://github.com/ellite/Wallos/compare/v2.45.0...v2.45.1) (2025-01-28)
### Bug Fixes
* improve czech translation ([e2dc269](https://github.com/ellite/Wallos/commit/e2dc2696310159900c1f8fbe0a090e66b29b778d))
* improve japanese translation ([#713](https://github.com/ellite/Wallos/issues/713)) ([e2dc269](https://github.com/ellite/Wallos/commit/e2dc2696310159900c1f8fbe0a090e66b29b778d))
* improve traditional chinese translation ([e2dc269](https://github.com/ellite/Wallos/commit/e2dc2696310159900c1f8fbe0a090e66b29b778d))
* setting pgid and puid for the container ([e2dc269](https://github.com/ellite/Wallos/commit/e2dc2696310159900c1f8fbe0a090e66b29b778d))
## [2.45.0](https://github.com/ellite/Wallos/compare/v2.44.1...v2.45.0) (2025-01-19)
### Features
* add czech translations ([#701](https://github.com/ellite/Wallos/issues/701)) ([426fdfa](https://github.com/ellite/Wallos/commit/426fdfa5c79d32c7d5a0722a0590d39547cfd1fa))
## [2.44.1](https://github.com/ellite/Wallos/compare/v2.44.0...v2.44.1) (2025-01-19)
### Bug Fixes
* error setting date of last exchange rates update ([#699](https://github.com/ellite/Wallos/issues/699)) ([d2f68c4](https://github.com/ellite/Wallos/commit/d2f68c457e9b1328caf983ddc6e2827430855aa6))
## [2.44.0](https://github.com/ellite/Wallos/compare/v2.43.1...v2.44.0) (2025-01-12)
### Features
* allow notifications on due date ([87f148d](https://github.com/ellite/Wallos/commit/87f148d1745bec19f5713b8a367a3615871e6e33))
### Bug Fixes
* don't expose disabled notifications to ical feed ([87f148d](https://github.com/ellite/Wallos/commit/87f148d1745bec19f5713b8a367a3615871e6e33))
* email notification test always sending to admins email ([87f148d](https://github.com/ellite/Wallos/commit/87f148d1745bec19f5713b8a367a3615871e6e33))
## [2.43.1](https://github.com/ellite/Wallos/compare/v2.43.0...v2.43.1) (2025-01-12)
### Bug Fixes
* edit / delete subscription menu not accessible ([#689](https://github.com/ellite/Wallos/issues/689)) ([b668d37](https://github.com/ellite/Wallos/commit/b668d37d38f799ee0dda5a69a4824d03dd21e1bc))
## [2.43.0](https://github.com/ellite/Wallos/compare/v2.42.2...v2.43.0) (2025-01-11)
### Features
* new api endpoint that returns the version ([ff13fcb](https://github.com/ellite/Wallos/commit/ff13fcb6547ec4a9c972a2c0f0b6f42d69620f8b))
* option to show progress of subscription cycle ([ff13fcb](https://github.com/ellite/Wallos/commit/ff13fcb6547ec4a9c972a2c0f0b6f42d69620f8b))
### Bug Fixes
* currency symbol for monthly budget ([ff13fcb](https://github.com/ellite/Wallos/commit/ff13fcb6547ec4a9c972a2c0f0b6f42d69620f8b))
## [2.42.2](https://github.com/ellite/Wallos/compare/v2.42.1...v2.42.2) (2024-12-21)
### Bug Fixes
* version number ([#668](https://github.com/ellite/Wallos/issues/668)) ([683a366](https://github.com/ellite/Wallos/commit/683a3662ff998066f5d8de3be88e4d40d766442a))
## [2.42.1](https://github.com/ellite/Wallos/compare/v2.42.0...v2.42.1) (2024-12-21)
### Bug Fixes
* remove debug echo on stats page ([#666](https://github.com/ellite/Wallos/issues/666)) ([d9a2488](https://github.com/ellite/Wallos/commit/d9a24885ffbbdb3c08d9015804eea8cb0fea6cea))
## [2.42.0](https://github.com/ellite/Wallos/compare/v2.41.0...v2.42.0) (2024-12-21)
### Features
* add total monthly cost trend graph to the statistics page ([e7185f9](https://github.com/ellite/Wallos/commit/e7185f92578b3103d097b12b8c4313635f263d9f))
* allow email notifications without authentication ([e7185f9](https://github.com/ellite/Wallos/commit/e7185f92578b3103d097b12b8c4313635f263d9f))
### Bug Fixes
* don't update next payment date for disabled subscriptions ([e7185f9](https://github.com/ellite/Wallos/commit/e7185f92578b3103d097b12b8c4313635f263d9f))
* xss security vulnerability with the avatar selection ([e7185f9](https://github.com/ellite/Wallos/commit/e7185f92578b3103d097b12b8c4313635f263d9f))
## [2.41.0](https://github.com/ellite/Wallos/compare/v2.40.0...v2.41.0) (2024-12-11)
### Features
* add payment cycle to csv/json export ([5e6bc90](https://github.com/ellite/Wallos/commit/5e6bc903bcd95580ed58f744977d92c6330b3d9f))
* run db migration after importing db ([5e6bc90](https://github.com/ellite/Wallos/commit/5e6bc903bcd95580ed58f744977d92c6330b3d9f))
* run db migration after restoring database ([5e6bc90](https://github.com/ellite/Wallos/commit/5e6bc903bcd95580ed58f744977d92c6330b3d9f))
* store weekly the total yearly cost of subscriptions ([5e6bc90](https://github.com/ellite/Wallos/commit/5e6bc903bcd95580ed58f744977d92c6330b3d9f))
### Bug Fixes
* double encoding in statistics labels ([5e6bc90](https://github.com/ellite/Wallos/commit/5e6bc903bcd95580ed58f744977d92c6330b3d9f))
## [2.40.0](https://github.com/ellite/Wallos/compare/v2.39.1...v2.40.0) (2024-12-10)
### Features
* add dutch translation ([#655](https://github.com/ellite/Wallos/issues/655)) ([b5a9880](https://github.com/ellite/Wallos/commit/b5a98806d1f453180ce15724fa198d248177e488))
## [2.39.1](https://github.com/ellite/Wallos/compare/v2.39.0...v2.39.1) (2024-12-06)
### Bug Fixes
* svg error on calendar page ([#650](https://github.com/ellite/Wallos/issues/650)) ([8ba79c0](https://github.com/ellite/Wallos/commit/8ba79c0725815c6de8458c74961bbdf23a7d3e9d))
## [2.39.0](https://github.com/ellite/Wallos/compare/v2.38.3...v2.39.0) (2024-12-06)
### Features
* add icalendar subscription ([f5ddbff](https://github.com/ellite/Wallos/commit/f5ddbff0c1e0be676604390101c56c04c778f56a))
## [2.38.3](https://github.com/ellite/Wallos/compare/v2.38.2...v2.38.3) (2024-12-06)
### Bug Fixes
* vulnerability on the restore database endpoints ([3b2de8b](https://github.com/ellite/Wallos/commit/3b2de8b7c22090afdf7115c25fd8b497a5626ea3))
## [2.38.2](https://github.com/ellite/Wallos/compare/v2.38.1...v2.38.2) (2024-11-19)
### Bug Fixes
* logo search positioned below other elements ([#637](https://github.com/ellite/Wallos/issues/637)) ([72f7e57](https://github.com/ellite/Wallos/commit/72f7e5791423c45f910a791b20aafba301d0172f))
## [2.38.1](https://github.com/ellite/Wallos/compare/v2.38.0...v2.38.1) (2024-11-17)
### Bug Fixes
* bug introduced on 2.38.0 on the subscriptions dashboard ([#634](https://github.com/ellite/Wallos/issues/634)) ([f63c543](https://github.com/ellite/Wallos/commit/f63c543cdd7512b216004db3b279884dbda87ce4))
## [2.38.0](https://github.com/ellite/Wallos/compare/v2.37.1...v2.38.0) (2024-11-17)
### Features
* add option for manual/automatic renewals ([6e44a26](https://github.com/ellite/Wallos/commit/6e44a26703486d0ba30ee6ae8d3c46bfc3c6630a))
* add some leeway for totp codes ([6e44a26](https://github.com/ellite/Wallos/commit/6e44a26703486d0ba30ee6ae8d3c46bfc3c6630a))
* add start date to subscriptions ([6e44a26](https://github.com/ellite/Wallos/commit/6e44a26703486d0ba30ee6ae8d3c46bfc3c6630a))
### Bug Fixes
* layout issue with subscriptions list during search ([6e44a26](https://github.com/ellite/Wallos/commit/6e44a26703486d0ba30ee6ae8d3c46bfc3c6630a))
## [2.37.1](https://github.com/ellite/Wallos/compare/v2.37.0...v2.37.1) (2024-11-15)
### Bug Fixes
* version mismatch ([#627](https://github.com/ellite/Wallos/issues/627)) ([c4a9b16](https://github.com/ellite/Wallos/commit/c4a9b1627fbc7278398bf2d8bf7cae2934d349ca))
## [2.37.0](https://github.com/ellite/Wallos/compare/v2.36.2...v2.37.0) (2024-11-15)
### Features
* add monthly statistics to the calendar page ([f085f8a](https://github.com/ellite/Wallos/commit/f085f8adece3af2548858f665db16d4843d3e622))
### Bug Fixes
* notifications being sent on the wrong day ([f085f8a](https://github.com/ellite/Wallos/commit/f085f8adece3af2548858f665db16d4843d3e622))
## [2.36.2](https://github.com/ellite/Wallos/compare/v2.36.1...v2.36.2) (2024-11-03)
### Bug Fixes
* only show swipe hint on mobile screens ([#612](https://github.com/ellite/Wallos/issues/612)) ([bd5e351](https://github.com/ellite/Wallos/commit/bd5e3511829a798ab47ca5e9c9d080aae45ae1a0))
## [2.36.1](https://github.com/ellite/Wallos/compare/v2.36.0...v2.36.1) (2024-11-03)
### Bug Fixes
* version number ([#610](https://github.com/ellite/Wallos/issues/610)) ([4bd40f1](https://github.com/ellite/Wallos/commit/4bd40f1c561e979322375b95aeccccd18c4780fd))
## [2.36.0](https://github.com/ellite/Wallos/compare/v2.35.0...v2.36.0) (2024-11-03)
### Features
* add hint for mobile swipe action ([#608](https://github.com/ellite/Wallos/issues/608)) ([49666f8](https://github.com/ellite/Wallos/commit/49666f867cdbaa4d4c0c1551d0b4b3023830606a))
## [2.35.0](https://github.com/ellite/Wallos/compare/v2.34.0...v2.35.0) (2024-11-01)
### Features
* new menu icons ([28444ab](https://github.com/ellite/Wallos/commit/28444abef1cee338e41e57cbf6f13666b917bbde))
* swipe subscription for actions on the experimental mobile navigation ([28444ab](https://github.com/ellite/Wallos/commit/28444abef1cee338e41e57cbf6f13666b917bbde))
## [2.34.0](https://github.com/ellite/Wallos/compare/v2.33.1...v2.34.0) (2024-10-31)
### Features
* link version update banner to github release ([f007adf](https://github.com/ellite/Wallos/commit/f007adf9658eb1fd095c2716e4146130535f6cb7))
* only show filters that are actually used ([f007adf](https://github.com/ellite/Wallos/commit/f007adf9658eb1fd095c2716e4146130535f6cb7))
### Bug Fixes
* filters for categories and payment method respect order from settings ([f007adf](https://github.com/ellite/Wallos/commit/f007adf9658eb1fd095c2716e4146130535f6cb7))
## [2.33.1](https://github.com/ellite/Wallos/compare/v2.33.0...v2.33.1) (2024-10-30)
### Bug Fixes
* improve localization ([6480f87](https://github.com/ellite/Wallos/commit/6480f8744094d5ce0f05d7d155925540ac73b156))
* layout issue on the settings page ([#598](https://github.com/ellite/Wallos/issues/598)) ([6480f87](https://github.com/ellite/Wallos/commit/6480f8744094d5ce0f05d7d155925540ac73b156))
## [2.33.0](https://github.com/ellite/Wallos/compare/v2.32.0...v2.33.0) (2024-10-29)
### Features
* replacement for disabled subscriptions, to more accurately calculate savings ([5c92528](https://github.com/ellite/Wallos/commit/5c9252880837a7886c903ddc7ae92c8fed29b452))
## [2.32.0](https://github.com/ellite/Wallos/compare/v2.31.1...v2.32.0) (2024-10-27)
### Features
* settings to allow to ignore certificates for some notification methods ([2a0e665](https://github.com/ellite/Wallos/commit/2a0e665e77eca804fa70dafc1a3a0010eb9da270))
## [2.31.1](https://github.com/ellite/Wallos/compare/v2.31.0...v2.31.1) (2024-10-25)
### Bug Fixes
* add missing {{days_until}} variable to string version of the webhook ([ebc7b83](https://github.com/ellite/Wallos/commit/ebc7b83e9a0a32aecf3b1aa933408bf9b6baea3a))
* display actual error message when email test fails ([ebc7b83](https://github.com/ellite/Wallos/commit/ebc7b83e9a0a32aecf3b1aa933408bf9b6baea3a))
## [2.31.0](https://github.com/ellite/Wallos/compare/v2.30.1...v2.31.0) (2024-10-22)
### Features
* handle webhook payload as string if it is not a json object ([#583](https://github.com/ellite/Wallos/issues/583)) ([ee834d6](https://github.com/ellite/Wallos/commit/ee834d6198fa3315facd23a734655adf391bb736))
## [2.30.1](https://github.com/ellite/Wallos/compare/v2.30.0...v2.30.1) (2024-10-14)
### Bug Fixes
* verify correct path before creating logos folder ([782ebcd](https://github.com/ellite/Wallos/commit/782ebcd64fc947ea82eabaac6bc26a32676271a1))
## [2.30.0](https://github.com/ellite/Wallos/compare/v2.29.2...v2.30.0) (2024-10-13)
### Features
* add vietnamese translation ([#573](https://github.com/ellite/Wallos/issues/573)) ([45ff10f](https://github.com/ellite/Wallos/commit/45ff10f953f4af681252ed4d77c32b375f9c396c))
## [2.29.2](https://github.com/ellite/Wallos/compare/v2.29.1...v2.29.2) (2024-10-11)
### Bug Fixes
* xss issue on the dashboard ([#568](https://github.com/ellite/Wallos/issues/568)) ([e642129](https://github.com/ellite/Wallos/commit/e6421296aa708b02c468b10e3c9d0f28012c1282))
## [2.29.1](https://github.com/ellite/Wallos/compare/v2.29.0...v2.29.1) (2024-10-11)
### Bug Fixes
* mysql injection vulnerability ([3d6a8c3](https://github.com/ellite/Wallos/commit/3d6a8c340843230eff97b459e85efbea55aac01f))
* new profile page not being cached by service worker ([3d6a8c3](https://github.com/ellite/Wallos/commit/3d6a8c340843230eff97b459e85efbea55aac01f))
## [2.29.0](https://github.com/ellite/Wallos/compare/v2.28.0...v2.29.0) (2024-10-09)
### Features
* add url and notes as variables for the notifications webhook ([790defb](https://github.com/ellite/Wallos/commit/790defb2b1d1cd3d8c93738155edb19f96d0aa2a))
### Bug Fixes
* bug when looping multiple subscriptions on the notifications webhook ([790defb](https://github.com/ellite/Wallos/commit/790defb2b1d1cd3d8c93738155edb19f96d0aa2a))
## [2.28.0](https://github.com/ellite/Wallos/compare/v2.27.3...v2.28.0) (2024-10-07)
### Features
* get admin setting api endpoint ([07d456a](https://github.com/ellite/Wallos/commit/07d456a9c3d9cc3eb9ae80edb666caa103cababe))
* get categories endpoint ([07d456a](https://github.com/ellite/Wallos/commit/07d456a9c3d9cc3eb9ae80edb666caa103cababe))
* get currencies endpoint ([07d456a](https://github.com/ellite/Wallos/commit/07d456a9c3d9cc3eb9ae80edb666caa103cababe))
* get fixer api endpoint ([07d456a](https://github.com/ellite/Wallos/commit/07d456a9c3d9cc3eb9ae80edb666caa103cababe))
* get household api endpoint ([07d456a](https://github.com/ellite/Wallos/commit/07d456a9c3d9cc3eb9ae80edb666caa103cababe))
* get notifications api endpoint ([07d456a](https://github.com/ellite/Wallos/commit/07d456a9c3d9cc3eb9ae80edb666caa103cababe))
* get payment methods api endpoint ([07d456a](https://github.com/ellite/Wallos/commit/07d456a9c3d9cc3eb9ae80edb666caa103cababe))
* get settings api endpoint ([07d456a](https://github.com/ellite/Wallos/commit/07d456a9c3d9cc3eb9ae80edb666caa103cababe))
* get subscriptions api endpoint ([07d456a](https://github.com/ellite/Wallos/commit/07d456a9c3d9cc3eb9ae80edb666caa103cababe))
* get user api endpoint ([07d456a](https://github.com/ellite/Wallos/commit/07d456a9c3d9cc3eb9ae80edb666caa103cababe))
## [2.27.3](https://github.com/ellite/Wallos/compare/v2.27.2...v2.27.3) (2024-10-05)
### Bug Fixes
* missing folders on baremetal installation ([#554](https://github.com/ellite/Wallos/issues/554)) ([03f34d1](https://github.com/ellite/Wallos/commit/03f34d1aee3f74c3bf9c53c04c1494106be4bb47))
* missing fonts ([03f34d1](https://github.com/ellite/Wallos/commit/03f34d1aee3f74c3bf9c53c04c1494106be4bb47))
## [2.27.2](https://github.com/ellite/Wallos/compare/v2.27.1...v2.27.2) (2024-10-04)
### Bug Fixes
* bump version ([#546](https://github.com/ellite/Wallos/issues/546)) ([c5460bd](https://github.com/ellite/Wallos/commit/c5460bd79bdd056e788774ac52cfd4262eada5e7))
## [2.27.1](https://github.com/ellite/Wallos/compare/v2.27.0...v2.27.1) (2024-10-04)
### Bug Fixes
* add missing assets to the service worker ([#542](https://github.com/ellite/Wallos/issues/542)) ([0251da2](https://github.com/ellite/Wallos/commit/0251da23f4254420a471fcd4c4951d0d0b1bb4df))
## [2.27.0](https://github.com/ellite/Wallos/compare/v2.26.0...v2.27.0) (2024-10-04)
### Features
* api endpoint to calculate monthly cost ([a173d27](https://github.com/ellite/Wallos/commit/a173d2765fd2a1a641f32fbea198775b1bdc0b00))
* fisrt api endpoint ([a173d27](https://github.com/ellite/Wallos/commit/a173d2765fd2a1a641f32fbea198775b1bdc0b00))
* redesigned experimental mobile navigation menu ([a173d27](https://github.com/ellite/Wallos/commit/a173d2765fd2a1a641f32fbea198775b1bdc0b00))
* split settings page into settings and profile page ([a173d27](https://github.com/ellite/Wallos/commit/a173d2765fd2a1a641f32fbea198775b1bdc0b00))
* user has api key available on profile page ([a173d27](https://github.com/ellite/Wallos/commit/a173d2765fd2a1a641f32fbea198775b1bdc0b00))
### Bug Fixes
* small fixes and typos ([a173d27](https://github.com/ellite/Wallos/commit/a173d2765fd2a1a641f32fbea198775b1bdc0b00))
## [2.26.0](https://github.com/ellite/Wallos/compare/v2.25.0...v2.26.0) (2024-09-29)
### Features
* add mobile menu navigation to experimental settings ([1dbba18](https://github.com/ellite/Wallos/commit/1dbba18446ac53568492af9d2aee3f90db7168ca))
* use browsers locale to set dates on the dashboard ([1dbba18](https://github.com/ellite/Wallos/commit/1dbba18446ac53568492af9d2aee3f90db7168ca))
## [2.25.0](https://github.com/ellite/Wallos/compare/v2.24.1...v2.25.0) (2024-09-28)
### Features
* add 2fa support ([#525](https://github.com/ellite/Wallos/issues/525)) ([2f16ab3](https://github.com/ellite/Wallos/commit/2f16ab3fdf89b8ba6b1010510d8b169aad425f38))
## [2.24.1](https://github.com/ellite/Wallos/compare/v2.24.0...v2.24.1) (2024-09-23)
### Bug Fixes
* small layout issue on the settings page ([0623ceb](https://github.com/ellite/Wallos/commit/0623cebe67182b493770615c518977907e11d359))
## [2.24.0](https://github.com/ellite/Wallos/compare/v2.23.2...v2.24.0) (2024-09-18)
### Features
* add button to clean up search field ([da3ee78](https://github.com/ellite/Wallos/commit/da3ee782f13c1eaa98a85de5dbe33714d173a323))
### Bug Fixes
* cases where theme and sort cookies could be missing ([da3ee78](https://github.com/ellite/Wallos/commit/da3ee782f13c1eaa98a85de5dbe33714d173a323))
* position of dropdown on rtl layout ([da3ee78](https://github.com/ellite/Wallos/commit/da3ee782f13c1eaa98a85de5dbe33714d173a323))
## [2.23.2](https://github.com/ellite/Wallos/compare/v2.23.1...v2.23.2) (2024-09-04)
### Bug Fixes
* sort order after edit subscription in case the cookie is missing ([87809fe](https://github.com/ellite/Wallos/commit/87809fea71b92c7518173fedd189d7e76ce11bfb))
## [2.23.1](https://github.com/ellite/Wallos/compare/v2.23.0...v2.23.1) (2024-09-01)
### Bug Fixes
* warning on top of dashboard page ([#512](https://github.com/ellite/Wallos/issues/512)) ([9056722](https://github.com/ellite/Wallos/commit/905672243b75e6b3d367d439bdbbb37d1b5ae0fa))
## [2.23.0](https://github.com/ellite/Wallos/compare/v2.22.1...v2.23.0) (2024-09-01)
### Features
* add multi email recipients ([fed0192](https://github.com/ellite/Wallos/commit/fed0192394e77409dae04d4ab3cdda0ba0c578a4))
* add option for also showing the original price on the dashboard ([fed0192](https://github.com/ellite/Wallos/commit/fed0192394e77409dae04d4ab3cdda0ba0c578a4))
* open edit form after cloning subscription ([fed0192](https://github.com/ellite/Wallos/commit/fed0192394e77409dae04d4ab3cdda0ba0c578a4))
* select multiple filters on the dashboard ([fed0192](https://github.com/ellite/Wallos/commit/fed0192394e77409dae04d4ab3cdda0ba0c578a4))
### Bug Fixes
* export.php csv header typo ([#499](https://github.com/ellite/Wallos/issues/499)) ([6e96c5d](https://github.com/ellite/Wallos/commit/6e96c5d4b0c7264ab37a85e9a8b8062f96f69c5c))
* typo on export subscriptions to csv ([fed0192](https://github.com/ellite/Wallos/commit/fed0192394e77409dae04d4ab3cdda0ba0c578a4))
## [2.22.1](https://github.com/ellite/Wallos/compare/v2.22.0...v2.22.1) (2024-08-11)
### Bug Fixes
* inline items in subscription form out of place ([#489](https://github.com/ellite/Wallos/issues/489)) ([3f33ba0](https://github.com/ellite/Wallos/commit/3f33ba0310af0c903db9bef1dd6668146219142c))
## [2.22.0](https://github.com/ellite/Wallos/compare/v2.21.3...v2.22.0) (2024-08-09)
### Features
* admin can manually trigger cronjobs ([1946ac9](https://github.com/ellite/Wallos/commit/1946ac9855696892b9a0790d46623614aa9aab2c))
### Bug Fixes
* only allow the system and admin to run the cronjobs ([1946ac9](https://github.com/ellite/Wallos/commit/1946ac9855696892b9a0790d46623614aa9aab2c))
* reduce size of the log files of the cronjobs ([1946ac9](https://github.com/ellite/Wallos/commit/1946ac9855696892b9a0790d46623614aa9aab2c))
## [2.21.3](https://github.com/ellite/Wallos/compare/v2.21.2...v2.21.3) (2024-08-08)
### Bug Fixes
* broken avatar upload when using the french language ([cf0d5d3](https://github.com/ellite/Wallos/commit/cf0d5d3df30909a0de7ef84aae2601d805617f90))
* more deprecation warnings on image uploads ([cf0d5d3](https://github.com/ellite/Wallos/commit/cf0d5d3df30909a0de7ef84aae2601d805617f90))
## [2.21.2](https://github.com/ellite/Wallos/compare/v2.21.1...v2.21.2) (2024-08-07)
### Bug Fixes
* add samesite directive to cookies ([8b0325c](https://github.com/ellite/Wallos/commit/8b0325c7d3c672754de220efd52b9ba9de8a9868))
* service worker precaching logout.php causes user to be logged out ([8b0325c](https://github.com/ellite/Wallos/commit/8b0325c7d3c672754de220efd52b9ba9de8a9868))
* sort by price ([8b0325c](https://github.com/ellite/Wallos/commit/8b0325c7d3c672754de220efd52b9ba9de8a9868))
## [2.21.1](https://github.com/ellite/Wallos/compare/v2.21.0...v2.21.1) (2024-08-06)
### Bug Fixes
* deprecation message for null value ([#479](https://github.com/ellite/Wallos/issues/479)) ([0274b1d](https://github.com/ellite/Wallos/commit/0274b1d5257f8f1c4156e2a342df6acf177ad726))
## [2.21.0](https://github.com/ellite/Wallos/compare/v2.20.1...v2.21.0) (2024-08-06)
### Features
* add option to list disabled subscriptions at the bottom ([3281f0c](https://github.com/ellite/Wallos/commit/3281f0ce35fbea237e21221d3a9026ed96ad84e5))
* notification for wallos version updates ([3281f0c](https://github.com/ellite/Wallos/commit/3281f0ce35fbea237e21221d3a9026ed96ad84e5))
## [2.20.1](https://github.com/ellite/Wallos/compare/v2.20.0...v2.20.1) (2024-07-29)
### Bug Fixes
* allow usernames with capital letters ([f241ba2](https://github.com/ellite/Wallos/commit/f241ba23018ee910ab859b2ce860b4c0678d6402))
* use 2 decimal places for price on the calendar ([f241ba2](https://github.com/ellite/Wallos/commit/f241ba23018ee910ab859b2ce860b4c0678d6402))
* use 2 decimal places for price when exporting ical in the calendar ([f241ba2](https://github.com/ellite/Wallos/commit/f241ba23018ee910ab859b2ce860b4c0678d6402))
## [2.20.0](https://github.com/ellite/Wallos/compare/v2.19.3...v2.20.0) (2024-07-19)
### Features
* export subscriptions as csv ([8f1e155](https://github.com/ellite/Wallos/commit/8f1e1554787c6e3ffaf7e73369a66794c0636713))
* export subscriptions as json ([8f1e155](https://github.com/ellite/Wallos/commit/8f1e1554787c6e3ffaf7e73369a66794c0636713))
* user can delete their own account ([8f1e155](https://github.com/ellite/Wallos/commit/8f1e1554787c6e3ffaf7e73369a66794c0636713))
## [2.19.3](https://github.com/ellite/Wallos/compare/v2.19.2...v2.19.3) (2024-07-15)
### Bug Fixes
* delete button on subscription form ([#460](https://github.com/ellite/Wallos/issues/460)) ([8cb4355](https://github.com/ellite/Wallos/commit/8cb43553fd2d3328fe9b1f7c5986e040071844c0))
## [2.19.2](https://github.com/ellite/Wallos/compare/v2.19.1...v2.19.2) (2024-07-15)
### Bug Fixes
* test ntfy without custom headers ([#456](https://github.com/ellite/Wallos/issues/456)) ([8fcfc92](https://github.com/ellite/Wallos/commit/8fcfc9264726ec1ded81ca2c51daa65ae9f4e7d8))
## [2.19.1](https://github.com/ellite/Wallos/compare/v2.19.0...v2.19.1) (2024-07-14)
### Bug Fixes
* unset sortOrder var ([a1fab4d](https://github.com/ellite/Wallos/commit/a1fab4dd1067f80054a2c52710edb859dba47127))
## [2.19.0](https://github.com/ellite/Wallos/compare/v2.18.0...v2.19.0) (2024-07-14)
### Features
* add alphanumeric sort order for subscriptions ([#449](https://github.com/ellite/Wallos/issues/449)) ([775e6ee](https://github.com/ellite/Wallos/commit/775e6ee39457edef420d5c36fb310a75fd47bff6))
## [2.18.0](https://github.com/ellite/Wallos/compare/v2.17.0...v2.18.0) (2024-07-14)
### Features
* disable display options checkbox when fixer key is not set ([5f10525](https://github.com/ellite/Wallos/commit/5f1052584b5ece93ebdcb5bce32210e2643a9f26))
* display error message on the statistics page when the fixer key is needed but is missing ([5f10525](https://github.com/ellite/Wallos/commit/5f1052584b5ece93ebdcb5bce32210e2643a9f26))
## [2.17.0](https://github.com/ellite/Wallos/compare/v2.16.1...v2.17.0) (2024-07-11)
### Features
* add filter and sort dashboard by subscription state ([afff992](https://github.com/ellite/Wallos/commit/afff992878287fdc51229297c455d1f69216c36e))
### Bug Fixes
* use the same font for inputs ([a539058](https://github.com/ellite/Wallos/commit/a5390580259105f14154b0d7ce1eb13631c471b1))
## [2.16.1](https://github.com/ellite/Wallos/compare/v2.16.0...v2.16.1) (2024-07-10)
### Bug Fixes
* error when logos folder is empty ([#439](https://github.com/ellite/Wallos/issues/439)) ([e2e5061](https://github.com/ellite/Wallos/commit/e2e5061d1506652384ceed018aa4330b8548b792))
## [2.16.0](https://github.com/ellite/Wallos/compare/v2.15.0...v2.16.0) (2024-07-10)
### Features
* add calendar to pwa shortcuts ([21ebf29](https://github.com/ellite/Wallos/commit/21ebf29f11405ab24b1b0ffd16eb667de4dfc189))
* change apple touch icon ([21ebf29](https://github.com/ellite/Wallos/commit/21ebf29f11405ab24b1b0ffd16eb667de4dfc189))
## [2.15.0](https://github.com/ellite/Wallos/compare/v2.14.2...v2.15.0) (2024-07-09)
### Features
* add maintenance tasks to admin page ([9f7f47b](https://github.com/ellite/Wallos/commit/9f7f47b5d1be2697c2c612bfddb6119c63a3d517))
* add support to upload svg logos ([9f7f47b](https://github.com/ellite/Wallos/commit/9f7f47b5d1be2697c2c612bfddb6119c63a3d517))
## [2.14.2](https://github.com/ellite/Wallos/compare/v2.14.1...v2.14.2) (2024-07-08)
### Bug Fixes
* broken subscription update query ([#431](https://github.com/ellite/Wallos/issues/431)) ([b00a985](https://github.com/ellite/Wallos/commit/b00a9855453663aeb2f1f4b7f0db3aca3994b12b))
## [2.14.1](https://github.com/ellite/Wallos/compare/v2.14.0...v2.14.1) (2024-07-05)
### Bug Fixes
* dashboard scrolling to top when opening a subscription ([#427](https://github.com/ellite/Wallos/issues/427)) ([cb03af8](https://github.com/ellite/Wallos/commit/cb03af8e46fb5ec5138ed7ef729f4b56a23d2b37))
## [2.14.0](https://github.com/ellite/Wallos/compare/v2.13.0...v2.14.0) (2024-07-05)
### Features
* add cancelation reminders ([#425](https://github.com/ellite/Wallos/issues/425)) ([c393146](https://github.com/ellite/Wallos/commit/c393146d9e3d494943de32ecd86983335358cf88))
## [2.13.0](https://github.com/ellite/Wallos/compare/v2.12.0...v2.13.0) (2024-07-04)
### Features
* uniformize layout and styles (+ checkboxes and radios) ([#423](https://github.com/ellite/Wallos/issues/423)) ([c166c7e](https://github.com/ellite/Wallos/commit/c166c7e84c06ceba5ab21341c8d56bd1aaf042ec))
## [2.12.0](https://github.com/ellite/Wallos/compare/v2.11.2...v2.12.0) (2024-07-03)
### Features
* ability to add custom css styles ([50bd104](https://github.com/ellite/Wallos/commit/50bd104b5b990605f457b540bec95eff5034473d))
* cache logos for offline use ([50bd104](https://github.com/ellite/Wallos/commit/50bd104b5b990605f457b540bec95eff5034473d))
* more uniform and aligned styles on the settings page ([50bd104](https://github.com/ellite/Wallos/commit/50bd104b5b990605f457b540bec95eff5034473d))
* rework styles of theme section on settings page ([50bd104](https://github.com/ellite/Wallos/commit/50bd104b5b990605f457b540bec95eff5034473d))
### Bug Fixes
* don't allow saving main and accent colors if they're the same ([50bd104](https://github.com/ellite/Wallos/commit/50bd104b5b990605f457b540bec95eff5034473d))
## [2.11.2](https://github.com/ellite/Wallos/compare/v2.11.1...v2.11.2) (2024-07-02)
### Bug Fixes
* menus checkmark position ([#419](https://github.com/ellite/Wallos/issues/419)) ([4da5d47](https://github.com/ellite/Wallos/commit/4da5d47e3ce8b8564921c07e7b785a367d378d6b))
## [2.11.1](https://github.com/ellite/Wallos/compare/v2.11.0...v2.11.1) (2024-06-30)
### Bug Fixes
* syntax error on svg logo ([#417](https://github.com/ellite/Wallos/issues/417)) ([b82f750](https://github.com/ellite/Wallos/commit/b82f750c8e844012a8a12e33f01719f42199e7ce))
## [2.11.0](https://github.com/ellite/Wallos/compare/v2.10.0...v2.11.0) (2024-06-30)
### Features
* theming engine custom colors now affect icons as well ([83e2066](https://github.com/ellite/Wallos/commit/83e2066e7bee99a152cc3c22f5b1dd9c9866c9fd))
## [2.10.0](https://github.com/ellite/Wallos/compare/v2.9.0...v2.10.0) (2024-06-27)
### Features
* add purple theme ([4d74c04](https://github.com/ellite/Wallos/commit/4d74c04f0e5bab5e1ece7a4a666f14d4a221fba6))
### Bug Fixes
* file name on ics export for subscriptions with non-ascii characters ([4d74c04](https://github.com/ellite/Wallos/commit/4d74c04f0e5bab5e1ece7a4a666f14d4a221fba6))
## [2.9.0](https://github.com/ellite/Wallos/compare/v2.8.0...v2.9.0) (2024-06-26)
### Features
* create users from the admin page ([#409](https://github.com/ellite/Wallos/issues/409)) ([6d2ffa6](https://github.com/ellite/Wallos/commit/6d2ffa6312b05f308117f2686681e2fcfaf734ec))
## [2.8.0](https://github.com/ellite/Wallos/compare/v2.7.0...v2.8.0) (2024-06-26)
### Features
* also show previous payments on the calendar for the current month ([c2e85d6](https://github.com/ellite/Wallos/commit/c2e85d6e109d9d07cc2fdbcb09b51564d1f73341))
* support automatic dark mode ([c2e85d6](https://github.com/ellite/Wallos/commit/c2e85d6e109d9d07cc2fdbcb09b51564d1f73341))
### Bug Fixes
* not every payment cycle was shown on the calendar ([c2e85d6](https://github.com/ellite/Wallos/commit/c2e85d6e109d9d07cc2fdbcb09b51564d1f73341))
## [2.7.0](https://github.com/ellite/Wallos/compare/v2.6.1...v2.7.0) (2024-06-25)
### Features
* export subscription as ics from the calendar view ([#404](https://github.com/ellite/Wallos/issues/404)) ([f1360f7](https://github.com/ellite/Wallos/commit/f1360f7d468ef5ae7e974ec1f9bb77831ea322bb))
## [2.6.1](https://github.com/ellite/Wallos/compare/v2.6.0...v2.6.1) (2024-06-25)
### Bug Fixes
* load php calendar extension ([#402](https://github.com/ellite/Wallos/issues/402)) ([c02ac77](https://github.com/ellite/Wallos/commit/c02ac770d7ac9fad1baec526b5d7dd71deaba59b))
## [2.6.0](https://github.com/ellite/Wallos/compare/v2.5.2...v2.6.0) (2024-06-25)
### Features
* add calendar view ([#399](https://github.com/ellite/Wallos/issues/399)) ([369f1a2](https://github.com/ellite/Wallos/commit/369f1a2bdcd9bdf3996b3dc8de8921f8954a069d))
## [2.5.2](https://github.com/ellite/Wallos/compare/v2.5.1...v2.5.2) (2024-06-24)
### Bug Fixes
* add ability to run container as an arbitrary user ([#396](https://github.com/ellite/Wallos/issues/396)) ([86fe2f3](https://github.com/ellite/Wallos/commit/86fe2f3ebb9c38ac34eaccd144a9550b7b314138))
## [2.5.1](https://github.com/ellite/Wallos/compare/v2.5.0...v2.5.1) (2024-06-21)
### Bug Fixes
* ntfy notifications ([#394](https://github.com/ellite/Wallos/issues/394)) ([17722c3](https://github.com/ellite/Wallos/commit/17722c31e31eec035d8896566e9eb5596951d022))
## [2.5.0](https://github.com/ellite/Wallos/compare/v2.4.2...v2.5.0) (2024-06-21)
### Features
* add option to clone subscription ([8304ed7](https://github.com/ellite/Wallos/commit/8304ed7b54f50ed7fa5ab520ff4d8d54f3ef34df))
* edit and delete options now available directly on the subscription list ([8304ed7](https://github.com/ellite/Wallos/commit/8304ed7b54f50ed7fa5ab520ff4d8d54f3ef34df))
### Bug Fixes
* typo on webhook payload ([8304ed7](https://github.com/ellite/Wallos/commit/8304ed7b54f50ed7fa5ab520ff4d8d54f3ef34df))
## [2.4.2](https://github.com/ellite/Wallos/compare/v2.4.1...v2.4.2) (2024-06-10)
### Bug Fixes
* update exchange cron only working for one user ([#384](https://github.com/ellite/Wallos/issues/384)) ([815eea7](https://github.com/ellite/Wallos/commit/815eea7e7be37e068e6173c229eb285ed8b7c30d))
## [2.4.1](https://github.com/ellite/Wallos/compare/v2.4.0...v2.4.1) (2024-06-09)
### Bug Fixes
* cronjob exchange update would not work with apilayer ([#381](https://github.com/ellite/Wallos/issues/381)) ([b0b4b7a](https://github.com/ellite/Wallos/commit/b0b4b7a65cd479e7532e72e826d3c01aead403c3))
## [2.4.0](https://github.com/ellite/Wallos/compare/v2.3.0...v2.4.0) (2024-06-07)
### Features
* add hability to disable login ([#378](https://github.com/ellite/Wallos/issues/378)) ([092be22](https://github.com/ellite/Wallos/commit/092be22183359f714fc9638d9013b742da828ed6))
## [2.3.0](https://github.com/ellite/Wallos/compare/v2.2.0...v2.3.0) (2024-06-05)
### Features
* add ntfy as notification method ([#377](https://github.com/ellite/Wallos/issues/377)) ([65edf09](https://github.com/ellite/Wallos/commit/65edf0963b73deff0f0f7f04427e69ce335bd776))
### Bug Fixes
* custom headers for webhook notifications ([#375](https://github.com/ellite/Wallos/issues/375)) ([7217088](https://github.com/ellite/Wallos/commit/7217088bb0732735a65322bce136d7d556b1acf3))
## [2.2.0](https://github.com/ellite/Wallos/compare/v2.1.0...v2.2.0) (2024-06-04)
### Features
* change filename of backup file ([fa99a73](https://github.com/ellite/Wallos/commit/fa99a735cd23918bab95baaf13b7a3142946d4b2))
* frequency is now up to 366 ([fa99a73](https://github.com/ellite/Wallos/commit/fa99a735cd23918bab95baaf13b7a3142946d4b2))
### Bug Fixes
* add webp support to gd on the container ([fa99a73](https://github.com/ellite/Wallos/commit/fa99a735cd23918bab95baaf13b7a3142946d4b2))
* translate: "no category" ([fa99a73](https://github.com/ellite/Wallos/commit/fa99a735cd23918bab95baaf13b7a3142946d4b2))
* trim fixer api key ([fa99a73](https://github.com/ellite/Wallos/commit/fa99a735cd23918bab95baaf13b7a3142946d4b2))
* update slovanian translations ([fa99a73](https://github.com/ellite/Wallos/commit/fa99a735cd23918bab95baaf13b7a3142946d4b2))
## [2.1.0](https://github.com/ellite/Wallos/compare/v2.0.0...v2.1.0) (2024-05-27)
### Features
* add slovenian translation ([03ceb8a](https://github.com/ellite/Wallos/commit/03ceb8a6e64c8cd4deb4019668fbf98acb57c5fe))
### Bug Fixes
* currency conversion failing on the statistics page ([03ceb8a](https://github.com/ellite/Wallos/commit/03ceb8a6e64c8cd4deb4019668fbf98acb57c5fe))
## [2.0.0](https://github.com/ellite/Wallos/compare/v1.29.1...v2.0.0) (2024-05-26)
### ⚠ BREAKING CHANGES
* allow registration of multiple users ([#340](https://github.com/ellite/Wallos/issues/340))
### Features
* add reset password functionality ([e1006e5](https://github.com/ellite/Wallos/commit/e1006e582388a7fab204f25c100347607b863e4e))
* administration area ([e1006e5](https://github.com/ellite/Wallos/commit/e1006e582388a7fab204f25c100347607b863e4e))
* allow registration of multiple users ([#340](https://github.com/ellite/Wallos/issues/340)) ([e1006e5](https://github.com/ellite/Wallos/commit/e1006e582388a7fab204f25c100347607b863e4e))
## [1.29.1](https://github.com/ellite/Wallos/compare/v1.29.0...v1.29.1) (2024-05-20)
### Bug Fixes
* calling htmlspecialchars_decode on null objects ([#338](https://github.com/ellite/Wallos/issues/338)) ([5050a28](https://github.com/ellite/Wallos/commit/5050a28f0e64e8c1eefb4f7cca8f6f6e473177e3))
## [1.29.0](https://github.com/ellite/Wallos/compare/v1.28.0...v1.29.0) (2024-05-20)
### Features
* subscriptions have personalized notification times ([#334](https://github.com/ellite/Wallos/issues/334)) ([c7146df](https://github.com/ellite/Wallos/commit/c7146dfd08c2a60d4ff6f7ac1f7cf5830fe28d9c))
## [1.28.0](https://github.com/ellite/Wallos/compare/v1.27.2...v1.28.0) (2024-05-17)
### Features
* add monthly budget field and statistics ([#329](https://github.com/ellite/Wallos/issues/329)) ([b622434](https://github.com/ellite/Wallos/commit/b622434ca0791d5c8026d641e1b32f8a2f0f42b8))
## [1.27.2](https://github.com/ellite/Wallos/compare/v1.27.1...v1.27.2) (2024-05-17)
### Bug Fixes
* duplicated messages on discord notifications ([d44b40b](https://github.com/ellite/Wallos/commit/d44b40b0ce80e91821fe7441c85e0d8794680618))
* possible division by 0 on statistics page ([d44b40b](https://github.com/ellite/Wallos/commit/d44b40b0ce80e91821fe7441c85e0d8794680618))
## [1.27.1](https://github.com/ellite/Wallos/compare/v1.27.0...v1.27.1) (2024-05-13)
### Bug Fixes
* import of translations for cronjobs was missing ([#321](https://github.com/ellite/Wallos/issues/321)) ([a524419](https://github.com/ellite/Wallos/commit/a524419e0a468147a2094dba81689dd643a0108b))
## [1.27.0](https://github.com/ellite/Wallos/compare/v1.26.2...v1.27.0) (2024-05-11)
### Features
* add korean translation ([#314](https://github.com/ellite/Wallos/issues/314)) ([bc40320](https://github.com/ellite/Wallos/commit/bc403206905b39c3aa88f3eb51e59b41e2a5e24e))
## [1.26.2](https://github.com/ellite/Wallos/compare/v1.26.1...v1.26.2) (2024-05-09)
### Bug Fixes
* russian translations ([#309](https://github.com/ellite/Wallos/issues/309)) ([8f890fc](https://github.com/ellite/Wallos/commit/8f890fc5d3a62a91feec50564179b3241ed538bf))
## [1.26.1](https://github.com/ellite/Wallos/compare/v1.26.0...v1.26.1) (2024-05-09)
### Bug Fixes
* background removal experimental setting ([#307](https://github.com/ellite/Wallos/issues/307)) ([bb5ee2e](https://github.com/ellite/Wallos/commit/bb5ee2e64c11b1415da3aa50119dfaa3783be37f))
## [1.26.0](https://github.com/ellite/Wallos/compare/v1.25.1...v1.26.0) (2024-05-08)
### Features
* add russian translation ([#305](https://github.com/ellite/Wallos/issues/305)) ([ae04d50](https://github.com/ellite/Wallos/commit/ae04d50329c1fb0117e186f89fef38b495cbbe9c))
## [1.25.1](https://github.com/ellite/Wallos/compare/v1.25.0...v1.25.1) (2024-05-07)
### Bug Fixes
* broken discord form ([#302](https://github.com/ellite/Wallos/issues/302)) ([b435d6a](https://github.com/ellite/Wallos/commit/b435d6a5cf6f80404c487b519334b2854aab9713))
## [1.25.0](https://github.com/ellite/Wallos/compare/v1.24.0...v1.25.0) (2024-05-06)
### Features
* add discord and pushover as notification agents ([#300](https://github.com/ellite/Wallos/issues/300)) ([8994829](https://github.com/ellite/Wallos/commit/899482982e7e200f5a7081ed6285475e5cb2a37d))
### Bug Fixes
* most error messages of the notifications endpoints would not reach the frontend ([8994829](https://github.com/ellite/Wallos/commit/899482982e7e200f5a7081ed6285475e5cb2a37d))
## [1.24.0](https://github.com/ellite/Wallos/compare/v1.23.0...v1.24.0) (2024-05-05)
### Features
* add new notification methods (telegram, webhooks, gotify) ([#295](https://github.com/ellite/Wallos/issues/295)) ([a408031](https://github.com/ellite/Wallos/commit/a408031ef8711bf87e9f8db35f52c498f250b235))
## [1.23.0](https://github.com/ellite/Wallos/compare/v1.22.0...v1.23.0) (2024-04-26)
### Features
* backup and restore ([#288](https://github.com/ellite/Wallos/issues/288)) ([7b509d2](https://github.com/ellite/Wallos/commit/7b509d2b3d769e14a9cb4fd183395dcecc9d993b))
## [1.22.0](https://github.com/ellite/Wallos/compare/v1.21.1...v1.22.0) (2024-04-20)
### Features
* option to hide disabled subscriptions ([#286](https://github.com/ellite/Wallos/issues/286)) ([b80ab4b](https://github.com/ellite/Wallos/commit/b80ab4bdc662c3e80a2fd42b8b286b69beac441c))
## [1.21.1](https://github.com/ellite/Wallos/compare/v1.21.0...v1.21.1) (2024-04-19)
### Bug Fixes
* small layout issues ([769f8a0](https://github.com/ellite/Wallos/commit/769f8a0587941bffd0d7463b7e7ffeb38a70e301))
## [1.21.0](https://github.com/ellite/Wallos/compare/v1.20.2...v1.21.0) (2024-04-19)
### Features
* add italian translation ([70e4234](https://github.com/ellite/Wallos/commit/70e42349caee5d6647b6b704643fe2b5e26dff4e))
* add themes and custom color options ([70e4234](https://github.com/ellite/Wallos/commit/70e42349caee5d6647b6b704643fe2b5e26dff4e))
## [1.20.2](https://github.com/ellite/Wallos/compare/v1.20.1...v1.20.2) (2024-04-11)
### Bug Fixes
* encoding for url and notes ([#273](https://github.com/ellite/Wallos/issues/273)) ([ad86eb5](https://github.com/ellite/Wallos/commit/ad86eb5b9c6e60004de2795170032d62b33ddcfb))
## [1.20.1](https://github.com/ellite/Wallos/compare/v1.20.0...v1.20.1) (2024-04-09)
### Bug Fixes
* special chars in subscriptions ([#271](https://github.com/ellite/Wallos/issues/271)) ([2683a7c](https://github.com/ellite/Wallos/commit/2683a7c4ba3c3575347d48f2c97b92b2ff0cc9f9))
## [1.20.0](https://github.com/ellite/Wallos/compare/v1.19.0...v1.20.0) (2024-04-07)
### Features
* add serbian translation ([#268](https://github.com/ellite/Wallos/issues/268)) ([55089c0](https://github.com/ellite/Wallos/commit/55089c0715ca315feb6a8795b07d9c36167494de))
## [1.19.0](https://github.com/ellite/Wallos/compare/v1.18.3...v1.19.0) (2024-04-03)
### Features
* add polish translation ([#263](https://github.com/ellite/Wallos/issues/263)) ([c752761](https://github.com/ellite/Wallos/commit/c7527610fafa49b18076971befa246b2730b79c4))
## [1.18.3](https://github.com/ellite/Wallos/compare/v1.18.2...v1.18.3) (2024-03-30)
### Bug Fixes
* on initial registration page, logo can be cut off ([#258](https://github.com/ellite/Wallos/issues/258)) ([dde8695](https://github.com/ellite/Wallos/commit/dde8695fb555f483ef8bc8f24db2a610301bab16))
## [1.18.2](https://github.com/ellite/Wallos/compare/v1.18.1...v1.18.2) (2024-03-28)
### Bug Fixes
* small icon size for payment icons ([#253](https://github.com/ellite/Wallos/issues/253)) ([8998e23](https://github.com/ellite/Wallos/commit/8998e23d370165ca158600550dbf0eb8c07d4bac))
## [1.18.1](https://github.com/ellite/Wallos/compare/v1.18.0...v1.18.1) (2024-03-25)
### Bug Fixes
* disabled inputs on dark theme ([#250](https://github.com/ellite/Wallos/issues/250)) ([11f0e7c](https://github.com/ellite/Wallos/commit/11f0e7ce63f37adb922e530a54f3e5cc9f640eee))
## [1.18.0](https://github.com/ellite/Wallos/compare/v1.17.3...v1.18.0) (2024-03-24)
### Features
* add custom avatar functionality ([#248](https://github.com/ellite/Wallos/issues/248)) ([1dbebd3](https://github.com/ellite/Wallos/commit/1dbebd3918ef6f27961f4e70b6ad007133f8ff93))
## [1.17.3](https://github.com/ellite/Wallos/compare/v1.17.2...v1.17.3) (2024-03-20)
### Bug Fixes
* next payment date not updating for disabled subscriptions ([#243](https://github.com/ellite/Wallos/issues/243)) ([75a5672](https://github.com/ellite/Wallos/commit/75a5672de32a59cc53c3c76a08793e6a33cce828))
## [1.17.2](https://github.com/ellite/Wallos/compare/v1.17.1...v1.17.2) (2024-03-18)
### Bug Fixes
* pwa not loading static files when offline ([#241](https://github.com/ellite/Wallos/issues/241)) ([4e3376d](https://github.com/ellite/Wallos/commit/4e3376df93ea7c2b3e184b2670ebe77fe9b15d6a))
## [1.17.1](https://github.com/ellite/Wallos/compare/v1.17.0...v1.17.1) (2024-03-18)
### Bug Fixes
* cronjobs running twice ([#239](https://github.com/ellite/Wallos/issues/239)) ([00cbf8d](https://github.com/ellite/Wallos/commit/00cbf8d9e3feac87292630f8db4571a99b542db4))
## [1.17.0](https://github.com/ellite/Wallos/compare/v1.16.3...v1.17.0) (2024-03-17)
### Features
* allow selecting tls or ssl for email notifications ([#237](https://github.com/ellite/Wallos/issues/237)) ([2462435](https://github.com/ellite/Wallos/commit/246243574328ead6d95d45b81b055761b01040a7))
## [1.16.3](https://github.com/ellite/Wallos/compare/v1.16.2...v1.16.3) (2024-03-17)
### Bug Fixes
* allow redirects on logo search ([ae73db7](https://github.com/ellite/Wallos/commit/ae73db77907786993f52f7273145dafa660c4d36))
* rename category after adding and sort order of categories ([ae73db7](https://github.com/ellite/Wallos/commit/ae73db77907786993f52f7273145dafa660c4d36))
## [1.16.2](https://github.com/ellite/Wallos/compare/v1.16.1...v1.16.2) (2024-03-13)
### Bug Fixes
* wrong folder for payment method logos ([#227](https://github.com/ellite/Wallos/issues/227)) ([f6c1ff2](https://github.com/ellite/Wallos/commit/f6c1ff2a6be6545c6c179722235db3cd724127fd))
## [1.16.1](https://github.com/ellite/Wallos/compare/v1.16.0...v1.16.1) (2024-03-12)
### Bug Fixes
* confusing wording for billing cycle ([94ad0cb](https://github.com/ellite/Wallos/commit/94ad0cb553d7f05b15e9ab27fbf4c26955fc3ff1))
## [1.16.0](https://github.com/ellite/Wallos/compare/v1.15.3...v1.16.0) (2024-03-10)
### Features
* allow sorting payment methods ([#217](https://github.com/ellite/Wallos/issues/217)) ([aef2d13](https://github.com/ellite/Wallos/commit/aef2d134c22f7dc95821ff711f7bca56228bfed6))
* don't allow to change currency code if in use ([aef2d13](https://github.com/ellite/Wallos/commit/aef2d134c22f7dc95821ff711f7bca56228bfed6))
## [1.15.3](https://github.com/ellite/Wallos/compare/v1.15.2...v1.15.3) (2024-03-10)
### Bug Fixes
* sql injection vulnerability when using filters ([#214](https://github.com/ellite/Wallos/issues/214)) ([cbdc188](https://github.com/ellite/Wallos/commit/cbdc188e5e7a2c357f5b0bcaeaf2e886cd2555e3))
## [1.15.2](https://github.com/ellite/Wallos/compare/v1.15.1...v1.15.2) (2024-03-09)
### Bug Fixes
* undefined var on the statistics page ([#211](https://github.com/ellite/Wallos/issues/211)) ([8b7a7b9](https://github.com/ellite/Wallos/commit/8b7a7b94e3ae9177be6d067d8fee0a05aa428f4a))
## [1.15.1](https://github.com/ellite/Wallos/compare/v1.15.0...v1.15.1) (2024-03-09)
### Bug Fixes
* undefined var if sort cookie is not set ([#207](https://github.com/ellite/Wallos/issues/207)) ([288c106](https://github.com/ellite/Wallos/commit/288c10624592aa04cc76cb8ae066331d65964650))
## [1.15.0](https://github.com/ellite/Wallos/compare/v1.14.1...v1.15.0) (2024-03-09)
### Features
* filters on the subscriptions page ([a396285](https://github.com/ellite/Wallos/commit/a396285b76cd87e598495f311a81dc68a7f66d36))
* search subscriptions by name ([a396285](https://github.com/ellite/Wallos/commit/a396285b76cd87e598495f311a81dc68a7f66d36))
## [1.14.1](https://github.com/ellite/Wallos/compare/v1.14.0...v1.14.1) (2024-03-08)
### Bug Fixes
* wrong message when deleting payment methods ([#202](https://github.com/ellite/Wallos/issues/202)) ([93a3d18](https://github.com/ellite/Wallos/commit/93a3d189794985c1d8cfd5558c482f66e79405a8))
## [1.14.0](https://github.com/ellite/Wallos/compare/v1.13.0...v1.14.0) (2024-03-08)
### Features
* add brazilian portuguese to available languages ([#198](https://github.com/ellite/Wallos/issues/198)) ([3ea9d98](https://github.com/ellite/Wallos/commit/3ea9d98da79e9b13ab9d93a56b89062ac19c31d7))
## [1.13.0](https://github.com/ellite/Wallos/compare/v1.12.1...v1.13.0) (2024-03-07)
### Features
* show name of most expensive subscription on statistics ([#194](https://github.com/ellite/Wallos/issues/194)) ([ede08b1](https://github.com/ellite/Wallos/commit/ede08b1f6ae2d52ac0f8e1aaa77edc1924f529ce))
## [1.12.1](https://github.com/ellite/Wallos/compare/v1.12.0...v1.12.1) (2024-03-06)
### Bug Fixes
* broken chinese language file ([#192](https://github.com/ellite/Wallos/issues/192)) ([94c1a91](https://github.com/ellite/Wallos/commit/94c1a91387ca05fad3a50e5f318d8439c7608cbe))
## [1.12.0](https://github.com/ellite/Wallos/compare/v1.11.3...v1.12.0) (2024-03-05)
### Features
* add filters to statistics page ([83234ab](https://github.com/ellite/Wallos/commit/83234ab8cd184f4693a148dc55bddef300c49e71))
* allow deletion of the default payment methods ([83234ab](https://github.com/ellite/Wallos/commit/83234ab8cd184f4693a148dc55bddef300c49e71))
* allow renaming / translation of payment methods ([83234ab](https://github.com/ellite/Wallos/commit/83234ab8cd184f4693a148dc55bddef300c49e71))
* allow sorting of categories in settings ([83234ab](https://github.com/ellite/Wallos/commit/83234ab8cd184f4693a148dc55bddef300c49e71))
## [1.11.3](https://github.com/ellite/Wallos/compare/v1.11.2...v1.11.3) (2024-03-02)
### Bug Fixes
* redirects with the service worker ([#183](https://github.com/ellite/Wallos/issues/183)) ([940bbbe](https://github.com/ellite/Wallos/commit/940bbbea9071a7c2687a3340bb8e9d6f4f884cc1))
## [1.11.2](https://github.com/ellite/Wallos/compare/v1.11.1...v1.11.2) (2024-03-02)
### Bug Fixes
* file upload bypass vulnerability ([#181](https://github.com/ellite/Wallos/issues/181)) ([0f7853f](https://github.com/ellite/Wallos/commit/0f7853f961ba2f68f8dcd358acaad6c6eb7980e6))
## [1.11.1](https://github.com/ellite/Wallos/compare/v1.11.0...v1.11.1) (2024-03-01)
### Bug Fixes
* security issue with image upload ([#175](https://github.com/ellite/Wallos/issues/175)) ([7b5e166](https://github.com/ellite/Wallos/commit/7b5e166e289f32b1b3451614b16e1f4c0b9d6f2a))
## [1.11.0](https://github.com/ellite/Wallos/compare/v1.10.0...v1.11.0) (2024-03-01)
### Features
* added custom payment methods ([#173](https://github.com/ellite/Wallos/issues/173)) ([e739622](https://github.com/ellite/Wallos/commit/e73962260678caf0843b6302f7fbb7d49469a1a9))
## [1.10.0](https://github.com/ellite/Wallos/compare/v1.9.1...v1.10.0) (2024-02-29)
### Features
* use brave search for the logos if google fails ([#169](https://github.com/ellite/Wallos/issues/169)) ([fff783e](https://github.com/ellite/Wallos/commit/fff783e4e87f04199817c7cb3b4bd28760d2b5f3))
## [1.9.1](https://github.com/ellite/Wallos/compare/v1.9.0...v1.9.1) (2024-02-28)
### Bug Fixes
* move display settings to the bottom ([ec25d4b](https://github.com/ellite/Wallos/commit/ec25d4bc5a35f68ff15d456ae6a1d3e98d124f5f))
* reorder subscription form ([ec25d4b](https://github.com/ellite/Wallos/commit/ec25d4bc5a35f68ff15d456ae6a1d3e98d124f5f))
* show email field on adding household member ([ec25d4b](https://github.com/ellite/Wallos/commit/ec25d4bc5a35f68ff15d456ae6a1d3e98d124f5f))
## [1.9.0](https://github.com/ellite/Wallos/compare/v1.8.3...v1.9.0) (2024-02-27)
### Features
* enable progressive web app ([a2a315e](https://github.com/ellite/Wallos/commit/a2a315e34dca2562bc11793cc5841c2082e811a9))
### Bug Fixes
* update packages to fix vulnerabilities ([a2a315e](https://github.com/ellite/Wallos/commit/a2a315e34dca2562bc11793cc5841c2082e811a9))
## [1.8.3](https://github.com/ellite/Wallos/compare/v1.8.2...v1.8.3) (2024-02-26)
### Bug Fixes
* remove service worker ([#157](https://github.com/ellite/Wallos/issues/157)) ([5ccadce](https://github.com/ellite/Wallos/commit/5ccadce2f139e5873889badc51a67bfaef8a9304))
## [1.8.2](https://github.com/ellite/Wallos/compare/v1.8.1...v1.8.2) (2024-02-26)
### Bug Fixes
* service worker redirect not set to follow ([3640b54](https://github.com/ellite/Wallos/commit/3640b547ee3ca28e7b872b9e2dbbcd1d31c54953))
## [1.8.1](https://github.com/ellite/Wallos/compare/v1.8.0...v1.8.1) (2024-02-26)
### Bug Fixes
* service worker has redirections ([4aca7bc](https://github.com/ellite/Wallos/commit/4aca7bcb3cdbb77958db8783c4f088df131db645))
## [1.8.0](https://github.com/ellite/Wallos/compare/v1.7.0...v1.8.0) (2024-02-26)
### Features
* convert wallos into a progressive web app ([#151](https://github.com/ellite/Wallos/issues/151)) ([19e2058](https://github.com/ellite/Wallos/commit/19e205897617ee894d8802f7e73fef46be386c30))
### Bug Fixes
* improve traditional chinese translations ([19e2058](https://github.com/ellite/Wallos/commit/19e205897617ee894d8802f7e73fef46be386c30))
## [1.7.0](https://github.com/ellite/Wallos/compare/v1.6.0...v1.7.0) (2024-02-25)
### Features
* add email for notifications to household members ([26363dd](https://github.com/ellite/Wallos/commit/26363dd5f364b5494c526a9769626b03bba45273))
## [1.6.0](https://github.com/ellite/Wallos/compare/v1.5.0...v1.6.0) (2024-02-24)
### Features
* add stats about inactive subscriptions ([#146](https://github.com/ellite/Wallos/issues/146)) ([ccac17a](https://github.com/ellite/Wallos/commit/ccac17a6f222cb1ee022fd30b7a1d34306dd0de2))
* sort disabled subscription at the bottom ([ccac17a](https://github.com/ellite/Wallos/commit/ccac17a6f222cb1ee022fd30b7a1d34306dd0de2))
## [1.5.0](https://github.com/ellite/Wallos/compare/v1.4.1...v1.5.0) (2024-02-23)
### Features
* allow to disable subscriptions ([#144](https://github.com/ellite/Wallos/issues/144)) ([50056d9](https://github.com/ellite/Wallos/commit/50056d9f03a46c166650474b3877b55a24873bb9))
## [1.4.1](https://github.com/ellite/Wallos/compare/v1.4.0...v1.4.1) (2024-02-22)
### Bug Fixes
* bug on saving fixer api key ([#142](https://github.com/ellite/Wallos/issues/142)) ([866eb28](https://github.com/ellite/Wallos/commit/866eb28e88495e851336b5e224274a823ff4173d))
## [1.4.0](https://github.com/ellite/Wallos/compare/v1.3.1...v1.4.0) (2024-02-21)
### Features
* persist display and experimental settings on the db ([f0a6f1a](https://github.com/ellite/Wallos/commit/f0a6f1a2f18b329c9f784a9f1953cd0e7616e1c6))
* small styles changed ([f0a6f1a](https://github.com/ellite/Wallos/commit/f0a6f1a2f18b329c9f784a9f1953cd0e7616e1c6))
## [1.3.1](https://github.com/ellite/Wallos/compare/v1.3.0...v1.3.1) (2024-02-20)
### Bug Fixes
* missing authentication check ([#133](https://github.com/ellite/Wallos/issues/133)) ([b887d3a](https://github.com/ellite/Wallos/commit/b887d3a0503585dadde4b1b59b023c981b0f7f66))
## [1.3.0](https://github.com/ellite/Wallos/compare/v1.2.0...v1.3.0) (2024-02-19)
### Features
* add apilayer as provider for fixer api ([0f19dd6](https://github.com/ellite/Wallos/commit/0f19dd688fe3a2156e7d26d1bf1e1f8b30ce79ad))
* add apilayer as provider for fixer api ([#127](https://github.com/ellite/Wallos/issues/127)) ([0f19dd6](https://github.com/ellite/Wallos/commit/0f19dd688fe3a2156e7d26d1bf1e1f8b30ce79ad))
* update exchange rate when saving api key ([0f19dd6](https://github.com/ellite/Wallos/commit/0f19dd688fe3a2156e7d26d1bf1e1f8b30ce79ad))
## [1.2.0](https://github.com/ellite/Wallos/compare/v1.1.0...v1.2.0) (2024-02-19)
### Features
* enable deployment in subdirectory ([e2af9af](https://github.com/ellite/Wallos/commit/e2af9afc32bfc248f594336c50d44ad6f36f197e))
## [1.1.0](https://github.com/ellite/Wallos/compare/v1.0.1...v1.1.0) (2024-02-18)
### Features
* new statistics per payment method ([#124](https://github.com/ellite/Wallos/issues/124)) ([6200fa5](https://github.com/ellite/Wallos/commit/6200fa5e87d3f60853c3d8b95f5d676e39b378f4))
## [1.0.1](https://github.com/ellite/Wallos/compare/v1.0.0...v1.0.1) (2024-02-18)
### Bug Fixes
* show translated no category when sorting by category ([#122](https://github.com/ellite/Wallos/issues/122)) ([330c061](https://github.com/ellite/Wallos/commit/330c061b74ad1580173f3d3bc7b14048492e22d2))
## 1.0.0 (2024-02-15)
### Features
* add workflow for building and publishing docker images ([970c96a](https://github.com/ellite/Wallos/commit/970c96a8c904809544c944071986be2a684daf50))
* specify image stability type when triggering build ([5b22cfd](https://github.com/ellite/Wallos/commit/5b22cfd87a94a865f53b282964961862bbea1861))
### Bug Fixes
* Currency not preselected on registration ([fc56cf6](https://github.com/ellite/Wallos/commit/fc56cf69ef22a07978022265b2e8344dc293eb14))
* Language sort order ([884a8e5](https://github.com/ellite/Wallos/commit/884a8e569339ddbcb89af4634c0c845b053affbb))
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to wallos
We welcome contributions from the community and look forward to working with you to improve this project!
## How to Contribute
1. **Fork the repository:** Start by forking the wallos repository to your own GitHub account.
2. **Clone your fork:** Clone the forked repository to your local machine (replace <YOUR_USERNAME> with your actual github username):
```bash
git clone https://github.com/<YOUR_USERNAME>/wallos.git
cd wallos
```
3. **Create a branch:** Create a new branch for your changes:
```bash
git checkout -b feature/your-feature-name
```
or
```bash
git checkout -b fix/your-bug-fix-name
```
4. **Make your changes:** Implement your feature or bug fix.
5. **Test your changes:** Ensure that your changes work as expected.
6. **Commit your changes:** Commit your changes with a clear and concise message:
```bash
git add .
git commit -m "Add your feature or fix"
```
7. **Push your changes:** Push your branch to your forked repository:
```bash
git push origin feature/your-feature-name
```
8. **Create a Pull Request:** Go to the wallos repository on GitHub (https://github.com/ellite/wallos) and create a pull request from your branch to the `main` branch.
## Pull Request Guidelines
* **One feature/fix per pull request:** Please keep pull requests focused on a single feature or bug fix.
* **Clear and descriptive title and description:** Provide a clear title and description of your changes.
* **Include relevant tests:** If possible, include tests for your changes.
* **Follow the project's coding style:** Adhere to the project's coding style and conventions.
* **Keep your pull request up to date:** If changes are requested, please update your pull request accordingly.
## Issues
* **Bug Reports:** If you find a bug, please open an issue with a clear description of the problem and steps to reproduce it.
* **Feature Requests:** If you have a feature request, please open an issue with a clear description of the feature and its benefits.
* **Priority:** Bug fixes will take priority over feature requests.
## Translations
If you want to contribute with a translation of wallos:
1. **Add your language code:**
* Open `includes/i18n/languages.php`.
* Add your language code in the format: `"<language_code>" => ["name" => "<Language Name>", "dir" => "<ltr or rtl>"],`.
* Please use the original language name and not the English translation.
* Example: `"pt" => ["name" => "Português", "dir" => "ltr"],`.
2. **Create language files:**
* Copy `includes/i18n/en.php` and rename it to your language code (e.g., `pt.php`).
* Translate all the values in the new language file.
* Copy `scripts/i18n/en.js` and rename it to your language code (e.g., `pt.js`).
* Translate all the values in the new javascript language file.
* **Note:** Incomplete translations will not be accepted.
3. **Create a Pull Request:** Follow the Pull Request Guidelines above.
## Contributors
<a href="https://github.com/ellite/wallos/graphs/contributors">
<img src="https://contrib.rocks/image?repo=ellite/wallos" />
</a>
Thank you for your contributions!
================================================
FILE: Dockerfile
================================================
# Use the php:8.3-fpm-alpine base image
FROM php:8.3-fpm-alpine
# Set working directory to /var/www/html
WORKDIR /var/www/html
# Update packages and install dependencies
RUN apk upgrade --no-cache && \
apk add --no-cache dumb-init shadow sqlite-dev libpng libpng-dev libjpeg-turbo libjpeg-turbo-dev freetype freetype-dev curl autoconf libgomp icu-dev icu-data-full nginx dcron tzdata imagemagick imagemagick-dev libzip-dev sqlite libwebp-dev && \
docker-php-ext-install pdo pdo_sqlite calendar && \
docker-php-ext-enable pdo pdo_sqlite && \
docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp && \
docker-php-ext-install -j$(nproc) gd intl zip && \
apk add --no-cache --virtual .build-deps $PHPIZE_DEPS && \
pecl install imagick && \
docker-php-ext-enable imagick && \
apk del .build-deps
# Copy your PHP application files into the container
COPY . .
# Copy Nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
COPY nginx.default.conf /etc/nginx/http.d/default.conf
# Remove nginx conf files from webroot
RUN rm -rf /var/www/html/nginx.conf && \
rm -rf /var/www/html/nginx.default.conf
# Copy the custom crontab file
COPY cronjobs /etc/cron.d/cronjobs
# Convert the line endings, allow read access to the cron file, and create cron log folder
RUN dos2unix /etc/cron.d/cronjobs && \
chmod 0644 /etc/cron.d/cronjobs && \
/usr/bin/crontab /etc/cron.d/cronjobs && \
mkdir /var/log/cron && \
chown -R www-data:www-data /var/www/html && \
chmod +x /var/www/html/startup.sh && \
echo 'pm.max_children = 15' >> /usr/local/etc/php-fpm.d/zz-docker.conf && \
echo 'pm.max_requests = 500' >> /usr/local/etc/php-fpm.d/zz-docker.conf
# Expose port 80 for Nginx
EXPOSE 80
ENTRYPOINT ["dumb-init", "--"]
# Requires docker engine 25+ for the --start-interval flag
HEALTHCHECK --interval=2m --timeout=2s --start-period=20s --start-interval=5s --retries=3 \
CMD ["curl", "-fsS", "http://127.0.0.1/health.php"]
# Start both PHP-FPM, Nginx
CMD ["/var/www/html/startup.sh"]
================================================
FILE: LICENSE.md
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: README.md
================================================
<div align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./images/siteicons/walloswhite.png">
<source media="(prefers-color-scheme: light)" srcset="./images/siteicons/wallos.png">
<img alt="Wallos" src="./images/siteicons/wallos.png">
</picture>
<p>Wallos: Open-Source Personal Subscription Tracker</p>
[](https://github.com/ellite/Wallos)
[](https://hub.docker.com/r/bellamy/wallos)
[](https://github.com/ellite/Wallos/graphs/contributors)
[](https://github.com/sponsors/ellite)
[](https://discord.gg/anex9GUrPW)
</div>
## Table of Contents
- [Introduction](#introduction)
- [Features](#features)
- [Demo](#demo)
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Baremetal](#baremetal)
- [Docker](#docker)
- [Installation](#installation)
- [Baremetal](#baremetal-1)
- [Updating](#updating)
- [Docker](#docker-1)
- [Docker-Compose](#docker-compose)
- [Usage](#usage)
- [Screenshots](#screenshots)
- [OIDC](#oidc)
- [API Documentation](#api-documentation)
- [Contributing](#contributing)
- [Contributors](#contributors)
- [Translations](#translations)
- [License](#license)
- [Links](#links)
## Introduction
Wallos is a powerful, open-source, and self-hostable web application designed to empower you in managing your finances with ease. Say goodbye to complicated spreadsheets and expensive financial software – Wallos simplifies the process of tracking expenses and helps you gain better control over your financial life.
## Features
- Subscription Management: Keep track of your recurring subscriptions and payments, ensuring you never miss a due date.
- Category Management: Organize your expenses into customizable categories, enabling you to gain insights into your spending habits.
- Multi-Currency support: Wallos supports multiple currencies, allowing you to manage your finances in the currency of your choice.
- Currency Conversion: Integrates with the Fixer API so you can get exchange rates and see all your subscriptions on your main currency.
- Data Privacy: As a self-hosted application, Wallos ensures that your financial data remains private and secure on your own server.
- Customization: Tailor Wallos to your needs with customizable categories, currencies, themes and other display options.
- Sorting Options: Allowing you to view your subscriptions from different perspectives.
- Logo Search: Wallos can search the web for the logo of your subscriptions if you don't have them available for upload.
- Mobile view: Wallos on the go.
- Statistics: Another perspective into your spendings.
- Notifications: Wallos supports multiple notification methods (email, discord, pushover, telegram, gotify and webhooks). Get notified about your upcoming payments.
- Multi Language support.
- OIDC with OAuth
- AI Recommendations with ChatGPT, Gemini or Local Ollama
## Demo
If you want to try Wallos, a demo is available at [https://demo.wallosapp.com](https://demo.wallosapp.com).
The database is reset every 2 hours.
To access the demo use the following credentials:
```python
Username: demo
Password: demo
```
## Getting Started
See instructions to run Wallos below.
### Prerequisites
#### Baremetal
- NGINX or APACHE websever running
- PHP 8.3 with the following modules enabled:
- curl
- dom
- gd
- imagick
- intl
- openssl
- sqlite3
- zip
- mbstring
- fpm
#### Docker
- Docker
### Installation
#### Baremetal
1. Download or clone this repo and move the files into your web root - usually `/var/www/html`
2. Rename `/db/wallos.empty.db` to `/db/wallos.db`
3. Run `http://domain.example/endpoints/db/migrate.php` on your browser
4. Add the following scripts to your cronjobs with `crontab -e`
```bash
0 1 * * * php /var/www/html/endpoints/cronjobs/updatenextpayment.php >> /var/log/cron/updatenextpayment.log 2>&1
0 2 * * * php /var/www/html/endpoints/cronjobs/updateexchange.php >> /var/log/cron/updateexchange.log 2>&1
0 8 * * * php /var/www/html/endpoints/cronjobs/sendcancellationnotifications.php >> /var/log/cron/sendcancellationnotifications.log 2>&1
0 9 * * * php /var/www/html/endpoints/cronjobs/sendnotifications.php >> /var/log/cron/sendnotifications.log 2>&1
*/2 * * * * php /var/www/html/endpoints/cronjobs/sendverificationemails.php >> /var/log/cron/sendverificationemail.log 2>&1
*/2 * * * * php /var/www/html/endpoints/cronjobs/sendresetpasswordemails.php >> /var/log/cron/sendresetpasswordemails.log 2>&1
0 */6 * * * php /var/www/html/endpoints/cronjobs/checkforupdates.php >> /var/log/cron/checkforupdates.log 2>&1
30 1 * * 1 php /var/www/html/endpoints/cronjobs/storetotalyearlycost.php >> /var/log/cron/storetotalyearlycost.log 2>&1
```
5. If your web root is not `/var/www/html/` adjust the cronjobs above accordingly.
#### Updating
1. Re-download the repo and move the files into the correct folder or do `git pull` (if you used git clone before)
2. Check the [Prerequisites](#baremetal) and install / enable the missing ones, if any.
3. Run `http://domain.example/endpoints/db/migrate.php`
#### Docker
```bash
docker run -d --name wallos -v /path/to/config/wallos/db:/var/www/html/db \
-v /path/to/config/wallos/logos:/var/www/html/images/uploads/logos \
-e TZ=Europe/Berlin -p 8282:80 --restart unless-stopped \
bellamy/wallos:latest
```
Disable healthcheck (optional, e.g., for Docker <25 or faster startup reporting):
```bash
docker run -d --name wallos -v /path/to/config/wallos/db:/var/www/html/db \
-v /path/to/config/wallos/logos:/var/www/html/images/uploads/logos \
-e TZ=Europe/Berlin -p 8282:80 --restart unless-stopped \
--health-cmd=NONE \
bellamy/wallos:latest
```
### Docker Compose
```
services:
wallos:
container_name: wallos
image: bellamy/wallos:latest
ports:
- "8282:80/tcp"
environment:
TZ: 'America/Toronto'
# Volumes store your data between container upgrades
volumes:
- './db:/var/www/html/db'
- './logos:/var/www/html/images/uploads/logos'
restart: unless-stopped
```
Disable healthcheck (optional, e.g., for Docker <25 or faster startup reporting):
```
services:
wallos:
container_name: wallos
image: bellamy/wallos:latest
ports:
- "8282:80/tcp"
environment:
TZ: 'America/Toronto'
volumes:
- './db:/var/www/html/db'
- './logos:/var/www/html/images/uploads/logos'
restart: unless-stopped
healthcheck:
test: ["NONE"]
```
## Usage
Just open the browser and open `ip:port` of the machine running wallos.
On the first time you run wallos a user account must be created.
Go to settings and personalise your Avatar and add members of your household. While there add / remove any categories and currencies.
Get a free API Key from [Fixer](https://fixer.io/#pricing_plan) and add it in the settings.
If you want to trigger an Update of the exchange rates, change your main currency after adding the API Key, and then change it back to your preferred one.
## Screenshots





 
 
## OIDC
OIDC can be enabled on the Admin page and can be used with providers that support OAuth.
## API Documentation
Wallos provides a comprehensive API that allows you to interact with the application programmatically. The API documentation is available at [https://api.wallosapp.com/](https://api.wallosapp.com/).
## Contributing
Feel free to open Pull requests with bug fixes and features. I'll do my best to keep an eye on those.
Feel free to open issues with bug reports or feature requests. Bug fixes will take priority.
I welcome contributions from the community and look forward to working with you to improve this project.
### Contributors
<a href="https://github.com/ellite/wallos/graphs/contributors">
<img src="https://contrib.rocks/image?repo=ellite/wallos" />
</a>
### Translations
If you want to contribute with a translation of wallos:
- Add your language code to `includes/i18n/languages.php` in the format `"en" => ["name" => "English", "dir" => "ltr"],`. Please use the original language name and not the english translation.
- Create a copy of the file `includes/i18n/en.php` and rename it to the language code you used above. Example: pt.php for "pt" => ["name" => "Português", "dir" => "ltr"],.
- Translate all the values on the language file to the new language. (Incomplete translations will not be accepted).
- Create a copy of the file `scripts/i18n/en.js` and rename it to the language code you used above. Example: pt.js for "pt" => ["name" => "Português", "dir" => "ltr"],.
- Translate all the values on the language file to the new language. (Incomplete translations will not be accepted).
## License
This project is licensed under the [GNU General Public License, Version 3](LICENSE.md) - see the [LICENSE.md](LICENSE.md) file for details.
### Why GPLv3?
I chose the GNU General Public License version 3 (GPLv3) for this project because it ensures that the software remains open source and freely available to the community. GPLv3 mandates that any derivative works or modifications must also be released under the same license, promoting the principles of software freedom.
I strongly believe in the importance of open source software and the collaborative nature of development, and I invite contributors to help improve this project.
## Links
- The author: [henrique.pt](https://henrique.pt)
- Wallos Landingpage: [wallosapp.com](https://wallosapp.com)
- Join the conversation: [Discord Server](https://discord.gg/anex9GUrPW)
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Reporting a Vulnerability
If you discover any security vulnerabilities in this project, please report them to the developer by emailing [wallos@henrique.pt](mailto:wallos@henrique.pt). I appreciate your help in keeping the project secure.
## Supported Versions
This project is currently supported with security updates for the following versions:
| Version | Supported |
| ------- | ------------------ |
| latest | :white_check_mark: |
| main | :white_check_mark: |
| 1.x.x | :x: |
## Security Measures
I take security seriously and am working on ways to implement security measures to protect the project.
What is being done currenty:
- Periodically scan the docker image for vulnerabilities with trivy.
## Reporting a Security Concern
If you have any security concerns or questions regarding the security of this project, please contact the developer at [wallos@henrique.pt](mailto:wallos@henrique.pt).
## Responsible Disclosure
I kindly request that you follow responsible disclosure practices and give me reasonable time to address any reported vulnerabilities before making them public.
================================================
FILE: about.php
================================================
<?php
require_once 'includes/header.php';
$wallosIsUpToDate = true;
if (!is_null($settings['latest_version'])) {
$latestVersion = $settings['latest_version'];
if (version_compare($version, $latestVersion) == -1) {
$wallosIsUpToDate = false;
}
}
?>
<section class="contain">
<section class="account-section">
<header>
<h2><?= translate('about', $i18n) ?></h2>
</header>
<div class="credits-list">
<div>
<h3>
Wallos <?= $version ?> <?= $demoMode ? "Demo" : "" ?>
</h3>
<span>
<?= translate('release_notes', $i18n) ?>
<a href="https://github.com/ellite/Wallos/releases/tag/<?= $version ?>" target="_blank"
title="<?= translate('external_url', $i18n) ?>" rel="noreferrer">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</span>
</div>
<?php if (!$wallosIsUpToDate): ?>
<div class="update-available">
<h3>
<i class="fa-solid fa-info-circle"></i>
<?= translate('update_available', $i18n) ?> <?= $latestVersion ?>
</h3>
<span>
<?= translate('release_notes', $i18n) ?>
<a href="https://github.com/ellite/Wallos/releases/tag/<?= $latestVersion ?>" target="_blank"
title="<?= translate('external_url', $i18n) ?>" rel="noreferrer">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</span>
</div>
<?php endif; ?>
<div>
<h3><?= translate('license', $i18n) ?></h3>
<span>
GPLv3
<a href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank"
title="<?= translate('external_url', $i18n) ?>" rel="noreferrer">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</span>
</div>
<div>
<h3><?= translate('issues_and_requests', $i18n) ?></h3>
<span>
GitHub
<a href="https://github.com/ellite/Wallos/issues" target="_blank"
title="<?= translate('external_url', $i18n) ?>" rel="noreferrer">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</span>
</div>
<div>
<h3><?= translate('the_author', $i18n) ?></h3>
<span>
https://henrique.pt
<a href="https://henrique.pt/" target="_blank" title="<?= translate('external_url', $i18n) ?>"
rel="noreferrer">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</span>
</div>
</div>
</section>
<section class="account-section">
<header>
<h2><?= translate("credits", $i18n) ?></h2>
</header>
<div class="credits-list">
<div>
<h3><?= translate('icons', $i18n) ?></h3>
<span>
https://www.streamlinehq.com/freebies/plump-flat-free
<a href="https://www.streamlinehq.com/freebies/plump-flat-free" target="_blank"
title="<?= translate('external_url', $i18n) ?>" rel="noreferrer">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</span>
</div>
<div>
<h3><?= translate('payment_icons', $i18n) ?></h3>
<span>
https://www.figma.com/file/5IMW8JfoXfB5GRlPNdTyeg/Credit-Cards-and-Payment-Methods-Icons-(Community)
<a href="https://www.figma.com/file/5IMW8JfoXfB5GRlPNdTyeg/Credit-Cards-and-Payment-Methods-Icons-(Community)"
target="_blank" title="<?= translate('external_url', $i18n) ?>" rel="noreferrer">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</span>
</div>
<div>
<h3>Chart.js</h3>
<span>
https://www.chartjs.org/
<a href="https://www.chartjs.org/" target="_blank" title="<?= translate('external_url', $i18n) ?>"
rel="noreferrer">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</span>
</div>
<div>
<h3>QRCode.js</h3>
<span>
https://github.com/davidshimjs/qrcodejs
<a href="https://github.com/davidshimjs/qrcodejs" target="_blank"
title="<?= translate('external_url', $i18n) ?>" rel="noreferrer">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</span>
</div>
<div>
<h3>Icons by icons8</h3>
<span>
https://icons8.com/
<a href="https://icons8.com/" target="_blank" title="<?= translate('external_url', $i18n) ?>"
rel="noreferrer">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
</a>
</span>
</div>
</div>
</section>
</section>
<?php
require_once 'includes/footer.php';
?>
================================================
FILE: admin.php
================================================
<?php
require_once 'includes/header.php';
if ($isAdmin != 1) {
header('Location: index.php');
exit;
}
// get admin settings from admin table
$stmt = $db->prepare('SELECT * FROM admin');
$result = $stmt->execute();
$settings = $result->fetchArray(SQLITE3_ASSOC);
// get OIDC settings
$stmt = $db->prepare('SELECT * FROM oauth_settings WHERE id = 1');
$result = $stmt->execute();
$oidcSettings = $result->fetchArray(SQLITE3_ASSOC);
if ($oidcSettings === false) {
// Table is empty or no row with id=1, set defaults
$oidcSettings = [
'name' => '',
'client_id' => '',
'client_secret' => '',
'authorization_url' => '',
'token_url' => '',
'user_info_url' => '',
'redirect_url' => '',
'logout_url' => '',
'user_identifier_field' => 'sub',
'scopes' => 'openid email profile',
'auth_style' => 'auto',
'auto_create_user' => 0,
'password_login_disabled' => 0
];
}
// get user accounts
$stmt = $db->prepare('SELECT id, username, email FROM user ORDER BY id ASC');
$result = $stmt->execute();
$users = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$users[] = $row;
}
$userCount = is_array($users) ? count($users) : 0;
$loginDisabledAllowed = $userCount == 1 && $settings['registrations_open'] == 0;
?>
<section class="contain settings">
<section class="account-section">
<header>
<h2><?= translate('registrations', $i18n) ?></h2>
</header>
<div class="admin-form">
<div class="form-group-inline">
<input type="checkbox" id="registrations" <?= $settings['registrations_open'] ? 'checked' : '' ?> />
<label for="registrations"><?= translate('enable_user_registrations', $i18n) ?></label>
</div>
<div class="form-group">
<label for="maxUsers"><?= translate('maximum_number_users', $i18n) ?></label>
<input type="number" id="maxUsers" autocomplete="off" value="<?= $settings['max_users'] ?>" />
</div>
<div class="settings-notes">
<p>
<i class="fa-solid fa-circle-info"></i>
<?= translate('max_users_info', $i18n) ?>
</p>
<p>
<i class="fa-solid fa-circle-info"></i>
By enabling user registrations, the setting to disable login will be unavailable.
</p>
</div>
<div class="form-group-inline">
<input type="checkbox" id="requireEmail" <?= $settings['require_email_verification'] ? 'checked' : '' ?>
<?= empty($settings['smtp_address']) ? 'disabled' : '' ?> />
<label for="requireEmail">
<?= translate('require_email_verification', $i18n) ?>
</label>
</div>
<?php
if (empty($settings['smtp_address'])) {
?>
<div class="settings-notes">
<p>
<i class="fa-solid fa-circle-info"></i>
<?= translate('configure_smtp_settings_to_enable', $i18n) ?>
</p>
</div>
<?php
}
?>
<div class="form-group">
<label for="serverUrl"><?= translate('server_url', $i18n) ?></label>
<input type="text" id="serverUrl" autocomplete="off" value="<?= $settings['server_url'] ?>" />
</div>
<div class="settings-notes">
<p>
<i class="fa-solid fa-circle-info"></i>
<?= translate('server_url_info', $i18n) ?>
</p>
<p>
<i class="fa-solid fa-circle-info"></i>
<?= translate('server_url_password_reset', $i18n) ?>
</p>
</div>
<hr>
<div class="form-group-inline">
<input type="checkbox" id="disableLogin" <?= $settings['login_disabled'] ? 'checked' : '' ?>
<?= $loginDisabledAllowed ? '' : 'disabled' ?> />
<label for="disableLogin"><?= translate('disable_login', $i18n) ?></label>
</div>
<div class="settings-notes">
<p>
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
<?= translate('disable_login_info', $i18n) ?>
</p>
<p>
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
<?= translate('disable_login_info2', $i18n) ?>
</p>
</div>
<div class="buttons">
<input type="submit" class="thin mobile-grow" value="<?= translate('save', $i18n) ?>"
id="saveAccountRegistrations" onClick="saveAccountRegistrationsButton()" />
</div>
</div>
</section>
<?php
if ($userCount >= 0) {
?>
<section class="account-section">
<header>
<h2><?= translate('user_management', $i18n) ?></h2>
</header>
<div class="user-list">
<?php
foreach ($users as $user) {
$userIcon = $user['id'] == 1 ? 'fa-user-tie' : 'fa-id-badge';
?>
<div class="form-group-inline" data-userid="<?= $user['id'] ?>">
<div class="user-list-row">
<div title="<?= translate('username', $i18n) ?>">
<div class="user-list-icon">
<i class="fa-solid <?= $userIcon ?>"></i>
</div>
<?= $user['username'] ?>
</div>
<div title="<?= translate('email', $i18n) ?>">
<div class="user-list-icon">
<i class="fa-solid fa-envelope"></i>
</div>
<a href="mailto:<?= $user['email'] ?>"><?= $user['email'] ?></a>
</div>
</div>
<div>
<?php
if ($user['id'] != 1) {
?>
<button class="image-button medium" onClick="removeUser(<?= $user['id'] ?>)"
title="<?= translate('delete_user', $i18n) ?>">
<?php include "images/siteicons/svg/delete.php"; ?>
</button>
<?php
} else {
?>
<button class="image-button medium disabled" disabled
title="<?= translate('delete_user', $i18n) ?>">
<?php include "images/siteicons/svg/delete.php"; ?>
</button>
<?php
}
?>
</div>
</div>
<?php
}
?>
</div>
<div class="settings-notes">
<p>
<i class="fa-solid fa-circle-info"></i>
<?= translate('delete_user_info', $i18n) ?>
</p>
</div>
<h2><?= translate('create_user', $i18n) ?></h2>
<div class="form-group">
<input type="text" id="newUsername" autocomplete="off"
placeholder="<?= translate('username', $i18n) ?>" />
</div>
<div class="form-group">
<input type="email" id="newEmail" autocomplete="off"
placeholder="<?= translate('email', $i18n) ?>" />
</div>
<div class="form-group-inline">
<input type="password" id="newPassword" autocomplete="off"
placeholder="<?= translate('password', $i18n) ?>" />
<input type="submit" class="thin" value="<?= translate('add', $i18n) ?>" id="addUserButton"
onClick="addUserButton()" />
</div>
</section>
<?php
}
?>
<section class="account-section">
<header>
<h2><?= translate('oidc_settings', $i18n) ?></h2>
</header>
<div class="admin-form">
<div class="form-group-inline">
<input type="checkbox" id="oidcEnabled" <?= $settings['oidc_oauth_enabled'] ? 'checked' : '' ?>
onchange="toggleOidcEnabled()" />
<label for="oidcEnabled"><?= translate('oidc_oauth_enabled', $i18n) ?></label>
</div>
<div class="form-group">
<input type="text" id="oidcName" placeholder="Provider Name" autocomplete="off"
value="<?= $oidcSettings['name'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcClientId" placeholder="Client ID" autocomplete="off"
value="<?= $oidcSettings['client_id'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcClientSecret" placeholder="Client Secret" autocomplete="off"
value="<?= $oidcSettings['client_secret'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcAuthUrl" placeholder="Auth URL" autocomplete="off"
value="<?= $oidcSettings['authorization_url'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcTokenUrl" placeholder="Token URL" autocomplete="off"
value="<?= $oidcSettings['token_url'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcUserInfoUrl" placeholder="User Info URL" autocomplete="off"
value="<?= $oidcSettings['user_info_url'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcRedirectUrl" placeholder="Redirect URL" autocomplete="off"
value="<?= $oidcSettings['redirect_url'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcLogoutUrl" placeholder="Logout URL" autocomplete="off"
value="<?= $oidcSettings['logout_url'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcUserIdentifierField" placeholder="User Identifier Field" autocomplete="off"
value="<?= $oidcSettings['user_identifier_field'] ?>" />
</div>
<div class="form-group">
<input type="text" id="oidcScopes" placeholder="Scopes" autocomplete="off"
value="<?= $oidcSettings['scopes'] ?>" />
</div>
<div class="form-group">
<input type="hidden" id="oidcAuthStyle" placeholder="Auth Style" autocomplete="off"
value="<?= $oidcSettings['auth_style'] ?>" />
</div>
<div class="form-group-inline">
<input type="checkbox" id="oidcAutoCreateUser" <?= $oidcSettings['auto_create_user'] ? 'checked' : '' ?> />
<label for="oidcAutoCreateUser"><?= translate('create_user_automatically', $i18n) ?></label>
</div>
<div class="form-group-inline">
<input type="checkbox" id="oidcPasswordLoginDisabled"
<?= $oidcSettings['password_login_disabled'] ? 'checked' : '' ?> />
<label for="oidcPasswordLoginDisabled"><?= translate('disable_password_login', $i18n) ?></label>
</div>
<div class="buttons">
<input type="submit" class="thin mobile-grow" value="<?= translate('save', $i18n) ?>"
id="saveOidcSettingsButton" onClick="saveOidcSettingsButton()" />
</div>
</div>
</section>
<section class="account-section">
<header>
<h2><?= translate('smtp_settings', $i18n) ?></h2>
</header>
<div class="admin-form">
<div class="form-group-inline">
<input type="text" name="smtpaddress" id="smtpaddress" autocomplete="off"
placeholder="<?= translate('smtp_address', $i18n) ?>" value="<?= $settings['smtp_address'] ?>" />
<input type="text" name="smtpport" id="smtpport" autocomplete="off"
placeholder="<?= translate('port', $i18n) ?>" class="one-third" value="<?= $settings['smtp_port'] ?>" />
</div>
<div class="form-group-inline">
<div>
<input type="radio" name="encryption" id="encryptionnone" value="none"
<?= empty($settings['encryption']) || $settings['encryption'] == "none" ? "checked" : "" ?> />
<label for="encryptionnone"><?= translate('none', $i18n) ?></label>
</div>
<div>
<input type="radio" name="encryption" id="encryptiontls" value="tls"
<?= $settings['encryption'] == "tls" ? "checked" : "" ?> />
<label for="encryptiontls"><?= translate('tls', $i18n) ?></label>
</div>
<div>
<input type="radio" name="encryption" id="encryptionssl" value="ssl"
<?= $settings['encryption'] == "ssl" ? "checked" : "" ?> />
<label for="encryptionssl"><?= translate('ssl', $i18n) ?></label>
</div>
</div>
<div class="form-group-inline">
<input type="text" name="smtpusername" id="smtpusername" autocomplete="off"
placeholder="<?= translate('smtp_username', $i18n) ?>" value="<?= $settings['smtp_username'] ?>" />
</div>
<div class="form-group-inline">
<input type="password" name="smtppassword" id="smtppassword" autocomplete="off"
placeholder="<?= translate('smtp_password', $i18n) ?>" value="<?= $settings['smtp_password'] ?>" />
</div>
<div class="form-group-inline">
<input type="text" name="fromemail" id="fromemail" autocomplete="off"
placeholder="<?= translate('from_email', $i18n) ?>" value="<?= $settings['from_email'] ?>" />
</div>
<div class="buttons">
<input type="button" class="secondary-button thin mobile-grow" value="<?= translate('test', $i18n) ?>"
id="testSmtpSettingsButton" onClick="testSmtpSettingsButton()" />
<input type="submit" class="thin mobile-grow" value="<?= translate('save', $i18n) ?>"
id="saveSmtpSettingsButton" onClick="saveSmtpSettingsButton()" />
</div>
<div class="settings-notes">
<p>
<i class="fa-solid fa-circle-info"></i> <?= translate('smtp_info', $i18n) ?>
</p>
<p>
<i class="fa-solid fa-circle-info"></i>
<?= translate('smtp_usage_info', $i18n) ?>
</p>
</div>
</div>
</section>
<section class="account-section">
<header>
<h2><?= translate('security_settings', $i18n) ?></h2> </header>
<div class="admin-form">
<div class="form-group-inline">
<input type="text" name="local_webhook_notifications_allowlist" id="local_webhook_notifications_allowlist" autocomplete="off"
placeholder="e.g., 192.168.1.5:8123, homeassistant.local" value="<?= htmlspecialchars($settings['local_webhook_notifications_allowlist'] ?? '', ENT_QUOTES, 'UTF-8') ?>" />
</div>
<div class="buttons">
<input type="submit" class="thin mobile-grow" value="<?= translate('save', $i18n) ?>"
id="saveSecuritySettingsButton" onClick="saveSecuritySettingsButton()" />
</div>
<div class="settings-notes">
<p>
<i class="fa-solid fa-circle-info"></i>
<?= translate('ssrf_protection_info', $i18n) ?>
</p>
<p>
<i class="fa-solid fa-circle-info"></i>
<?= translate('local_webhook_info', $i18n) ?>
</p>
</div>
</div>
</section>
<?php
// Get latest version from admin table
if (!is_null($settings['latest_version'])) {
$latestVersion = $settings['latest_version'];
$hasUpdate = version_compare($version, $latestVersion) == -1;
} else {
$hasUpdate = false;
}
// find unused upload logos
// Get all logos in the subscriptions table
$query = 'SELECT logo FROM subscriptions';
$stmt = $db->prepare($query);
$result = $stmt->execute();
$logosOnDisk = [];
$logosOnDB = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$logosOnDB[] = $row['logo'];
}
// Get all logos in the payment_methods table
$query = 'SELECT icon FROM payment_methods';
$stmt = $db->prepare($query);
$result = $stmt->execute();
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
if (!strstr($row['icon'], "images/uploads/icons/")) {
$logosOnDB[] = $row['icon'];
}
}
$logosOnDB = array_unique($logosOnDB);
// Get all logos in the uploads folder
$uploadDir = 'images/uploads/logos/';
$uploadFiles = scandir($uploadDir);
foreach ($uploadFiles as $file) {
if ($file != '.' && $file != '..' && $file != 'avatars') {
$logosOnDisk[] = ['logo' => $file];
}
}
// Find unused logos
$unusedLogos = [];
foreach ($logosOnDisk as $disk) {
$found = false;
foreach ($logosOnDB as $dbLogo) {
if ($disk['logo'] == $dbLogo) {
$found = true;
break;
}
}
if (!$found) {
$unusedLogos[] = $disk;
}
}
$logosToDelete = count($unusedLogos);
?>
<section class="account-section">
<header>
<h2>
<?= translate('maintenance_tasks', $i18n) ?>
</h2>
</header>
<div class="maintenance-tasks">
<h3><?= translate('update', $i18n) ?></h3>
<div class="form-group">
<?php
if ($hasUpdate) {
?>
<div class="updates-list">
<p><?= translate('new_version_available', $i18n) ?>.</p>
<p>
<?= translate('current_version', $i18n) ?>:
<span>
<?= $version ?>
<a href="https://github.com/ellite/Wallos/releases/tag/<?= $version ?>" target="_blank">
<i class="fa-solid fa-external-link"></i>
</a>
</span>
</p>
<p>
<?= translate('latest_version', $i18n) ?>:
<span>
<?= $latestVersion ?>
<a href="https://github.com/ellite/Wallos/releases/tag/<?= $latestVersion ?>"
target="_blank">
<i class="fa-solid fa-external-link"></i>
</a>
</span>
</p>
</div>
<?php
} else {
?>
<?= translate('on_current_version', $i18n) ?>
<?php
}
?>
</div>
<div class="form-group-inline">
<input type="checkbox" id="updateNotification" <?= $settings['update_notification'] ? 'checked' : '' ?>
onchange="toggleUpdateNotification()" />
<label for="updateNotification"><?= translate('show_update_notification', $i18n) ?></label>
</div>
<h3><?= translate('orphaned_logos', $i18n) ?></h3>
<div class="form-group-inline">
<input type="button" class="button thin mobile-grow" value="<?= translate('delete', $i18n) ?>"
id="deleteUnusedLogos" onClick="deleteUnusedLogos()" <?= $logosToDelete == 0 ? 'disabled' : '' ?> />
<span class="number-of-logos bold"><?= $logosToDelete ?></span>
<?= translate('orphaned_logos', $i18n) ?>
</div>
<h3><?= translate('cronjobs', $i18n) ?></h3>
<div>
<div class="inline-row">
<input type="button" value="Check for Updates" class="button tiny mobile-grow"
onclick="executeCronJob('checkforupdates')">
<input type="button" value="Send Notifications" class="button tiny mobile-grow"
onclick="executeCronJob('sendnotifications')">
<input type="button" value="Send Cancellation Notifications" class="button tiny mobile-grow"
onclick="executeCronJob('sendcancellationnotifications')">
<input type="button" value="Send Password Reset Emails" class="button tiny mobile-grow"
onclick="executeCronJob('sendresetpasswordemails')">
<input type="button" value="Send Verification Emails" class="button tiny mobile-grow"
onclick="executeCronJob('sendverificationemails')">
<input type="button" value="Update Exchange Rates" class="button tiny mobile-grow"
onclick="executeCronJob('updateexchange')">
<input type="button" value="Update Next Payments" class="button tiny mobile-grow"
onclick="executeCronJob('updatenextpayment')">
<input type="button" value="Store Total Yearly Cost" class="button tiny mobile-grow"
onclick="executeCronJob('storetotalyearlycost')">
</div>
<div class="inline-row">
<textarea id="cronjobResult" class="thin" readonly></textarea>
</div>
</div>
</div>
</section>
<section class="account-section">
<header>
<h2><?= translate('backup_and_restore', $i18n) ?></h2>
</header>
<div class="form-group-inline">
<input type="button" class="button thin mobile-grow" value="<?= translate('backup', $i18n) ?>" id="backupDB"
onClick="backupDB()" />
<input type="button" class="secondary-button thin mobile-grow" value="<?= translate('restore', $i18n) ?>"
id="restoreDB" onClick="openRestoreDBFileSelect()" />
<input type="file" name="restoreDBFile" id="restoreDBFile" style="display: none;" onChange="restoreDB()"
accept=".zip">
</div>
<div class="settings-notes">
<p>
<i class="fa-solid fa-circle-info"></i>
<?= translate('restore_info', $i18n) ?>
</p>
</div>
</section>
</section>
<script src="scripts/admin.js?<?= $version ?>"></script>
<?php
require_once 'includes/footer.php';
?>
================================================
FILE: api/admin/get_admin_settings.php
================================================
<?php
/*
This API Endpoint accepts both POST and GET requests.
It receives the following parameters:
- api_key: the API key of the user.
It returns a JSON object with the following properties:
- success: whether the request was successful (boolean).
- title: the title of the response (string).
- admin_settings: an object containing the admin settings.
- notes: warning messages or additional information (array).
Example response:
{
"success": true,
"title": "admin_settings",
"admin_settings": {
"registrations_open": 1,
"max_users": 100,
"require_email_verification": 1,
"server_url": "http://example.com",
"smtp_address": "smtp.example.com",
"smtp_port": 587,
"smtp_username": "admin@example.com",
"smtp_password": "********",
"from_email": "no-reply@example.com",
"encryption": "tls",
"login_disabled": 0,
"latest_version": "v1.0.0",
"update_notification": 1
},
"notes": []
}
*/
require_once '../../includes/connect_endpoint.php';
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER["REQUEST_METHOD"] === "POST" || $_SERVER["REQUEST_METHOD"] === "GET") {
// if the parameters are not set, return an error
$apiKey = $_REQUEST['api_key'] ?? $_REQUEST['apiKey'] ?? null;
if (!$apiKey) {
$response = [
"success" => false,
"title" => "Missing parameters"
];
echo json_encode($response);
exit;
}
// Get user from API key
$sql = "SELECT * FROM user WHERE api_key = :apiKey";
$stmt = $db->prepare($sql);
$stmt->bindValue(':apiKey', $apiKey);
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
// If the user is not found, return an error
if (!$user) {
$response = [
"success" => false,
"title" => "Invalid API key"
];
echo json_encode($response);
exit;
}
$userId = $user['id'];
if ($userId !== 1) {
$response = [
"success" => false,
"title" => "Invalid user"
];
echo json_encode($response);
exit;
}
$sql = "SELECT * FROM 'admin'";
$stmt = $db->prepare($sql);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$admin_settings = $result->fetchArray(SQLITE3_ASSOC);
if ($admin_settings) {
unset($admin_settings['id']);
// if the smtp_password is set, hide it
if (isset($admin_settings['smtp_password'])) {
$admin_settings['smtp_password'] = "********";
}
}
$response = [
"success" => true,
"title" => "admin_settings",
"admin_settings" => $admin_settings,
"notes" => []
];
echo json_encode($response);
$db->close();
} else {
$response = [
"success" => false,
"title" => "Invalid request method"
];
echo json_encode($response);
exit;
}
?>
================================================
FILE: api/admin/get_oidc_settings.php
================================================
<?php
/*
This API Endpoint accepts both POST and GET requests.
It receives the following parameters:
- api_key: the API key of the user.
It returns a JSON object with the following properties:
- success: whether the request was successful (boolean).
- title: the title of the response (string).
- oidc_settings: an object containing the OIDC settings.
- notes: warning messages or additional information (array).
Example response:
{
"success": true,
"title": "oidc_settings",
"oidc_settings": {
"name": "Authentik",
"client_id": "CJMLcyyS94cUMXkitNZuokayArnn23TXxpeUv48E",
"client_secret": "SzfQBIibfN0gEAgCORrKnGnrYe9yqASWAYUuu1byelVosCHlnoqAdWlMDppblyuByb38Zw78AAlgMmdK6SWpGjOU4IiqaoltkAEh52trcqCB8briP1TqqXZdar4xfhVw",
"authorization_url": "https://auth.bellamylab.com/application/o/authorize/",
"token_url": "https://auth.bellamylab.com/application/o/token/",
"user_info_url": "https://auth.bellamylab.com/application/o/userinfo/",
"redirect_url": "http://localhost:80/wallos",
"logout_url": "https://auth.bellamylab.com/application/o/wallos/end-session/",
"user_identifier_field": "sub",
"scopes": "openid email profile",
"auth_style": "auto",
"created_at": "2025-07-20 20:31:50",
"updated_at": "2025-07-20 20:31:50",
"auto_create_user": 0,
"password_login_disabled": 0
},
"notes": []
}
*/
require_once '../../includes/connect_endpoint.php';
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER["REQUEST_METHOD"] === "POST" || $_SERVER["REQUEST_METHOD"] === "GET") {
// if the parameters are not set, return an error
$apiKey = $_REQUEST['api_key'] ?? $_REQUEST['apiKey'] ?? null;
if (!$apiKey) {
$response = [
"success" => false,
"title" => "Missing parameters"
];
echo json_encode($response);
exit;
}
// Get user from API key
$sql = "SELECT * FROM user WHERE api_key = :apiKey";
$stmt = $db->prepare($sql);
$stmt->bindValue(':apiKey', $apiKey);
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
// If the user is not found, return an error
if (!$user) {
$response = [
"success" => false,
"title" => "Invalid API key"
];
echo json_encode($response);
exit;
}
$userId = $user['id'];
if ($userId !== 1) {
$response = [
"success" => false,
"title" => "Invalid user"
];
echo json_encode($response);
exit;
}
$sql = "SELECT * FROM 'oauth_settings' WHERE id = 1";
$stmt = $db->prepare($sql);
$result = $stmt->execute();
$oidc_settings = $result->fetchArray(SQLITE3_ASSOC);
if ($oidc_settings) {
unset($oidc_settings['id']);
}
$response = [
"success" => true,
"title" => "oidc_settings",
"oidc_settings" => $oidc_settings,
"notes" => []
];
echo json_encode($response);
$db->close();
} else {
$response = [
"success" => false,
"title" => "Invalid request method"
];
echo json_encode($response);
exit;
}
?>
================================================
FILE: api/admin/set_disable_password_login.php
================================================
<?php
/*
This API Endpoint accepts POST requests only.
It receives the following parameters:
- api_key: the API key of the user.
- disable: '1' to disable password login, '0' to enable it.
It returns a JSON object with the following properties:
- success: whether the request was successful (boolean).
- title: the title of the response (string).
- message: detailed information or error message (string).
Example response:
{
"success": true,
"title": "Updated",
"message": "Password login has been disabled."
}
*/
require_once '../../includes/connect_endpoint.php';
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode([
'success' => false,
'title' => 'Invalid request method',
'message' => 'Only POST requests are allowed.'
]);
exit;
}
$apiKey = $_POST['api_key'] ?? null;
// Authenticate user first
if (!$apiKey) {
echo json_encode([
'success' => false,
'title' => 'Missing API key',
'message' => 'API key is required.'
]);
exit;
}
$sql = "SELECT * FROM user WHERE api_key = :apiKey";
$stmt = $db->prepare($sql);
$stmt->bindValue(':apiKey', $apiKey);
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
if (!$user || $user['id'] !== 1) {
echo json_encode([
'success' => false,
'title' => 'Unauthorized',
'message' => 'Invalid API key or insufficient privileges.'
]);
exit;
}
// Now check 'disable' parameter only after authentication
$disable = $_POST['disable'] ?? null;
if (!isset($disable)) {
echo json_encode([
'success' => false,
'title' => 'Missing parameter',
'message' => 'Parameter "disable" is required.'
]);
exit;
}
if (!in_array($disable, ['0', '1'], true)) {
echo json_encode([
'success' => false,
'title' => 'Invalid parameter',
'message' => 'Parameter "disable" must be "0" or "1".'
]);
exit;
}
// Update the password_login_disabled setting
$updateSql = "UPDATE oauth_settings SET password_login_disabled = :disable WHERE id = 1";
$updateStmt = $db->prepare($updateSql);
$updateStmt->bindValue(':disable', intval($disable), SQLITE3_INTEGER);
$updateResult = $updateStmt->execute();
if ($updateResult) {
echo json_encode([
'success' => true,
'title' => 'Updated',
'message' => "Password login has been " . ($disable === '1' ? "disabled" : "enabled") . "."
]);
} else {
echo json_encode([
'success' => false,
'title' => 'Database error',
'message' => 'Failed to update the setting.'
]);
}
$db->close();
================================================
FILE: api/categories/get_categories.php
================================================
<?php
/*
This API Endpoint accepts both POST and GET requests.
It receives the following parameters:
- api_key: the API key of the user.
It returns a JSON object with the following properties:
- success: whether the request was successful (boolean).
- title: the title of the response (string).
- categories: an array of categories.
- notes: warning messages or additional information (array).
Example response:
{
"success": true,
"title": "categories",
"categories": [
{
"id": 1,
"name": "General",
"order": 1,
"in_use": true
},
{
"id": 2,
"name": "Entertainment",
"order": 2,
"in_use": true
},
{
"id": 3,
"name": "Music",
"order": 3,
"in_use": true
}
],
"notes": []
}
*/
require_once '../../includes/connect_endpoint.php';
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER["REQUEST_METHOD"] === "POST" || $_SERVER["REQUEST_METHOD"] === "GET") {
// if the parameters are not set, return an error
$apiKey = $_REQUEST['api_key'] ?? $_REQUEST['apiKey'] ?? null;
if (!$apiKey) {
$response = [
"success" => false,
"title" => "Missing parameters"
];
echo json_encode($response);
exit;
}
// Get user from API key
$sql = "SELECT * FROM user WHERE api_key = :apiKey";
$stmt = $db->prepare($sql);
$stmt->bindValue(':apiKey', $apiKey);
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
// If the user is not found, return an error
if (!$user) {
$response = [
"success" => false,
"title" => "Invalid API key"
];
echo json_encode($response);
exit;
}
$userId = $user['id'];
$sql = "SELECT * FROM categories WHERE user_id = :userId";
$stmt = $db->prepare($sql);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$categories = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$categories[] = $row;
}
foreach ($categories as $key => $value) {
unset($categories[$key]['user_id']);
// Check if it's in use in any subscription
$categoryId = $categories[$key]['id'];
$sql = "SELECT COUNT(*) as count FROM subscriptions WHERE user_id = :userId AND category_id = :categoryId";
$stmt = $db->prepare($sql);
$stmt->bindValue(':categoryId', $categoryId);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$count = $result->fetchArray(SQLITE3_ASSOC);
if ($count['count'] > 0) {
$categories[$key]['in_use'] = true;
} else {
$categories[$key]['in_use'] = false;
}
}
$response = [
"success" => true,
"title" => "categories",
"categories" => $categories,
"notes" => []
];
echo json_encode($response);
$db->close();
} else {
$response = [
"success" => false,
"title" => "Invalid request method"
];
echo json_encode($response);
exit;
}
?>
================================================
FILE: api/currencies/get_currencies.php
================================================
<?php
/*
This API Endpoint accepts both POST and GET requests.
It receives the following parameters:
- api_key: the API key of the user.
It returns a JSON object with the following properties:
- success: whether the request was successful (boolean).
- title: the title of the response (string).
- main_currency: the main currency of the user (integer).
- currencies: an array of currencies.
- notes: warning messages or additional information (array).
Example response:
{
"success": true,
"title": "currencies",
"main_currency": 3,
"currencies": [
{
"id": 1,
"name": "US Dollar",
"symbol": "$",
"code": "USD",
"rate": "1.1000",
"in_use": true
},
{
"id": 2,
"name": "Japanese Yen",
"symbol": "¥",
"code": "JPY",
"rate": "150.0000",
"in_use": true
},
{
"id": 3,
"name": "Euro",
"symbol": "€",
"code": "EUR",
"rate": "1.0000",
"in_use": true
}
],
"notes": []
}
*/
require_once '../../includes/connect_endpoint.php';
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER["REQUEST_METHOD"] === "POST" || $_SERVER["REQUEST_METHOD"] === "GET") {
// if the parameters are not set, return an error
$apiKey = $_REQUEST['api_key'] ?? $_REQUEST['apiKey'] ?? null;
if (!$apiKey) {
$response = [
"success" => false,
"title" => "Missing parameters"
];
echo json_encode($response);
exit;
}
// Get user from API key
$sql = "SELECT * FROM user WHERE api_key = :apiKey";
$stmt = $db->prepare($sql);
$stmt->bindValue(':apiKey', $apiKey);
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
// If the user is not found, return an error
if (!$user) {
$response = [
"success" => false,
"title" => "Invalid API key"
];
echo json_encode($response);
exit;
}
$userId = $user['id'];
$sql = "SELECT * FROM currencies WHERE user_id = :userId";
$stmt = $db->prepare($sql);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$currencies = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$currencies[] = $row;
}
foreach ($currencies as $key => $value) {
unset($currencies[$key]['user_id']);
// Check if it's in use in any subscription
$currencyId = $currencies[$key]['id'];
$sql = "SELECT COUNT(*) as count FROM subscriptions WHERE user_id = :userId AND currency_id = :currencyId";
$stmt = $db->prepare($sql);
$stmt->bindValue(':currencyId', $currencyId);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$count = $result->fetchArray(SQLITE3_ASSOC);
if ($count['count'] > 0) {
$currencies[$key]['in_use'] = true;
} else {
$currencies[$key]['in_use'] = false;
}
}
$mainCurrency = $user['main_currency'];
$response = [
"success" => true,
"title" => "currencies",
"main_currency" => $mainCurrency,
"currencies" => $currencies,
"notes" => []
];
echo json_encode($response);
$db->close();
} else {
$response = [
"success" => false,
"title" => "Invalid request method"
];
echo json_encode($response);
exit;
}
?>
================================================
FILE: api/fixer/get_fixer.php
================================================
<?php
/*
This API Endpoint accepts both POST and GET requests.
It receives the following parameters:
- api_key: the API key of the user.
It returns a JSON object with the following properties:
- success: whether the request was successful (boolean).
- title: the title of the response (string).
- fixer: an object containing the Fixer settings.
- notes: warning messages or additional information (array).
Example response:
{
"success": true,
"title": "fixer",
"fixer": {
"api_key": "********",
"provider": 0,
"provider_name": "Fixer.io"
},
"notes": []
}
*/
require_once '../../includes/connect_endpoint.php';
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER["REQUEST_METHOD"] === "POST" || $_SERVER["REQUEST_METHOD"] === "GET") {
// if the parameters are not set, return an error
$apiKey = $_REQUEST['api_key'] ?? $_REQUEST['apiKey'] ?? null;
if (!$apiKey) {
$response = [
"success" => false,
"title" => "Missing parameters"
];
echo json_encode($response);
exit;
}
// Get user from API key
$sql = "SELECT * FROM user WHERE api_key = :apiKey";
$stmt = $db->prepare($sql);
$stmt->bindValue(':apiKey', $apiKey);
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
// If the user is not found, return an error
if (!$user) {
$response = [
"success" => false,
"title" => "Invalid API key"
];
echo json_encode($response);
exit;
}
$userId = $user['id'];
$providers = [
0 => "Fixer.io",
1 => "APILayer.com"
];
$query = "SELECT * FROM fixer WHERE user_id = :userId";
$stmt = $db->prepare($query);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$fixer = $result->fetchArray(SQLITE3_ASSOC);
$notes = [];
if ($fixer) {
unset($fixer['user_id']);
$fixer['provider_name'] = $providers[$fixer['provider']];
if ($fixer['api_key']) {
$fixer['api_key'] = "********";
}
} else {
$fixer = [];
$notes[] = "No fixer settings found";
}
$response = [
"success" => true,
"title" => "fixer",
"fixer" => $fixer,
"notes" => $notes
];
echo json_encode($response);
$db->close();
} else {
$response = [
"success" => false,
"title" => "Invalid request method"
];
echo json_encode($response);
exit;
}
?>
================================================
FILE: api/household/get_household.php
================================================
<?php
/*
This API Endpoint accepts both POST and GET requests.
It receives the following parameters:
- api_key: the API key of the user.
It returns a JSON object with the following properties:
- success: whether the request was successful (boolean).
- title: the title of the response (string).
- household: an array of household members.
- notes: warning messages or additional information (array).
Example response:
{
"success": true,
"title": "household",
"household": [
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"in_use": true
},
{
"id": 2,
"name": "Jane Doe",
"email": "jane@example.com",
"in_use": true
}
],
"notes": []
}
*/
require_once '../../includes/connect_endpoint.php';
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER["REQUEST_METHOD"] === "POST" || $_SERVER["REQUEST_METHOD"] === "GET") {
// if the parameters are not set, return an error
$apiKey = $_REQUEST['api_key'] ?? $_REQUEST['apiKey'] ?? null;
if (!$apiKey) {
$response = [
"success" => false,
"title" => "Missing parameters"
];
echo json_encode($response);
exit;
}
// Get user from API key
$sql = "SELECT * FROM user WHERE api_key = :apiKey";
$stmt = $db->prepare($sql);
$stmt->bindValue(':apiKey', $apiKey);
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
// If the user is not found, return an error
if (!$user) {
$response = [
"success" => false,
"title" => "Invalid API key"
];
echo json_encode($response);
exit;
}
$userId = $user['id'];
$sql = "SELECT * FROM household WHERE user_id = :userId";
$stmt = $db->prepare($sql);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$household = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$household[] = $row;
}
foreach ($household as $key => $value) {
unset($household[$key]['user_id']);
// Check if is used in any subscriptions
$sql = "SELECT * FROM subscriptions WHERE user_id = :userId AND payer_user_id = :householdId";
$stmt = $db->prepare($sql);
$stmt->bindValue(':userId', $userId);
$stmt->bindValue(':householdId', $household[$key]['id']);
$result = $stmt->execute();
$subscription = $result->fetchArray(SQLITE3_ASSOC);
if ($subscription) {
$household[$key]['in_use'] = true;
} else {
$household[$key]['in_use'] = false;
}
}
$response = [
"success" => true,
"title" => "household",
"household" => $household,
"notes" => []
];
echo json_encode($response);
$db->close();
} else {
$response = [
"success" => false,
"title" => "Invalid request method"
];
echo json_encode($response);
exit;
}
?>
================================================
FILE: api/notifications/get_notification_settings.php
================================================
<?php
/*
This API Endpoint accepts both POST and GET requests.
It receives the following parameters:
- api_key: the API key of the user.
It returns a JSON object with the following properties:
- success: whether the request was successful (boolean).
- title: the title of the response (string).
- notification_settings: an object containing the notification settings, for the enabled methods.
- notes: warning messages or additional information (array).
Example response:
{
"success": true,
"title": "notification_settings",
"notification_settings": {
"email_notifications": {
"enabled": 1,
"smtp_address": "smtp.example.com",
"smtp_port": 587,
"smtp_username": "user@example.com",
"smtp_password": "********",
"from_email": "no-reply@example.com",
"encryption": "tls",
"other_emails": "other@example.com"
},
"ntfy_notifications": {
"enabled": 0,
"host": "http://notify.example.com",
"topic": "example_topic",
"headers": "********"
}
},
"notes": []
}
*/
require_once '../../includes/connect_endpoint.php';
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER["REQUEST_METHOD"] === "POST" || $_SERVER["REQUEST_METHOD"] === "GET") {
// if the parameters are not set, return an error
$apiKey = $_REQUEST['api_key'] ?? $_REQUEST['apiKey'] ?? null;
if (!$apiKey) {
$response = [
"success" => false,
"title" => "Missing parameters"
];
echo json_encode($response);
exit;
}
// Get user from API key
$sql = "SELECT * FROM user WHERE api_key = :apiKey";
$stmt = $db->prepare($sql);
$stmt->bindValue(':apiKey', $apiKey);
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
// If the user is not found, return an error
if (!$user) {
$response = [
"success" => false,
"title" => "Invalid API key"
];
echo json_encode($response);
exit;
}
$userId = $user['id'];
$query = "SELECT * FROM notification_settings WHERE user_id = :userId";
$stmt = $db->prepare($query);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$notification_settings = $result->fetchArray(SQLITE3_ASSOC);
if ($notification_settings) {
unset($notification_settings['user_id']);
} else {
$notification_settings = [];
}
$query = "SELECT * FROM email_notifications WHERE user_id = :userId";
$stmt = $db->prepare($query);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$email_notifications = $result->fetchArray(SQLITE3_ASSOC);
if ($email_notifications) {
unset($email_notifications['user_id']);
if (isset($email_notifications['smtp_password'])) {
$email_notifications['smtp_password'] = "********";
}
$notification_settings['email_notifications'] = $email_notifications;
}
$query = "SELECT * FROM discord_notifications WHERE user_id = :userId";
$stmt = $db->prepare($query);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$discord_notifications = $result->fetchArray(SQLITE3_ASSOC);
if ($discord_notifications) {
unset($discord_notifications['user_id']);
$notification_settings['discord_notifications'] = $discord_notifications;
}
$query = "SELECT * FROM gotify_notifications WHERE user_id = :userId";
$stmt = $db->prepare($query);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$gotify_notifications = $result->fetchArray(SQLITE3_ASSOC);
if ($gotify_notifications) {
unset($gotify_notifications['user_id']);
if (isset($gotify_notifications['token'])) {
$gotify_notifications['token'] = "********";
}
$notification_settings['gotify_notifications'] = $gotify_notifications;
}
$query = "SELECT * FROM ntfy_notifications WHERE user_id = :userId";
$stmt = $db->prepare($query);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$ntfy_notifications = $result->fetchArray(SQLITE3_ASSOC);
if ($ntfy_notifications) {
unset($ntfy_notifications['user_id']);
if (isset($ntfy_notifications['headers'])) {
$ntfy_notifications['headers'] = "********";
}
$notification_settings['ntfy_notifications'] = $ntfy_notifications;
}
$query = "SELECT * FROM pushover_notifications WHERE user_id = :userId";
$stmt = $db->prepare($query);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$pushover_notifications = $result->fetchArray(SQLITE3_ASSOC);
if ($pushover_notifications) {
unset($pushover_notifications['user_id']);
if (isset($pushover_notifications['token'])) {
$pushover_notifications['token'] = "********";
}
$notification_settings['pushover_notifications'] = $pushover_notifications;
}
$query = "SELECT * FROM telegram_notifications WHERE user_id = :userId";
$stmt = $db->prepare($query);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$telegram_notifications = $result->fetchArray(SQLITE3_ASSOC);
if ($telegram_notifications) {
unset($telegram_notifications['user_id']);
if (isset($telegram_notifications['bot_token'])) {
$telegram_notifications['bot_token'] = "********";
}
$notification_settings['telegram_notifications'] = $telegram_notifications;
}
$query = "SELECT * FROM webhook_notifications WHERE user_id = :userId";
$stmt = $db->prepare($query);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$webhook_notifications = $result->fetchArray(SQLITE3_ASSOC);
if ($webhook_notifications) {
unset($webhook_notifications['user_id']);
if (isset($webhook_notifications['headers'])) {
$webhook_notifications['headers'] = "********";
}
$notification_settings['webhook_notifications'] = $webhook_notifications;
}
// Serverchan notifications
$query = "SELECT * FROM serverchan_notifications WHERE user_id = :userId";
$stmt = $db->prepare($query);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$serverchan_notifications = $result->fetchArray(SQLITE3_ASSOC);
if ($serverchan_notifications) {
unset($serverchan_notifications['user_id']);
if (isset($serverchan_notifications['sendkey'])) {
$serverchan_notifications['sendkey'] = "********";
}
$notification_settings['serverchan_notifications'] = $serverchan_notifications;
}
$response = [
"success" => true,
"title" => "notification_settings",
"notification_settings" => $notification_settings,
"notes" => []
];
echo json_encode($response);
$db->close();
} else {
$response = [
"success" => false,
"title" => "Invalid request method"
];
echo json_encode($response);
exit;
}
?>
================================================
FILE: api/payment_methods/get_payment_methods.php
================================================
<?php
/*
This API Endpoint accepts both POST and GET requests.
It receives the following parameters:
- api_key: the API key of the user.
It returns a JSON object with the following properties:
- success: whether the request was successful (boolean).
- title: the title of the response (string).
- payment_methods: an array of payment methods.
- notes: warning messages or additional information (array).
Example response:
{
"success": true,
"title": "payment_methods",
"payment_methods": [
{
"id": 1,
"name": "PayPal",
"icon": "images/uploads/icons/paypal.png",
"enabled": 1,
"order": 1,
"in_use": true
},
{
"id": 2,
"name": "Credit Card",
"icon": "images/uploads/icons/creditcard.png",
"enabled": 1,
"order": 2,
"in_use": true
},
{
"id": 3,
"name": "Bank Transfer",
"icon": "images/uploads/icons/banktransfer.png",
"enabled": 1,
"order": 3,
"in_use": false
},
{
"id": 4,
"name": "Direct Debit",
"icon": "images/uploads/icons/directdebit.png",
"enabled": 1,
"order": 4,
"in_use": false
},
{
"id": 5,
"name": "Cash",
"icon": "images/uploads/icons/cash.png",
"enabled": 1,
"order": 5,
"in_use": false
},
{
"id": 6,
"name": "Google Pay",
"icon": "images/uploads/icons/googlepay.png",
"enabled": 1,
"order": 6,
"in_use": true
}
],
"notes": []
}
*/
require_once '../../includes/connect_endpoint.php';
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER["REQUEST_METHOD"] === "POST" || $_SERVER["REQUEST_METHOD"] === "GET") {
// if the parameters are not set, return an error
$apiKey = $_REQUEST['api_key'] ?? $_REQUEST['apiKey'] ?? null;
if (!$apiKey) {
$response = [
"success" => false,
"title" => "Missing parameters"
];
echo json_encode($response);
exit;
}
// Get user from API key
$sql = "SELECT * FROM user WHERE api_key = :apiKey";
$stmt = $db->prepare($sql);
$stmt->bindValue(':apiKey', $apiKey);
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
// If the user is not found, return an error
if (!$user) {
$response = [
"success" => false,
"title" => "Invalid API key"
];
echo json_encode($response);
exit;
}
$userId = $user['id'];
$sql = "SELECT * FROM payment_methods WHERE user_id = :userId";
$stmt = $db->prepare($sql);
$stmt->bindValue(':userId', $userId);
$result = $stmt->execute();
$payment_methods = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$payment_methods[] = $row;
}
foreach ($payment_methods as $key => $value) {
unset($payment_methods[$key]['user_id']);
// Check if is used in any subscriptions
$sql = "SELECT * FROM subscriptions WHERE user_id = :userId AND payment_method_id = :paymentMethodId";
$stmt = $db->prepare($sql);
$stmt->bindValue(':userId', $userId);
$stmt->bindValue(':paymentMethodId', $payment_methods[$key]['id']);
$result = $stmt->execute();
$subscription = $result->fetchArray(SQLITE3_ASSOC);
if ($subscription) {
$payment_methods[$key]['in_use'] = true;
} else {
$payment_methods[$key]['in_use'] = false;
}
}
$response = [
"success" => true,
"title" => "payment_methods",
"payment_methods" => $payment_methods,
"notes" => []
];
echo json_encode($response);
$db->close();
} else {
$response = [
"success" => false,
"title" => "Invalid request method"
];
echo json_encode($response);
exit;
}
?>
================================================
FILE: api/settings/get_settings.php
========
gitextract_dfd08_8w/ ├── .dockerignore ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ └── build-release.yaml ├── .gitignore ├── .tmp/ │ └── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── SECURITY.md ├── about.php ├── admin.php ├── api/ │ ├── admin/ │ │ ├── get_admin_settings.php │ │ ├── get_oidc_settings.php │ │ └── set_disable_password_login.php │ ├── categories/ │ │ └── get_categories.php │ ├── currencies/ │ │ └── get_currencies.php │ ├── fixer/ │ │ └── get_fixer.php │ ├── household/ │ │ └── get_household.php │ ├── notifications/ │ │ └── get_notification_settings.php │ ├── payment_methods/ │ │ └── get_payment_methods.php │ ├── settings/ │ │ └── get_settings.php │ ├── status/ │ │ └── version.php │ ├── subscriptions/ │ │ ├── get_ical_feed.php │ │ ├── get_monthly_cost.php │ │ └── get_subscriptions.php │ └── users/ │ └── get_user.php ├── calendar.php ├── cronjobs ├── docker-compose.yaml ├── endpoints/ │ ├── admin/ │ │ ├── adduser.php │ │ ├── deleteunusedlogos.php │ │ ├── deleteuser.php │ │ ├── enableoidc.php │ │ ├── saveoidcsettings.php │ │ ├── saveopenregistrations.php │ │ ├── savesecuritysettings.php │ │ ├── savesmtpsettings.php │ │ └── updatenotification.php │ ├── ai/ │ │ ├── delete_recommendation.php │ │ ├── fetch_models.php │ │ ├── generate_recommendations.php │ │ └── save_settings.php │ ├── categories/ │ │ └── category.php │ ├── cronjobs/ │ │ ├── checkforupdates.php │ │ ├── cleanupresettokens.php │ │ ├── createdatabase.php │ │ ├── sendcancellationnotifications.php │ │ ├── sendnotifications.php │ │ ├── sendresetpasswordemails.php │ │ ├── sendverificationemails.php │ │ ├── settimezone.php │ │ ├── storetotalyearlycost.php │ │ ├── updateexchange.php │ │ ├── updatenextpayment.php │ │ └── validate.php │ ├── currency/ │ │ ├── currency.php │ │ ├── fixer_api_key.php │ │ └── update_exchange.php │ ├── db/ │ │ ├── backup.php │ │ ├── import.php │ │ ├── migrate.php │ │ └── restore.php │ ├── household/ │ │ └── household.php │ ├── logos/ │ │ └── search.php │ ├── notifications/ │ │ ├── savediscordnotifications.php │ │ ├── saveemailnotifications.php │ │ ├── savegotifynotifications.php │ │ ├── savemattermostnotifications.php │ │ ├── savenotificationsettings.php │ │ ├── saventfynotifications.php │ │ ├── savepushovernotifications.php │ │ ├── savepushplusnotifications.php │ │ ├── saveserverchannotifications.php │ │ ├── savetelegramnotifications.php │ │ ├── savewebhooknotifications.php │ │ ├── testdiscordnotifications.php │ │ ├── testemailnotifications.php │ │ ├── testgotifynotifications.php │ │ ├── testmattermostnotifications.php │ │ ├── testntfynotifications.php │ │ ├── testpushovernotifications.php │ │ ├── testpushplusnotifications.php │ │ ├── testserverchannotifications.php │ │ ├── testtelegramnotifications.php │ │ └── testwebhooknotifications.php │ ├── payments/ │ │ ├── add.php │ │ ├── delete.php │ │ ├── get.php │ │ ├── rename.php │ │ ├── search.php │ │ ├── sort.php │ │ └── toggle.php │ ├── settings/ │ │ ├── colortheme.php │ │ ├── convert_currency.php │ │ ├── customcss.php │ │ ├── customtheme.php │ │ ├── deleteaccount.php │ │ ├── disabled_to_bottom.php │ │ ├── hide_disabled.php │ │ ├── mobile_navigation.php │ │ ├── monthly_price.php │ │ ├── remove_background.php │ │ ├── resettheme.php │ │ ├── show_original_price.php │ │ ├── subscription_progress.php │ │ └── theme.php │ ├── subscription/ │ │ ├── add.php │ │ ├── clone.php │ │ ├── delete.php │ │ ├── exportcalendar.php │ │ ├── get.php │ │ ├── getcalendar.php │ │ └── renew.php │ ├── subscriptions/ │ │ ├── export.php │ │ └── get.php │ └── user/ │ ├── budget.php │ ├── delete_avatar.php │ ├── disable_totp.php │ ├── enable_totp.php │ ├── regenerateapikey.php │ └── save_user.php ├── health.php ├── images/ │ └── siteicons/ │ └── svg/ │ ├── automatic.php │ ├── category.php │ ├── check.php │ ├── clone.php │ ├── delete.php │ ├── edit.php │ ├── export_ical.php │ ├── logo.php │ ├── manual.php │ ├── mobile-menu/ │ │ ├── about.php │ │ ├── admin.php │ │ ├── calendar.php │ │ ├── clone.php │ │ ├── delete.php │ │ ├── edit.php │ │ ├── home.php │ │ ├── logout.php │ │ ├── profile.php │ │ ├── renew.php │ │ ├── settings.php │ │ ├── statistics.php │ │ └── subscriptions.php │ ├── notes.php │ ├── payment.php │ ├── renew.php │ ├── save.php │ ├── subscription.php │ ├── web.php │ └── websearch.php ├── includes/ │ ├── checkredirect.php │ ├── checksession.php │ ├── checkuser.php │ ├── connect.php │ ├── connect_endpoint.php │ ├── connect_endpoint_crontabs.php │ ├── currency_formatter.php │ ├── filters_menu.php │ ├── footer.php │ ├── getdbkeys.php │ ├── getsettings.php │ ├── header.php │ ├── i18n/ │ │ ├── ca.php │ │ ├── cs.php │ │ ├── da.php │ │ ├── de.php │ │ ├── el.php │ │ ├── en.php │ │ ├── es.php │ │ ├── fr.php │ │ ├── getlang.php │ │ ├── id.php │ │ ├── it.php │ │ ├── jp.php │ │ ├── ko.php │ │ ├── languages.php │ │ ├── nl.php │ │ ├── pl.php │ │ ├── pt.php │ │ ├── pt_br.php │ │ ├── ro.php │ │ ├── ru.php │ │ ├── sl.php │ │ ├── sr.php │ │ ├── sr_lat.php │ │ ├── tr.php │ │ ├── uk.php │ │ ├── vi.php │ │ ├── zh_cn.php │ │ └── zh_tw.php │ ├── inputvalidation.php │ ├── list_subscriptions.php │ ├── oidc/ │ │ ├── handle_oidc_callback.php │ │ ├── oidc_create_user.php │ │ └── oidc_login.php │ ├── sort_options.php │ ├── ssrf_helper.php │ ├── stats_calculations.php │ ├── validate_endpoint.php │ ├── validate_endpoint_admin.php │ └── version.php ├── index.php ├── libs/ │ ├── OTPHP/ │ │ ├── Factory.php │ │ ├── FactoryInterface.php │ │ ├── HOTP.php │ │ ├── HOTPInterface.php │ │ ├── InternalClock.php │ │ ├── OTP.php │ │ ├── OTPInterface.php │ │ ├── ParameterTrait.php │ │ ├── TOTP.php │ │ ├── TOTPInterface.php │ │ └── Url.php │ ├── PHPMailer/ │ │ ├── Exception.php │ │ ├── PHPMailer.php │ │ └── SMTP.php │ ├── Psr/ │ │ └── Clock/ │ │ └── ClockInterface.php │ ├── constant_time_encoding/ │ │ ├── Base32.php │ │ ├── Base32Hex.php │ │ ├── Base64.php │ │ ├── Base64DotSlash.php │ │ ├── Base64DotSlashOrdered.php │ │ ├── Base64UrlSafe.php │ │ ├── Binary.php │ │ ├── EncoderInterface.php │ │ ├── Encoding.php │ │ ├── Hex.php │ │ └── RFC4648.php │ └── csrf.php ├── login.php ├── logos.php ├── logout.php ├── manifest.json ├── migrations/ │ ├── 000001.php │ ├── 000002.php │ ├── 000003.php │ ├── 000004.php │ ├── 000005.php │ ├── 000006.php │ ├── 000007.php │ ├── 000008.php │ ├── 000009.php │ ├── 000010.php │ ├── 000011.php │ ├── 000012.php │ ├── 000013.php │ ├── 000014.php │ ├── 000015.php │ ├── 000016.php │ ├── 000017.php │ ├── 000018.php │ ├── 000019.php │ ├── 000020.php │ ├── 000021.php │ ├── 000022.php │ ├── 000023.php │ ├── 000024.php │ ├── 000025.php │ ├── 000026.php │ ├── 000027.php │ ├── 000028.php │ ├── 000029.php │ ├── 000030.php │ ├── 000031.php │ ├── 000032.php │ ├── 000033.php │ ├── 000034.php │ ├── 000035.php │ ├── 000036.php │ ├── 000037.php │ ├── 000038.php │ ├── 000039.php │ ├── 000040.php │ ├── 000041.php │ ├── 000042.php │ ├── 000043.php │ └── 000044.php ├── nginx.conf ├── nginx.default.conf ├── passwordreset.php ├── profile.php ├── registration.php ├── robots.txt ├── scripts/ │ ├── admin.js │ ├── all.js │ ├── calendar.js │ ├── common.js │ ├── dashboard.js │ ├── i18n/ │ │ ├── ca.js │ │ ├── cs.js │ │ ├── da.js │ │ ├── de.js │ │ ├── el.js │ │ ├── en.js │ │ ├── es.js │ │ ├── fr.js │ │ ├── getlang.js │ │ ├── id.js │ │ ├── it.js │ │ ├── jp.js │ │ ├── ko.js │ │ ├── nl.js │ │ ├── pl.js │ │ ├── pt.js │ │ ├── pt_br.js │ │ ├── ro.js │ │ ├── ru.js │ │ ├── sl.js │ │ ├── sr.js │ │ ├── sr_lat.js │ │ ├── tr.js │ │ ├── uk.js │ │ ├── vi.js │ │ ├── zh_cn.js │ │ └── zh_tw.js │ ├── libs/ │ │ └── chart.js │ ├── login.js │ ├── notifications.js │ ├── profile.js │ ├── registration.js │ ├── settings.js │ ├── stats.js │ ├── subscriptions.js │ └── theme.js ├── service-worker.js ├── settings.php ├── startup.sh ├── stats.php ├── styles/ │ ├── barlow.css │ ├── brands.css │ ├── dark-theme.css │ ├── login-dark-theme.css │ ├── login.css │ ├── styles.css │ ├── theme.css │ └── themes/ │ ├── green.css │ ├── purple.css │ ├── red.css │ └── yellow.css ├── subscriptions.php ├── totp.php └── verifyemail.php
SYMBOL INDEX (1507 symbols across 74 files)
FILE: api/subscriptions/get_ical_feed.php
function getPriceConverted (line 29) | function getPriceConverted($price, $currency, $database)
FILE: api/subscriptions/get_subscriptions.php
function getPriceConverted (line 111) | function getPriceConverted($price, $currency, $database)
FILE: calendar.php
function getPriceConverted (line 4) | function getPriceConverted($price, $currency, $database, $userId)
FILE: endpoints/admin/adduser.php
function validate (line 96) | function validate($value)
FILE: endpoints/ai/generate_recommendations.php
function getPricePerMonth (line 7) | function getPricePerMonth($cycle, $frequency, $price)
function describeFrequency (line 23) | function describeFrequency($cycle, $frequency)
function describeCurrency (line 40) | function describeCurrency($currencyId, $currencies)
FILE: endpoints/categories/category.php
function handleAddCategory (line 26) | function handleAddCategory($db, $userId, $i18n)
function handleEditCategory (line 64) | function handleEditCategory($db, $userId, $i18n)
function handleDeleteCategory (line 98) | function handleDeleteCategory($db, $userId, $i18n)
function handleSortCategories (line 145) | function handleSortCategories($db, $userId, $i18n)
FILE: endpoints/cronjobs/sendnotifications.php
function getDaysText (line 30) | function getDaysText($days)
function formatPrice (line 41) | function formatPrice($price, $currencyCode, $currencySymbol)
FILE: endpoints/cronjobs/storetotalyearlycost.php
function getPricePerMonth (line 15) | function getPricePerMonth($cycle, $frequency, $price)
function getPriceConverted (line 33) | function getPriceConverted($price, $currency, $database, $userId)
FILE: endpoints/currency/currency.php
function handleAddCurrency (line 24) | function handleAddCurrency($db, $userId, $i18n)
function handleEditCurrency (line 47) | function handleEditCurrency($db, $userId, $i18n)
function handleDeleteCurrency (line 85) | function handleDeleteCurrency($db, $userId, $i18n)
FILE: endpoints/db/backup.php
function addFolderToZip (line 5) | function addFolderToZip($dir, $zipArchive, $zipdir = '')
FILE: endpoints/db/import.php
function emptyRestoreFolder (line 13) | function emptyRestoreFolder() {
FILE: endpoints/db/migrate.php
function errorHandler (line 2) | function errorHandler($severity, $message, $file, $line)
FILE: endpoints/db/restore.php
function emptyRestoreFolder (line 5) | function emptyRestoreFolder()
FILE: endpoints/household/household.php
function handleAddMember (line 23) | function handleAddMember($db, $userId, $i18n)
function handleEditMember (line 48) | function handleEditMember($db, $userId, $i18n)
function handleDeleteMember (line 85) | function handleDeleteMember($db, $userId, $i18n)
FILE: endpoints/logos/search.php
function applyProxy (line 5) | function applyProxy($ch) {
function curlGet (line 18) | function curlGet($url, $headers = []) {
function getVqdToken (line 40) | function getVqdToken($query) {
function fetchDDGImages (line 48) | function fetchDDGImages($query, $vqd) {
function fetchBraveImages (line 71) | function fetchBraveImages($query) {
FILE: endpoints/notifications/testserverchannotifications.php
function sc_send (line 19) | function sc_send($text, $desp = '', $key = '') {
FILE: endpoints/payments/add.php
function sanitizeFilename (line 13) | function sanitizeFilename($filename)
function validateFileExtension (line 21) | function validateFileExtension($fileExtension)
function getLogoFromUrl (line 27) | function getLogoFromUrl($url, $uploadDir, $name, $i18n, $settings)
function saveLogo (line 78) | function saveLogo($imageData, $uploadFile, $name, $settings)
function resizeAndUploadLogo (line 120) | function resizeAndUploadLogo($uploadedFile, $uploadDir, $name)
FILE: endpoints/payments/rename.php
function validate (line 6) | function validate($value)
FILE: endpoints/payments/search.php
function applyProxy (line 4) | function applyProxy($ch) {
function curlGet (line 16) | function curlGet($url, $headers = []) {
function getVqdToken (line 44) | function getVqdToken($query) {
function fetchDDGImages (line 52) | function fetchDDGImages($query, $vqd) {
function fetchBraveImages (line 75) | function fetchBraveImages($query) {
FILE: endpoints/subscription/add.php
function sanitizeFilename (line 13) | function sanitizeFilename($filename)
function validateFileExtension (line 21) | function validateFileExtension($fileExtension)
function getLogoFromUrl (line 27) | function getLogoFromUrl($url, $uploadDir, $name, $settings, $i18n)
function saveLogo (line 92) | function saveLogo($imageData, $uploadFile, $name, $settings)
function resizeAndUploadLogo (line 139) | function resizeAndUploadLogo($uploadedFile, $uploadDir, $name, $settings)
FILE: endpoints/user/disable_totp.php
function trigger_deprecation (line 8) | function trigger_deprecation($package, $version, $message, ...$args)
FILE: endpoints/user/enable_totp.php
function trigger_deprecation (line 8) | function trigger_deprecation($package, $version, $message, ...$args)
function base32_encode (line 23) | function base32_encode($hex)
FILE: endpoints/user/save_user.php
function update_exchange_rate (line 11) | function update_exchange_rate($db, $userId)
function sanitizeFilename (line 108) | function sanitizeFilename($filename)
function validateFileExtension (line 116) | function validateFileExtension($fileExtension)
function resizeAndUploadAvatar (line 122) | function resizeAndUploadAvatar($uploadedFile, $uploadDir, $name)
FILE: includes/currency_formatter.php
class CurrencyFormatter (line 3) | final class CurrencyFormatter
method getInstance (line 7) | private static function getInstance()
method format (line 20) | public static function format($amount, $currency)
FILE: includes/header.php
function hex2rgb (line 63) | function hex2rgb($hex)
FILE: includes/i18n/getlang.php
function translate (line 12) | function translate($text, $translations)
FILE: includes/inputvalidation.php
function validate (line 3) | function validate($value)
FILE: includes/list_subscriptions.php
function getBillingCycle (line 5) | function getBillingCycle($cycle, $frequency, $i18n)
function getSubscriptionProgress (line 19) | function getSubscriptionProgress($cycle, $frequency, $next_payment)
function getPricePerMonth (line 49) | function getPricePerMonth($cycle, $frequency, $price)
function getPriceConverted (line 68) | function getPriceConverted($price, $currency, $database)
function formatPrice (line 84) | function formatPrice($price, $currencyCode, $currencies)
function formatDate (line 105) | function formatDate($date, $lang = 'en')
function printSubscriptions (line 134) | function printSubscriptions($subscriptions, $sort, $categories, $members...
FILE: includes/oidc/handle_oidc_callback.php
function generate_username_from_email (line 3) | function generate_username_from_email($email)
FILE: includes/ssrf_helper.php
function is_cgnat_ip (line 8) | function is_cgnat_ip($ip) {
function validate_webhook_url_for_ssrf (line 23) | function validate_webhook_url_for_ssrf($url, $db, $i18n) {
function is_url_safe_for_ssrf (line 92) | function is_url_safe_for_ssrf($url, $db) {
FILE: includes/stats_calculations.php
function getPricePerMonth (line 3) | function getPricePerMonth($cycle, $frequency, $price)
function getPriceConverted (line 21) | function getPriceConverted($price, $currency, $database, $userId)
FILE: index.php
function formatPrice (line 6) | function formatPrice($price, $currencyCode, $currencies)
function formatDate (line 27) | function formatDate($date, $lang = 'en')
FILE: libs/OTPHP/Factory.php
class Factory (line 18) | final class Factory implements FactoryInterface
method loadFromProvisioningUri (line 20) | public static function loadFromProvisioningUri(string $uri, ?ClockInte...
method populateParameters (line 44) | private static function populateParameters(OTPInterface $otp, Url $dat...
method populateOTP (line 51) | private static function populateOTP(OTPInterface $otp, Url $data): void
method createOTP (line 74) | private static function createOTP(Url $parsed_url, ClockInterface $clo...
method getLabel (line 96) | private static function getLabel(string $data): string
FILE: libs/OTPHP/FactoryInterface.php
type FactoryInterface (line 7) | interface FactoryInterface
method loadFromProvisioningUri (line 15) | public static function loadFromProvisioningUri(string $uri): OTPInterf...
FILE: libs/OTPHP/HOTP.php
class HOTP (line 13) | final class HOTP extends OTP implements HOTPInterface
method create (line 17) | public static function create(
method createFromSecret (line 34) | public static function createFromSecret(string $secret): self
method generate (line 44) | public static function generate(): self
method getCounter (line 52) | public function getCounter(): int
method getProvisioningUri (line 60) | public function getProvisioningUri(): string
method verify (line 72) | public function verify(string $otp, null|int $counter = null, null|int...
method setCounter (line 85) | public function setCounter(int $counter): void
method getParameterMap (line 93) | protected function getParameterMap(): array
method updateCounter (line 105) | private function updateCounter(int $counter): void
method getWindow (line 113) | private function getWindow(null|int $window): int
method verifyOtpWithWindow (line 123) | private function verifyOtpWithWindow(string $otp, int $counter, null|i...
FILE: libs/OTPHP/HOTPInterface.php
type HOTPInterface (line 7) | interface HOTPInterface extends OTPInterface
method getCounter (line 14) | public function getCounter(): int;
method create (line 28) | public static function create(
method setCounter (line 35) | public function setCounter(int $counter): void;
FILE: libs/OTPHP/InternalClock.php
class InternalClock (line 13) | final class InternalClock implements ClockInterface
method now (line 15) | public function now(): DateTimeImmutable
FILE: libs/OTPHP/OTP.php
class OTP (line 17) | abstract class OTP implements OTPInterface
method __construct (line 26) | protected function __construct(string $secret)
method getQrCodeUri (line 31) | public function getQrCodeUri(string $uri, string $placeholder): string
method at (line 41) | public function at(int $input): string
method generateSecret (line 49) | final protected static function generateSecret(): string
method generateOTP (line 61) | protected function generateOTP(int $input): string
method filterOptions (line 78) | protected function filterOptions(array &$options): void
method generateURI (line 99) | protected function generateURI(string $type, array $options): string
method compareOTP (line 120) | protected function compareOTP(string $safe, string $user): bool
method getDecodedSecret (line 128) | private function getDecodedSecret(): string
method intToByteString (line 140) | private function intToByteString(int $int): string
FILE: libs/OTPHP/OTPInterface.php
type OTPInterface (line 7) | interface OTPInterface
method createFromSecret (line 18) | public static function createFromSecret(string $secret): self;
method generate (line 23) | public static function generate(): self;
method setSecret (line 28) | public function setSecret(string $secret): void;
method setDigits (line 30) | public function setDigits(int $digits): void;
method setDigest (line 35) | public function setDigest(string $digest): void;
method at (line 44) | public function at(int $input): string;
method verify (line 54) | public function verify(string $otp, null|int $input = null, null|int $...
method getSecret (line 59) | public function getSecret(): string;
method setLabel (line 64) | public function setLabel(string $label): void;
method getLabel (line 69) | public function getLabel(): null|string;
method getIssuer (line 74) | public function getIssuer(): ?string;
method setIssuer (line 79) | public function setIssuer(string $issuer): void;
method isIssuerIncludedAsParameter (line 84) | public function isIssuerIncludedAsParameter(): bool;
method setIssuerIncludedAsParameter (line 86) | public function setIssuerIncludedAsParameter(bool $issuer_included_as_...
method getDigits (line 91) | public function getDigits(): int;
method getDigest (line 96) | public function getDigest(): string;
method getParameter (line 101) | public function getParameter(string $parameter): mixed;
method hasParameter (line 106) | public function hasParameter(string $parameter): bool;
method getParameters (line 111) | public function getParameters(): array;
method setParameter (line 116) | public function setParameter(string $parameter, mixed $value): void;
method getProvisioningUri (line 123) | public function getProvisioningUri(): string;
method getQrCodeUri (line 131) | public function getQrCodeUri(string $uri, string $placeholder): string;
FILE: libs/OTPHP/ParameterTrait.php
type ParameterTrait (line 14) | trait ParameterTrait
method getParameters (line 36) | public function getParameters(): array
method getSecret (line 47) | public function getSecret(): string
method getLabel (line 55) | public function getLabel(): null|string
method setLabel (line 60) | public function setLabel(string $label): void
method getIssuer (line 65) | public function getIssuer(): null|string
method setIssuer (line 70) | public function setIssuer(string $issuer): void
method isIssuerIncludedAsParameter (line 75) | public function isIssuerIncludedAsParameter(): bool
method setIssuerIncludedAsParameter (line 80) | public function setIssuerIncludedAsParameter(bool $issuer_included_as_...
method getDigits (line 85) | public function getDigits(): int
method getDigest (line 93) | public function getDigest(): string
method hasParameter (line 101) | public function hasParameter(string $parameter): bool
method getParameter (line 106) | public function getParameter(string $parameter): mixed
method setParameter (line 115) | public function setParameter(string $parameter, mixed $value): void
method setSecret (line 131) | public function setSecret(string $secret): void
method setDigits (line 136) | public function setDigits(int $digits): void
method setDigest (line 141) | public function setDigest(string $digest): void
method getParameterMap (line 149) | protected function getParameterMap(): array
method hasColon (line 189) | private function hasColon(string $value): bool
FILE: libs/OTPHP/TOTP.php
class TOTP (line 15) | final class TOTP extends OTP implements TOTPInterface
method __construct (line 19) | public function __construct(string $secret, ?ClockInterface $clock = n...
method create (line 34) | public static function create(
method createFromSecret (line 54) | public static function createFromSecret(string $secret, ?ClockInterfac...
method generate (line 65) | public static function generate(?ClockInterface $clock = null): self
method getPeriod (line 70) | public function getPeriod(): int
method getEpoch (line 78) | public function getEpoch(): int
method expiresIn (line 86) | public function expiresIn(): int
method at (line 98) | public function at(int $input): string
method now (line 103) | public function now(): string
method verify (line 119) | public function verify(string $otp, null|int $timestamp = null, null|i...
method getProvisioningUri (line 143) | public function getProvisioningUri(): string
method setPeriod (line 157) | public function setPeriod(int $period): void
method setEpoch (line 162) | public function setEpoch(int $epoch): void
method getParameterMap (line 170) | protected function getParameterMap(): array
method filterOptions (line 192) | protected function filterOptions(array &$options): void
method timecode (line 208) | private function timecode(int $timestamp): int
FILE: libs/OTPHP/TOTPInterface.php
type TOTPInterface (line 7) | interface TOTPInterface extends OTPInterface
method create (line 25) | public static function create(
method setPeriod (line 32) | public function setPeriod(int $period): void;
method setEpoch (line 34) | public function setEpoch(int $epoch): void;
method now (line 41) | public function now(): string;
method getPeriod (line 46) | public function getPeriod(): int;
method expiresIn (line 48) | public function expiresIn(): int;
method getEpoch (line 50) | public function getEpoch(): int;
FILE: libs/OTPHP/Url.php
class Url (line 14) | final class Url
method __construct (line 23) | public function __construct(
method getScheme (line 35) | public function getScheme(): string
method getHost (line 43) | public function getHost(): string
method getPath (line 51) | public function getPath(): string
method getSecret (line 59) | public function getSecret(): string
method getQuery (line 67) | public function getQuery(): array
method fromString (line 75) | public static function fromString(string $uri): self
FILE: libs/PHPMailer/Exception.php
class Exception (line 29) | class Exception extends \Exception
method errorMessage (line 36) | public function errorMessage()
FILE: libs/PHPMailer/PHPMailer.php
class PHPMailer (line 32) | class PHPMailer
method __construct (line 826) | public function __construct($exceptions = null)
method __destruct (line 838) | public function __destruct()
method mailPassthru (line 858) | private function mailPassthru($to, $subject, $body, $header, $params)
method edebug (line 892) | protected function edebug($str)
method isHTML (line 947) | public function isHTML($isHtml = true)
method isSMTP (line 959) | public function isSMTP()
method isMail (line 967) | public function isMail()
method isSendmail (line 975) | public function isSendmail()
method isQmail (line 990) | public function isQmail()
method addAddress (line 1012) | public function addAddress($address, $name = '')
method addCC (line 1027) | public function addCC($address, $name = '')
method addBCC (line 1042) | public function addBCC($address, $name = '')
method addReplyTo (line 1057) | public function addReplyTo($address, $name = '')
method addOrEnqueueAnAddress (line 1076) | protected function addOrEnqueueAnAddress($kind, $address, $name)
method setBoundaries (line 1135) | public function setBoundaries()
method addAnAddress (line 1155) | protected function addAnAddress($kind, $address, $name = '')
method parseAddresses (line 1216) | public static function parseAddresses($addrstr, $useimap = true, $char...
method setFrom (line 1305) | public function setFrom($address, $name = '', $auto = true)
method getLastMessageID (line 1346) | public function getLastMessageID()
method validateAddress (line 1375) | public static function validateAddress($address, $patternselect = null)
method idnSupported (line 1442) | public static function idnSupported()
method punyencodeAddress (line 1461) | public function punyencodeAddress($address)
method send (line 1509) | public function send()
method preSend (line 1535) | public function preSend()
method postSend (line 1673) | public function postSend()
method sendmailSend (line 1719) | protected function sendmailSend($header, $body)
method isShellSafe (line 1827) | protected static function isShellSafe($string)
method isPermittedPath (line 1868) | protected static function isPermittedPath($path)
method fileIsAccessible (line 1881) | protected static function fileIsAccessible($path)
method mailSend (line 1906) | protected function mailSend($header, $body)
method getSMTPInstance (line 1983) | public function getSMTPInstance()
method setSMTPInstance (line 1997) | public function setSMTPInstance(SMTP $smtp)
method smtpSend (line 2019) | protected function smtpSend($header, $body)
method smtpConnect (line 2104) | public function smtpConnect($options = null)
method smtpClose (line 2253) | public function smtpClose()
method setLanguage (line 2273) | public function setLanguage($langcode = 'en', $lang_path = '')
method getTranslations (line 2397) | public function getTranslations()
method addrAppend (line 2417) | public function addrAppend($type, $addr)
method addrFormat (line 2435) | public function addrFormat($addr)
method wrapText (line 2457) | public function wrapText($message, $length, $qp_mode = false)
method utf8CharBoundary (line 2557) | public function utf8CharBoundary($encodedText, $maxLength)
method setWordWrap (line 2601) | public function setWordWrap()
method createHeader (line 2625) | public function createHeader()
method getMailMIME (line 2725) | public function getMailMIME()
method getSentMIMEMessage (line 2777) | public function getSentMIMEMessage()
method generateId (line 2788) | protected function generateId()
method createBody (line 2820) | public function createBody()
method getBoundaries (line 3100) | public function getBoundaries()
method getBoundary (line 3118) | protected function getBoundary($boundary, $charSet, $contentType, $enc...
method endBoundary (line 3149) | protected function endBoundary($boundary)
method setMessageType (line 3158) | protected function setMessageType()
method headerLine (line 3185) | public function headerLine($name, $value)
method textLine (line 3197) | public function textLine($value)
method addAttachment (line 3219) | public function addAttachment(
method getAttachments (line 3272) | public function getAttachments()
method attachAll (line 3288) | protected function attachAll($disposition_type, $boundary)
method encodeFile (line 3398) | protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
method encodeString (line 3433) | public function encodeString($str, $encoding = self::ENCODING_BASE64)
method encodeHeader (line 3479) | public function encodeHeader($str, $position = 'text')
method hasMultiBytes (line 3568) | public function hasMultiBytes($str)
method has8bitChars (line 3585) | public function has8bitChars($text)
method base64EncodeWrapMB (line 3602) | public function base64EncodeWrapMB($str, $linebreak = null)
method encodeQP (line 3643) | public function encodeQP($string)
method encodeQ (line 3658) | public function encodeQ($str, $position = 'text')
method addStringAttachment (line 3716) | public function addStringAttachment(
method addEmbeddedImage (line 3779) | public function addEmbeddedImage(
method addStringEmbeddedImage (line 3849) | public function addStringEmbeddedImage(
method validateEncoding (line 3898) | protected function validateEncoding($encoding)
method cidExists (line 3920) | protected function cidExists($cid)
method inlineImageExists (line 3936) | public function inlineImageExists()
method attachmentExists (line 3952) | public function attachmentExists()
method alternativeExists (line 3968) | public function alternativeExists()
method clearQueuedAddresses (line 3978) | public function clearQueuedAddresses($kind)
method clearAddresses (line 3991) | public function clearAddresses()
method clearCCs (line 4003) | public function clearCCs()
method clearBCCs (line 4015) | public function clearBCCs()
method clearReplyTos (line 4027) | public function clearReplyTos()
method clearAllRecipients (line 4036) | public function clearAllRecipients()
method clearAttachments (line 4048) | public function clearAttachments()
method clearCustomHeaders (line 4056) | public function clearCustomHeaders()
method setError (line 4066) | protected function setError($msg)
method rfcDate (line 4092) | public static function rfcDate()
method serverHostname (line 4107) | protected function serverHostname()
method isValidHost (line 4134) | public static function isValidHost($host)
method lang (line 4166) | protected function lang($key)
method getSmtpErrorMessage (line 4193) | private function getSmtpErrorMessage($base_key)
method isError (line 4212) | public function isError()
method addCustomHeader (line 4228) | public function addCustomHeader($name, $value = null)
method getCustomHeaders (line 4254) | public function getCustomHeaders()
method msgHTML (line 4280) | public function msgHTML($message, $basedir = '', $advanced = false)
method html2text (line 4398) | public function html2text($html, $advanced = false)
method _mime_types (line 4418) | public static function _mime_types($ext = '')
method filenameToType (line 4551) | public static function filenameToType($filename)
method mb_pathinfo (line 4575) | public static function mb_pathinfo($path, $options = null)
method set (line 4625) | public function set($name, $value = '')
method secureHeader (line 4644) | public function secureHeader($str)
method normalizeBreaks (line 4659) | public static function normalizeBreaks($text, $breaktype = null)
method stripTrailingWSP (line 4681) | public static function stripTrailingWSP($text)
method stripTrailingBreaks (line 4693) | public static function stripTrailingBreaks($text)
method getLE (line 4703) | public static function getLE()
method setLE (line 4713) | protected static function setLE($le)
method sign (line 4726) | public function sign($cert_filename, $key_filename, $key_pass, $extrac...
method DKIM_QP (line 4741) | public function DKIM_QP($txt)
method DKIM_Sign (line 4766) | public function DKIM_Sign($signHeader)
method DKIM_HeaderC (line 4808) | public function DKIM_HeaderC($signHeader)
method DKIM_BodyC (line 4852) | public function DKIM_BodyC($body)
method DKIM_Add (line 4875) | public function DKIM_Add($headers_line, $subject, $body)
method hasLineLongerThanMax (line 5019) | public static function hasLineLongerThanMax($str)
method quotedString (line 5034) | public static function quotedString($str)
method getToAddresses (line 5052) | public function getToAddresses()
method getCcAddresses (line 5063) | public function getCcAddresses()
method getBccAddresses (line 5074) | public function getBccAddresses()
method getReplyToAddresses (line 5085) | public function getReplyToAddresses()
method getAllRecipientAddresses (line 5096) | public function getAllRecipientAddresses()
method doCallback (line 5113) | protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body...
method getOAuth (line 5125) | public function getOAuth()
method setOAuth (line 5133) | public function setOAuth(OAuthTokenProvider $oauth)
FILE: libs/PHPMailer/SMTP.php
class SMTP (line 31) | class SMTP
method edebug (line 264) | protected function edebug($str, $level = 0)
method connect (line 323) | public function connect($host, $port = null, $timeout = 30, $options =...
method getSMTPConnection (line 382) | protected function getSMTPConnection($host, $port = null, $timeout = 3...
method startTLS (line 457) | public function startTLS()
method authenticate (line 498) | public function authenticate(
method hmac (line 632) | protected function hmac($data, $key)
method connected (line 664) | public function connected()
method close (line 691) | public function close()
method data (line 716) | public function data($msg_data)
method hello (line 809) | public function hello($host = '')
method sendHello (line 835) | protected function sendHello($hello, $host)
method parseHelloFields (line 854) | protected function parseHelloFields($type)
method mail (line 902) | public function mail($from)
method quit (line 922) | public function quit($close_on_error = true)
method recipient (line 946) | public function recipient($address, $dsn = '')
method reset (line 981) | public function reset()
method sendCommand (line 995) | protected function sendCommand($command, $commandstring, $expect)
method sendAndMail (line 1069) | public function sendAndMail($from)
method verify (line 1081) | public function verify($name)
method noop (line 1092) | public function noop()
method turn (line 1106) | public function turn()
method client_send (line 1122) | public function client_send($data, $command = '')
method getError (line 1146) | public function getError()
method getServerExtList (line 1156) | public function getServerExtList()
method getServerExt (line 1178) | public function getServerExt($name)
method getLastReply (line 1206) | public function getLastReply()
method get_lines (line 1220) | protected function get_lines()
method setVerp (line 1309) | public function setVerp($enabled = false)
method getVerp (line 1319) | public function getVerp()
method setError (line 1332) | protected function setError($message, $detail = '', $smtp_code = '', $...
method setDebugOutput (line 1347) | public function setDebugOutput($method = 'echo')
method getDebugOutput (line 1357) | public function getDebugOutput()
method setDebugLevel (line 1367) | public function setDebugLevel($level = 0)
method getDebugLevel (line 1377) | public function getDebugLevel()
method setTimeout (line 1387) | public function setTimeout($timeout = 0)
method getTimeout (line 1397) | public function getTimeout()
method errorHandler (line 1410) | protected function errorHandler($errno, $errmsg, $errfile = '', $errli...
method recordLastTransactionID (line 1433) | protected function recordLastTransactionID()
method getLastTransactionID (line 1462) | public function getLastTransactionID()
FILE: libs/Psr/Clock/ClockInterface.php
type ClockInterface (line 7) | interface ClockInterface
method now (line 12) | public function now(): DateTimeImmutable;
FILE: libs/constant_time_encoding/Base32.php
class Base32 (line 38) | abstract class Base32 implements EncoderInterface
method decode (line 47) | public static function decode(
method decodeUpper (line 62) | public static function decodeUpper(
method encode (line 77) | public static function encode(
method encodeUnpadded (line 91) | public static function encodeUnpadded(
method encodeUpper (line 105) | public static function encodeUpper(
method encodeUpperUnpadded (line 119) | public static function encodeUpperUnpadded(
method decode5Bits (line 133) | protected static function decode5Bits(int $src): int
method decode5BitsUpper (line 155) | protected static function decode5BitsUpper(int $src): int
method encode5Bits (line 175) | protected static function encode5Bits(int $src): string
method encode5BitsUpper (line 194) | protected static function encode5BitsUpper(int $src): string
method decodeNoPadding (line 209) | public static function decodeNoPadding(
method doDecode (line 244) | protected static function doDecode(
method doEncode (line 455) | protected static function doEncode(
FILE: libs/constant_time_encoding/Base32Hex.php
class Base32Hex (line 34) | abstract class Base32Hex extends Base32
method decode5Bits (line 43) | protected static function decode5Bits(int $src): int
method decode5BitsUpper (line 63) | protected static function decode5BitsUpper(int $src): int
method encode5Bits (line 83) | protected static function encode5Bits(int $src): string
method encode5BitsUpper (line 102) | protected static function encode5BitsUpper(int $src): string
FILE: libs/constant_time_encoding/Base64.php
class Base64 (line 38) | abstract class Base64 implements EncoderInterface
method encode (line 50) | public static function encode(
method encodeUnpadded (line 67) | public static function encodeUnpadded(
method doEncode (line 81) | protected static function doEncode(
method decode (line 140) | public static function decode(
method decodeNoPadding (line 239) | public static function decodeNoPadding(
method decode6Bits (line 272) | protected static function decode6Bits(int $src): int
method encode6Bits (line 301) | protected static function encode6Bits(int $src): string
FILE: libs/constant_time_encoding/Base64DotSlash.php
class Base64DotSlash (line 34) | abstract class Base64DotSlash extends Base64
method decode6Bits (line 47) | protected static function decode6Bits(int $src): int
method encode6Bits (line 73) | protected static function encode6Bits(int $src): string
FILE: libs/constant_time_encoding/Base64DotSlashOrdered.php
class Base64DotSlashOrdered (line 34) | abstract class Base64DotSlashOrdered extends Base64
method decode6Bits (line 47) | protected static function decode6Bits(int $src): int
method encode6Bits (line 70) | protected static function encode6Bits(int $src): string
FILE: libs/constant_time_encoding/Base64UrlSafe.php
class Base64UrlSafe (line 34) | abstract class Base64UrlSafe extends Base64
method decode6Bits (line 48) | protected static function decode6Bits(int $src): int
method encode6Bits (line 77) | protected static function encode6Bits(int $src): string
FILE: libs/constant_time_encoding/Binary.php
class Binary (line 38) | abstract class Binary
method safeStrlen (line 48) | public static function safeStrlen(
method safeSubstr (line 74) | public static function safeSubstr(
FILE: libs/constant_time_encoding/EncoderInterface.php
type EncoderInterface (line 32) | interface EncoderInterface
method encode (line 41) | public static function encode(string $binString): string;
method decode (line 51) | public static function decode(string $encodedString, bool $strictPaddi...
FILE: libs/constant_time_encoding/Encoding.php
class Encoding (line 34) | abstract class Encoding
method base32Encode (line 43) | public static function base32Encode(
method base32EncodeUpper (line 57) | public static function base32EncodeUpper(
method base32Decode (line 71) | public static function base32Decode(
method base32DecodeUpper (line 85) | public static function base32DecodeUpper(
method base32HexEncode (line 99) | public static function base32HexEncode(
method base32HexEncodeUpper (line 113) | public static function base32HexEncodeUpper(
method base32HexDecode (line 127) | public static function base32HexDecode(
method base32HexDecodeUpper (line 141) | public static function base32HexDecodeUpper(
method base64Encode (line 155) | public static function base64Encode(
method base64Decode (line 169) | public static function base64Decode(
method base64EncodeDotSlash (line 184) | public static function base64EncodeDotSlash(
method base64DecodeDotSlash (line 201) | public static function base64DecodeDotSlash(
method base64EncodeDotSlashOrdered (line 216) | public static function base64EncodeDotSlashOrdered(
method base64DecodeDotSlashOrdered (line 233) | public static function base64DecodeDotSlashOrdered(
method hexEncode (line 248) | public static function hexEncode(
method hexDecode (line 263) | public static function hexDecode(
method hexEncodeUpper (line 278) | public static function hexEncodeUpper(
method hexDecodeUpper (line 292) | public static function hexDecodeUpper(
FILE: libs/constant_time_encoding/Hex.php
class Hex (line 35) | abstract class Hex implements EncoderInterface
method encode (line 45) | public static function encode(
method encodeUpper (line 74) | public static function encodeUpper(
method decode (line 105) | public static function decode(
FILE: libs/constant_time_encoding/RFC4648.php
class RFC4648 (line 37) | abstract class RFC4648
method base64Encode (line 49) | public static function base64Encode(
method base64Decode (line 66) | public static function base64Decode(
method base64UrlSafeEncode (line 83) | public static function base64UrlSafeEncode(
method base64UrlSafeDecode (line 100) | public static function base64UrlSafeDecode(
method base32Encode (line 117) | public static function base32Encode(
method base32Decode (line 134) | public static function base32Decode(
method base32HexEncode (line 151) | public static function base32HexEncode(
method base32HexDecode (line 168) | public static function base32HexDecode(
method base16Encode (line 185) | public static function base16Encode(
method base16Decode (line 200) | public static function base16Decode(
FILE: libs/csrf.php
function generate_csrf_token (line 7) | function generate_csrf_token(): string {
function verify_csrf_token (line 14) | function verify_csrf_token(?string $token): bool {
FILE: registration.php
function validate (line 11) | function validate($value)
FILE: scripts/admin.js
function makeFetchCall (line 1) | function makeFetchCall(url, data, button) {
function testSmtpSettingsButton (line 26) | function testSmtpSettingsButton() {
function saveSmtpSettingsButton (line 49) | function saveSmtpSettingsButton() {
function backupDB (line 95) | function backupDB() {
function openRestoreDBFileSelect (line 138) | function openRestoreDBFileSelect() {
function restoreDB (line 142) | function restoreDB() {
function saveAccountRegistrationsButton (line 190) | function saveAccountRegistrationsButton() {
function saveSecuritySettingsButton (line 232) | function saveSecuritySettingsButton() {
function removeUser (line 266) | function removeUser(userId) {
function addUserButton (line 295) | function addUserButton() {
function deleteUnusedLogos (line 334) | function deleteUnusedLogos() {
function toggleUpdateNotification (line 362) | function toggleUpdateNotification() {
function executeCronJob (line 393) | function executeCronJob(job) {
function toggleOidcEnabled (line 411) | function toggleOidcEnabled() {
function saveOidcSettingsButton (line 445) | function saveOidcSettingsButton() {
FILE: scripts/calendar.js
function nextMonth (line 1) | function nextMonth(currentMonth, currentYear) {
function prevMonth (line 11) | function prevMonth(currentMonth, currentYear) {
function currentMoth (line 21) | function currentMoth() {
function closeSubscriptionModal (line 25) | function closeSubscriptionModal() {
function openSubscriptionModal (line 30) | function openSubscriptionModal(subscriptionId) {
function decodeHtmlEntities (line 76) | function decodeHtmlEntities(str) {
function exportCalendar (line 82) | function exportCalendar(subscriptionId) {
function showExportPopup (line 110) | function showExportPopup() {
function closePopup (line 120) | function closePopup() {
function copyToClipboard (line 124) | function copyToClipboard() {
FILE: scripts/common.js
function toggleDropdown (line 3) | function toggleDropdown() {
function showErrorMessage (line 9) | function showErrorMessage(message) {
function showSuccessMessage (line 40) | function showSuccessMessage(message) {
function getCookie (line 106) | function getCookie(name) {
FILE: scripts/dashboard.js
function updateAiRecommendationNumbers (line 2) | function updateAiRecommendationNumbers() {
FILE: scripts/i18n/getlang.js
function translate (line 1) | function translate(key) {
FILE: scripts/libs/chart.js
method Colors (line 13) | get Colors(){return Go}
method Decimation (line 13) | get Decimation(){return Qo}
method Filler (line 13) | get Filler(){return ma}
method Legend (line 13) | get Legend(){return ya}
method SubTitle (line 13) | get SubTitle(){return ka}
method Title (line 13) | get Title(){return Ma}
method Tooltip (line 13) | get Tooltip(){return Ba}
function e (line 13) | function e(){}
function s (line 13) | function s(t){return null==t}
function n (line 13) | function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object...
function o (line 13) | function o(t){return null!==t&&"[object Object]"===Object.prototype.toSt...
function a (line 13) | function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(...
function r (line 13) | function r(t,e){return a(t)?t:e}
function l (line 13) | function l(t,e){return void 0===t?e:t}
function d (line 13) | function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}
function u (line 13) | function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--...
function f (line 13) | function f(t,e){let i,s,n,o;if(!t||!e||t.length!==e.length)return!1;for(...
function g (line 13) | function g(t){if(n(t))return t.map(g);if(o(t)){const e=Object.create(nul...
function p (line 13) | function p(t){return-1===["__proto__","prototype","constructor"].indexOf...
function m (line 13) | function m(t,e,i,s){if(!p(t))return;const n=e[t],a=i[t];o(n)&&o(a)?b(n,a...
function b (line 13) | function b(t,e,i){const s=n(e)?e:[e],a=s.length;if(!o(t))return t;const ...
function x (line 13) | function x(t,e){return b(t,e,{merger:_})}
function _ (line 13) | function _(t,e,i){if(!p(t))return;const s=e[t],n=i[t];o(s)&&o(n)?x(s,n):...
function v (line 13) | function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s...
function M (line 13) | function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{...
function w (line 13) | function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}
function D (line 13) | function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"=...
function V (line 13) | function V(t,e,i){return Math.abs(t-e)<i}
function B (line 13) | function B(t){const e=Math.round(t);t=V(t,e,t/1e3)?e:t;const i=Math.pow(...
function W (line 13) | function W(t){const e=[],i=Math.sqrt(t);let s;for(s=1;s<i;s++)t%s==0&&(e...
function N (line 13) | function N(t){return!isNaN(parseFloat(t))&&isFinite(t)}
function H (line 13) | function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}
function j (line 13) | function j(t,e,i){let s,n,o;for(s=0,n=t.length;s<n;s++)o=t[s][i],isNaN(o...
function $ (line 13) | function $(t){return t*(C/180)}
function Y (line 13) | function Y(t){return t*(180/C)}
function U (line 13) | function U(t){if(!a(t))return;let e=1,i=0;for(;Math.round(t*e)/e!==t;)e*...
function X (line 13) | function X(t,e){const i=e.x-t.x,s=e.y-t.y,n=Math.sqrt(i*i+s*s);let o=Mat...
function q (line 13) | function q(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}
function K (line 13) | function K(t,e){return(t-e+A)%O-C}
function G (line 13) | function G(t){return(t%O+O)%O}
function Z (line 13) | function Z(t,e,i,s){const n=G(t),o=G(e),a=G(i),r=G(o-n),l=G(a-n),h=G(n-o...
function J (line 13) | function J(t,e,i){return Math.max(e,Math.min(i,t))}
function Q (line 13) | function Q(t){return J(t,-32768,32767)}
function tt (line 13) | function tt(t,e,i,s=1e-6){return t>=Math.min(e,i)-s&&t<=Math.max(e,i)+s}
function et (line 13) | function et(t,e,i){i=i||(i=>t[i]<e);let s,n=t.length-1,o=0;for(;n-o>1;)s...
function nt (line 13) | function nt(t,e,i){let s=0,n=t.length;for(;s<n&&t[s]<e;)s++;for(;n>s&&t[...
function at (line 13) | function at(t,e){t._chartjs?t._chartjs.listeners.push(e):(Object.defineP...
function rt (line 13) | function rt(t,e){const i=t._chartjs;if(!i)return;const s=i.listeners,n=s...
function lt (line 13) | function lt(t){const e=new Set(t);return e.size===t.length?t:Array.from(e)}
function ct (line 13) | function ct(t,e){let i=[],s=!1;return function(...n){i=n,s||(s=!0,ht.cal...
function dt (line 13) | function dt(t,e){let i;return function(...s){return e?(clearTimeout(i),i...
function pt (line 13) | function pt(t,e,i){const s=e.length;let n=0,o=s;if(t._sorted){const{iSca...
function mt (line 13) | function mt(t){const{xScale:e,yScale:i,_scaleRanges:s}=t,n={xmin:e.min,x...
class bt (line 13) | class bt{constructor(){this._request=null,this._charts=new Map,this._run...
method constructor (line 13) | constructor(){this._request=null,this._charts=new Map,this._running=!1...
method _notify (line 13) | _notify(t,e,i,s){const n=e.listeners[s],o=e.duration;n.forEach((s=>s({...
method _refresh (line 13) | _refresh(){this._request||(this._running=!0,this._request=ht.call(wind...
method _update (line 13) | _update(t=Date.now()){let e=0;this._charts.forEach(((i,s)=>{if(!i.runn...
method _getAnims (line 13) | _getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running...
method listen (line 13) | listen(t,e,i){this._getAnims(t).listeners[e].push(i)}
method add (line 13) | add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}
method has (line 13) | has(t){return this._getAnims(t).items.length>0}
method start (line 13) | start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now...
method running (line 13) | running(t){if(!this._running)return!1;const e=this._charts.get(t);retu...
method stop (line 13) | stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;cons...
method remove (line 13) | remove(t){return this._charts.delete(t)}
function _t (line 19) | function _t(t){return t+.5|0}
function vt (line 19) | function vt(t){return yt(_t(2.55*t),0,255)}
function Mt (line 19) | function Mt(t){return yt(_t(255*t),0,255)}
function wt (line 19) | function wt(t){return yt(_t(t/2.55)/100,0,1)}
function kt (line 19) | function kt(t){return yt(_t(100*t),0,100)}
function At (line 19) | function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;re...
function Lt (line 19) | function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Ma...
function Et (line 19) | function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4...
function Rt (line 19) | function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i...
function It (line 19) | function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=M...
function zt (line 19) | function zt(t,e,i,s){return(Array.isArray(e)?t(e[0],e[1],e[2]):t(e,i,s))...
function Ft (line 19) | function Ft(t,e,i){return zt(Lt,t,e,i)}
function Vt (line 19) | function Vt(t){return(t%360+360)%360}
function Bt (line 19) | function Bt(t){const e=Tt.exec(t);let i,s=255;if(!e)return;e[5]!==i&&(s=...
function jt (line 19) | function jt(t){Ht||(Ht=function(){const t={},e=Object.keys(Nt),i=Object....
function Xt (line 19) | function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*...
function qt (line 19) | function qt(t,e){return t?Object.assign(e||{},t):t}
function Kt (line 19) | function Kt(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.lengt...
function Gt (line 19) | function Gt(t){return"r"===t.charAt(0)?function(t){const e=$t.exec(t);le...
class Zt (line 19) | class Zt{constructor(t){if(t instanceof Zt)return t;const e=typeof t;let...
method constructor (line 19) | constructor(t){if(t instanceof Zt)return t;const e=typeof t;let i;var ...
method valid (line 19) | get valid(){return this._valid}
method rgb (line 19) | get rgb(){var t=qt(this._rgb);return t&&(t.a=wt(t.a)),t}
method rgb (line 19) | set rgb(t){this._rgb=Kt(t)}
method rgbString (line 19) | rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, $...
method hexString (line 19) | hexString(){return this._valid?At(this._rgb):void 0}
method hslString (line 19) | hslString(){return this._valid?function(t){if(!t)return;const e=It(t),...
method mix (line 19) | mix(t,e){if(t){const i=this.rgb,s=t.rgb;let n;const o=e===n?.5:e,a=2*o...
method interpolate (line 19) | interpolate(t,e){return t&&(this._rgb=function(t,e,i){const s=Ut(wt(t....
method clone (line 19) | clone(){return new Zt(this.rgb)}
method alpha (line 19) | alpha(t){return this._rgb.a=Mt(t),this}
method clearer (line 19) | clearer(t){return this._rgb.a*=1-t,this}
method greyscale (line 19) | greyscale(){const t=this._rgb,e=_t(.3*t.r+.59*t.g+.11*t.b);return t.r=...
method opaquer (line 19) | opaquer(t){return this._rgb.a*=1+t,this}
method negate (line 19) | negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,...
method lighten (line 19) | lighten(t){return Xt(this._rgb,2,t),this}
method darken (line 19) | darken(t){return Xt(this._rgb,2,-t),this}
method saturate (line 19) | saturate(t){return Xt(this._rgb,1,t),this}
method desaturate (line 19) | desaturate(t){return Xt(this._rgb,1,-t),this}
method rotate (line 19) | rotate(t){return function(t,e){var i=It(t);i[0]=Vt(i[0]+e),i=Ft(i),t.r...
function Jt (line 19) | function Jt(t){if(t&&"object"==typeof t){const e=t.toString();return"[ob...
function Qt (line 19) | function Qt(t){return Jt(t)?t:new Zt(t)}
function te (line 19) | function te(t){return Jt(t)?t:new Zt(t).saturate(.5).darken(.1).hexStrin...
function ne (line 19) | function ne(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify...
method numeric (line 19) | numeric(t,e,i){if(0===t)return"0";const s=this.chart.options.locale;let ...
method logarithmic (line 19) | logarithmic(t,e,i){if(0===t)return"0";const s=i[e].significand||t/Math.p...
function he (line 19) | function he(t,e){if(!e)return t;const i=e.split(".");for(let e=0,s=i.len...
function ce (line 19) | function ce(t,e,i){return"string"==typeof e?b(he(t,e),i):b(he(t,""),e)}
class de (line 19) | class de{constructor(t,e){this.animation=void 0,this.backgroundColor="rg...
method constructor (line 19) | constructor(t,e){this.animation=void 0,this.backgroundColor="rgba(0,0,...
method set (line 19) | set(t,e){return ce(this,t,e)}
method get (line 19) | get(t){return he(this,t)}
method describe (line 19) | describe(t,e){return ce(le,t,e)}
method override (line 19) | override(t,e){return ce(re,t,e)}
method route (line 19) | route(t,e,i,s){const n=he(this,t),a=he(this,i),r="_"+e;Object.definePr...
method apply (line 19) | apply(t){t.forEach((t=>t(this)))}
function fe (line 19) | function fe(){return"undefined"!=typeof window&&"undefined"!=typeof docu...
function ge (line 19) | function ge(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.to...
function pe (line 19) | function pe(t,e,i){let s;return"string"==typeof t?(s=parseInt(t,10),-1!=...
function be (line 19) | function be(t,e){return me(t).getPropertyValue(e)}
function _e (line 19) | function _e(t,e,i){const s={};i=i?"-"+i:"";for(let n=0;n<4;n++){const o=...
function ve (line 19) | function ve(t,e){if("native"in t)return t;const{canvas:i,currentDevicePi...
function we (line 19) | function we(t,e,i,s){const n=me(t),o=_e(n,"margin"),a=pe(n.maxWidth,t,"c...
function ke (line 19) | function ke(t,e,i){const s=e||1,n=Math.floor(t.height*s),o=Math.floor(t....
method passive (line 19) | get passive(){return t=!0,!1}
function Pe (line 19) | function Pe(t,e){const i=be(t,e),s=i&&i.match(/^(\d+)(\.\d+)?px$/);retur...
function De (line 19) | function De(t){return!t||s(t.size)||s(t.family)?null:(t.style?t.style+" ...
function Ce (line 19) | function Ce(t,e,i,s,n){let o=e[n];return o||(o=e[n]=t.measureText(n).wid...
function Oe (line 19) | function Oe(t,e,i,s){let o=(s=s||{}).data=s.data||{},a=s.garbageCollect=...
function Ae (line 19) | function Ae(t,e,i){const s=t.currentDevicePixelRatio,n=0!==i?Math.max(i/...
function Te (line 19) | function Te(t,e){(e=e||t.getContext("2d")).save(),e.resetTransform(),e.c...
function Le (line 19) | function Le(t,e,i,s){Ee(t,e,i,s,null)}
function Ee (line 19) | function Ee(t,e,i,s,n){let o,a,r,l,h,c,d,u;const f=e.pointStyle,g=e.rota...
function Re (line 19) | function Re(t,e,i){return i=i||.5,!e||t&&t.x>e.left-i&&t.x<e.right+i&&t....
function Ie (line 19) | function Ie(t,e){t.save(),t.beginPath(),t.rect(e.left,e.top,e.right-e.le...
function ze (line 19) | function ze(t){t.restore()}
function Fe (line 19) | function Fe(t,e,i,s,n){if(!e)return t.lineTo(i.x,i.y);if("middle"===n){c...
function Ve (line 19) | function Ve(t,e,i,s){if(!e)return t.lineTo(i.x,i.y);t.bezierCurveTo(s?e....
function Be (line 19) | function Be(t,e,i,s,n){if(n.strikethrough||n.underline){const o=t.measur...
function We (line 19) | function We(t,e){const i=t.fillStyle;t.fillStyle=e.color,t.fillRect(e.le...
function Ne (line 19) | function Ne(t,e,i,o,a,r={}){const l=n(e)?e:[e],h=r.strokeWidth>0&&""!==r...
function He (line 19) | function He(t,e){const{x:i,y:s,w:n,h:o,radius:a}=e;t.arc(i+a.topLeft,s+a...
function je (line 19) | function je(t,e=[""],i,s,n=(()=>t[0])){const o=i||t;void 0===s&&(s=ti("_...
function $e (line 19) | function $e(t,e,i,s){const a={_cacheable:!1,_proxy:t,_context:e,_subProx...
function Ye (line 19) | function Ye(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scri...
function qe (line 19) | function qe(t,e,i){if(Object.prototype.hasOwnProperty.call(t,e))return t...
function Ke (line 19) | function Ke(t,e,i){return S(t)?t(e,i):t}
function Ze (line 19) | function Ze(t,e,i,s,n){for(const o of e){const e=Ge(i,o);if(e){t.add(e);...
function Je (line 19) | function Je(t,e,i,s){const a=e._rootScopes,r=Ke(e._fallback,i,s),l=[...t...
function Qe (line 19) | function Qe(t,e,i,s,n){for(;i;)i=Ze(t,e,i,s,n);return i}
function ti (line 19) | function ti(t,e){for(const i of e){if(!i)continue;const e=i[t];if(void 0...
function ei (line 19) | function ei(t){let e=t._keys;return e||(e=t._keys=function(t){const e=ne...
function ii (line 19) | function ii(t,e,i,s){const{iScale:n}=t,{key:o="r"}=this._parsing,a=new A...
function ai (line 19) | function ai(t,e,i,s){const n=t.skip?e:t,o=e,a=i.skip?e:i,r=q(o,n),l=q(a,...
function ri (line 19) | function ri(t,e="x"){const i=oi(e),s=t.length,n=Array(s).fill(0),o=Array...
function li (line 19) | function li(t,e,i){return Math.max(Math.min(t,i),e)}
function hi (line 19) | function hi(t,e,i,s,n){let o,a,r,l;if(e.spanGaps&&(t=t.filter((t=>!t.ski...
method easeInOutElastic (line 19) | easeInOutElastic(t){const e=.1125;return ci(t)?t:t<.5?.5*di(2*t,e,.45):....
method easeInBack (line 19) | easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)}
method easeOutBack (line 19) | easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1}
method easeInOutBack (line 19) | easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*...
method easeOutBounce (line 19) | easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1....
function gi (line 19) | function gi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:t.y+i*(e.y-t.y)}}
function pi (line 19) | function pi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:"middle"===s?i<.5?t.y:e....
function mi (line 19) | function mi(t,e,i,s){const n={x:t.cp2x,y:t.cp2y},o={x:e.cp1x,y:e.cp1y},a...
function _i (line 19) | function _i(t,e){const i=(""+t).match(bi);if(!i||"normal"===i[1])return ...
function vi (line 19) | function vi(t,e){const i={},s=o(e),n=s?Object.keys(e):e,a=o(t)?s?i=>l(t[...
function Mi (line 19) | function Mi(t){return vi(t,{top:"y",right:"x",bottom:"y",left:"x"})}
function wi (line 19) | function wi(t){return vi(t,["topLeft","topRight","bottomLeft","bottomRig...
function ki (line 19) | function ki(t){const e=Mi(t);return e.width=e.left+e.right,e.height=e.to...
function Si (line 19) | function Si(t,e){t=t||{},e=e||ue.font;let i=l(t.size,e.size);"string"==t...
function Pi (line 19) | function Pi(t,e,i,s){let o,a,r,l=!0;for(o=0,a=t.length;o<a;++o)if(r=t[o]...
function Di (line 19) | function Di(t,e,i){const{min:s,max:n}=t,o=c(e,(n-s)/2),a=(t,e)=>i&&0===t...
function Ci (line 19) | function Ci(t,e){return Object.assign(Object.create(t),e)}
function Oi (line 19) | function Oi(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t...
function Ai (line 19) | function Ai(t,e){let i,s;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,s=[i.ge...
function Ti (line 19) | function Ti(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style....
function Li (line 19) | function Li(t){return"angle"===t?{between:Z,compare:K,normalize:G}:{betw...
function Ei (line 19) | function Ei({start:t,end:e,count:i,loop:s,style:n}){return{start:t%i,end...
function Ri (line 19) | function Ri(t,e,i){if(!i)return[t];const{property:s,start:n,end:o}=i,a=e...
function Ii (line 19) | function Ii(t,e){const i=[],s=t.segments;for(let n=0;n<s.length;n++){con...
function zi (line 19) | function zi(t,e){const i=t.points,s=t.options.spanGaps,n=i.length;if(!n)...
function Fi (line 19) | function Fi(t,e,i,s){return s&&s.setContext&&i?function(t,e,i,s){const n...
function Vi (line 19) | function Vi(t){return{backgroundColor:t.backgroundColor,borderCapStyle:t...
function Bi (line 19) | function Bi(t,e){if(!e)return!1;const i=[],s=function(t,e){return Jt(e)?...
function Ni (line 19) | function Ni(t,e,i,s){const{controller:n,data:o,_sorted:a}=t,r=n._cachedM...
function Hi (line 19) | function Hi(t,e,i,s,n){const o=t.getSortedVisibleDatasetMetas(),a=i[e];f...
function ji (line 19) | function ji(t,e,i,s,n){const o=[];if(!n&&!t.isPointInArea(e))return o;re...
function $i (line 19) | function $i(t,e,i,s,n,o){let a=[];const r=function(t){const e=-1!==t.ind...
function Yi (line 19) | function Yi(t,e,i,s,n,o){return o||t.isPointInArea(e)?"r"!==i||s?$i(t,e,...
function Ui (line 19) | function Ui(t,e,i,s,n){const o=[],a="x"===i?"inXRange":"inYRange";let r=...
method index (line 19) | index(t,e,i,s){const n=ve(e,t),o=i.axis||"x",a=i.includeInvisible||!1,r=...
method dataset (line 19) | dataset(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1...
method nearest (line 19) | nearest(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1...
function Ki (line 19) | function Ki(t,e){return t.filter((t=>t.pos===e))}
function Gi (line 19) | function Gi(t,e){return t.filter((t=>-1===qi.indexOf(t.pos)&&t.box.axis=...
function Zi (line 19) | function Zi(t,e){return t.sort(((t,i)=>{const s=e?i:t,n=e?t:i;return s.w...
function Ji (line 19) | function Ji(t,e){const i=function(t){const e={};for(const i of t){const{...
function Qi (line 19) | function Qi(t,e,i,s){return Math.max(t[i],e[i])+Math.max(t[s],e[s])}
function ts (line 19) | function ts(t,e){t.top=Math.max(t.top,e.top),t.left=Math.max(t.left,e.le...
function es (line 19) | function es(t,e,i,s){const{pos:n,box:a}=i,r=t.maxPadding;if(!o(n)){i.siz...
function is (line 19) | function is(t,e){const i=e.maxPadding;function s(t){const s={left:0,top:...
function ss (line 19) | function ss(t,e,i,s){const n=[];let o,a,r,l,h,c;for(o=0,a=t.length,h=0;o...
function ns (line 19) | function ns(t,e,i,s,n){t.top=i,t.left=e,t.right=e+s,t.bottom=i+n,t.width...
function os (line 19) | function os(t,e,i,s){const n=i.padding;let{x:o,y:a}=e;for(const r of t){...
method addBox (line 19) | addBox(t,e){t.boxes||(t.boxes=[]),e.fullSize=e.fullSize||!1,e.position=e...
method removeBox (line 19) | removeBox(t,e){const i=t.boxes?t.boxes.indexOf(e):-1;-1!==i&&t.boxes.spl...
method configure (line 19) | configure(t,e,i){e.fullSize=i.fullSize,e.position=i.position,e.weight=i....
method update (line 19) | update(t,e,i,s){if(!t)return;const n=ki(t.options.layout.padding),o=Math...
class rs (line 19) | class rs{acquireContext(t,e){}releaseContext(t){return!1}addEventListene...
method acquireContext (line 19) | acquireContext(t,e){}
method releaseContext (line 19) | releaseContext(t){return!1}
method addEventListener (line 19) | addEventListener(t,e,i){}
method removeEventListener (line 19) | removeEventListener(t,e,i){}
method getDevicePixelRatio (line 19) | getDevicePixelRatio(){return 1}
method getMaximumSize (line 19) | getMaximumSize(t,e,i,s){return e=Math.max(0,e||t.width),i=i||t.height,...
method isAttached (line 19) | isAttached(t){return!0}
method updateConfig (line 19) | updateConfig(t){}
class ls (line 19) | class ls extends rs{acquireContext(t){return t&&t.getContext&&t.getConte...
method acquireContext (line 19) | acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}
method updateConfig (line 19) | updateConfig(t){t.options.animation=!1}
function fs (line 19) | function fs(t,e,i){t.canvas.removeEventListener(e,i,us)}
function gs (line 19) | function gs(t,e){for(const i of t)if(i===e||i.contains(e))return!0}
function ps (line 19) | function ps(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1...
function ms (line 19) | function ms(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1...
function _s (line 19) | function _s(){const t=window.devicePixelRatio;t!==xs&&(xs=t,bs.forEach((...
function ys (line 19) | function ys(t,e,i){const s=t.canvas,n=s&&ge(s);if(!n)return;const o=ct((...
function vs (line 19) | function vs(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){bs.delete...
function Ms (line 19) | function Ms(t,e,i){const s=t.canvas,n=ct((e=>{null!==t.ctx&&i(function(t...
class ws (line 19) | class ws extends rs{acquireContext(t,e){const i=t&&t.getContext&&t.getCo...
method acquireContext (line 19) | acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return...
method releaseContext (line 19) | releaseContext(t){const e=t.canvas;if(!e[hs])return!1;const i=e[hs].in...
method addEventListener (line 19) | addEventListener(t,e,i){this.removeEventListener(t,e);const s=t.$proxi...
method removeEventListener (line 19) | removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),s=i[e];if...
method getDevicePixelRatio (line 19) | getDevicePixelRatio(){return window.devicePixelRatio}
method getMaximumSize (line 19) | getMaximumSize(t,e,i,s){return we(t,e,i,s)}
method isAttached (line 19) | isAttached(t){const e=ge(t);return!(!e||!e.isConnected)}
function ks (line 19) | function ks(t){return!fe()||"undefined"!=typeof OffscreenCanvas&&t insta...
method color (line 19) | color(t,e,i){const s=Qt(t||Ps),n=s.valid&&Qt(e||Ps);return n&&n.valid?n....
class Cs (line 19) | class Cs{constructor(t,e,i,s){const n=e[i];s=Pi([t.to,s,n,t.from]);const...
method constructor (line 19) | constructor(t,e,i,s){const n=e[i];s=Pi([t.to,s,n,t.from]);const o=Pi([...
method active (line 19) | active(){return this._active}
method update (line 19) | update(t,e,i){if(this._active){this._notify(!1);const s=this._target[t...
method cancel (line 19) | cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._no...
method tick (line 19) | tick(t){const e=t-this._start,i=this._duration,s=this._prop,n=this._fr...
method wait (line 19) | wait(){const t=this._promises||(this._promises=[]);return new Promise(...
method _notify (line 19) | _notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t<i....
class Os (line 19) | class Os{constructor(t,e){this._chart=t,this._properties=new Map,this.co...
method constructor (line 19) | constructor(t,e){this._chart=t,this._properties=new Map,this.configure...
method configure (line 19) | configure(t){if(!o(t))return;const e=Object.keys(ue.animation),i=this....
method _animateOptions (line 19) | _animateOptions(t,e){const i=e.options,s=function(t,e){if(!e)return;le...
method _createAnimations (line 19) | _createAnimations(t,e){const i=this._properties,s=[],n=t.$animations||...
method update (line 19) | update(t,e){if(0===this._properties.size)return void Object.assign(t,e...
function As (line 19) | function As(t,e){const i=t&&t.options||{},s=i.reverse,n=void 0===i.min?e...
function Ts (line 19) | function Ts(t,e){const i=[],s=t._getSortedDatasetMetas(e);let n,o;for(n=...
function Ls (line 19) | function Ls(t,e,i,s={}){const n=t.keys,o="single"===s.mode;let r,l,h,c;i...
function Es (line 19) | function Es(t,e){const i=t&&t.options.stacked;return i||void 0===i&&void...
function Rs (line 19) | function Rs(t,e,i){const s=t[e]||(t[e]={});return s[i]||(s[i]={})}
function Is (line 19) | function Is(t,e,i,s){for(const n of e.getMatchingVisibleMetas(s).reverse...
function zs (line 19) | function zs(t,e){const{chart:i,_cachedMeta:s}=t,n=i._stacks||(i._stacks=...
function Fs (line 19) | function Fs(t,e){const i=t.scales;return Object.keys(i).filter((t=>i[t]....
function Vs (line 19) | function Vs(t,e){const i=t.controller.index,s=t.vScale&&t.vScale.axis;if...
class Ns (line 19) | class Ns{static defaults={};static datasetElementType=null;static dataEl...
method constructor (line 19) | constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cache...
method initialize (line 19) | initialize(){const t=this._cachedMeta;this.configure(),this.linkScales...
method updateIndex (line 19) | updateIndex(t){this.index!==t&&Vs(this._cachedMeta),this.index=t}
method linkScales (line 19) | linkScales(){const t=this.chart,e=this._cachedMeta,i=this.getDataset()...
method getDataset (line 19) | getDataset(){return this.chart.data.datasets[this.index]}
method getMeta (line 19) | getMeta(){return this.chart.getDatasetMeta(this.index)}
method getScaleForId (line 19) | getScaleForId(t){return this.chart.scales[t]}
method _getOtherScale (line 19) | _getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScal...
method reset (line 19) | reset(){this._update("reset")}
method _destroy (line 19) | _destroy(){const t=this._cachedMeta;this._data&&rt(this._data,this),t....
method _dataCheck (line 19) | _dataCheck(){const t=this.getDataset(),e=t.data||(t.data=[]),i=this._d...
method addElements (line 19) | addElements(){const t=this._cachedMeta;this._dataCheck(),this.datasetE...
method buildOrUpdateElements (line 19) | buildOrUpdateElements(t){const e=this._cachedMeta,i=this.getDataset();...
method configure (line 19) | configure(){const t=this.chart.config,e=t.datasetScopeKeys(this._type)...
method parse (line 19) | parse(t,e){const{_cachedMeta:i,_data:s}=this,{iScale:a,_stacked:r}=i,l...
method parsePrimitiveData (line 19) | parsePrimitiveData(t,e,i,s){const{iScale:n,vScale:o}=t,a=n.axis,r=o.ax...
method parseArrayData (line 19) | parseArrayData(t,e,i,s){const{xScale:n,yScale:o}=t,a=new Array(s);let ...
method parseObjectData (line 19) | parseObjectData(t,e,i,s){const{xScale:n,yScale:o}=t,{xAxisKey:a="x",yA...
method getParsed (line 19) | getParsed(t){return this._cachedMeta._parsed[t]}
method getDataElement (line 19) | getDataElement(t){return this._cachedMeta.data[t]}
method applyStack (line 19) | applyStack(t,e,i){const s=this.chart,n=this._cachedMeta,o=e[t.axis];re...
method updateRangeFromParsed (line 19) | updateRangeFromParsed(t,e,i,s){const n=i[e.axis];let o=null===n?NaN:n;...
method getMinMax (line 19) | getMinMax(t,e){const i=this._cachedMeta,s=i._parsed,n=i._sorted&&t===i...
method getAllParsedValues (line 19) | getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let s,n,o;...
method getMaxOverflow (line 19) | getMaxOverflow(){return!1}
method getLabelAndValue (line 19) | getLabelAndValue(t){const e=this._cachedMeta,i=e.iScale,s=e.vScale,n=t...
method _update (line 19) | _update(t){const e=this._cachedMeta;this.update(t||"default"),e._clip=...
method update (line 19) | update(t){}
method draw (line 19) | draw(){const t=this._ctx,e=this.chart,i=this._cachedMeta,s=i.data||[],...
method getStyle (line 19) | getStyle(t,e){const i=e?"active":"default";return void 0===t&&this._ca...
method getContext (line 19) | getContext(t,e,i){const s=this.getDataset();let n;if(t>=0&&t<this._cac...
method resolveDatasetElementOptions (line 19) | resolveDatasetElementOptions(t){return this._resolveElementOptions(thi...
method resolveDataElementOptions (line 19) | resolveDataElementOptions(t,e){return this._resolveElementOptions(this...
method _resolveElementOptions (line 19) | _resolveElementOptions(t,e="default",i){const s="active"===e,n=this._c...
method _resolveAnimations (line 19) | _resolveAnimations(t,e,i){const s=this.chart,n=this._cachedDataOpts,o=...
method getSharedOptions (line 19) | getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sh...
method includeOptions (line 19) | includeOptions(t,e){return!e||Bs(t)||this.chart._animationsDisabled}
method _getSharedOptions (line 19) | _getSharedOptions(t,e){const i=this.resolveDataElementOptions(t,e),s=t...
method updateElement (line 19) | updateElement(t,e,i,s){Bs(s)?Object.assign(t,i):this._resolveAnimation...
method updateSharedOptions (line 19) | updateSharedOptions(t,e,i){t&&!Bs(e)&&this._resolveAnimations(void 0,e...
method _setStyle (line 19) | _setStyle(t,e,i,s){t.active=s;const n=this.getStyle(e,s);this._resolve...
method removeHoverStyle (line 19) | removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}
method setHoverStyle (line 19) | setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}
method _removeDatasetHoverStyle (line 19) | _removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._s...
method _setDatasetHoverStyle (line 19) | _setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setS...
method _resyncElements (line 19) | _resyncElements(t){const e=this._data,i=this._cachedMeta.data;for(cons...
method _insertElements (line 19) | _insertElements(t,e,i=!0){const s=this._cachedMeta,n=s.data,o=t+e;let ...
method updateElements (line 19) | updateElements(t,e,i,s){}
method _removeElements (line 19) | _removeElements(t,e){const i=this._cachedMeta;if(this._parsing){const ...
method _sync (line 19) | _sync(t){if(this._parsing)this._syncList.push(t);else{const[e,i,s]=t;t...
method _onDataPush (line 19) | _onDataPush(){const t=arguments.length;this._sync(["_insertElements",t...
method _onDataPop (line 19) | _onDataPop(){this._sync(["_removeElements",this._cachedMeta.data.lengt...
method _onDataShift (line 19) | _onDataShift(){this._sync(["_removeElements",0,1])}
method _onDataSplice (line 19) | _onDataSplice(t,e){e&&this._sync(["_removeElements",t,e]);const i=argu...
method _onDataUnshift (line 19) | _onDataUnshift(){this._sync(["_insertElements",0,arguments.length])}
class Hs (line 19) | class Hs{static defaults={};static defaultRoutes=void 0;x;y;active=!1;op...
method tooltipPosition (line 19) | tooltipPosition(t){const{x:e,y:i}=this.getProps(["x","y"],t);return{x:...
method hasValue (line 19) | hasValue(){return N(this.x)&&N(this.y)}
method getProps (line 19) | getProps(t,e){const i=this.$animations;if(!e||!i)return this;const s={...
function js (line 19) | function js(t,e){const i=t.options.ticks,n=function(t){const e=t.options...
function $s (line 19) | function $s(t,e,i,s,n){const o=l(s,0),a=Math.min(l(n,t.length),t.length)...
function Xs (line 19) | function Xs(t,e){const i=[],s=t.length/e,n=t.length;let o=0;for(;o<n;o+=...
function qs (line 19) | function qs(t,e,i){const s=t.ticks.length,n=Math.min(e,s-1),o=t._startPi...
function Ks (line 19) | function Ks(t){return t.drawTicks?t.tickLength:0}
function Gs (line 19) | function Gs(t,e){if(!t.display)return 0;const i=Si(t.font,e),s=ki(t.padd...
function Zs (line 19) | function Zs(t,e,i){let s=ut(t);return(i&&"right"!==e||!i&&"right"===e)&&...
class Js (line 19) | class Js extends Hs{constructor(t){super(),this.id=t.id,this.type=t.type...
method constructor (line 19) | constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void...
method init (line 19) | init(t){this.options=t.setContext(this.getContext()),this.axis=t.axis,...
method parse (line 19) | parse(t,e){return t}
method getUserBounds (line 19) | getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMa...
method getMinMax (line 19) | getMinMax(t){let e,{min:i,max:s,minDefined:n,maxDefined:o}=this.getUse...
method getPadding (line 19) | getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,ri...
method getTicks (line 19) | getTicks(){return this.ticks}
method getLabels (line 19) | getLabels(){const t=this.chart.data;return this.options.labels||(this....
method getLabelItems (line 19) | getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._...
method beforeLayout (line 19) | beforeLayout(){this._cache={},this._dataLimitsCached=!1}
method beforeUpdate (line 19) | beforeUpdate(){d(this.options.beforeUpdate,[this])}
method update (line 19) | update(t,e,i){const{beginAtZero:s,grace:n,ticks:o}=this.options,a=o.sa...
method configure (line 19) | configure(){let t,e,i=this.options.reverse;this.isHorizontal()?(t=this...
method afterUpdate (line 19) | afterUpdate(){d(this.options.afterUpdate,[this])}
method beforeSetDimensions (line 19) | beforeSetDimensions(){d(this.options.beforeSetDimensions,[this])}
method setDimensions (line 19) | setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.lef...
method afterSetDimensions (line 19) | afterSetDimensions(){d(this.options.afterSetDimensions,[this])}
method _callHooks (line 19) | _callHooks(t){this.chart.notifyPlugins(t,this.getContext()),d(this.opt...
method beforeDataLimits (line 19) | beforeDataLimits(){this._callHooks("beforeDataLimits")}
method determineDataLimits (line 19) | determineDataLimits(){}
method afterDataLimits (line 19) | afterDataLimits(){this._callHooks("afterDataLimits")}
method beforeBuildTicks (line 19) | beforeBuildTicks(){this._callHooks("beforeBuildTicks")}
method buildTicks (line 19) | buildTicks(){return[]}
method afterBuildTicks (line 19) | afterBuildTicks(){this._callHooks("afterBuildTicks")}
method beforeTickToLabelConversion (line 19) | beforeTickToLabelConversion(){d(this.options.beforeTickToLabelConversi...
method generateTickLabels (line 19) | generateTickLabels(t){const e=this.options.ticks;let i,s,n;for(i=0,s=t...
method afterTickToLabelConversion (line 19) | afterTickToLabelConversion(){d(this.options.afterTickToLabelConversion...
method beforeCalculateLabelRotation (line 19) | beforeCalculateLabelRotation(){d(this.options.beforeCalculateLabelRota...
method calculateLabelRotation (line 19) | calculateLabelRotation(){const t=this.options,e=t.ticks,i=Us(this.tick...
method afterCalculateLabelRotation (line 19) | afterCalculateLabelRotation(){d(this.options.afterCalculateLabelRotati...
method afterAutoSkip (line 19) | afterAutoSkip(){}
method beforeFit (line 19) | beforeFit(){d(this.options.beforeFit,[this])}
method fit (line 19) | fit(){const t={width:0,height:0},{chart:e,options:{ticks:i,title:s,gri...
method _calculatePadding (line 19) | _calculatePadding(t,e,i,s){const{ticks:{align:n,padding:o},position:a}...
method _handleMargins (line 19) | _handleMargins(){this._margins&&(this._margins.left=Math.max(this.padd...
method afterFit (line 19) | afterFit(){d(this.options.afterFit,[this])}
method isHorizontal (line 19) | isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||...
method isFullSize (line 19) | isFullSize(){return this.options.fullSize}
method _convertTicksToLabels (line 19) | _convertTicksToLabels(t){let e,i;for(this.beforeTickToLabelConversion(...
method _getLabelSizes (line 19) | _getLabelSizes(){let t=this._labelSizes;if(!t){const e=this.options.ti...
method _computeLabelSizes (line 19) | _computeLabelSizes(t,e,i){const{ctx:o,_longestTextCache:a}=this,r=[],l...
method getLabelForValue (line 19) | getLabelForValue(t){return t}
method getPixelForValue (line 19) | getPixelForValue(t,e){return NaN}
method getValueForPixel (line 19) | getValueForPixel(t){}
method getPixelForTick (line 19) | getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:th...
method getPixelForDecimal (line 19) | getPixelForDecimal(t){this._reversePixels&&(t=1-t);const e=this._start...
method getDecimalForPixel (line 19) | getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return...
method getBasePixel (line 19) | getBasePixel(){return this.getPixelForValue(this.getBaseValue())}
method getBaseValue (line 19) | getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}
method getContext (line 19) | getContext(t){const e=this.ticks||[];if(t>=0&&t<e.length){const i=e[t]...
method _tickSize (line 19) | _tickSize(){const t=this.options.ticks,e=$(this.labelRotation),i=Math....
method _isVisible (line 19) | _isVisible(){const t=this.options.display;return"auto"!==t?!!t:this.ge...
method _computeGridLineItems (line 19) | _computeGridLineItems(t){const e=this.axis,i=this.chart,s=this.options...
method _computeLabelItems (line 19) | _computeLabelItems(t){const e=this.axis,i=this.options,{position:s,tic...
method _getXAxisLabelAlignment (line 19) | _getXAxisLabelAlignment(){const{position:t,ticks:e}=this.options;if(-$...
method _getYAxisLabelAlignment (line 19) | _getYAxisLabelAlignment(t){const{position:e,ticks:{crossAlign:i,mirror...
method _computeLabelArea (line 19) | _computeLabelArea(){if(this.options.ticks.mirror)return;const t=this.c...
method drawBackground (line 19) | drawBackground(){const{ctx:t,options:{backgroundColor:e},left:i,top:s,...
method getLineWidthForValue (line 19) | getLineWidthForValue(t){const e=this.options.grid;if(!this._isVisible(...
method drawGrid (line 19) | drawGrid(t){const e=this.options.grid,i=this.ctx,s=this._gridLineItems...
method drawBorder (line 19) | drawBorder(){const{chart:t,ctx:e,options:{border:i,grid:s}}=this,n=i.s...
method drawLabels (line 19) | drawLabels(t){if(!this.options.ticks.display)return;const e=this.ctx,i...
method drawTitle (line 19) | drawTitle(){const{ctx:t,options:{position:e,title:i,reverse:s}}=this;i...
method draw (line 19) | draw(t){this._isVisible()&&(this.drawBackground(),this.drawGrid(t),thi...
method _layers (line 19) | _layers(){const t=this.options,e=t.ticks&&t.ticks.z||0,i=l(t.grid&&t.g...
method getMatchingVisibleMetas (line 19) | getMatchingVisibleMetas(t){const e=this.chart.getSortedVisibleDatasetM...
method _resolveTickFontOptions (line 19) | _resolveTickFontOptions(t){return Si(this.options.ticks.setContext(thi...
method _maxDigits (line 19) | _maxDigits(){const t=this._resolveTickFontOptions(0).lineHeight;return...
class Qs (line 19) | class Qs{constructor(t,e,i){this.type=t,this.scope=e,this.override=i,thi...
method constructor (line 19) | constructor(t,e,i){this.type=t,this.scope=e,this.override=i,this.items...
method isForType (line 19) | isForType(t){return Object.prototype.isPrototypeOf.call(this.type.prot...
method register (line 19) | register(t){const e=Object.getPrototypeOf(t);let i;(function(t){return...
method get (line 19) | get(t){return this.items[t]}
method unregister (line 19) | unregister(t){const e=this.items,i=t.id,s=this.scope;i in e&&delete e[...
class tn (line 19) | class tn{constructor(){this.controllers=new Qs(Ns,"datasets",!0),this.el...
method constructor (line 19) | constructor(){this.controllers=new Qs(Ns,"datasets",!0),this.elements=...
method add (line 19) | add(...t){this._each("register",t)}
method remove (line 19) | remove(...t){this._each("unregister",t)}
method addControllers (line 19) | addControllers(...t){this._each("register",t,this.controllers)}
method addElements (line 19) | addElements(...t){this._each("register",t,this.elements)}
method addPlugins (line 19) | addPlugins(...t){this._each("register",t,this.plugins)}
method addScales (line 19) | addScales(...t){this._each("register",t,this.scales)}
method getController (line 19) | getController(t){return this._get(t,this.controllers,"controller")}
method getElement (line 19) | getElement(t){return this._get(t,this.elements,"element")}
method getPlugin (line 19) | getPlugin(t){return this._get(t,this.plugins,"plugin")}
method getScale (line 19) | getScale(t){return this._get(t,this.scales,"scale")}
method removeControllers (line 19) | removeControllers(...t){this._each("unregister",t,this.controllers)}
method removeElements (line 19) | removeElements(...t){this._each("unregister",t,this.elements)}
method removePlugins (line 19) | removePlugins(...t){this._each("unregister",t,this.plugins)}
method removeScales (line 19) | removeScales(...t){this._each("unregister",t,this.scales)}
method _each (line 19) | _each(t,e,i){[...e].forEach((e=>{const s=i||this._getRegistryForType(e...
method _exec (line 19) | _exec(t,e,i){const s=w(t);d(i["before"+s],[],i),e[t](i),d(i["after"+s]...
method _getRegistryForType (line 19) | _getRegistryForType(t){for(let e=0;e<this._typedRegistries.length;e++)...
method _get (line 19) | _get(t,e,i){const s=e.get(t);if(void 0===s)throw new Error('"'+t+'" is...
class sn (line 19) | class sn{constructor(){this._init=[]}notify(t,e,i,s){"beforeInit"===e&&(...
method constructor (line 19) | constructor(){this._init=[]}
method notify (line 19) | notify(t,e,i,s){"beforeInit"===e&&(this._init=this._createDescriptors(...
method _notify (line 19) | _notify(t,e,i,s){s=s||{};for(const n of t){const t=n.plugin;if(!1===d(...
method invalidate (line 19) | invalidate(){s(this._cache)||(this._oldCache=this._cache,this._cache=v...
method _descriptors (line 19) | _descriptors(t){if(this._cache)return this._cache;const e=this._cache=...
method _createDescriptors (line 19) | _createDescriptors(t,e){const i=t&&t.config,s=l(i.options&&i.options.p...
method _notifyStateChanges (line 19) | _notifyStateChanges(t){const e=this._oldCache||[],i=this._cache,s=(t,e...
function nn (line 19) | function nn(t,e){return e||!1!==t?!0===t?{}:t:null}
function on (line 19) | function on(t,{plugin:e,local:i},s,n){const o=t.pluginScopeKeys(e),a=t.g...
function an (line 19) | function an(t,e){const i=ue.datasets[t]||{};return((e.datasets||{})[t]||...
function rn (line 19) | function rn(t){if("x"===t||"y"===t||"r"===t)return t}
function ln (line 19) | function ln(t,...e){if(rn(t))return t;for(const s of e){const e=s.axis||...
function hn (line 19) | function hn(t,e,i){if(i[e+"AxisID"]===t)return{axis:e}}
function cn (line 19) | function cn(t,e){const i=re[t.type]||{scales:{}},s=e.scales||{},n=an(t.t...
function dn (line 19) | function dn(t){const e=t.options||(t.options={});e.plugins=l(e.plugins,{...
function un (line 19) | function un(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels...
function pn (line 19) | function pn(t,e){let i=fn.get(t);return i||(i=e(),fn.set(t,i),gn.add(i)),i}
class bn (line 19) | class bn{constructor(t){this._config=function(t){return(t=t||{}).data=un...
method constructor (line 19) | constructor(t){this._config=function(t){return(t=t||{}).data=un(t.data...
method platform (line 19) | get platform(){return this._config.platform}
method type (line 19) | get type(){return this._config.type}
method type (line 19) | set type(t){this._config.type=t}
method data (line 19) | get data(){return this._config.data}
method data (line 19) | set data(t){this._config.data=un(t)}
method options (line 19) | get options(){return this._config.options}
method options (line 19) | set options(t){this._config.options=t}
method plugins (line 19) | get plugins(){return this._config.plugins}
method update (line 19) | update(){const t=this._config;this.clearCache(),dn(t)}
method clearCache (line 19) | clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}
method datasetScopeKeys (line 19) | datasetScopeKeys(t){return pn(t,(()=>[[`datasets.${t}`,""]]))}
method datasetAnimationScopeKeys (line 19) | datasetAnimationScopeKeys(t,e){return pn(`${t}.transition.${e}`,(()=>[...
method datasetElementScopeKeys (line 19) | datasetElementScopeKeys(t,e){return pn(`${t}-${e}`,(()=>[[`datasets.${...
method pluginScopeKeys (line 19) | pluginScopeKeys(t){const e=t.id;return pn(`${this.type}-plugin-${e}`,(...
method _cachedScopes (line 19) | _cachedScopes(t,e){const i=this._scopeCache;let s=i.get(t);return s&&!...
method getOptionScopes (line 19) | getOptionScopes(t,e,i){const{options:s,type:n}=this,o=this._cachedScop...
method chartOptionScopes (line 19) | chartOptionScopes(){const{options:t,type:e}=this;return[t,re[e]||{},ue...
method resolveNamedOptions (line 19) | resolveNamedOptions(t,e,i,s=[""]){const o={$shared:!0},{resolver:a,sub...
method createResolver (line 19) | createResolver(t,e,i=[""],s){const{resolver:n}=xn(this._resolverCache,...
function xn (line 19) | function xn(t,e,i){let s=t.get(e);s||(s=new Map,t.set(e,s));const n=i.jo...
function vn (line 19) | function vn(t,e){return"top"===t||"bottom"===t||-1===yn.indexOf(t)&&"x"=...
function Mn (line 19) | function Mn(t,e){return function(i,s){return i[t]===s[t]?i[e]-s[e]:i[t]-...
function wn (line 19) | function wn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("af...
function kn (line 19) | function kn(t){const e=t.chart,i=e.options.animation;d(i&&i.onProgress,[...
function Sn (line 19) | function Sn(t){return fe()&&"string"==typeof t?t=document.getElementById...
function Cn (line 19) | function Cn(t,e,i){const s=Object.keys(t);for(const n of s){const s=+n;i...
function On (line 19) | function On(t,e,i){return t.options.clip?t[i]:e[i]}
class An (line 19) | class An{static defaults=ue;static instances=Pn;static overrides=re;stat...
method register (line 19) | static register(...t){en.add(...t),Tn()}
method unregister (line 19) | static unregister(...t){en.remove(...t),Tn()}
method constructor (line 19) | constructor(t,e){const s=this.config=new bn(e),n=Sn(t),o=Dn(n);if(o)th...
method aspectRatio (line 19) | get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},...
method data (line 19) | get data(){return this.config.data}
method data (line 19) | set data(t){this.config.data=t}
method options (line 19) | get options(){return this._options}
method options (line 19) | set options(t){this.config.options=t}
method registry (line 19) | get registry(){return en}
method _initialize (line 19) | _initialize(){return this.notifyPlugins("beforeInit"),this.options.res...
method clear (line 19) | clear(){return Te(this.canvas,this.ctx),this}
method stop (line 19) | stop(){return xt.stop(this),this}
method resize (line 19) | resize(t,e){xt.running(this)?this._resizeBeforeDraw={width:t,height:e}...
method _resize (line 19) | _resize(t,e){const i=this.options,s=this.canvas,n=i.maintainAspectRati...
method ensureScalesHaveIDs (line 19) | ensureScalesHaveIDs(){u(this.options.scales||{},((t,e)=>{t.id=e}))}
method buildOrUpdateScales (line 19) | buildOrUpdateScales(){const t=this.options,e=t.scales,i=this.scales,s=...
method _updateMetasets (line 19) | _updateMetasets(){const t=this._metasets,e=this.data.datasets.length,i...
method _removeUnreferencedMetasets (line 19) | _removeUnreferencedMetasets(){const{_metasets:t,data:{datasets:e}}=thi...
method buildOrUpdateControllers (line 19) | buildOrUpdateControllers(){const t=[],e=this.data.datasets;let i,s;for...
method _resetElements (line 19) | _resetElements(){u(this.data.datasets,((t,e)=>{this.getDatasetMeta(e)....
method reset (line 19) | reset(){this._resetElements(),this.notifyPlugins("reset")}
method update (line 19) | update(t){const e=this.config;e.update();const i=this._options=e.creat...
method _updateScales (line 19) | _updateScales(){u(this.scales,(t=>{as.removeBox(this,t)})),this.ensure...
method _checkEventBindings (line 19) | _checkEventBindings(){const t=this.options,e=new Set(Object.keys(this....
method _updateHiddenIndices (line 19) | _updateHiddenIndices(){const{_hiddenIndices:t}=this,e=this._getUniform...
method _getUniformDataChanges (line 19) | _getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)re...
method _updateLayout (line 19) | _updateLayout(t){if(!1===this.notifyPlugins("beforeLayout",{cancelable...
method _updateDatasets (line 19) | _updateDatasets(t){if(!1!==this.notifyPlugins("beforeDatasetsUpdate",{...
method _updateDataset (line 19) | _updateDataset(t,e){const i=this.getDatasetMeta(t),s={meta:i,index:t,m...
method render (line 19) | render(){!1!==this.notifyPlugins("beforeRender",{cancelable:!0})&&(xt....
method draw (line 19) | draw(){let t;if(this._resizeBeforeDraw){const{width:t,height:e}=this._...
method _getSortedDatasetMetas (line 19) | _getSortedDatasetMetas(t){const e=this._sortedMetasets,i=[];let s,n;fo...
method getSortedVisibleDatasetMetas (line 19) | getSortedVisibleDatasetMetas(){return this._getSortedDatasetMetas(!0)}
method _drawDatasets (line 19) | _drawDatasets(){if(!1===this.notifyPlugins("beforeDatasetsDraw",{cance...
method _drawDataset (line 19) | _drawDataset(t){const e=this.ctx,i=t._clip,s=!i.disabled,n=function(t,...
method isPointInArea (line 19) | isPointInArea(t){return Re(t,this.chartArea,this._minPadding)}
method getElementsAtEventForMode (line 19) | getElementsAtEventForMode(t,e,i,s){const n=Xi.modes[e];return"function...
method getDatasetMeta (line 19) | getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let s...
method getContext (line 19) | getContext(){return this.$context||(this.$context=Ci(null,{chart:this,...
method getVisibleDatasetCount (line 19) | getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().le...
method isDatasetVisible (line 19) | isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const...
method setDatasetVisibility (line 19) | setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}
method toggleDataVisibility (line 19) | toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}
method getDataVisibility (line 19) | getDataVisibility(t){return!this._hiddenIndices[t]}
method _updateVisibility (line 19) | _updateVisibility(t,e,i){const s=i?"show":"hide",n=this.getDatasetMeta...
method hide (line 19) | hide(t,e){this._updateVisibility(t,e,!1)}
method show (line 19) | show(t,e){this._updateVisibility(t,e,!0)}
method _destroyDatasetMeta (line 19) | _destroyDatasetMeta(t){const e=this._metasets[t];e&&e.controller&&e.co...
method _stop (line 19) | _stop(){let t,e;for(this.stop(),xt.remove(this),t=0,e=this.data.datase...
method destroy (line 19) | destroy(){this.notifyPlugins("beforeDestroy");const{canvas:t,ctx:e}=th...
method toBase64Image (line 19) | toBase64Image(...t){return this.canvas.toDataURL(...t)}
method bindEvents (line 19) | bindEvents(){this.bindUserEvents(),this.options.responsive?this.bindRe...
method bindUserEvents (line 19) | bindUserEvents(){const t=this._listeners,e=this.platform,i=(i,s)=>{e.a...
method bindResponsiveEvents (line 19) | bindResponsiveEvents(){this._responsiveListeners||(this._responsiveLis...
method unbindEvents (line 19) | unbindEvents(){u(this._listeners,((t,e)=>{this.platform.removeEventLis...
method updateHoverStyle (line 19) | updateHoverStyle(t,e,i){const s=i?"set":"remove";let n,o,a,r;for("data...
method getActiveElements (line 19) | getActiveElements(){return this._active||[]}
method setActiveElements (line 19) | setActiveElements(t){const e=this._active||[],i=t.map((({datasetIndex:...
method notifyPlugins (line 19) | notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}
method isPluginEnabled (line 19) | isPluginEnabled(t){return 1===this._plugins._cache.filter((e=>e.plugin...
method _updateHoverStyles (line 19) | _updateHoverStyles(t,e,i){const s=this.options.hover,n=(t,e)=>t.filter...
method _eventHandler (line 19) | _eventHandler(t,e){const i={event:t,replay:e,cancelable:!0,inChartArea...
method _handleEvent (line 19) | _handleEvent(t,e,i){const{_active:s=[],options:n}=this,o=e,a=this._get...
method _getActiveElements (line 19) | _getActiveElements(t,e,i,s){if("mouseout"===t.type)return[];if(!i)retu...
function Tn (line 19) | function Tn(){return u(An.instances,(t=>t._plugins.invalidate()))}
function Ln (line 19) | function Ln(){throw new Error("This method is not implemented: Check tha...
class En (line 19) | class En{static override(t){Object.assign(En.prototype,t)}options;constr...
method override (line 19) | static override(t){Object.assign(En.prototype,t)}
method constructor (line 19) | constructor(t){this.options=t||{}}
method init (line 19) | init(){}
method formats (line 19) | formats(){return Ln()}
method parse (line 19) | parse(){return Ln()}
method format (line 19) | format(){return Ln()}
method add (line 19) | add(){return Ln()}
method diff (line 19) | diff(){return Ln()}
method startOf (line 19) | startOf(){return Ln()}
method endOf (line 19) | endOf(){return Ln()}
function In (line 19) | function In(t){const e=t.iScale,i=function(t,e){if(!t._cache.$bar){const...
function zn (line 19) | function zn(t,e,i,s){return n(t)?function(t,e,i,s){const n=i.parse(t[0],...
function Fn (line 19) | function Fn(t,e,i,s){const n=t.iScale,o=t.vScale,a=n.getLabels(),r=n===o...
function Vn (line 19) | function Vn(t){return t&&void 0!==t.barStart&&void 0!==t.barEnd}
function Bn (line 19) | function Bn(t,e,i,s){let n=e.borderSkipped;const o={};if(!n)return void(...
function Wn (line 19) | function Wn(t,e,i,s){var n,o,a;return s?(a=i,t=Nn(t=(n=t)===(o=e)?a:n===...
function Nn (line 19) | function Nn(t,e,i){return"start"===t?e:"end"===t?i:t}
function Hn (line 19) | function Hn(t,{inflateAmount:e},i){t.inflateAmount="auto"===e?1===i?.33:...
class jn (line 19) | class jn extends Ns{static id="doughnut";static defaults={datasetElement...
method generateLabels (line 19) | generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length...
method onClick (line 19) | onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}
method constructor (line 19) | constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadi...
method linkScales (line 19) | linkScales(){}
method parse (line 19) | parse(t,e){const i=this.getDataset().data,s=this._cachedMeta;if(!1===t...
method _getRotation (line 19) | _getRotation(){return $(this.options.rotation-90)}
method _getCircumference (line 19) | _getCircumference(){return $(this.options.circumference)}
method _getRotationExtents (line 19) | _getRotationExtents(){let t=O,e=-O;for(let i=0;i<this.chart.data.datas...
method update (line 19) | update(t){const e=this.chart,{chartArea:i}=e,s=this._cachedMeta,n=s.da...
method _circumference (line 19) | _circumference(t,e){const i=this.options,s=this._cachedMeta,n=this._ge...
method updateElements (line 19) | updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.chartArea...
method calculateTotal (line 19) | calculateTotal(){const t=this._cachedMeta,e=t.data;let i,s=0;for(i=0;i...
method calculateCircumference (line 19) | calculateCircumference(t){const e=this._cachedMeta.total;return e>0&&!...
method getLabelAndValue (line 19) | getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.lab...
method getMaxBorderWidth (line 19) | getMaxBorderWidth(t){let e=0;const i=this.chart;let s,n,o,a,r;if(!t)fo...
method getMaxOffset (line 19) | getMaxOffset(t){let e=0;for(let i=0,s=t.length;i<s;++i){const t=this.r...
method _getRingWeightOffset (line 19) | _getRingWeightOffset(t){let e=0;for(let i=0;i<t;++i)this.chart.isDatas...
method _getRingWeight (line 19) | _getRingWeight(t){return Math.max(l(this.chart.data.datasets[t].weight...
method _getVisibleDatasetWeightTotal (line 19) | _getVisibleDatasetWeightTotal(){return this._getRingWeightOffset(this....
class $n (line 19) | class $n extends Ns{static id="polarArea";static defaults={dataElementTy...
method generateLabels (line 19) | generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length...
method onClick (line 19) | onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}
method constructor (line 19) | constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=v...
method getLabelAndValue (line 19) | getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.lab...
method parseObjectData (line 19) | parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}
method update (line 19) | update(t){const e=this._cachedMeta.data;this._updateRadius(),this.upda...
method getMinMax (line 19) | getMinMax(){const t=this._cachedMeta,e={min:Number.POSITIVE_INFINITY,m...
method _updateRadius (line 19) | _updateRadius(){const t=this.chart,e=t.chartArea,i=t.options,s=Math.mi...
method updateElements (line 19) | updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.options.a...
method countVisibleElements (line 19) | countVisibleElements(){const t=this._cachedMeta;let e=0;return t.data....
method _computeAngle (line 19) | _computeAngle(t,e,i){return this.chart.getDataVisibility(t)?$(this.res...
method parsePrimitiveData (line 19) | parsePrimitiveData(t,e,i,s){return Fn(t,e,i,s)}
method parseArrayData (line 19) | parseArrayData(t,e,i,s){return Fn(t,e,i,s)}
method parseObjectData (line 19) | parseObjectData(t,e,i,s){const{iScale:n,vScale:o}=t,{xAxisKey:a="x",yAxi...
method updateRangeFromParsed (line 19) | updateRangeFromParsed(t,e,i,s){super.updateRangeFromParsed(t,e,i,s);cons...
method getMaxOverflow (line 19) | getMaxOverflow(){return 0}
method getLabelAndValue (line 19) | getLabelAndValue(t){const e=this._cachedMeta,{iScale:i,vScale:s}=e,n=thi...
method initialize (line 19) | initialize(){this.enableOptionSharing=!0,super.initialize();this._cached...
method update (line 19) | update(t){const e=this._cachedMeta;this.updateElements(e.data,0,e.data.l...
method updateElements (line 19) | updateElements(t,e,i,n){const o="reset"===n,{index:a,_cachedMeta:{vScale...
method _getStacks (line 19) | _getStacks(t,e){const{iScale:i}=this._cachedMeta,n=i.getMatchingVisibleM...
method _getStackCount (line 19) | _getStackCount(t){return this._getStacks(void 0,t).length}
method _getStackIndex (line 19) | _getStackIndex(t,e,i){const s=this._getStacks(t,i),n=void 0!==e?s.indexO...
method _getRuler (line 19) | _getRuler(){const t=this.options,e=this._cachedMeta,i=e.iScale,s=[];let ...
method _calculateBarValuePixels (line 19) | _calculateBarValuePixels(t){const{_cachedMeta:{vScale:e,_stacked:i,index...
method _calculateBarIndexPixels (line 19) | _calculateBarIndexPixels(t,e){const i=e.scale,n=this.options,o=n.skipNul...
method draw (line 19) | draw(){const t=this._cachedMeta,e=t.vScale,i=t.data,s=i.length;let n=0;f...
method initialize (line 19) | initialize(){this.enableOptionSharing=!0,super.initialize()}
method parsePrimitiveData (line 19) | parsePrimitiveData(t,e,i,s){const n=super.parsePrimitiveData(t,e,i,s);fo...
method parseArrayData (line 19) | parseArrayData(t,e,i,s){const n=super.parseArrayData(t,e,i,s);for(let t=...
method parseObjectData (line 19) | parseObjectData(t,e,i,s){const n=super.parseObjectData(t,e,i,s);for(let ...
method getMaxOverflow (line 19) | getMaxOverflow(){const t=this._cachedMeta.data;let e=0;for(let i=t.lengt...
method getLabelAndValue (line 19) | getLabelAndValue(t){const e=this._cachedMeta,i=this.chart.data.labels||[...
method update (line 19) | update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length...
method updateElements (line 19) | updateElements(t,e,i,s){const n="reset"===s,{iScale:o,vScale:a}=this._ca...
method resolveDataElementOptions (line 19) | resolveDataElementOptions(t,e){const i=this.getParsed(t);let s=super.res...
method initialize (line 19) | initialize(){this.enableOptionSharing=!0,this.supportsDecimation=!0,supe...
method update (line 19) | update(t){const e=this._cachedMeta,{dataset:i,data:s=[],_dataset:n}=e,o=...
method updateElements (line 19) | updateElements(t,e,i,n){const o="reset"===n,{iScale:a,vScale:r,_stacked:...
method getMaxOverflow (line 19) | getMaxOverflow(){const t=this._cachedMeta,e=t.dataset,i=e.options&&e.opt...
method draw (line 19) | draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart...
method getLabelAndValue (line 19) | getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);...
method parseObjectData (line 19) | parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}
method update (line 19) | update(t){const e=this._cachedMeta,i=e.dataset,s=e.data||[],n=e.iScale.g...
method updateElements (line 19) | updateElements(t,e,i,s){const n=this._cachedMeta.rScale,o="reset"===s;fo...
method getLabelAndValue (line 19) | getLabelAndValue(t){const e=this._cachedMeta,i=this.chart.data.labels||[...
method update (line 19) | update(t){const e=this._cachedMeta,{data:i=[]}=e,s=this.chart._animation...
method addElements (line 19) | addElements(){const{showLine:t}=this.options;!this.datasetElementType&&t...
method updateElements (line 19) | updateElements(t,e,i,n){const o="reset"===n,{iScale:a,vScale:r,_stacked:...
method getMaxOverflow (line 19) | getMaxOverflow(){const t=this._cachedMeta,e=t.data||[];if(!this.options....
function Un (line 19) | function Un(t,e,i,s){const n=vi(t.options.borderRadius,["outerStart","ou...
function Xn (line 19) | function Xn(t,e,i,s){return{x:i+t*Math.cos(e),y:s+t*Math.sin(e)}}
function qn (line 19) | function qn(t,e,i,s,n,o){const{x:a,y:r,startAngle:l,pixelMargin:h,innerR...
function Kn (line 19) | function Kn(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r,...
function Gn (line 19) | function Gn(t,e,i=e){t.lineCap=l(i.borderCapStyle,e.borderCapStyle),t.se...
function Zn (line 19) | function Zn(t,e,i){t.lineTo(i.x,i.y)}
function Jn (line 19) | function Jn(t,e,i={}){const s=t.length,{start:n=0,end:o=s-1}=i,{start:a,...
function Qn (line 19) | function Qn(t,e,i,s){const{points:n,options:o}=e,{count:a,start:r,loop:l...
function to (line 19) | function to(t,e,i,s){const n=e.points,{count:o,start:a,ilen:r}=Jn(n,i,s)...
function eo (line 19) | function eo(t){const e=t.options,i=e.borderDash&&e.borderDash.length;ret...
function so (line 19) | function so(t,e,i,s){io&&!e.options.segment?function(t,e,i,s){let n=e._p...
class no (line 19) | class no extends Hs{static id="line";static defaults={borderCapStyle:"bu...
method constructor (line 19) | constructor(t){super(),this.animated=!0,this.options=void 0,this._char...
method updateControlPoints (line 19) | updateControlPoints(t,e){const i=this.options;if((i.tension||"monotone...
method points (line 19) | set points(t){this._points=t,delete this._segments,delete this._path,t...
method points (line 19) | get points(){return this._points}
method segments (line 19) | get segments(){return this._segments||(this._segments=zi(this,this.opt...
method first (line 19) | first(){const t=this.segments,e=this.points;return t.length&&e[t[0].st...
method last (line 19) | last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-...
method interpolate (line 19) | interpolate(t,e){const i=this.options,s=t[e],n=this.points,o=Ii(this,{...
method pathSegment (line 19) | pathSegment(t,e,i){return eo(this)(t,this,e,i)}
method path (line 19) | path(t,e,i){const s=this.segments,n=eo(this);let o=this._loop;e=e||0,i...
method draw (line 19) | draw(t,e,i,s){const n=this.options||{};(this.points||[]).length&&n.bor...
function oo (line 19) | function oo(t,e,i,s){const n=t.options,{[i]:o}=t.getProps([i],s);return ...
function ao (line 19) | function ao(t,e){const{x:i,y:s,base:n,width:o,height:a}=t.getProps(["x",...
function ro (line 19) | function ro(t,e,i,s){return t?0:J(e,i,s)}
function lo (line 19) | function lo(t){const e=ao(t),i=e.right-e.left,s=e.bottom-e.top,n=functio...
function ho (line 19) | function ho(t,e,i,s){const n=null===e,o=null===i,a=t&&!(n&&o)&&ao(t,s);r...
function co (line 19) | function co(t,e){t.rect(e.x,e.y,e.w,e.h)}
function uo (line 19) | function uo(t,e,i={}){const s=t.x!==i.x?-e:0,n=t.y!==i.y?-e:0,o=(t.x+t.w...
method constructor (line 19) | constructor(t){super(),this.options=void 0,this.circumference=void 0,thi...
method inRange (line 19) | inRange(t,e,i){const s=this.getProps(["x","y"],i),{angle:n,distance:o}=X...
method getCenterPoint (line 19) | getCenterPoint(t){const{x:e,y:i,startAngle:s,endAngle:n,innerRadius:o,ou...
method tooltipPosition (line 19) | tooltipPosition(t){return this.getCenterPoint(t)}
method draw (line 19) | draw(t){const{options:e,circumference:i}=this,s=(e.offset||0)/4,n=(e.spa...
method constructor (line 19) | constructor(t){super(),this.options=void 0,this.horizontal=void 0,this.b...
method draw (line 19) | draw(t){const{inflateAmount:e,options:{borderColor:i,backgroundColor:s}}...
method inRange (line 19) | inRange(t,e,i){return ho(this,t,e,i)}
method inXRange (line 19) | inXRange(t,e){return ho(this,t,null,e)}
method inYRange (line 19) | inYRange(t,e){return ho(this,null,t,e)}
method getCenterPoint (line 19) | getCenterPoint(t){const{x:e,y:i,base:s,horizontal:n}=this.getProps(["x",...
method getRange (line 19) | getRange(t){return"x"===t?this.width/2:this.height/2}
method constructor (line 19) | constructor(t){super(),this.options=void 0,this.parsed=void 0,this.skip=...
method inRange (line 19) | inRange(t,e,i){const s=this.options,{x:n,y:o}=this.getProps(["x","y"],i)...
method inXRange (line 19) | inXRange(t,e){return oo(this,t,"x",e)}
method inYRange (line 19) | inYRange(t,e){return oo(this,t,"y",e)}
method getCenterPoint (line 19) | getCenterPoint(t){const{x:e,y:i}=this.getProps(["x","y"],t);return{x:e,y...
method size (line 19) | size(t){let e=(t=t||this.options||{}).radius||0;e=Math.max(e,e&&t.hoverR...
method draw (line 19) | draw(t,e){const i=this.options;this.skip||i.radius<.1||!Re(this,e,this.s...
method getRange (line 19) | getRange(){const t=this.options||{};return t.radius+t.hitRadius}
function go (line 19) | function go(t,e,i,s){const n=t.indexOf(e);if(-1===n)return((t,e,i,s)=>("...
function po (line 19) | function po(t){const e=this.getLabels();return t>=0&&t<e.length?e[t]:t}
function mo (line 19) | function mo(t,e,{horizontal:i,minRotation:s}){const n=$(s),o=(i?Math.sin...
class bo (line 19) | class bo extends Js{constructor(t){super(t),this.start=void 0,this.end=v...
method constructor (line 19) | constructor(t){super(t),this.start=void 0,this.end=void 0,this._startV...
method parse (line 19) | parse(t,e){return s(t)||("number"==typeof t||t instanceof Number)&&!is...
method handleTickRangeOptions (line 19) | handleTickRangeOptions(){const{beginAtZero:t}=this.options,{minDefined...
method getTickLimit (line 19) | getTickLimit(){const t=this.options.ticks;let e,{maxTicksLimit:i,stepS...
method computeTickLimit (line 19) | computeTickLimit(){return Number.POSITIVE_INFINITY}
method buildTicks (line 19) | buildTicks(){const t=this.options,e=t.ticks;let i=this.getTickLimit();...
method configure (line 19) | configure(){const t=this.ticks;let e=this.min,i=this.max;if(super.conf...
method getLabelForValue (line 19) | getLabelForValue(t){return ne(t,this.chart.options.locale,this.options...
class xo (line 19) | class xo extends bo{static id="linear";static defaults={ticks:{callback:...
method determineDataLimits (line 19) | determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a...
method computeTickLimit (line 19) | computeTickLimit(){const t=this.isHorizontal(),e=t?this.width:this.hei...
method getPixelForValue (line 19) | getPixelForValue(t){return null===t?NaN:this.getPixelForDecimal((t-thi...
method getValueForPixel (line 19) | getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)...
function vo (line 19) | function vo(t){return 1===t/Math.pow(10,_o(t))}
function Mo (line 19) | function Mo(t,e,i){const s=Math.pow(10,i),n=Math.floor(t/s);return Math....
function wo (line 19) | function wo(t,{min:e,max:i}){e=r(t.min,e);const s=[],n=_o(e);let o=funct...
class ko (line 19) | class ko extends Js{static id="logarithmic";static defaults={ticks:{call...
method constructor (line 19) | constructor(t){super(t),this.start=void 0,this.end=void 0,this._startV...
method parse (line 19) | parse(t,e){const i=bo.prototype.parse.apply(this,[t,e]);if(0!==i)retur...
method determineDataLimits (line 19) | determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a...
method handleTickRangeOptions (line 19) | handleTickRangeOptions(){const{minDefined:t,maxDefined:e}=this.getUser...
method buildTicks (line 19) | buildTicks(){const t=this.options,e=wo({min:this._userMin,max:this._us...
method getLabelForValue (line 19) | getLabelForValue(t){return void 0===t?"0":ne(t,this.chart.options.loca...
method configure (line 19) | configure(){const t=this.min;super.configure(),this._startValue=z(t),t...
method getPixelForValue (line 19) | getPixelForValue(t){return void 0!==t&&0!==t||(t=this.min),null===t||i...
method getValueForPixel (line 19) | getValueForPixel(t){const e=this.getDecimalForPixel(t);return Math.pow...
function So (line 19) | function So(t){const e=t.ticks;if(e.display&&t.display){const t=ki(e.bac...
function Po (line 19) | function Po(t,e,i,s,n){return t===s||t===n?{start:e-i/2,end:e+i/2}:t<s||...
function Do (line 19) | function Do(t){const e={l:t.left+t._padding.left,r:t.right-t._padding.ri...
function Co (line 19) | function Co(t,e,i,s,n){const o=Math.abs(Math.sin(i)),a=Math.abs(Math.cos...
function Oo (line 19) | function Oo(t,e,i){const s=t.drawingArea,{extra:n,additionalAngle:o,padd...
function Ao (line 19) | function Ao(t,e){if(!e)return!0;const{left:i,top:s,right:n,bottom:o}=t;r...
function To (line 19) | function To(t,e,i){const{left:n,top:o,right:a,bottom:r}=i,{backdropColor...
function Lo (line 19) | function Lo(t,e,i,s){const{ctx:n}=t;if(i)n.arc(t.xCenter,t.yCenter,e,0,O...
class Eo (line 19) | class Eo extends bo{static id="radialLinear";static defaults={display:!0...
method constructor (line 19) | constructor(t){super(t),this.xCenter=void 0,this.yCenter=void 0,this.d...
method setDimensions (line 19) | setDimensions(){const t=this._padding=ki(So(this.options)/2),e=this.wi...
method determineDataLimits (line 19) | determineDataLimits(){const{min:t,max:e}=this.getMinMax(!1);this.min=a...
method computeTickLimit (line 19) | computeTickLimit(){return Math.ceil(this.drawingArea/So(this.options))}
method generateTickLabels (line 19) | generateTickLabels(t){bo.prototype.generateTickLabels.call(this,t),thi...
method fit (line 19) | fit(){const t=this.options;t.display&&t.pointLabels.display?Do(this):t...
method setCenterPoint (line 19) | setCenterPoint(t,e,i,s){this.xCenter+=Math.floor((t-e)/2),this.yCenter...
method getIndexAngle (line 19) | getIndexAngle(t){return G(t*(O/(this._pointLabels.length||1))+$(this.o...
method getDistanceFromCenterForValue (line 19) | getDistanceFromCenterForValue(t){if(s(t))return NaN;const e=this.drawi...
method getValueForDistanceFromCenter (line 19) | getValueForDistanceFromCenter(t){if(s(t))return NaN;const e=t/(this.dr...
method getPointLabelContext (line 19) | getPointLabelContext(t){const e=this._pointLabels||[];if(t>=0&&t<e.len...
method getPointPosition (line 19) | getPointPosition(t,e,i=0){const s=this.getIndexAngle(t)-E+i;return{x:M...
method getPointPositionForValue (line 19) | getPointPositionForValue(t,e){return this.getPointPosition(t,this.getD...
method getBasePosition (line 19) | getBasePosition(t){return this.getPointPositionForValue(t||0,this.getB...
method getPointLabelPosition (line 19) | getPointLabelPosition(t){const{left:e,top:i,right:s,bottom:n}=this._po...
method drawBackground (line 19) | drawBackground(){const{backgroundColor:t,grid:{circular:e}}=this.optio...
method drawGrid (line 19) | drawGrid(){const t=this.ctx,e=this.options,{angleLines:i,grid:s,border...
method drawBorder (line 19) | drawBorder(){}
method drawLabels (line 19) | drawLabels(){const t=this.ctx,e=this.options,i=e.ticks;if(!i.display)r...
method drawTitle (line 19) | drawTitle(){}
function zo (line 19) | function zo(t,e){return t-e}
function Fo (line 19) | function Fo(t,e){if(s(e))return null;const i=t._adapter,{parser:n,round:...
function Vo (line 19) | function Vo(t,e,i,s){const n=Io.length;for(let o=Io.indexOf(t);o<n-1;++o...
function Bo (line 19) | function Bo(t,e,i){if(i){if(i.length){const{lo:s,hi:n}=et(i,e);t[i[s]>=e...
function Wo (line 19) | function Wo(t,e,i){const s=[],n={},o=e.length;let a,r;for(a=0;a<o;++a)r=...
class No (line 19) | class No extends Js{static id="time";static defaults={bounds:"data",adap...
method constructor (line 19) | constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._u...
method init (line 19) | init(t,e={}){const i=t.time||(t.time={}),s=this._adapter=new Rn._date(...
method parse (line 19) | parse(t,e){return void 0===t?null:Fo(this,t)}
method beforeLayout (line 19) | beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all...
method determineDataLimits (line 19) | determineDataLimits(){const t=this.options,e=this._adapter,i=t.time.un...
method _getLabelBounds (line 19) | _getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSIT...
method buildTicks (line 19) | buildTicks(){const t=this.options,e=t.time,i=t.ticks,s="labels"===i.so...
method afterAutoSkip (line 19) | afterAutoSkip(){this.options.offsetAfterAutoskip&&this.initOffsets(thi...
method initOffsets (line 19) | initOffsets(t=[]){let e,i,s=0,n=0;this.options.offset&&t.length&&(e=th...
method _generate (line 19) | _generate(){const t=this._adapter,e=this.min,i=this.max,s=this.options...
method getLabelForValue (line 19) | getLabelForValue(t){const e=this._adapter,i=this.options.time;return i...
method format (line 19) | format(t,e){const i=this.options.time.displayFormats,s=this._unit,n=e|...
method _tickFormatFunction (line 19) | _tickFormatFunction(t,e,i,s){const n=this.options,o=n.ticks.callback;i...
method generateTickLabels (line 19) | generateTickLabels(t){let e,i,s;for(e=0,i=t.length;e<i;++e)s=t[e],s.la...
method getDecimalForValue (line 19) | getDecimalForValue(t){return null===t?NaN:(t-this.min)/(this.max-this....
method getPixelForValue (line 19) | getPixelForValue(t){const e=this._offsets,i=this.getDecimalForValue(t)...
method getValueForPixel (line 19) | getValueForPixel(t){const e=this._offsets,i=this.getDecimalForPixel(t)...
method _getLabelSize (line 19) | _getLabelSize(t){const e=this.options.ticks,i=this.ctx.measureText(t)....
method _getLabelCapacity (line 19) | _getLabelCapacity(t){const e=this.options.time,i=e.displayFormats,s=i[...
method getDataTimestamps (line 19) | getDataTimestamps(){let t,e,i=this._cache.data||[];if(i.length)return ...
method getLabelTimestamps (line 19) | getLabelTimestamps(){const t=this._cache.labels||[];let e,i;if(t.lengt...
method normalize (line 19) | normalize(t){return lt(t.sort(zo))}
function Ho (line 19) | function Ho(t,e,i){let s,n,o,a,r=0,l=t.length-1;i?(e>=t[r].pos&&e<=t[l]....
method constructor (line 19) | constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this....
method init (line 19) | init(t){const e=this._addedLabels;if(e.length){const t=this.getLabels();...
method parse (line 19) | parse(t,e){if(s(t))return null;const i=this.getLabels();return((t,e)=>nu...
method determineDataLimits (line 19) | determineDataLimits(){const{minDefined:t,maxDefined:e}=this.getUserBound...
method buildTicks (line 19) | buildTicks(){const t=this.min,e=this.max,i=this.options.offset,s=[];let ...
method getLabelForValue (line 19) | getLabelForValue(t){return po.call(this,t)}
method configure (line 19) | configure(){super.configure(),this.isHorizontal()||(this._reversePixels=...
method getPixelForValue (line 19) | getPixelForValue(t){return"number"!=typeof t&&(t=this.parse(t)),null===t...
method getPixelForTick (line 19) | getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this...
method getValueForPixel (line 19) | getValueForPixel(t){return Math.round(this._startValue+this.getDecimalFo...
method getBasePixel (line 19) | getBasePixel(){return this.bottom}
method constructor (line 19) | constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRa...
method initOffsets (line 19) | initOffsets(){const t=this._getTimestampsForTable(),e=this._table=this.b...
method buildLookupTable (line 19) | buildLookupTable(t){const{min:e,max:i}=this,s=[],n=[];let o,a,r,l,h;for(...
method _generate (line 19) | _generate(){const t=this.min,e=this.max;let i=super.getDataTimestamps();...
method _getTimestampsForTable (line 19) | _getTimestampsForTable(){let t=this._cache.all||[];if(t.length)return t;...
method getDecimalForValue (line 19) | getDecimalForValue(t){return(Ho(this._table,t)-this._minPos)/this._table...
method getValueForPixel (line 19) | getValueForPixel(t){const e=this._offsets,i=this.getDecimalForPixel(t)/e...
function Uo (line 19) | function Uo(t){return $o[t%$o.length]}
function Xo (line 19) | function Xo(t){return Yo[t%Yo.length]}
function qo (line 19) | function qo(t){let e=0;return(i,s)=>{const n=t.getDatasetMeta(s).control...
function Ko (line 19) | function Ko(t){let e;for(e in t)if(t[e].borderColor||t[e].backgroundColo...
method beforeLayout (line 19) | beforeLayout(t,e,i){if(!i.enabled)return;const{data:{datasets:s},options...
function Zo (line 19) | function Zo(t){if(t._decimated){const e=t._data;delete t._decimated,dele...
function Jo (line 19) | function Jo(t){t.data.datasets.forEach((t=>{Zo(t)}))}
method destroy (line 19) | destroy(t){Jo(t)}
function ta (line 19) | function ta(t,e,i,s){if(s)return;let n=e[t],o=i[t];return"angle"===t&&(n...
function ea (line 19) | function ea(t,e,i){for(;e>t;e--){const t=i[e];if(!isNaN(t.x)&&!isNaN(t.y...
function ia (line 19) | function ia(t,e,i,s){return t&&e?s(t[i],e[i]):t?t[i]:e?e[i]:0}
function sa (line 19) | function sa(t,e){let i=[],s=!1;return n(t)?(s=!0,i=t):i=function(t,e){co...
function na (line 19) | function na(t){return t&&!1!==t.fill}
function oa (line 19) | function oa(t,e,i){let s=t[e].fill;const n=[e];let o;if(!i)return s;for(...
function aa (line 19) | function aa(t,e,i){const s=function(t){const e=t.options,i=e.fill;let s=...
function ra (line 19) | function ra(t,e,i){const s=[];for(let n=0;n<i.length;n++){const o=i[n],{...
function la (line 19) | function la(t,e,i){const s=t.interpolate(e,i);if(!s)return{};const n=s[i...
class ha (line 19) | class ha{constructor(t){this.x=t.x,this.y=t.y,this.radius=t.radius}pathS...
method constructor (line 19) | constructor(t){this.x=t.x,this.y=t.y,this.radius=t.radius}
method pathSegment (line 19) | pathSegment(t,e,i){const{x:s,y:n,radius:o}=this;return e=e||{start:0,e...
method interpolate (line 19) | interpolate(t){const{x:e,y:i,radius:s}=this,n=t.angle;return{x:e+Math....
function ca (line 19) | function ca(t){const{chart:e,fill:i,line:s}=t;if(a(i))return function(t,...
function da (line 19) | function da(t,e,i){const s=ca(e),{line:n,scale:o,axis:a}=e,r=n.options,l...
function ua (line 19) | function ua(t,e,i){const{segments:s,points:n}=e;let o=!0,a=!1;t.beginPat...
function fa (line 19) | function fa(t,e){const{line:i,target:s,property:n,color:o,scale:a}=e,r=f...
function ga (line 19) | function ga(t,e,i){const{top:s,bottom:n}=e.chart.chartArea,{property:o,s...
function pa (line 19) | function pa(t,e,i,s){const n=e.interpolate(i,s);n&&t.lineTo(n.x,n.y)}
method afterDatasetsUpdate (line 19) | afterDatasetsUpdate(t,e,i){const s=(t.data.datasets||[]).length,n=[];let...
method beforeDraw (line 19) | beforeDraw(t,e,i){const s="beforeDraw"===i.drawTime,n=t.getSortedVisible...
method beforeDatasetsDraw (line 19) | beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;co...
method beforeDatasetDraw (line 19) | beforeDatasetDraw(t,e,i){const s=e.meta.$filler;na(s)&&"beforeDatasetDra...
class xa (line 19) | class xa extends Hs{constructor(t){super(),this._added=!1,this.legendHit...
method constructor (line 19) | constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hov...
method update (line 19) | update(t,e,i){this.maxWidth=t,this.maxHeight=e,this._margins=i,this.se...
method setDimensions (line 19) | setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.lef...
method buildLabels (line 19) | buildLabels(){const t=this.options.labels||{};let e=d(t.generateLabels...
method fit (line 19) | fit(){const{options:t,ctx:e}=this;if(!t.display)return void(this.width...
method _fitRows (line 19) | _fitRows(t,e,i,s){const{ctx:n,maxWidth:o,options:{labels:{padding:a}}}...
method _fitCols (line 19) | _fitCols(t,e,i,s){const{ctx:n,maxHeight:o,options:{labels:{padding:a}}...
method adjustHitBoxes (line 19) | adjustHitBoxes(){if(!this.options.display)return;const t=this._compute...
method isHorizontal (line 19) | isHorizontal(){return"top"===this.options.position||"bottom"===this.op...
method draw (line 19) | draw(){if(this.options.display){const t=this.ctx;Ie(t,this),this._draw...
method _draw (line 19) | _draw(){const{options:t,columnSizes:e,lineWidths:i,ctx:s}=this,{align:...
method drawTitle (line 19) | drawTitle(){const t=this.options,e=t.title,i=Si(e.font),s=ki(e.padding...
method _computeTitleHeight (line 19) | _computeTitleHeight(){const t=this.options.title,e=Si(t.font),i=ki(t.p...
method _getLegendItemAt (line 19) | _getLegendItemAt(t,e){let i,s,n;if(tt(t,this.left,this.right)&&tt(e,th...
method handleEvent (line 19) | handleEvent(t){const e=this.options;if(!function(t,e){if(("mousemove"=...
function _a (line 19) | function _a(t,e){return e*(t.text?t.text.length:0)}
method start (line 19) | start(t,e,i){const s=t.legend=new xa({ctx:t.ctx,options:i,chart:t});as.c...
method stop (line 19) | stop(t){as.removeBox(t,t.legend),delete t.legend}
method beforeUpdate (line 19) | beforeUpdate(t,e,i){const s=t.legend;as.configure(t,s,i),s.options=i}
method afterUpdate (line 19) | afterUpdate(t){const e=t.legend;e.buildLabels(),e.adjustHitBoxes()}
method afterEvent (line 19) | afterEvent(t,e){e.replay||t.legend.handleEvent(e.event)}
method onClick (line 19) | onClick(t,e,i){const s=e.datasetIndex,n=i.chart;n.isDatasetVisible(s)?(n...
method generateLabels (line 19) | generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,point...
class va (line 19) | class va extends Hs{constructor(t){super(),this.chart=t.chart,this.optio...
method constructor (line 19) | constructor(t){super(),this.chart=t.chart,this.options=t.options,this....
method update (line 19) | update(t,e){const i=this.options;if(this.left=0,this.top=0,!i.display)...
method isHorizontal (line 19) | isHorizontal(){const t=this.options.position;return"top"===t||"bottom"...
method _drawArgs (line 19) | _drawArgs(t){const{top:e,left:i,bottom:s,right:n,options:o}=this,a=o.a...
method draw (line 19) | draw(){const t=this.ctx,e=this.options;if(!e.display)return;const i=Si...
method start (line 19) | start(t,e,i){!function(t,e){const i=new va({ctx:t.ctx,options:e,chart:t}...
method stop (line 19) | stop(t){const e=t.titleBlock;as.removeBox(t,e),delete t.titleBlock}
method beforeUpdate (line 19) | beforeUpdate(t,e,i){const s=t.titleBlock;as.configure(t,s,i),s.options=i}
method start (line 19) | start(t,e,i){const s=new va({ctx:t.ctx,options:i,chart:t});as.configure(...
method stop (line 19) | stop(t){as.removeBox(t,wa.get(t)),wa.delete(t)}
method beforeUpdate (line 19) | beforeUpdate(t,e,i){const s=wa.get(t);as.configure(t,s,i),s.options=i}
method average (line 19) | average(t){if(!t.length)return!1;let e,i,s=0,n=0,o=0;for(e=0,i=t.length;...
method nearest (line 19) | nearest(t,e){if(!t.length)return!1;let i,s,n,o=e.x,a=e.y,r=Number.POSITI...
function Pa (line 19) | function Pa(t,e){return e&&(n(e)?Array.prototype.push.apply(t,e):t.push(...
function Da (line 19) | function Da(t){return("string"==typeof t||t instanceof String)&&t.indexO...
function Ca (line 19) | function Ca(t,e){const{element:i,datasetIndex:s,index:n}=e,o=t.getDatase...
function Oa (line 19) | function Oa(t,e){const i=t.chart.ctx,{body:s,footer:n,title:o}=t,{boxWid...
function Aa (line 19) | function Aa(t,e,i,s){const{x:n,width:o}=i,{width:a,chartArea:{left:r,rig...
function Ta (line 19) | function Ta(t,e,i){const s=i.yAlign||e.yAlign||function(t,e){const{y:i,h...
function La (line 19) | function La(t,e,i,s){const{caretSize:n,caretPadding:o,cornerRadius:a}=t,...
function Ea (line 19) | function Ea(t,e,i){const s=ki(i.padding);return"center"===e?t.x+t.width/...
function Ra (line 19) | function Ra(t){return Pa([],Da(t))}
function Ia (line 19) | function Ia(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tool...
method title (line 19) | title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,s=i?i.length:...
method label (line 19) | label(t){if(this&&this.options&&"dataset"===this.options.mode)return t.l...
method labelColor (line 19) | labelColor(t){const e=t.chart.getDatasetMeta(t.datasetIndex).controller....
method labelTextColor (line 19) | labelTextColor(){return this.options.bodyColor}
method labelPointStyle (line 19) | labelPointStyle(t){const e=t.chart.getDatasetMeta(t.datasetIndex).contro...
function Fa (line 19) | function Fa(t,e,i,s){const n=t[e].call(i,s);return void 0===n?za[e].call...
class Va (line 19) | class Va extends Hs{static positioners=Sa;constructor(t){super(),this.op...
method constructor (line 19) | constructor(t){super(),this.opacity=0,this._active=[],this._eventPosit...
method initialize (line 19) | initialize(t){this.options=t,this._cachedAnimations=void 0,this.$conte...
method _resolveAnimations (line 19) | _resolveAnimations(){const t=this._cachedAnimations;if(t)return t;cons...
method getContext (line 19) | getContext(){return this.$context||(this.$context=(t=this.chart.getCon...
method getTitle (line 19) | getTitle(t,e){const{callbacks:i}=e,s=Fa(i,"beforeTitle",this,t),n=Fa(i...
method getBeforeBody (line 19) | getBeforeBody(t,e){return Ra(Fa(e.callbacks,"beforeBody",this,t))}
method getBody (line 19) | getBody(t,e){const{callbacks:i}=e,s=[];return u(t,(t=>{const e={before...
method getAfterBody (line 19) | getAfterBody(t,e){return Ra(Fa(e.callbacks,"afterBody",this,t))}
method getFooter (line 19) | getFooter(t,e){const{callbacks:i}=e,s=Fa(i,"beforeFooter",this,t),n=Fa...
method _createItems (line 19) | _createItems(t){const e=this._active,i=this.chart.data,s=[],n=[],o=[];...
method update (line 19) | update(t,e){const i=this.options.setContext(this.getContext()),s=this....
method drawCaret (line 19) | drawCaret(t,e,i,s){const n=this.getCaretPosition(t,i,s);e.lineTo(n.x1,...
method getCaretPosition (line 19) | getCaretPosition(t,e,i){const{xAlign:s,yAlign:n}=this,{caretSize:o,cor...
method drawTitle (line 19) | drawTitle(t,e,i){const s=this.title,n=s.length;let o,a,r;if(n){const l...
method _drawColorBox (line 19) | _drawColorBox(t,e,i,s,n){const a=this.labelColors[i],r=this.labelPoint...
method drawBody (line 19) | drawBody(t,e,i){const{body:s}=this,{bodySpacing:n,bodyAlign:o,displayC...
method drawFooter (line 19) | drawFooter(t,e,i){const s=this.footer,n=s.length;let o,a;if(n){const r...
method drawBackground (line 19) | drawBackground(t,e,i,s){const{xAlign:n,yAlign:o}=this,{x:a,y:r}=t,{wid...
method _updateAnimationTarget (line 19) | _updateAnimationTarget(t){const e=this.chart,i=this.$animations,s=i&&i...
method _willRender (line 19) | _willRender(){return!!this.opacity}
method draw (line 19) | draw(t){const e=this.options.setContext(this.getContext());let i=this....
method getActiveElements (line 19) | getActiveElements(){return this._active||[]}
method setActiveElements (line 19) | setActiveElements(t,e){const i=this._active,s=t.map((({datasetIndex:t,...
method handleEvent (line 19) | handleEvent(t,e,i=!0){if(e&&this._ignoreReplayEvents)return!1;this._ig...
method _getActiveElements (line 19) | _getActiveElements(t,e,i,s){const n=this.options;if("mouseout"===t.typ...
method _positionChanged (line 19) | _positionChanged(t,e){const{caretX:i,caretY:s,options:n}=this,o=Sa[n.p...
method afterInit (line 19) | afterInit(t,e,i){i&&(t.tooltip=new Va({chart:t,options:i}))}
method beforeUpdate (line 19) | beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)}
method reset (line 19) | reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)}
method afterDraw (line 19) | afterDraw(t){const e=t.tooltip;if(e&&e._willRender()){const i={tooltip:e...
method afterEvent (line 19) | afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.e...
FILE: scripts/notifications.js
function openNotificationsSettings (line 1) | function openNotificationsSettings(type) {
function makeFetchCall (line 22) | function makeFetchCall(url, data, button) {
function saveNotifications (line 47) | function saveNotifications() {
function saveNotificationsEmailButton (line 58) | function saveNotificationsEmailButton() {
function testNotificationEmailButton (line 85) | function testNotificationEmailButton() {
function saveNotificationsWebhookButton (line 108) | function saveNotificationsWebhookButton() {
function testNotificationsWebhookButton (line 131) | function testNotificationsWebhookButton() {
function saveNotificationsTelegramButton (line 156) | function saveNotificationsTelegramButton() {
function testNotificationsTelegramButton (line 173) | function testNotificationsTelegramButton() {
function testNotificationsPushPlusButton (line 190) | function testNotificationsPushPlusButton() {
function saveNotificationsPushPlusButton (line 205) | function saveNotificationsPushPlusButton() {
function testNotificationsMattermostButton (line 220) | function testNotificationsMattermostButton() {
function saveNotificationsMattermostButton (line 239) | function saveNotificationsMattermostButton() {
function saveNotificationsGotifyButton (line 258) | function saveNotificationsGotifyButton() {
function testNotificationsGotifyButton (line 278) | function testNotificationsGotifyButton() {
function saveNotificationsPushoverButton (line 297) | function saveNotificationsPushoverButton() {
function testNotificationsPushoverButton (line 314) | function testNotificationsPushoverButton() {
function saveNotificationsDiscordButton (line 331) | function saveNotificationsDiscordButton() {
function testNotificationsDiscordButton (line 350) | function testNotificationsDiscordButton() {
function testNotificationsNtfyButton (line 369) | function testNotificationsNtfyButton() {
function saveNotificationsNtfyButton (line 388) | function saveNotificationsNtfyButton() {
function testNotificationsServerchanButton (line 409) | function testNotificationsServerchanButton() {
function saveNotificationsServerchanButton (line 424) | function saveNotificationsServerchanButton() {
FILE: scripts/profile.js
function toggleAvatarSelect (line 45) | function toggleAvatarSelect() {
function closeAvatarSelect (line 54) | function closeAvatarSelect() {
function changeAvatar (line 67) | function changeAvatar(src) {
function successfulUpload (line 71) | function successfulUpload(field, msg) {
function deleteAvatar (line 91) | function deleteAvatar(path) {
function enableTotp (line 117) | function enableTotp() {
function openTotpPopup (line 150) | function openTotpPopup() {
function closeTotpPopup (line 158) | function closeTotpPopup() {
function submitTotp (line 170) | function submitTotp() {
function copyBackupCodes (line 210) | function copyBackupCodes() {
function downloadBackupCodes (line 223) | function downloadBackupCodes() {
function closeTotpDisablePopup (line 238) | function closeTotpDisablePopup() {
function disableTotp (line 243) | function disableTotp() {
function submitDisableTotp (line 248) | function submitDisableTotp() {
function regenerateApiKey (line 275) | function regenerateApiKey() {
function exportAsJson (line 304) | function exportAsJson() {
function exportAsCsv (line 327) | function exportAsCsv() {
function deleteAccount (line 352) | function deleteAccount(userId) {
FILE: scripts/registration.js
function setCookie (line 1) | function setCookie(name, value, days) {
function storeFormFieldValue (line 11) | function storeFormFieldValue(fieldId) {
function storeFormFields (line 18) | function storeFormFields() {
function restoreFormFieldValue (line 28) | function restoreFormFieldValue(fieldId) {
function restoreFormFields (line 35) | function restoreFormFields() {
function removeFromStorage (line 45) | function removeFromStorage() {
function changeLanguage (line 55) | function changeLanguage(selectedLanguage) {
function runDatabaseMigration (line 61) | function runDatabaseMigration() {
function showErrorMessage (line 71) | function showErrorMessage(message) {
function showSuccessMessage (line 102) | function showSuccessMessage(message) {
function openRestoreDBFileSelect (line 134) | function openRestoreDBFileSelect() {
function restoreDB (line 138) | function restoreDB() {
function checkThemeNeedsUpdate (line 173) | function checkThemeNeedsUpdate() {
function enableGoToLoginButton (line 185) | function enableGoToLoginButton() {
FILE: scripts/settings.js
function saveBudget (line 21) | function saveBudget() {
function addMemberButton (line 53) | function addMemberButton(memberId) {
function removeMember (line 126) | function removeMember(memberId) {
function editMember (line 160) | function editMember(memberId) {
function addCategoryButton (line 211) | function addCategoryButton(categoryId) {
function removeCategory (line 285) | function removeCategory(categoryId) {
function editCategory (line 319) | function editCategory(categoryId) {
function addCurrencyButton (line 368) | function addCurrencyButton(currencyId) {
function removeCurrency (line 452) | function removeCurrency(currencyId) {
function editCurrency (line 485) | function editCurrency(currencyId) {
function togglePayment (line 538) | function togglePayment(paymentId) {
function renamePayment (line 608) | function renamePayment(paymentId, newName) {
function handleFileSelect (line 653) | function handleFileSelect(event) {
function setSearchButtonStatus (line 672) | function setSearchButtonStatus() {
function searchPaymentIcon (line 685) | function searchPaymentIcon() {
function displayImageResults (line 709) | function displayImageResults(imageSources) {
function selectWebIcon (line 726) | function selectWebIcon(url) {
function closeIconSearch (line 735) | function closeIconSearch() {
function resetFormIcon (line 742) | function resetFormIcon() {
function reloadPaymentMethods (line 748) | function reloadPaymentMethods() {
function addPaymentMethod (line 759) | function addPaymentMethod() {
function deletePaymentMethod (line 797) | function deletePaymentMethod(paymentId) {
function savePaymentMethodsSorting (line 823) | function savePaymentMethodsSorting() {
function addFixerKeyButton (line 886) | function addFixerKeyButton() {
function storeSettingsOnDB (line 934) | function storeSettingsOnDB(endpoint, value) {
function setShowMonthlyPrice (line 953) | function setShowMonthlyPrice() {
function setConvertCurrency (line 960) | function setConvertCurrency() {
function setRemoveBackground (line 967) | function setRemoveBackground() {
function setHideDisabled (line 974) | function setHideDisabled() {
function setDisabledToBottom (line 981) | function setDisabledToBottom() {
function setShowOriginalPrice (line 988) | function setShowOriginalPrice() {
function setMobileNavigation (line 995) | function setMobileNavigation() {
function setShowSubscriptionProgress (line 1002) | function setShowSubscriptionProgress() {
function saveCategorySorting (line 1009) | function saveCategorySorting() {
function fetch_ai_models (line 1049) | function fetch_ai_models() {
function toggleAiInputs (line 1083) | function toggleAiInputs() {
function toggleAiApiKeyVisibility (line 1115) | function toggleAiApiKeyVisibility() {
function saveAiSettingsButton (line 1132) | function saveAiSettingsButton() {
function runAiRecommendations (line 1166) | function runAiRecommendations() {
FILE: scripts/stats.js
function loadGraph (line 1) | function loadGraph(container, dataPoints, currency, run) {
function loadLineGraph (line 48) | function loadLineGraph(container, dataPoints, currency, run) {
function closeSubMenus (line 93) | function closeSubMenus() {
function toggleSubMenu (line 122) | function toggleSubMenu(subMenu) {
function clearFilters (line 177) | function clearFilters() {
FILE: scripts/subscriptions.js
function toggleOpenSubscription (line 5) | function toggleOpenSubscription(subId) {
function toggleSortOptions (line 10) | function toggleSortOptions() {
function toggleNotificationDays (line 16) | function toggleNotificationDays() {
function resetForm (line 22) | function resetForm() {
function fillEditFormFields (line 54) | function fillEditFormFields(subscription) {
function openEditSubscription (line 133) | function openEditSubscription(event, id) {
function addSubscription (line 161) | function addSubscription() {
function closeAddSubscription (line 173) | function closeAddSubscription() {
function handleFileSelect (line 184) | function handleFileSelect(event) {
function deleteSubscription (line 203) | function deleteSubscription(event, id) {
function cloneSubscription (line 236) | function cloneSubscription(event, id) {
function renewSubscription (line 269) | function renewSubscription(event, id) {
function setSearchButtonStatus (line 302) | function setSearchButtonStatus() {
function searchLogo (line 315) | function searchLogo() {
function displayImageResults (line 339) | function displayImageResults(imageSources) {
function selectWebLogo (line 356) | function selectWebLogo(url) {
function closeLogoSearch (line 365) | function closeLogoSearch() {
function fetchSubscriptions (line 372) | function fetchSubscriptions(id, event, initiator) {
function setSortOption (line 423) | function setSortOption(sortOption) {
function convertSvgToPng (line 442) | function convertSvgToPng(file, callback) {
function dataURLtoFile (line 463) | function dataURLtoFile(dataurl, filename) {
function submitFormData (line 477) | function submitFormData(formData, submitButton, endpoint) {
function searchSubscriptions (line 543) | function searchSubscriptions() {
function clearSearch (line 565) | function clearSearch() {
function closeSubMenus (line 572) | function closeSubMenus() {
function setSwipeElements (line 580) | function setSwipeElements() {
function toggleSubMenu (line 666) | function toggleSubMenu(subMenu) {
function toggleReplacementSub (line 676) | function toggleReplacementSub() {
function clearFilters (line 760) | function clearFilters() {
function expandActions (line 787) | function expandActions(event, subscriptionId) {
function swipeHintAnimation (line 812) | function swipeHintAnimation() {
function autoFillNextPaymentDate (line 836) | function autoFillNextPaymentDate(e) {
function toISOStringWithTimezone (line 883) | function toISOStringWithTimezone(date) {
FILE: scripts/theme.js
function switchTheme (line 1) | function switchTheme() {
function setDarkTheme (line 34) | function setDarkTheme(theme) {
function setTheme (line 100) | function setTheme(themeColor) {
function resetCustomColors (line 158) | function resetCustomColors() {
function saveCustomColors (line 202) | function saveCustomColors() {
function saveCustomCss (line 237) | function saveCustomCss() {
FILE: service-worker.js
constant STATIC_CACHE (line 1) | const STATIC_CACHE = 'static-cache-v1';
constant PAGES_CACHE (line 2) | const PAGES_CACHE = 'pages-cache-v1';
constant LOGOS_CACHE (line 3) | const LOGOS_CACHE = 'logos-cache-v1';
Condensed preview — 345 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,595K chars).
[
{
"path": ".dockerignore",
"chars": 117,
"preview": "# Exclude .vscode directory\r\n.vscode/\r\n\r\n# Exclude .git directory\r\n.git/\r\n.github/\r\n\r\n*.md\r\n.gitignore\r\n.dockerignore"
},
{
"path": ".github/FUNDING.yml",
"chars": 116,
"preview": "# These are supported funding model platforms\n\ngithub: [ellite]\ncustom: ['https://www.paypal.com/paypalme/miguelr']\n"
},
{
"path": ".github/workflows/build-release.yaml",
"chars": 3246,
"preview": "name: Build & Release\non:\n push:\n branches:\n - \"*\"\n pull_request:\n branches:\n - main\npermissions:\n co"
},
{
"path": ".gitignore",
"chars": 118,
"preview": "/db/*\r\n!/db/wallos.empty.db\r\n/images/uploads/logos/*\r\n!/images/uploads/logos/wallos.png\r\n.DS_Store\r\n.idea/\r\n.vscode/\r\n"
},
{
"path": ".tmp/.gitignore",
"chars": 14,
"preview": "*\n!.gitignore\n"
},
{
"path": "CHANGELOG.md",
"chars": 76804,
"preview": "# Changelog\n\n## [4.7.2](https://github.com/ellite/Wallos/compare/v4.7.1...v4.7.2) (2026-03-19)\n\n\n### Bug Fixes\n\n* passwo"
},
{
"path": "CONTRIBUTING.md",
"chars": 3219,
"preview": "# Contributing to wallos\n\nWe welcome contributions from the community and look forward to working with you to improve th"
},
{
"path": "Dockerfile",
"chars": 2056,
"preview": "# Use the php:8.3-fpm-alpine base image\nFROM php:8.3-fpm-alpine\n\n# Set working directory to /var/www/html\nWORKDIR /var/w"
},
{
"path": "LICENSE.md",
"chars": 35801,
"preview": "GNU GENERAL PUBLIC LICENSE\r\n Version 3, 29 June 2007\r\n\r\n Copyright (C) 2007 Free Software Foundati"
},
{
"path": "README.md",
"chars": 10716,
"preview": "<div align=\"center\">\r\n <picture>\r\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"./images/siteicons/walloswhi"
},
{
"path": "SECURITY.md",
"chars": 1187,
"preview": "# Security Policy\r\n\r\n## Reporting a Vulnerability\r\n\r\nIf you discover any security vulnerabilities in this project, pleas"
},
{
"path": "about.php",
"chars": 6041,
"preview": "<?php\r\nrequire_once 'includes/header.php';\r\n\r\n$wallosIsUpToDate = true;\r\nif (!is_null($settings['latest_version'])) {\r\n "
},
{
"path": "admin.php",
"chars": 24533,
"preview": "<?php\r\nrequire_once 'includes/header.php';\r\n\r\nif ($isAdmin != 1) {\r\n header('Location: index.php');\r\n exit;\r\n}\r\n\r\n"
},
{
"path": "api/admin/get_admin_settings.php",
"chars": 3068,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- api_key: the "
},
{
"path": "api/admin/get_oidc_settings.php",
"chars": 3285,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- api_key: the "
},
{
"path": "api/admin/set_disable_password_login.php",
"chars": 2770,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts POST requests only.\r\nIt receives the following parameters:\r\n- api_key: the API key "
},
{
"path": "api/categories/get_categories.php",
"chars": 3246,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- api_key: the "
},
{
"path": "api/currencies/get_currencies.php",
"chars": 3570,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- api_key: the "
},
{
"path": "api/fixer/get_fixer.php",
"chars": 2645,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- api_key: the "
},
{
"path": "api/household/get_household.php",
"chars": 3141,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- api_key: the "
},
{
"path": "api/notifications/get_notification_settings.php",
"chars": 7337,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- api_key: the "
},
{
"path": "api/payment_methods/get_payment_methods.php",
"chars": 4032,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- api_key: the "
},
{
"path": "api/settings/get_settings.php",
"chars": 3253,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- api_key: the "
},
{
"path": "api/status/version.php",
"chars": 2147,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- api_key: the "
},
{
"path": "api/subscriptions/get_ical_feed.php",
"chars": 8196,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- convert_curre"
},
{
"path": "api/subscriptions/get_monthly_cost.php",
"chars": 6642,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- month: the mo"
},
{
"path": "api/subscriptions/get_subscriptions.php",
"chars": 13127,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- member: comma"
},
{
"path": "api/users/get_user.php",
"chars": 2307,
"preview": "<?php\r\n/*\r\nThis API Endpoint accepts both POST and GET requests.\r\nIt receives the following parameters:\r\n- api_key: the "
},
{
"path": "calendar.php",
"chars": 15811,
"preview": "<?php\r\nrequire_once 'includes/header.php';\r\n\r\nfunction getPriceConverted($price, $currency, $database, $userId)\r\n{\r\n $q"
},
{
"path": "cronjobs",
"chars": 1239,
"preview": "# Run the scripts every day\r\n0 1 * * * /usr/local/bin/php /var/www/html/endpoints/cronjobs/updatenextpayment.php >> /var"
},
{
"path": "docker-compose.yaml",
"chars": 341,
"preview": "services:\n wallos:\n container_name: wallos\n image: bellamy/wallos:latest\n ports:\n - \"8282:80/tcp\"\n env"
},
{
"path": "endpoints/admin/adduser.php",
"chars": 12501,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint_admin.php';\r\n"
},
{
"path": "endpoints/admin/deleteunusedlogos.php",
"chars": 1456,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint_admin.php';"
},
{
"path": "endpoints/admin/deleteuser.php",
"chars": 4262,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint_admin.php';"
},
{
"path": "endpoints/admin/enableoidc.php",
"chars": 722,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint_admin.php';"
},
{
"path": "endpoints/admin/saveoidcsettings.php",
"chars": 4117,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint_admin.php';"
},
{
"path": "endpoints/admin/saveopenregistrations.php",
"chars": 2118,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint_admin.php';"
},
{
"path": "endpoints/admin/savesecuritysettings.php",
"chars": 1106,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint_admin.php';"
},
{
"path": "endpoints/admin/savesmtpsettings.php",
"chars": 1564,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint_admin.php';"
},
{
"path": "endpoints/admin/updatenotification.php",
"chars": 731,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint_admin.php';"
},
{
"path": "endpoints/ai/delete_recommendation.php",
"chars": 995,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$inp"
},
{
"path": "endpoints/ai/fetch_models.php",
"chars": 4991,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequ"
},
{
"path": "endpoints/ai/generate_recommendations.php",
"chars": 12602,
"preview": "<?php\r\nset_time_limit(300);\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_"
},
{
"path": "endpoints/ai/save_settings.php",
"chars": 3232,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequir"
},
{
"path": "endpoints/categories/category.php",
"chars": 5811,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/inputvalidation.php';\r\nrequire_"
},
{
"path": "endpoints/cronjobs/checkforupdates.php",
"chars": 1136,
"preview": "<?php\r\n\r\nrequire_once 'validate.php';\r\nrequire_once __DIR__ . '/../../includes/connect_endpoint_crontabs.php';\r\n\r\n$optio"
},
{
"path": "endpoints/cronjobs/cleanupresettokens.php",
"chars": 397,
"preview": "<?php\r\n\r\nrequire_once 'validate.php';\r\nrequire_once __DIR__ . '/../../includes/connect_endpoint_crontabs.php';\r\n\r\n$delet"
},
{
"path": "endpoints/cronjobs/createdatabase.php",
"chars": 8432,
"preview": "<?php\r\n\r\n$databaseFile = __DIR__ . '/../../db/wallos.db';\r\n\r\nif (!file_exists($databaseFile)) {\r\n echo \"Database does"
},
{
"path": "endpoints/cronjobs/sendcancellationnotifications.php",
"chars": 28834,
"preview": "<?php\r\nuse PHPMailer\\PHPMailer\\PHPMailer;\r\nuse PHPMailer\\PHPMailer\\SMTP;\r\nuse PHPMailer\\PHPMailer\\Exception;\r\n\r\nrequire_"
},
{
"path": "endpoints/cronjobs/sendnotifications.php",
"chars": 43636,
"preview": "<?php\r\nuse PHPMailer\\PHPMailer\\PHPMailer;\r\nuse PHPMailer\\PHPMailer\\SMTP;\r\nuse PHPMailer\\PHPMailer\\Exception;\r\n\r\nrequire_"
},
{
"path": "endpoints/cronjobs/sendresetpasswordemails.php",
"chars": 3579,
"preview": "<?php\r\nuse PHPMailer\\PHPMailer\\PHPMailer;\r\nuse PHPMailer\\PHPMailer\\SMTP;\r\nuse PHPMailer\\PHPMailer\\Exception;\r\n\r\nrequire_"
},
{
"path": "endpoints/cronjobs/sendverificationemails.php",
"chars": 3709,
"preview": "<?php\r\nuse PHPMailer\\PHPMailer\\PHPMailer;\r\nuse PHPMailer\\PHPMailer\\SMTP;\r\nuse PHPMailer\\PHPMailer\\Exception;\r\n\r\nrequire_"
},
{
"path": "endpoints/cronjobs/settimezone.php",
"chars": 211,
"preview": "<?php\r\n\r\n$timezone = getenv('TZ');\r\nif ($timezone == '') {\r\n $timezone = date_default_timezone_get();\r\n if ($timez"
},
{
"path": "endpoints/cronjobs/storetotalyearlycost.php",
"chars": 3038,
"preview": "<?php\r\n\r\nrequire_once __DIR__ . '/../../includes/connect_endpoint_crontabs.php';\r\n\r\nrequire 'settimezone.php';\r\n\r\nif (ph"
},
{
"path": "endpoints/cronjobs/updateexchange.php",
"chars": 4767,
"preview": "<?php\r\nrequire_once 'validate.php';\r\nrequire_once __DIR__ . '/../../includes/connect_endpoint_crontabs.php';\r\n\r\nrequire "
},
{
"path": "endpoints/cronjobs/updatenextpayment.php",
"chars": 2552,
"preview": "<?php\r\n\r\nrequire_once 'validate.php';\r\nrequire_once __DIR__ . '/../../includes/connect_endpoint_crontabs.php';\r\n\r\nrequir"
},
{
"path": "endpoints/cronjobs/validate.php",
"chars": 259,
"preview": "<?php\r\n\r\nsession_start();\r\n\r\n$userId = 0;\r\nif (isset($_SESSION['loggedin']) && $_SESSION['loggedin'] === true) {\r\n $u"
},
{
"path": "endpoints/currency/currency.php",
"chars": 5575,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/inputvalidation.php';\r\nrequire_"
},
{
"path": "endpoints/currency/fixer_api_key.php",
"chars": 2051,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$new"
},
{
"path": "endpoints/currency/update_exchange.php",
"chars": 4423,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$sho"
},
{
"path": "endpoints/db/backup.php",
"chars": 1926,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint_admin.php';\r\n"
},
{
"path": "endpoints/db/import.php",
"chars": 4112,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\n\r\n$result = $db->query(\"SELECT COUNT(*) as count FROM user\")"
},
{
"path": "endpoints/db/migrate.php",
"chars": 1835,
"preview": "<?php\nfunction errorHandler($severity, $message, $file, $line)\n{\n throw new ErrorException($message, 0, $severity, $f"
},
{
"path": "endpoints/db/restore.php",
"chars": 3496,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint_admin.php';\r\n"
},
{
"path": "endpoints/household/household.php",
"chars": 4628,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/inputvalidation.php';\r\nrequire_"
},
{
"path": "endpoints/logos/search.php",
"chars": 3887,
"preview": "<?php\r\nif (isset($_GET['search'])) {\r\n $searchTerm = urlencode($_GET['search'] . \" logo\");\r\n\r\n function applyProxy"
},
{
"path": "endpoints/notifications/savediscordnotifications.php",
"chars": 2603,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequ"
},
{
"path": "endpoints/notifications/saveemailnotifications.php",
"chars": 3274,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/notifications/savegotifynotifications.php",
"chars": 2880,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequir"
},
{
"path": "endpoints/notifications/savemattermostnotifications.php",
"chars": 2816,
"preview": "<?php\nrequire_once '../../includes/connect_endpoint.php';\nrequire_once '../../includes/validate_endpoint.php';\nrequire_o"
},
{
"path": "endpoints/notifications/savenotificationsettings.php",
"chars": 1872,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$p"
},
{
"path": "endpoints/notifications/saventfynotifications.php",
"chars": 3057,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequ"
},
{
"path": "endpoints/notifications/savepushovernotifications.php",
"chars": 2264,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n\r\n"
},
{
"path": "endpoints/notifications/savepushplusnotifications.php",
"chars": 2179,
"preview": "<?php\nrequire_once '../../includes/connect_endpoint.php';\nrequire_once '../../includes/validate_endpoint.php';\n\n\n $po"
},
{
"path": "endpoints/notifications/saveserverchannotifications.php",
"chars": 2004,
"preview": "<?php\nrequire_once '../../includes/connect_endpoint.php';\nrequire_once '../../includes/validate_endpoint.php';\n\n$postDat"
},
{
"path": "endpoints/notifications/savetelegramnotifications.php",
"chars": 2297,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n\r\n$p"
},
{
"path": "endpoints/notifications/savewebhooknotifications.php",
"chars": 3213,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequir"
},
{
"path": "endpoints/notifications/testdiscordnotifications.php",
"chars": 2694,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequ"
},
{
"path": "endpoints/notifications/testemailnotifications.php",
"chars": 2738,
"preview": "<?php\r\n\r\nuse PHPMailer\\PHPMailer\\PHPMailer;\r\nuse PHPMailer\\PHPMailer\\SMTP;\r\nuse PHPMailer\\PHPMailer\\Exception;\r\n\r\nrequir"
},
{
"path": "endpoints/notifications/testgotifynotifications.php",
"chars": 2679,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequir"
},
{
"path": "endpoints/notifications/testmattermostnotifications.php",
"chars": 2670,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequ"
},
{
"path": "endpoints/notifications/testntfynotifications.php",
"chars": 2580,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequ"
},
{
"path": "endpoints/notifications/testpushovernotifications.php",
"chars": 1566,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$p"
},
{
"path": "endpoints/notifications/testpushplusnotifications.php",
"chars": 2152,
"preview": "<?php\nrequire_once '../../includes/connect_endpoint.php';\nrequire_once '../../includes/validate_endpoint.php';\n\n$postDat"
},
{
"path": "endpoints/notifications/testserverchannotifications.php",
"chars": 1700,
"preview": "<?php\nrequire_once '../../includes/connect_endpoint.php';\nrequire_once '../../includes/validate_endpoint.php';\n\n$postDat"
},
{
"path": "endpoints/notifications/testtelegramnotifications.php",
"chars": 1610,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/notifications/testwebhooknotifications.php",
"chars": 3600,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequ"
},
{
"path": "endpoints/payments/add.php",
"chars": 9215,
"preview": "<?php\r\nerror_reporting(E_ERROR | E_PARSE);\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../inc"
},
{
"path": "endpoints/payments/delete.php",
"chars": 886,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$i"
},
{
"path": "endpoints/payments/get.php",
"chars": 2431,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\n\r\nif (isset($_SESSION['loggedin']) && $_SESSION['loggedin'"
},
{
"path": "endpoints/payments/rename.php",
"chars": 1349,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\nfu"
},
{
"path": "endpoints/payments/search.php",
"chars": 4105,
"preview": "<?php\r\n\r\nif (isset($_GET['search'])) {\r\n function applyProxy($ch) {\r\n $proxy = getenv('https_proxy')\r\n "
},
{
"path": "endpoints/payments/sort.php",
"chars": 752,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$p"
},
{
"path": "endpoints/payments/toggle.php",
"chars": 1454,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\nif ("
},
{
"path": "endpoints/settings/colortheme.php",
"chars": 1007,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$p"
},
{
"path": "endpoints/settings/convert_currency.php",
"chars": 940,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/settings/customcss.php",
"chars": 873,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$p"
},
{
"path": "endpoints/settings/customtheme.php",
"chars": 1638,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$p"
},
{
"path": "endpoints/settings/deleteaccount.php",
"chars": 4454,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$p"
},
{
"path": "endpoints/settings/disabled_to_bottom.php",
"chars": 954,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/settings/hide_disabled.php",
"chars": 919,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/settings/mobile_navigation.php",
"chars": 900,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$p"
},
{
"path": "endpoints/settings/monthly_price.php",
"chars": 921,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n\r\n$p"
},
{
"path": "endpoints/settings/remove_background.php",
"chars": 947,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/settings/resettheme.php",
"chars": 509,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$s"
},
{
"path": "endpoints/settings/show_original_price.php",
"chars": 961,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/settings/subscription_progress.php",
"chars": 1010,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/settings/theme.php",
"chars": 935,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/subscription/add.php",
"chars": 13064,
"preview": "<?php\r\nerror_reporting(E_ERROR | E_PARSE);\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../inc"
},
{
"path": "endpoints/subscription/clone.php",
"chars": 3391,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/subscription/delete.php",
"chars": 1175,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/subscription/exportcalendar.php",
"chars": 2997,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\nrequir"
},
{
"path": "endpoints/subscription/get.php",
"chars": 2398,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\n\r\nif (isset($_SESSION['loggedin']) && $_SESSION['loggedin'] "
},
{
"path": "endpoints/subscription/getcalendar.php",
"chars": 1856,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/getdbkeys.php';\r\n\r\nif (!isset($"
},
{
"path": "endpoints/subscription/renew.php",
"chars": 2555,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/subscriptions/export.php",
"chars": 2063,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\n\r\nif (!isset($_SESSION['loggedin']) || $_SESSION['loggedin']"
},
{
"path": "endpoints/subscriptions/get.php",
"chars": 8610,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\n\r\nrequire_once '../../includes/currency_formatter.php';\r\nreq"
},
{
"path": "endpoints/user/budget.php",
"chars": 859,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/inputvalidation.php';\r\nrequir"
},
{
"path": "endpoints/user/delete_avatar.php",
"chars": 2371,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$i"
},
{
"path": "endpoints/user/disable_totp.php",
"chars": 4633,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/inputvalidation.php';\r\nrequir"
},
{
"path": "endpoints/user/enable_totp.php",
"chars": 5020,
"preview": "<?php\r\n\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/inputvalidation.php';\r\nrequir"
},
{
"path": "endpoints/user/regenerateapikey.php",
"chars": 838,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/validate_endpoint.php';\r\n\r\n$pos"
},
{
"path": "endpoints/user/save_user.php",
"chars": 13849,
"preview": "<?php\r\nrequire_once '../../includes/connect_endpoint.php';\r\nrequire_once '../../includes/inputvalidation.php';\r\nrequire_"
},
{
"path": "health.php",
"chars": 86,
"preview": "<?php\n\nheader('Content-Type: text/plain');\nhttp_response_code(200);\n\necho 'OK';\nexit;\n"
},
{
"path": "images/siteicons/svg/automatic.php",
"chars": 1624,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n<path fill=\"curre"
},
{
"path": "images/siteicons/svg/category.php",
"chars": 1513,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 48 48\" id=\"Page-Setting--Streamline-Plump.svg\" height=\""
},
{
"path": "images/siteicons/svg/check.php",
"chars": 1554,
"preview": "<?php\r\nheader('Content-Type: image/svg+xml');\r\necho '\r\n<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewB"
},
{
"path": "images/siteicons/svg/clone.php",
"chars": 2195,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\" height=\"48\" width=\"48\">\r\n <g id=\"add-layer-2--layer-add-des"
},
{
"path": "images/siteicons/svg/delete.php",
"chars": 1550,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 48 48\" id=\"Recycle-Bin-2--Streamline-Plump.svg\" height="
},
{
"path": "images/siteicons/svg/edit.php",
"chars": 1708,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 48 48\" id=\"Pencil-Square--Streamline-Plump.svg\" height="
},
{
"path": "images/siteicons/svg/export_ical.php",
"chars": 297,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" id=\"mdi-calendar-export\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\r\n <path "
},
{
"path": "images/siteicons/svg/logo.php",
"chars": 33028,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"2146\" height=\"798\" viewBox=\"0 0 2146 798\"\r\n preserveAspectRatio=\"xMidY"
},
{
"path": "images/siteicons/svg/manual.php",
"chars": 3204,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n<path fill=\"curre"
},
{
"path": "images/siteicons/svg/mobile-menu/about.php",
"chars": 1027,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n <path fill=\"c"
},
{
"path": "images/siteicons/svg/mobile-menu/admin.php",
"chars": 4993,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n<path fill=\"curre"
},
{
"path": "images/siteicons/svg/mobile-menu/calendar.php",
"chars": 1458,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n <path fill=\"c"
},
{
"path": "images/siteicons/svg/mobile-menu/clone.php",
"chars": 3826,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n<path fill=\"curre"
},
{
"path": "images/siteicons/svg/mobile-menu/delete.php",
"chars": 2161,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n<path fill=\"curre"
},
{
"path": "images/siteicons/svg/mobile-menu/edit.php",
"chars": 807,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n<path fill=\"curre"
},
{
"path": "images/siteicons/svg/mobile-menu/home.php",
"chars": 1942,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n <path fill=\"c"
},
{
"path": "images/siteicons/svg/mobile-menu/logout.php",
"chars": 1894,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n <path fill=\"c"
},
{
"path": "images/siteicons/svg/mobile-menu/profile.php",
"chars": 4308,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n <path fill=\"c"
},
{
"path": "images/siteicons/svg/mobile-menu/renew.php",
"chars": 1831,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n <path fill=\"c"
},
{
"path": "images/siteicons/svg/mobile-menu/settings.php",
"chars": 3776,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n <path fill=\"c"
},
{
"path": "images/siteicons/svg/mobile-menu/statistics.php",
"chars": 348,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n <path fill=\"c"
},
{
"path": "images/siteicons/svg/mobile-menu/subscriptions.php",
"chars": 5320,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"100\" height=\"100\" viewBox=\"0 0 50 50\">\r\n <path fill=\"c"
},
{
"path": "images/siteicons/svg/notes.php",
"chars": 2194,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 48 48\" id=\"Notepad-Text--Streamline-Plump.svg\" height=\""
},
{
"path": "images/siteicons/svg/payment.php",
"chars": 4470,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 48 48\" id=\"Payment-Recieve-7--Streamline-Plump.svg\" hei"
},
{
"path": "images/siteicons/svg/renew.php",
"chars": 1580,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 48 48\" id=\"Rotate-Left--Streamline-Plump\" height=\"48\" w"
},
{
"path": "images/siteicons/svg/save.php",
"chars": 1692,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 48 48\" id=\"File-Check-Alternate--Streamline-Plump.svg\" "
},
{
"path": "images/siteicons/svg/subscription.php",
"chars": 1802,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 48 48\" id=\"Layers-1--Streamline-Plump.svg\" height=\"48\" "
},
{
"path": "images/siteicons/svg/web.php",
"chars": 1929,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 48 48\" id=\"Web--Streamline-Plump.svg\" height=\"48\" width"
},
{
"path": "images/siteicons/svg/websearch.php",
"chars": 1772,
"preview": "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 48 48\" id=\"Search-Visual--Streamline-Plump.svg\" height="
},
{
"path": "includes/checkredirect.php",
"chars": 531,
"preview": "<?php\r\n\r\n$currentPage = basename($_SERVER['PHP_SELF']);\r\nif ($currentPage == 'index.php') {\r\n // Redirect to subscrip"
},
{
"path": "includes/checksession.php",
"chars": 3724,
"preview": "<?php\r\n// Handle OIDC first\r\nif (session_status() === PHP_SESSION_NONE) {\r\n session_start();\r\n}\r\n\r\nif (isset($_GET['c"
},
{
"path": "includes/checkuser.php",
"chars": 161,
"preview": "<?php\r\n$query = \"SELECT COUNT(*) as count FROM user\";\r\n$result = $db->query($query);\r\n$row = $result->fetchArray(SQLITE3"
},
{
"path": "includes/connect.php",
"chars": 174,
"preview": "<?php\r\n\r\n$databaseFile = 'db/wallos.db';\r\n\r\n$db = new SQLite3($databaseFile);\r\n$db->busyTimeout(5000);\r\n\r\nif (!$db) {\r\n "
},
{
"path": "includes/connect_endpoint.php",
"chars": 450,
"preview": "<?php\r\n\r\n$databaseFile = '../../db/wallos.db';\r\n$db = new SQLite3($databaseFile);\r\n$db->busyTimeout(5000);\r\n\r\nif (!$db) "
},
{
"path": "includes/connect_endpoint_crontabs.php",
"chars": 367,
"preview": "<?php\r\n\r\n$databaseFile = __DIR__ . '/../db/wallos.db';\r\n$db = new SQLite3($databaseFile);\r\n$db->busyTimeout(5000);\r\n\r\nif"
},
{
"path": "includes/currency_formatter.php",
"chars": 673,
"preview": "<?php\n\nfinal class CurrencyFormatter\n{\n private static $instance;\n\n private static function getInstance()\n {\n "
},
{
"path": "includes/filters_menu.php",
"chars": 4221,
"preview": "<div class=\"filtermenu-content\">\r\n <?php\r\n if (count($members) > 1) {\r\n ?>\r\n <div class=\"filtermenu-submenu\">\r\n "
},
{
"path": "includes/footer.php",
"chars": 903,
"preview": "</main>\r\n\r\n<div class=\"toast\" id=\"errorToast\">\r\n <div class=\"toast-content\">\r\n <i class=\"fas fa-solid fa-x toast-ico"
},
{
"path": "includes/getdbkeys.php",
"chars": 2103,
"preview": "<?php\r\n\r\n $currencies = array();\r\n $query = \"SELECT * FROM currencies WHERE user_id = :userId\";\r\n $stmt = $db->"
},
{
"path": "includes/getsettings.php",
"chars": 2659,
"preview": "<?php\r\n\r\n$query = \"SELECT * FROM settings WHERE user_id = :userId\";\r\n$stmt = $db->prepare($query);\r\n$stmt->bindValue(':u"
},
{
"path": "includes/header.php",
"chars": 10841,
"preview": "<?php\r\nrequire_once 'connect.php';\r\nrequire_once 'checkuser.php';\r\nrequire_once 'checksession.php';\r\nrequire_once 'check"
},
{
"path": "includes/i18n/ca.php",
"chars": 23200,
"preview": "<?php\r\n\r\n$i18n = [\r\n // Registration page\r\n \"create_account\" => \"Necessites crear un compte abans de poder iniciar"
},
{
"path": "includes/i18n/cs.php",
"chars": 21493,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"Než se budete moct přihlásit, musíte si vytvořit účet"
},
{
"path": "includes/i18n/da.php",
"chars": 21296,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"Du skal oprette en konto, før du kan logge ind\",\n "
},
{
"path": "includes/i18n/de.php",
"chars": 23587,
"preview": "<?php\r\n\r\n$i18n = [\r\n // Registration page\r\n \"create_account\" => \"Bitte erstelle zunächst einen Account, um dich ei"
},
{
"path": "includes/i18n/el.php",
"chars": 23780,
"preview": "<?php\r\n\r\n$i18n = [\r\n // Registration page\r\n \"create_account\" => \"Πρέπει να δημιουργήσεις έναν λογαριασμό για να μπ"
},
{
"path": "includes/i18n/en.php",
"chars": 21243,
"preview": "<?php\r\n\r\n$i18n = [\r\n // Registration page\r\n \"create_account\" => \"You need to create an account before you're able "
},
{
"path": "includes/i18n/es.php",
"chars": 23509,
"preview": "<?php\r\n\r\n$i18n = [\r\n // Registration page\r\n \"create_account\" => \"Necesitas crear una cuenta antes de poder iniciar"
},
{
"path": "includes/i18n/fr.php",
"chars": 23708,
"preview": "<?php\n\n$i18n = [\n // Page d'inscription\n \"create_account\" => \"Vous devez créer un compte avant de pouvoir vous con"
},
{
"path": "includes/i18n/getlang.php",
"chars": 561,
"preview": "<?php\r\n\r\n$lang = \"en\";\r\nif (isset($_COOKIE['language'])) {\r\n $selectedLanguage = $_COOKIE['language'];\r\n\r\n if (arr"
},
{
"path": "includes/i18n/id.php",
"chars": 21853,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"Anda perlu membuat akun sebelum dapat masuk\",\n \"us"
},
{
"path": "includes/i18n/it.php",
"chars": 23231,
"preview": "<?php\r\n\r\n$i18n = [\r\n // Registration\r\n \"create_account\" => 'Devi creare un account prima di poter accedere',\r\n "
},
{
"path": "includes/i18n/jp.php",
"chars": 16336,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"ログインする前にアカウントを作成する必要があります\",\n \"username\" => \"ユーザー名\""
},
{
"path": "includes/i18n/ko.php",
"chars": 16364,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"로그인 하기 전에 회원가입을 진행해야 합니다.\",\n \"username\" => \"유저명\",\n"
},
{
"path": "includes/i18n/languages.php",
"chars": 1519,
"preview": "<?php\r\n// File Name => Language Name\r\n$languages = [\r\n // English first\r\n \"en\" => [\"name\" => \"English\", \"dir\" => \""
},
{
"path": "includes/i18n/nl.php",
"chars": 21980,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"Maak een account aan om te kunnen inloggen\",\n \"use"
},
{
"path": "includes/i18n/pl.php",
"chars": 22746,
"preview": "<?php\r\n\r\n$i18n = [\r\n // Registration page\r\n \"create_account\" => \"Musisz utworzyć konto, zanim będziesz mógł się za"
},
{
"path": "includes/i18n/pt.php",
"chars": 22377,
"preview": "<?php\r\n\r\n$i18n = [\r\n // Registration page\r\n \"create_account\" => \"Tem que criar uma conta antes de poder iniciar se"
},
{
"path": "includes/i18n/pt_br.php",
"chars": 22004,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"É necessário criar uma conta antes de poder se logar\""
},
{
"path": "includes/i18n/ro.php",
"chars": 22046,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"Trebuie să creezi un cont înainte de a te putea conec"
},
{
"path": "includes/i18n/ru.php",
"chars": 22850,
"preview": "<?php\r\n\r\n$i18n = [\r\n // Registration page\r\n \"create_account\" => \"Вам необходимо создать учетную запись, прежде чем"
},
{
"path": "includes/i18n/sl.php",
"chars": 21536,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"Preden se lahko prijavite, morate ustvariti račun\",\n "
},
{
"path": "includes/i18n/sr.php",
"chars": 21666,
"preview": "<?php\n\n$i18n = [\n // Страница за регистрацију\n \"create_account\" => \"Морате креирати налог пре него што можете приј"
},
{
"path": "includes/i18n/sr_lat.php",
"chars": 21909,
"preview": "<?php\n\n$i18n = [\n // Stranica za registraciju\n \"create_account\" => \"Morate kreirati nalog pre nego što se možete p"
},
{
"path": "includes/i18n/tr.php",
"chars": 21841,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"Giriş yapabilmeniz için önce bir hesap oluşturmanız g"
},
{
"path": "includes/i18n/uk.php",
"chars": 22433,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"Вам необхідно створити обліковий запис, перш ніж ви з"
},
{
"path": "includes/i18n/vi.php",
"chars": 21686,
"preview": "<?php\n\n$i18n = [\n // Registration page\n \"create_account\" => \"Bạn cần tạo tài khoản trước khi có thể đăng nhập\",\n "
},
{
"path": "includes/i18n/zh_cn.php",
"chars": 14907,
"preview": "<?php\n\n$i18n = [\n // 注册页面\n \"create_account\" => \"请创建帐号后登录\",\n \"username\" => \"用户名\",\n \"password\" => \"密码\",\n \"e"
},
{
"path": "includes/i18n/zh_tw.php",
"chars": 14884,
"preview": "<?php\n\n$i18n = [\n // 註冊頁面\n \"create_account\" => \"您需要先建立帳號才能登入\",\n \"username\" => \"使用者名稱\",\n \"password\" => \"密碼\",\n"
},
{
"path": "includes/inputvalidation.php",
"chars": 170,
"preview": "<?php\r\n\r\nfunction validate($value)\r\n{\r\n $value = trim($value);\r\n $value = stripslashes($value);\r\n $value = html"
},
{
"path": "includes/list_subscriptions.php",
"chars": 16108,
"preview": "<?php\r\n\r\nrequire_once 'i18n/getlang.php';\r\n\r\nfunction getBillingCycle($cycle, $frequency, $i18n)\r\n{\r\n switch ($cycle)"
},
{
"path": "includes/oidc/handle_oidc_callback.php",
"chars": 4265,
"preview": "<?php\r\n\r\nfunction generate_username_from_email($email)\r\n{\r\n if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)"
},
{
"path": "includes/oidc/oidc_create_user.php",
"chars": 9388,
"preview": "<?php\r\n\r\n// Try to extract first and last name from \"name\"\r\n$fullName = $userInfo['name'] ?? '';\r\n$parts = explode(' ', "
},
{
"path": "includes/oidc/oidc_login.php",
"chars": 1876,
"preview": "<?php\r\n\r\nif (!isset($userData)) {\r\n die(\"User data missing for OIDC login.\");\r\n}\r\n\r\n$userId = $userData['id'];\r\n$user"
},
{
"path": "includes/sort_options.php",
"chars": 2183,
"preview": "<div class=\"sort-options\" id=\"sort-options\">\r\n <ul>\r\n <li <?= $sortOrder == \"name\" ? 'class=\"selected\"' : \"\" ?"
},
{
"path": "includes/ssrf_helper.php",
"chars": 5074,
"preview": "<?php\r\n\r\n/**\r\n * Checks if an IP falls in the RFC 6598 Carrier-Grade NAT range (100.64.0.0/10).\r\n * PHP's FILTER_FLAG_NO"
}
]
// ... and 145 more files (download for full content)
About this extraction
This page contains the full source code of the ellite/Wallos GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 345 files (2.3 MB), approximately 620.8k tokens, and a symbol index with 1507 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.